UNPKG

@tanstack/ai

Version:

Type-safe TypeScript AI SDK for streaming chat, tool calling, agents, structured outputs, and multimodal generation.

2,598 lines 105 kB
import { stripToSpecMiddleware } from "../../strip-to-spec-middleware.js";
import { isCancelRequestedReason } from "./cancel.js";
import { CapabilityRegistry } from "./middleware/capabilities.js";
import { getRunDetached } from "./middleware/run-store.js";
import { publishRunDetachedSignal } from "../../delivery-detach.js";
import { publishRunDisconnectHandler } from "../../delivery-disconnect.js";
import { EventType } from "../../types.js";
import { resolveDebugOption } from "../../logger/resolve.js";
import { streamToText } from "../../stream-to-response.js";
import { canonicalInterruptJson, digestInterruptJson } from "../../interrupt-serialization.js";
import "../../interrupts.js";
import { convertSchemaForStructuredOutput, convertSchemaToJsonSchema, isStandardSchema, parseWithStandardSchema } from "./tools/schema-converter.js";
import { hashSchemaInput, normalizeApprovalSchema } from "./tools/approval-schema.js";
import { INTERRUPT_BINDING_METADATA_KEY, InterruptResumeValidationError, readInterruptBinding, readUnopenedInterruptBinding, validateInterruptResumeBatch } from "../../interrupt-resume.js";
import { INTERRUPT_PAYLOAD_METADATA_KEY, createInterruptBinding, rehydrateInterruptRequest } from "../../interrupt-definition.js";
import { readGenericInterruptContinuation } from "../../generic-interrupt-continuation.js";
import { normalizeToolResult } from "../../utilities/tool-result.js";
import { isProviderExecutedToolCall } from "../../utilities/provider-executed.js";
import { LazyToolManager } from "./tools/lazy-tool-manager.js";
import { assertUniqueToolNames } from "./tools/unique-tool-names.js";
import { MiddlewareAbortError, ToolCallManager, executeToolCalls } from "./tools/tool-calls.js";
import { maxIterations } from "./agent-loop-strategies.js";
import { convertMessagesToModelMessages, generateMessageId, modelMessageToUIMessage, safeJsonStringify } from "./messages.js";
import { MiddlewareRunner } from "./middleware/compose.js";
import { provideSandboxRuntime } from "./middleware/sandbox-runtime.js";
import { provideRunDisconnect } from "./middleware/run-disconnect.js";
import { validateCapabilities } from "./middleware/validate.js";
import { MCPManager } from "./mcp/manager.js";
import { provideGenericInterruptDefinitionRegistry } from "./middleware/generic-interrupts.js";
import "./adapter.js";
import { devtoolsMiddleware } from "@tanstack/ai-event-client";
import { undoNullWidening } from "@tanstack/ai-utils";
//#region src/activities/chat/index.ts
/**
* Text Activity
*
* Handles agentic text generation, one-shot text generation, and agentic structured output.
* This is a self-contained module with implementation, types, and JSDoc.
*/
/** The adapter kind this activity handles */
var kind = "text";
var interruptBindingMetadataKey = INTERRUPT_BINDING_METADATA_KEY;
function isInterruptSubmissionError(value) {
	if (value === null || typeof value !== "object" || Array.isArray(value)) return false;
	if (!("scope" in value) || !("code" in value) || !("message" in value) || !("source" in value) || !("retryable" in value) || !("threadId" in value) || !("interruptedRunId" in value) || !("generation" in value) || typeof value.code !== "string" || typeof value.message !== "string" || typeof value.retryable !== "boolean" || typeof value.threadId !== "string" || typeof value.interruptedRunId !== "string" || typeof value.generation !== "number") return false;
	if (value.scope === "item") return "interruptId" in value && typeof value.interruptId === "string" && (value.source === "client" || value.source === "server");
	return value.scope === "batch" && "interruptIds" in value && Array.isArray(value.interruptIds) && value.interruptIds.every((id) => typeof id === "string") && (value.source === "client" || value.source === "server" || value.source === "transport");
}
function structuralInterruptFailure(error) {
	if (!(error instanceof Error) || error.name !== "InterruptResumeValidationError" || !("errors" in error) || !Array.isArray(error.errors) || error.errors.length === 0 || !error.errors.every(isInterruptSubmissionError)) return;
	return {
		error,
		errors: error.errors
	};
}
function normalizePublicInterruptBinding(value, expectedInterruptId) {
	return readInterruptBinding({
		id: expectedInterruptId,
		reason: "",
		metadata: { [INTERRUPT_BINDING_METADATA_KEY]: value }
	});
}
/**
* Create typed options for the chat() function without executing.
* This is useful for pre-defining configurations with full type inference.
*
* @example
* ```ts
* const chatOptions = createChatOptions({
*   adapter: anthropicText('claude-sonnet-4-5'),
* })
*
* const stream = chat({ ...chatOptions, messages })
* ```
*/
function createChatOptions(options) {
	return options;
}
/**
* Combine two optional AbortSignals into one that aborts when either does.
* Returns the other signal directly when one is absent or already aborted.
* (Manual implementation — `AbortSignal.any` requires Node >= 20.3.)
*/
function combineAbortSignals(a, b) {
	if (!a) return b;
	if (!b) return a;
	if (a.aborted) return a;
	if (b.aborted) return b;
	const controller = new AbortController();
	const onAbort = (source) => () => {
		controller.abort(source.reason);
	};
	a.addEventListener("abort", onAbort(a), { once: true });
	b.addEventListener("abort", onAbort(b), { once: true });
	return controller.signal;
}
var TextEngine = class {
	adapter;
	interruptDefinitions;
	params;
	systemPrompts;
	tools;
	loopStrategy;
	toolCallManager;
	lazyToolManager;
	/** A public interruption terminal must always have this run's start event. */
	hasPublicRunStarted = false;
	initialMessageCount;
	requestId;
	streamId;
	effectiveRequest;
	effectiveSignal;
	messages;
	iterationCount = 0;
	/** Cumulative tool calls counted in this run (emitted + pending resume). */
	toolCallCount = 0;
	/** Tool calls in the most recent budgeted batch (0 when none). */
	lastTurnToolCallCount = 0;
	/** Tool call IDs already counted toward `toolCallCount` (avoids double-count on resume). */
	countedToolCallIds = /* @__PURE__ */ new Set();
	lastFinishReason = null;
	streamStartTime = 0;
	totalChunkCount = 0;
	currentMessageId = null;
	currentMessageCreatedAt = null;
	streamIdentityCaptured = false;
	accumulatedContent = "";
	accumulatedThinking = [];
	currentThinkingContent = "";
	currentThinkingSignature = "";
	hasSeenReasoningEvents = false;
	eventOptions;
	eventToolNames;
	finishedEvent = null;
	streamedToolErrorResults = /* @__PURE__ */ new Map();
	deferredToolCallRunFinishedChunks = [];
	/** The model terminal is held until afterModel can choose an interrupt. */
	deferredModelRunFinishedChunks = [];
	earlyTermination = false;
	toolPhase = "continue";
	cyclePhase = "processText";
	initialApprovals;
	initialClientToolResults;
	resumeApprovals = /* @__PURE__ */ new Map();
	resumeClientToolResults = /* @__PURE__ */ new Map();
	resumeDeniedToolResults = /* @__PURE__ */ new Map();
	resumeCancelledToolCallIds = /* @__PURE__ */ new Set();
	resumeGenericInterrupts = /* @__PURE__ */ new Map();
	resumeGenericInterruptRequests = /* @__PURE__ */ new Map();
	threadId;
	runIdOverride;
	parentRunIdOverride;
	middlewareRunner;
	middlewareCtx;
	sandboxFileQueue = [];
	deferredPromises = [];
	abortReason;
	middlewareAbortController;
	toolAbortSignal;
	terminalHookCalled = false;
	/**
	* Latched the first time the delivery socket closes; see `notifyDisconnected`.
	* Also read by `subscribe` so a listener registered AFTER the disconnect (a
	* middleware whose `setup` was still running at the time — the common case) is
	* called immediately rather than never.
	*/
	disconnected = false;
	disconnectListeners = [];
	logger;
	structuredOutputResult = null;
	structuredOutputMessageId = null;
	structuredOutputMessageCreatedAt = null;
	combinedStartEmitted = false;
	combinedStructuredMessageId = null;
	validatedStructuredOutput = void 0;
	hasValidatedStructuredOutput = false;
	finalizationError = null;
	combinedCompleteEmitted = false;
	finalStructuredOutput;
	constructor(config, logger) {
		this.logger = logger;
		this.adapter = config.adapter;
		this.interruptDefinitions = new Map((config.params.interrupts ?? []).map((definition) => [definition.id, definition]));
		this.finalStructuredOutput = config.finalStructuredOutput;
		this.params = config.params;
		this.systemPrompts = config.params.systemPrompts || [];
		this.loopStrategy = config.params.agentLoopStrategy || maxIterations(5);
		this.initialMessageCount = config.params.messages.length;
		const { approvals, clientToolResults } = this.extractClientStateFromOriginalMessages(config.params.messages);
		this.initialApprovals = approvals;
		this.initialClientToolResults = clientToolResults;
		this.messages = convertMessagesToModelMessages(config.params.messages);
		assertUniqueToolNames(config.params.tools || []);
		this.lazyToolManager = new LazyToolManager(config.params.tools || [], this.messages, config.params.lazyToolsConfig);
		this.tools = this.lazyToolManager.getActiveTools();
		this.toolCallManager = new ToolCallManager(this.tools);
		this.requestId = this.createId("chat");
		this.streamId = this.createId("stream");
		this.effectiveRequest = config.params.abortController ? { signal: config.params.abortController.signal } : void 0;
		this.effectiveSignal = config.params.abortController?.signal;
		this.threadId = config.params.threadId || config.params.conversationId || this.createId("thread");
		this.runIdOverride = config.params.runId;
		this.parentRunIdOverride = config.params.parentRunId;
		const allMiddleware = [
			devtoolsMiddleware(),
			...config.middleware || [],
			stripToSpecMiddleware()
		];
		this.middlewareRunner = new MiddlewareRunner(allMiddleware, logger);
		this.middlewareAbortController = new AbortController();
		this.toolAbortSignal = combineAbortSignals(this.effectiveSignal, this.middlewareAbortController.signal);
		this.middlewareCtx = {
			requestId: this.requestId,
			streamId: this.streamId,
			runId: this.runIdOverride ?? this.requestId,
			parentRunId: this.parentRunIdOverride,
			threadId: this.threadId,
			conversationId: this.threadId,
			phase: "init",
			iteration: 0,
			chunkIndex: 0,
			signal: this.effectiveSignal,
			abort: (reason) => {
				this.abortReason = reason;
				this.middlewareAbortController?.abort(reason);
			},
			context: config.context,
			defer: (promise) => {
				this.deferredPromises.push(promise);
			},
			activity: "chat",
			provider: config.adapter.name,
			model: config.params.model,
			source: "server",
			streaming: true,
			systemPrompts: this.systemPrompts,
			toolNames: void 0,
			options: void 0,
			modelOptions: config.params.modelOptions,
			messageCount: this.initialMessageCount,
			hasTools: this.tools.length > 0,
			currentMessageId: null,
			accumulatedContent: "",
			messages: this.messages,
			createId: (prefix) => this.createId(prefix),
			capabilities: new CapabilityRegistry(),
			get: (capability) => capability[0](this.middlewareCtx),
			getOptional: (capability) => capability[0](this.middlewareCtx, { optional: true }),
			provide: (capability, value) => capability[1](this.middlewareCtx, value)
		};
		provideRunDisconnect(this.middlewareCtx, { subscribe: (listener) => {
			this.disconnectListeners.push(listener);
			if (this.disconnected) this.runDisconnectListener(listener);
		} });
		provideGenericInterruptDefinitionRegistry(this.middlewareCtx, { definitions: this.interruptDefinitions });
		provideSandboxRuntime(this.middlewareCtx, {
			logger: this.logger,
			emit: (event) => {
				this.logger.sandbox(`file ${event.type} ${event.path}`, { event: {
					type: event.type,
					path: event.path,
					timestamp: event.timestamp
				} });
				this.middlewareRunner.runSandboxFile(this.middlewareCtx, event).catch((err) => {
					this.logger.errors("sandbox file hook failed", { error: err });
				});
				this.sandboxFileQueue.push(this.createCustomEventChunk("sandbox.file", {
					type: event.type,
					path: event.path,
					timestamp: event.timestamp
				}));
			},
			emitFileDiff: (value) => {
				this.sandboxFileQueue.push(this.createCustomEventChunk("sandbox.file.diff", value));
			}
		});
	}
	/** Get the accumulated content after the chat loop completes */
	getAccumulatedContent() {
		return this.accumulatedContent;
	}
	/** Get the final messages array after the chat loop completes */
	getMessages() {
		return this.messages;
	}
	/** Returns the structured-output result if finalization ran successfully. */
	getStructuredOutputResult() {
		return this.structuredOutputResult;
	}
	/**
	* Returns the validated structured-output value (the result of running
	* `finalStructuredOutput.validate` against the raw structured-output data)
	* wrapped in a `{ value }` object so callers can distinguish "no validation
	* happened" from "validation produced undefined". Returns `null` when no
	* validator was configured or validation hasn't been performed yet.
	*/
	getValidatedStructuredOutput() {
		return this.hasValidatedStructuredOutput ? { value: this.validatedStructuredOutput } : null;
	}
	/** Returns the recorded finalization error, if any. */
	getFinalizationError() {
		return this.finalizationError;
	}
	async *run() {
		this.beforeRun();
		this.logger.agentLoop("run started", { threadId: this.middlewareCtx.threadId });
		try {
			await this.middlewareRunner.runSetup(this.middlewareCtx);
			this.middlewareCtx.phase = "init";
			const initialConfig = this.buildMiddlewareConfig();
			const transformedConfig = await this.middlewareRunner.runOnConfig(this.middlewareCtx, initialConfig);
			this.applyMiddlewareConfig(transformedConfig);
			await this.applyEphemeralInterruptResume(transformedConfig);
			await this.applyDurableGenericInterruptResolution();
			await this.middlewareRunner.runOnStart(this.middlewareCtx);
			if (this.earlyTermination) {
				yield* this.emitSuccessfulEarlyTermination();
				if (!this.terminalHookCalled) {
					this.terminalHookCalled = true;
					await this.middlewareRunner.runOnFinish(this.middlewareCtx, {
						finishReason: this.lastFinishReason,
						duration: Date.now() - this.streamStartTime,
						content: this.accumulatedContent,
						usage: this.finishedEvent?.usage
					});
				}
				return;
			}
			if ((yield* this.checkForPendingToolCalls()) === "wait") return;
			if (!(!!this.finalStructuredOutput && this.tools.length === 0 && this.finalStructuredOutput.nativeCombined !== true)) do {
				if (this.earlyTermination) break;
				if (this.isCancelled()) return;
				this.logger.agentLoop(`iteration=${this.middlewareCtx.iteration}`, { iteration: this.middlewareCtx.iteration });
				await this.beginCycle();
				if (this.cyclePhase === "processText") {
					this.middlewareCtx.phase = "beforeModel";
					this.middlewareCtx.iteration = this.iterationCount;
					const iterConfig = this.buildMiddlewareConfig();
					const iterTransformedConfig = await this.middlewareRunner.runOnConfig(this.middlewareCtx, iterConfig);
					this.applyMiddlewareConfig(iterTransformedConfig);
					if (yield* this.emitBoundaryInterrupts("beforeModel", this.createSyntheticFinishedEvent())) {
						this.setToolPhase("wait");
						return;
					}
					yield* this.streamModelResponse();
					if (this.earlyTermination) break;
					if (yield* this.emitBoundaryInterrupts("afterModel", this.finishedEvent ?? this.createSyntheticFinishedEvent())) {
						this.setToolPhase("wait");
						return;
					}
					if (this.shouldExecuteToolPhase()) {
						this.deferredToolCallRunFinishedChunks.push(...this.deferredModelRunFinishedChunks);
						this.deferredModelRunFinishedChunks = [];
					} else yield* this.flushDeferredModelRunFinishedChunks();
				} else yield* this.processToolCalls();
				this.endCycle();
			} while (await this.shouldContinue());
			this.logger.agentLoop("run finished", { finishReason: this.lastFinishReason });
			if (this.finalStructuredOutput && this.toolPhase !== "wait" && !this.isCancelled() && !this.finalizationError && !this.earlyTermination) {
				if (this.finalStructuredOutput.nativeCombined === true) yield* this.harvestCombinedStructuredOutput();
				else yield* this.runStructuredFinalization();
			}
			if (!this.terminalHookCalled && this.toolPhase !== "wait" && !this.isCancelled()) {
				if (this.finalizationError) {
					this.terminalHookCalled = true;
					const errForHook = new Error(this.finalizationError.message, this.finalizationError.cause !== void 0 ? { cause: this.finalizationError.cause } : void 0);
					if (this.finalizationError.code !== void 0) Object.defineProperty(errForHook, "code", {
						value: this.finalizationError.code,
						enumerable: true
					});
					await this.middlewareRunner.runOnError(this.middlewareCtx, {
						error: errForHook,
						duration: Date.now() - this.streamStartTime
					});
				} else {
					this.addTerminalAssistantMessages();
					this.terminalHookCalled = true;
					await this.middlewareRunner.runOnFinish(this.middlewareCtx, {
						finishReason: this.lastFinishReason,
						duration: Date.now() - this.streamStartTime,
						content: this.accumulatedContent,
						usage: this.finishedEvent?.usage
					});
				}
			}
		} catch (error) {
			if (error instanceof Error && error.name === "InterruptReplaySignal" && "continuationRunId" in error && typeof error.continuationRunId === "string") {
				this.terminalHookCalled = true;
				yield {
					type: EventType.RUN_FINISHED,
					timestamp: Date.now(),
					threadId: this.threadId,
					runId: this.runIdOverride ?? this.requestId,
					finishReason: "stop",
					outcome: { type: "success" },
					result: {
						replayed: true,
						continuationRunId: error.continuationRunId
					}
				};
				return;
			}
			const interruptFailure = structuralInterruptFailure(error);
			if (interruptFailure) {
				this.terminalHookCalled = true;
				this.logger.errors("chat interrupt resume failed", {
					error,
					threadId: this.middlewareCtx.threadId
				});
				await this.middlewareRunner.runOnError(this.middlewareCtx, {
					error: interruptFailure.error,
					duration: Date.now() - this.streamStartTime
				});
				yield this.buildInterruptRunErrorChunk(error);
				return;
			}
			if (!this.terminalHookCalled) {
				this.terminalHookCalled = true;
				if (error instanceof MiddlewareAbortError) {
					this.abortReason = error.message;
					await this.middlewareRunner.runOnAbort(this.middlewareCtx, {
						reason: error.message,
						duration: Date.now() - this.streamStartTime,
						cancelRequested: isCancelRequestedReason(error.message)
					});
				} else {
					this.logger.errors("chat run failed", {
						error,
						threadId: this.middlewareCtx.threadId
					});
					await this.middlewareRunner.runOnError(this.middlewareCtx, {
						error,
						duration: Date.now() - this.streamStartTime
					});
				}
			}
			if (!(error instanceof MiddlewareAbortError)) throw error;
		} finally {
			if (!this.terminalHookCalled && this.isCancelled()) {
				this.terminalHookCalled = true;
				const reason = this.resolveAbortReason();
				await this.middlewareRunner.runOnAbort(this.middlewareCtx, {
					reason,
					duration: Date.now() - this.streamStartTime,
					cancelRequested: isCancelRequestedReason(reason)
				});
			}
			if (this.deferredPromises.length > 0) await Promise.allSettled(this.deferredPromises);
		}
	}
	beforeRun() {
		this.streamStartTime = Date.now();
		const { tools, metadata } = this.params;
		const options = {};
		if (metadata !== void 0) options.metadata = metadata;
		this.eventOptions = Object.keys(options).length > 0 ? options : void 0;
		this.eventToolNames = tools?.map((t) => t.name);
		this.middlewareCtx.options = this.eventOptions;
		this.middlewareCtx.toolNames = this.eventToolNames;
	}
	async beginCycle() {
		if (this.cyclePhase === "processText") await this.beginIteration();
	}
	endCycle() {
		if (this.cyclePhase === "processText") {
			this.cyclePhase = "executeToolCalls";
			return;
		}
		this.cyclePhase = "processText";
		this.iterationCount++;
	}
	async beginIteration() {
		this.currentMessageId = this.createId("msg");
		this.currentMessageCreatedAt = /* @__PURE__ */ new Date();
		this.streamIdentityCaptured = false;
		this.accumulatedContent = "";
		this.accumulatedThinking = [];
		this.currentThinkingContent = "";
		this.currentThinkingSignature = "";
		this.hasSeenReasoningEvents = false;
		this.finishedEvent = null;
		this.streamedToolErrorResults.clear();
		this.middlewareCtx.currentMessageId = this.currentMessageId;
		this.middlewareCtx.accumulatedContent = "";
		await this.middlewareRunner.runOnIteration(this.middlewareCtx, {
			iteration: this.iterationCount,
			messageId: this.currentMessageId
		});
	}
	async *streamModelResponse() {
		const { metadata, modelOptions } = this.params;
		const toolsWithJsonSchemas = this.tools.map((tool) => ({
			...tool,
			inputSchema: tool.inputSchema ? convertSchemaToJsonSchema(tool.inputSchema) : void 0,
			outputSchema: tool.outputSchema ? convertSchemaToJsonSchema(tool.outputSchema) : void 0
		}));
		this.middlewareCtx.phase = "modelStream";
		const providerName = this.adapter.provider ?? this.adapter.name;
		this.logger.request(`activity=chat provider=${providerName} model=${this.params.model} messages=${this.messages.length} tools=${this.tools.length} stream=true`, {
			provider: providerName,
			model: this.params.model,
			messageCount: this.messages.length,
			toolCount: this.tools.length
		});
		const combinedSchema = this.finalStructuredOutput?.nativeCombined === true ? this.finalStructuredOutput.jsonSchema : void 0;
		const { approvals } = this.collectClientState();
		const adapterApprovals = /* @__PURE__ */ new Map();
		for (const [approvalId, resolution] of approvals) adapterApprovals.set(approvalId, typeof resolution === "boolean" ? resolution : resolution.approved);
		for await (const chunk of this.adapter.chatStream({
			model: this.params.model,
			messages: this.messages,
			tools: toolsWithJsonSchemas,
			metadata,
			request: this.effectiveRequest,
			modelOptions,
			systemPrompts: this.systemPrompts,
			logger: this.logger,
			threadId: this.threadId,
			runId: this.runIdOverride,
			parentRunId: this.parentRunIdOverride,
			capabilities: this.middlewareCtx,
			approvals: adapterApprovals,
			...combinedSchema ? { outputSchema: combinedSchema } : {}
		})) {
			if (this.isCancelled()) break;
			this.totalChunkCount++;
			this.handleStreamChunk(chunk);
			if (chunk.type === EventType.CUSTOM && chunk.name === "structured-output.start") {
				this.combinedStartEmitted = true;
				const startValue = chunk.value;
				if (startValue && typeof startValue === "object" && "messageId" in startValue && typeof startValue.messageId === "string") {
					this.combinedStructuredMessageId = startValue.messageId;
					this.captureStructuredOutputMessageIdentity(startValue.messageId);
				}
			}
			let outboundChunk = chunk;
			if (this.finalStructuredOutput?.source === "event" && chunk.type === EventType.CUSTOM && chunk.name === "structured-output.complete") {
				const parsed = readStructuredOutputCompleteValue(chunk.value);
				if (parsed) {
					const object = this.finalStructuredOutput.normalize ? this.finalStructuredOutput.normalize(parsed.object) : parsed.object;
					this.structuredOutputResult = {
						data: object,
						rawText: parsed.raw
					};
					this.combinedCompleteEmitted = true;
					const value = chunk.value;
					const completeMessageId = readCustomEventMessageId(value);
					if (completeMessageId) {
						this.combinedStructuredMessageId = completeMessageId;
						this.captureStructuredOutputMessageIdentity(completeMessageId);
					}
					if (object !== parsed.object && value && typeof value === "object") outboundChunk = {
						...chunk,
						value: {
							...value,
							object
						}
					};
				}
			}
			if (this.finalStructuredOutput?.nativeCombined === true && this.finalStructuredOutput.yieldChunks && this.finalStructuredOutput.source !== "event" && !this.combinedStartEmitted && chunk.type === EventType.TEXT_MESSAGE_START) {
				this.combinedStartEmitted = true;
				const messageId = typeof chunk.messageId === "string" && chunk.messageId !== "" ? chunk.messageId : generateMessageId();
				this.combinedStructuredMessageId = messageId;
				const synthStart = {
					type: EventType.CUSTOM,
					name: "structured-output.start",
					value: { messageId },
					model: this.params.model,
					timestamp: Date.now(),
					threadId: this.threadId,
					...this.runIdOverride ? { runId: this.runIdOverride } : {}
				};
				const synthOutputs = await this.middlewareRunner.runOnChunk(this.middlewareCtx, synthStart);
				for (const outputChunk of synthOutputs) {
					yield outputChunk;
					this.middlewareCtx.chunkIndex++;
				}
			}
			const outputChunks = await this.middlewareRunner.runOnChunk(this.middlewareCtx, outboundChunk);
			const suppressAgentLifecycle = !!this.finalStructuredOutput && this.finalStructuredOutput.yieldChunks && this.finalStructuredOutput.nativeCombined !== true;
			for (const outputChunk of outputChunks) {
				if (suppressAgentLifecycle && (outputChunk.type === EventType.RUN_STARTED || outputChunk.type === EventType.RUN_FINISHED)) continue;
				if (outputChunk.type === EventType.RUN_FINISHED) {
					this.deferredModelRunFinishedChunks.push(outputChunk);
					continue;
				}
				if (this.shouldDeferToolCallRunFinished(outputChunk)) {
					this.deferredToolCallRunFinishedChunks.push(outputChunk);
					continue;
				}
				if (outputChunk.type === EventType.RUN_STARTED) this.hasPublicRunStarted = true;
				this.logger.output(`type=${outputChunk.type}`, { chunk: outputChunk });
				yield outputChunk;
				this.middlewareCtx.chunkIndex++;
			}
			if (chunk.type === "RUN_FINISHED" && chunk.usage) await this.middlewareRunner.runOnUsage(this.middlewareCtx, chunk.usage);
			yield* this.drainSandboxFileQueue();
			if (this.earlyTermination) break;
		}
		yield* this.drainSandboxFileQueue();
	}
	handleStreamChunk(chunk) {
		switch (chunk.type) {
			case "TEXT_MESSAGE_START":
				if (typeof chunk.messageId === "string" && chunk.messageId !== "") this.captureStreamMessageIdentity(chunk.messageId);
				break;
			case "TEXT_MESSAGE_CONTENT":
				this.handleTextMessageContentEvent(chunk);
				break;
			case "TOOL_CALL_START":
				this.handleToolCallStartEvent(chunk);
				break;
			case "TOOL_CALL_ARGS":
				this.handleToolCallArgsEvent(chunk);
				break;
			case "TOOL_CALL_END":
				this.handleToolCallEndEvent(chunk);
				break;
			case "RUN_FINISHED":
				this.handleRunFinishedEvent(chunk);
				break;
			case "RUN_ERROR":
				this.handleRunErrorEvent(chunk);
				break;
			case "STEP_STARTED":
				this.handleStepStartedEvent();
				break;
			case "STEP_FINISHED":
				this.handleStepFinishedEvent(chunk);
				break;
			case "REASONING_MESSAGE_CONTENT": this.handleReasoningMessageContentEvent(chunk);
		}
	}
	handleTextMessageContentEvent(chunk) {
		if (chunk.content) this.accumulatedContent = chunk.content;
		else this.accumulatedContent += chunk.delta;
		this.middlewareCtx.accumulatedContent = this.accumulatedContent;
	}
	captureStreamMessageIdentity(messageId) {
		this.currentMessageId = messageId;
		this.middlewareCtx.currentMessageId = messageId;
		if (!this.streamIdentityCaptured) {
			this.currentMessageCreatedAt = /* @__PURE__ */ new Date();
			this.streamIdentityCaptured = true;
		}
	}
	captureStructuredOutputMessageIdentity(messageId) {
		this.structuredOutputMessageId = messageId;
		this.structuredOutputMessageCreatedAt ??= /* @__PURE__ */ new Date();
	}
	handleToolCallStartEvent(chunk) {
		if (typeof chunk.parentMessageId === "string" && chunk.parentMessageId !== "") this.captureStreamMessageIdentity(chunk.parentMessageId);
		this.toolCallManager.addToolCallStartEvent(chunk);
	}
	handleToolCallArgsEvent(chunk) {
		this.toolCallManager.addToolCallArgsEvent(chunk);
	}
	handleToolCallEndEvent(chunk) {
		this.toolCallManager.completeToolCall(chunk);
		if (chunk.state !== "output-error" || chunk.result === void 0) return;
		const toolCall = this.toolCallManager.getToolCalls().find((candidate) => candidate.id === chunk.toolCallId);
		if (!toolCall) return;
		this.streamedToolErrorResults.set(chunk.toolCallId, {
			toolCallId: chunk.toolCallId,
			toolName: toolCall.function.name,
			result: chunk.result,
			...chunk.input !== void 0 && { input: chunk.input },
			state: "output-error"
		});
	}
	handleRunFinishedEvent(chunk) {
		this.finishedEvent = chunk;
		this.lastFinishReason = chunk.finishReason ?? null;
	}
	handleRunErrorEvent(chunk) {
		this.earlyTermination = true;
		if (this.finalizationError === null) {
			const message = chunk.message || chunk.error?.message || "Run failed";
			this.finalizationError = {
				message,
				...chunk.code !== void 0 ? { code: chunk.code } : chunk.error?.code !== void 0 ? { code: chunk.error.code } : {}
			};
		}
	}
	finalizeCurrentThinkingStep() {
		if (this.currentThinkingContent) {
			this.accumulatedThinking.push({
				content: this.currentThinkingContent,
				...this.currentThinkingSignature && { signature: this.currentThinkingSignature }
			});
			this.currentThinkingContent = "";
			this.currentThinkingSignature = "";
		}
	}
	handleStepStartedEvent() {
		this.finalizeCurrentThinkingStep();
	}
	handleStepFinishedEvent(chunk) {
		if (!this.hasSeenReasoningEvents) {
			if (chunk.delta) this.currentThinkingContent += chunk.delta;
			else if (chunk.content) {
				if (chunk.content.startsWith(this.currentThinkingContent)) this.currentThinkingContent = chunk.content;
				else if (!this.currentThinkingContent.startsWith(chunk.content)) this.currentThinkingContent += chunk.content;
			}
		}
		if (chunk.signature) this.currentThinkingSignature = chunk.signature;
	}
	handleReasoningMessageContentEvent(chunk) {
		this.hasSeenReasoningEvents = true;
		this.currentThinkingContent += chunk.delta;
	}
	/**
	* Tools available for execution this turn. The discovery tool is dropped
	* from the advertised set (`this.tools`) once every lazy tool is discovered,
	* but a model may still re-request discovery; this widens execution lookup
	* to include it so such calls don't fail with "Unknown tool". Centralised so
	* both execution sites (`processToolCalls` and `checkForPendingToolCalls`)
	* stay in sync.
	*/
	resolveExecutableTools(toolCalls) {
		return this.lazyToolManager.getExecutableTools(this.tools, toolCalls.map((tc) => tc.function.name));
	}
	async *checkForPendingToolCalls() {
		const pendingToolCalls = this.getPendingToolCallsFromMessages();
		if (pendingToolCalls.length === 0) return "continue";
		const finishEvent = this.createSyntheticFinishedEvent();
		this.recordToolCalls(pendingToolCalls);
		const undiscoveredLazyResults = [];
		const executablePendingCalls = pendingToolCalls.filter((tc) => {
			if (this.lazyToolManager.isUndiscoveredLazyTool(tc.function.name)) {
				undiscoveredLazyResults.push({
					toolCallId: tc.id,
					toolName: tc.function.name,
					result: { error: this.lazyToolManager.getUndiscoveredToolError(tc.function.name) },
					state: "output-error"
				});
				return false;
			}
			return true;
		});
		const deferredErrorResults = [...undiscoveredLazyResults];
		const argsMap = /* @__PURE__ */ new Map();
		for (const tc of pendingToolCalls) argsMap.set(tc.id, tc.function.arguments);
		if (executablePendingCalls.length === 0) {
			if (deferredErrorResults.length > 0) for (const chunk of this.buildToolResultChunks(deferredErrorResults, finishEvent, argsMap)) yield* this.pipeThroughMiddleware(chunk);
			return "continue";
		}
		this.middlewareCtx.phase = "beforeTools";
		if (yield* this.emitBoundaryInterrupts("beforeTools", finishEvent, executablePendingCalls)) {
			this.setToolPhase("wait");
			return "wait";
		}
		const { approvals, clientToolResults } = this.collectClientState();
		const generator = executeToolCalls(executablePendingCalls, this.resolveExecutableTools(executablePendingCalls), approvals, clientToolResults, (eventName, data) => this.createCustomEventChunk(eventName, data), {
			onBeforeToolCall: async (toolCall, tool, args) => {
				this.logger.tools(`phase=before name=${toolCall.function.name}`, {
					name: toolCall.function.name,
					args
				});
				const hookCtx = {
					toolCall,
					tool,
					args,
					toolName: toolCall.function.name,
					toolCallId: toolCall.id
				};
				return this.middlewareRunner.runOnBeforeToolCall(this.middlewareCtx, hookCtx);
			},
			onAfterToolCall: async (info) => {
				this.logger.tools(`phase=after name=${info.toolName}`, {
					name: info.toolName,
					result: info.result
				});
				await this.middlewareRunner.runOnAfterToolCall(this.middlewareCtx, info);
			}
		}, this.middlewareCtx.context, this.toolAbortSignal, {
			deniedToolResults: this.resumeDeniedToolResults,
			cancelledToolCallIds: this.resumeCancelledToolCallIds
		});
		const executionResult = yield* this.drainToolCallGenerator(generator);
		if (this.isMiddlewareAborted()) {
			this.setToolPhase("stop");
			return "stop";
		}
		const allResults = [...executionResult.results, ...deferredErrorResults];
		await this.middlewareRunner.runOnToolPhaseComplete(this.middlewareCtx, {
			toolCalls: pendingToolCalls,
			results: allResults,
			needsApproval: executionResult.needsApproval,
			needsClientExecution: executionResult.needsClientExecution
		});
		if (executionResult.needsApproval.length > 0 || executionResult.needsClientExecution.length > 0) {
			this.discardDeferredToolCallRunFinishedChunks();
			if (allResults.length > 0) for (const chunk of this.buildToolResultChunks(allResults, finishEvent)) yield* this.pipeThroughMiddleware(chunk);
			const emitted = yield* this.emitActionableInterruptBoundary(finishEvent, executionResult.needsApproval, executionResult.needsClientExecution);
			this.setToolPhase(emitted ? "wait" : "stop");
			return emitted ? "wait" : "stop";
		}
		const toolResultChunks = this.buildToolResultChunks(allResults, finishEvent);
		for (const chunk of toolResultChunks) yield* this.pipeThroughMiddleware(chunk);
		return "continue";
	}
	async *processToolCalls() {
		if (!this.shouldExecuteToolPhase()) {
			this.lastTurnToolCallCount = 0;
			this.setToolPhase("stop");
			return;
		}
		const toolCalls = this.toolCallManager.getToolCalls();
		const finishEvent = this.finishedEvent;
		if (!finishEvent || toolCalls.length === 0) {
			this.lastTurnToolCallCount = 0;
			this.setToolPhase("stop");
			return;
		}
		this.recordToolCalls(toolCalls);
		this.addAssistantToolCallMessage(toolCalls);
		const undiscoveredLazyResults = [];
		const executableToolCalls = toolCalls.filter((tc) => {
			if (this.streamedToolErrorResults.has(tc.id)) return false;
			if (this.lazyToolManager.isUndiscoveredLazyTool(tc.function.name)) {
				undiscoveredLazyResults.push({
					toolCallId: tc.id,
					toolName: tc.function.name,
					result: { error: this.lazyToolManager.getUndiscoveredToolError(tc.function.name) },
					state: "output-error"
				});
				return false;
			}
			return true;
		});
		const deferredErrorResults = [...this.streamedToolErrorResults.values(), ...undiscoveredLazyResults];
		if (executableToolCalls.length === 0) {
			yield* this.flushDeferredToolCallRunFinishedChunks();
			if (deferredErrorResults.length > 0) for (const chunk of this.buildToolResultChunks(deferredErrorResults, finishEvent)) yield* this.pipeThroughMiddleware(chunk);
			this.toolCallManager.clear();
			this.setToolPhase("continue");
			return;
		}
		this.middlewareCtx.phase = "beforeTools";
		if (yield* this.emitBoundaryInterrupts("beforeTools", finishEvent, executableToolCalls)) {
			this.setToolPhase("wait");
			return;
		}
		const { approvals, clientToolResults } = this.collectClientState();
		const generator = executeToolCalls(executableToolCalls, this.resolveExecutableTools(executableToolCalls), approvals, clientToolResults, (eventName, data) => this.createCustomEventChunk(eventName, data), {
			onBeforeToolCall: async (toolCall, tool, args) => {
				this.logger.tools(`phase=before name=${toolCall.function.name}`, {
					name: toolCall.function.name,
					args
				});
				const hookCtx = {
					toolCall,
					tool,
					args,
					toolName: toolCall.function.name,
					toolCallId: toolCall.id
				};
				return this.middlewareRunner.runOnBeforeToolCall(this.middlewareCtx, hookCtx);
			},
			onAfterToolCall: async (info) => {
				this.logger.tools(`phase=after name=${info.toolName}`, {
					name: info.toolName,
					result: info.result
				});
				await this.middlewareRunner.runOnAfterToolCall(this.middlewareCtx, info);
			}
		}, this.middlewareCtx.context, this.toolAbortSignal, {
			deniedToolResults: this.resumeDeniedToolResults,
			cancelledToolCallIds: this.resumeCancelledToolCallIds
		});
		const executionResult = yield* this.drainToolCallGenerator(generator);
		this.middlewareCtx.phase = "afterTools";
		if (this.isMiddlewareAborted()) {
			this.setToolPhase("stop");
			return;
		}
		const allResults = [...executionResult.results, ...deferredErrorResults];
		await this.middlewareRunner.runOnToolPhaseComplete(this.middlewareCtx, {
			toolCalls,
			results: allResults,
			needsApproval: executionResult.needsApproval,
			needsClientExecution: executionResult.needsClientExecution
		});
		const afterToolBoundaryChunks = this.buildToolResultChunks(allResults, finishEvent);
		const afterToolRequests = await this.middlewareRunner.runOnInterruptBoundary(this.middlewareCtx);
		if (afterToolRequests.length > 0) {
			for (const chunk of afterToolBoundaryChunks) yield* this.pipeThroughMiddleware(chunk);
			yield* this.emitBoundaryInterrupts("afterTools", finishEvent, toolCalls, afterToolRequests);
			this.setToolPhase("wait");
			return;
		}
		if (executionResult.needsApproval.length > 0 || executionResult.needsClientExecution.length > 0) {
			if (allResults.length > 0) for (const chunk of afterToolBoundaryChunks) yield* this.pipeThroughMiddleware(chunk);
			const emitted = yield* this.emitActionableInterruptBoundary(finishEvent, executionResult.needsApproval, executionResult.needsClientExecution);
			this.setToolPhase(emitted ? "wait" : "stop");
			return;
		}
		yield* this.flushDeferredToolCallRunFinishedChunks();
		const toolResultChunks = afterToolBoundaryChunks;
		for (const chunk of toolResultChunks) yield* this.pipeThroughMiddleware(chunk);
		if (this.lazyToolManager.hasNewlyDiscoveredTools()) {
			this.tools = this.lazyToolManager.getActiveTools();
			this.toolCallManager = new ToolCallManager(this.tools);
			this.setToolPhase("continue");
			return;
		}
		this.toolCallManager.clear();
		this.setToolPhase("continue");
	}
	shouldDeferToolCallRunFinished(chunk) {
		return chunk.type === EventType.RUN_FINISHED && this.finishedEvent?.finishReason === "tool_calls" && this.tools.length > 0 && this.toolCallManager.hasToolCalls();
	}
	*flushDeferredToolCallRunFinishedChunks() {
		for (const chunk of this.deferredToolCallRunFinishedChunks) {
			this.logger.output(`type=${chunk.type}`, { chunk });
			yield chunk;
			this.middlewareCtx.chunkIndex++;
		}
		this.deferredToolCallRunFinishedChunks = [];
	}
	*flushDeferredModelRunFinishedChunks() {
		for (const chunk of this.deferredModelRunFinishedChunks) {
			this.logger.output(`type=${chunk.type}`, { chunk });
			yield chunk;
			this.middlewareCtx.chunkIndex++;
		}
		this.deferredModelRunFinishedChunks = [];
	}
	async *emitSyntheticRunStarted(finishEvent) {
		if (this.hasPublicRunStarted) return;
		yield* this.pipeThroughMiddleware({
			type: EventType.RUN_STARTED,
			runId: finishEvent.runId,
			threadId: finishEvent.threadId,
			model: finishEvent.model,
			timestamp: Date.now()
		});
	}
	async *emitSuccessfulEarlyTermination() {
		this.lastFinishReason = "stop";
		const finishEvent = {
			...this.createSyntheticFinishedEvent(),
			finishReason: "stop"
		};
		yield* this.emitSyntheticRunStarted(finishEvent);
		yield* this.pipeThroughMiddleware({
			...finishEvent,
			timestamp: Date.now(),
			outcome: { type: "success" }
		});
	}
	discardDeferredToolCallRunFinishedChunks() {
		this.deferredToolCallRunFinishedChunks = [];
	}
	shouldExecuteToolPhase() {
		return this.finishedEvent?.finishReason === "tool_calls" && this.tools.length > 0 && this.toolCallManager.hasToolCalls();
	}
	addAssistantToolCallMessage(toolCalls) {
		this.finalizeCurrentThinkingStep();
		this.messages = [...this.messages, {
			role: "assistant",
			content: this.accumulatedContent || null,
			toolCalls,
			id: this.currentMessageId ?? void 0,
			createdAt: this.currentMessageCreatedAt ?? void 0,
			...this.accumulatedThinking.length > 0 && { thinking: this.accumulatedThinking }
		}];
		this.middlewareCtx.messages = this.messages;
	}
	addTerminalAssistantMessages() {
		this.finalizeCurrentThinkingStep();
		const structuredResult = this.structuredOutputResult;
		const raw = structuredResult ? structuredResult.rawText || safeJsonStringify(structuredResult.data) : "";
		const structuredOutput = structuredResult ? {
			type: "structured-output",
			status: "complete",
			data: structuredResult.data,
			partial: structuredResult.data,
			raw,
			...structuredResult.reasoning !== void 0 ? { reasoning: structuredResult.reasoning } : {}
		} : void 0;
		const nativeCombined = this.finalStructuredOutput?.nativeCombined === true;
		const eventSourced = this.finalStructuredOutput?.source === "event";
		const structuredId = this.structuredOutputMessageId ?? this.combinedStructuredMessageId ?? this.currentMessageId ?? this.createId("msg");
		const splitStructuredMessage = Boolean(structuredOutput) && (!nativeCombined || eventSourced) && this.currentMessageId != null && structuredId !== this.currentMessageId;
		const messages = [...this.middlewareCtx.messages];
		const existingStructuredIndex = messages.findIndex((message) => message.role === "assistant" && message.id === structuredId);
		const currentTurnAlreadyRecorded = messages.some((message) => message.role === "assistant" && message.id === this.currentMessageId);
		const thinking = this.accumulatedThinking.length > 0 ? this.accumulatedThinking : void 0;
		const startedLength = messages.length;
		if (structuredOutput && existingStructuredIndex >= 0) {
			const existing = messages[existingStructuredIndex];
			if (existing) messages[existingStructuredIndex] = {
				...existing,
				content: raw || existing.content,
				structuredOutput
			};
		} else if (structuredOutput && !splitStructuredMessage) {
			if (!currentTurnAlreadyRecorded) messages.push({
				role: "assistant",
				content: this.accumulatedContent || raw || null,
				id: structuredId,
				createdAt: this.currentMessageCreatedAt ?? this.structuredOutputMessageCreatedAt ?? /* @__PURE__ */ new Date(),
				structuredOutput,
				...thinking ? { thinking } : {}
			});
		} else {
			if (!currentTurnAlreadyRecorded && (this.accumulatedContent !== "" || thinking)) messages.push({
				role: "assistant",
				content: this.accumulatedContent || null,
				id: this.currentMessageId ?? this.createId("msg"),
				createdAt: this.currentMessageCreatedAt ?? /* @__PURE__ */ new Date(),
				...thinking ? { thinking } : {}
			});
			if (structuredOutput) messages.push({
				role: "assistant",
				content: raw || null,
				id: structuredId,
				createdAt: this.structuredOutputMessageCreatedAt ?? /* @__PURE__ */ new Date(),
				structuredOutput
			});
		}
		if (messages.length === startedLength && existingStructuredIndex < 0) return;
		this.messages = messages;
		this.middlewareCtx.messages = this.messages;
	}
	/**
	* Extract client state (approvals and client tool results) from original messages.
	* This is called in the constructor BEFORE converting to ModelMessage format,
	* because the parts array (which contains approval state) is lost during conversion.
	*/
	extractClientStateFromOriginalMessages(originalMessages) {
		const approvals = /* @__PURE__ */ new Map();
		const clientToolResults = /* @__PURE__ */ new Map();
		for (const message of originalMessages) if (message.role === "assistant" && message.parts) {
			for (const part of message.parts) if (part.type === "tool-call") {
				if (part.output !== void 0 && !part.approval) clientToolResults.set(part.id, part.output);
				if (part.approval?.id && part.approval?.approved !== void 0 && part.state === "approval-responded") approvals.set(part.approval.id, part.approval.approved);
			}
		}
		return {
			approvals,
			clientToolResults
		};
	}
	collectClientState() {
		const approvals = new Map(this.initialApprovals);
		const clientToolResults = new Map(this.initialClientToolResults);
		for (const [approvalId, approved] of this.resumeApprovals) approvals.set(approvalId, approved);
		for (const [toolCallId, result] of this.resumeClientToolResults) clientToolResults.set(toolCallId, result);
		for (const message of this.messages) if (message.role === "tool" && message.toolCallId) {
			let output;
			if (Array.isArray(message.content)) output = message.content;
			else try {
				output = JSON.parse(message.content);
			} catch {
				output = message.content;
			}
			if (output && typeof output === "object" && output.pendingExecution === true) continue;
			clientToolResults.set(message.toolCallId, output);
		}
		return {
			approvals,
			clientToolResults
		};
	}
	genericInterruptId() {
		return this.createId("interrupt");
	}
	buildActionableInterrupts(approvals, clientRequests, genericRequests = [], genericInterruptIds = []) {
		const interrupts = [];
		for (const approval of approvals) {
			const tool = this.tools.find((candidate) => candidate.name === approval.toolName);
			const normalized = normalizeApprovalSchema(tool?.approvalSchema, tool?.inputSchema);
			interrupts.push({
				id: approval.approvalId,
				reason: "tool_call",
				message: `Approval required to run ${approval.toolName}`,
				toolCallId: approval.toolCallId,
				responseSchema: normalized.responseSchema,
				metadata: {
					kind: "approval",
					toolName: approval.toolName,
					input: approval.input,
					[interruptBindingMetadataKey]: {
						v: 1,
						kind: "tool-approval",
						interruptId: approval.approvalId,
						toolName: approval.toolName,
						toolCallId: approval.toolCallId,
						originalArgs: approval.input,
						inputSchemaHash: hashSchemaInput(tool?.inputSchema),
						approvalSchemaHash: normalized.approvalSchemaHash,
						responseSchemaHash: normalized.responseSchemaHash
					}
				}
			});
		}
		for (const clientTool of clientRequests) {
			const tool = this.tools.find((candidate) => candidate.name === clientTool.toolName);
			const responseSchema = convertSchemaToJsonSchema(tool?.outputSchema) ?? {};
			interrupts.push({
				id: `client_tool_${clientTool.toolCallId}`,
				reason: "tanstack:client_tool_execution",
				message: `Client tool ${clientTool.toolName} is ready to run`,
				toolCallId: clientTool.toolCallId,
				responseSchema,
				metadata: {
					kind: "client_tool",
					toolName: clientTool.toolName,
					input: clientTool.input,
					[interruptBindingMetadataKey]: {
						v: 1,
						kind: "client-tool-execution",
						interruptId: `client_tool_${clientTool.toolCallId}`,
						toolName: clientTool.toolName,
						toolCallId: clientTool.toolCallId,
						outputSchemaHash: hashSchemaInput(tool?.outputSchema),
						responseSchemaHash: digestInterruptJson(canonicalInterruptJson(responseSchema))
					}
				}
			});
		}
		for (const [index, request] of genericRequests.entries()) {
			const batchIndex = interrupts.length;
			const id = genericInterruptIds[index];
			if (!id) throw new Error("Generic interrupt id is unavailable.");
			const preEmission = createInterruptBinding(request, { batchIndex });
			interrupts.push({
				id,
				reason: request.reason,
				message: request.message,
				...preEmission.descriptor.responseSchemaCanonicalJson !== void 0 ? { responseSchema: JSON.parse(preEmission.descriptor.responseSchemaCanonicalJson) } : {},
				...request.expiresAt !== void 0 ? { expiresAt: request.expiresAt } : {},
				metadata: {
					[interruptBindingMetadataKey]: {
						v: 1,
						kind: "generic",
						interruptId: id,
						definitionId: preEmission.descriptor.definitionId,
						key: preEmission.descriptor.key,
						batchIndex,
						...request.expiresAt !== void 0 ? { expiresAt: request.expiresAt } : {},
						...preEmission.descriptor.payloadSchemaHash ? { payloadSchemaHash: preEmission.descriptor.payloadSchemaHash } : {},
						...preEmission.descriptor.responseSchemaHash !== void 0 ? { responseSchemaHash: preEmission.descriptor.responseSchemaHash } : {}
					},
					...preEmission.payload !== void 0 ? { [INTERRUPT_PAYLOAD_METADATA_KEY]: preEmission.payload } : {}
				}
			});
		}
		const ids = /* @__PURE__ */ new Set();
		for (const interrupt of interrupts) {
			if (ids.has(interrupt.id)) throw new Error(`Duplicate interrupt id in final batch: ${interrupt.id}`);
			ids.add(interrupt.id);
		}
		return interrupts;
	}
	buildInterruptFinishedChunk(finishEvent, approvals, clientRequests, genericRequests = [], genericInterruptIds) {
		return {
			...finishEvent,
			timestamp: Date.now(),
			outcome: {
				type: "interrupt",
				interrupts: this.buildActionableInterrupts(approvals, clientRequests, genericRequests, genericInterruptIds)
			}
		};
	}
	buildMessagesSnapshotChunk() {
		const messages = this.messages.map((message, index) => {
			const content = typeof message.content === "string" ? message.content : message.content === null ? void 0 : JSON.stringify(message.content);
			const id = message.id || `snapshot_${this.runIdOverride ?? this.requestId}_${index}`;
			const parts = message.role === "assistant" && (message.thinking?.length || message.structuredOutput) ? modelMessageToUIMessage(message, id).parts : void 0;
			return {
				id,
				role: message.role,
				...content !== void 0 ? { content } : {},
				...parts ? { parts } : {},
				..."toolCalls" in message && message.toolCalls ? { toolCalls: message.toolCalls } : {},
				..."toolCallId" in message && message.toolCallId ? { toolCallId: message.toolCallId } : {}
			};
		});
		return {
			type: EventType.MESSAGES_SNAPSHOT,
			timestamp: Date.now(),
			model: this.params.model,
			messages
		};
	}
	publicInterruptTerminal(chunk) {
		if (chunk.type !== EventType.RUN_FINISHED || chunk.outcome?.type !== "interrupt") return chunk;
		return {
			...chunk,
			outcome: {
				...chunk.outcome,
				interrupts: chunk.outcome.interrupts.map((interrupt) => {
					if (!interrupt.metadata || typeof interrupt.metadata !== "object" || Array.isArray(interrupt.metadata)) return interrupt;
					const metadata = { ...interrupt.metadata };
					const binding = normalizePublicInterruptBinding(metadata[interruptBindingMetadataKey], interrupt.id);
					if (binding) metadata[interruptBindingMetadataKey] = binding;
					else delete metadata[interruptBindingMetadataKey];
					return {
						...interrupt,
						metadata
					};
				})
			}
		};
	}
	interruptFailure(error) {
		const structured = structuralInterruptFailure(error);
		if (structured) return {
			message: structured.error.message,
			code: structured.errors[0]?.code ?? "server",
			errors: structured.errors
		};
		if (error && typeof error === "object" && "errors" in error) {
			const errors = error.errors;
			if (Array.isArray(errors)) {
				const first = errors[0];
				if (first && typeof first === "object") return {
					message: "message" in first && typeof first.message === "string" ? first.message : "Interrupt persistence failed.",
					code: "code" in first && typeof first.code === "string" ? first.code : "server"
				};
			}
		}
		return {
			message: error instanceof Error ? error.message : "Interrupt persistence failed.",
			code: "server"
		};
	}
	buildInterruptRunErrorChunk(error) {
		const failure = this.interruptFailure(error);
		return {
			type: EventType.RUN_ERROR,
			timestamp: Date.now(),
			runId: this.runIdOverride ?? this.requestId,
			threadId: this.threadId,
			message: failure.message,
			code: failure.code,
			error: {
				message: failure.message,
				code: failure.code
			},
			...failure.errors !== void 0 ? { "tanstack:interruptErrors": failure.errors } : {}
		};
	}
	async *emitInterruptRunError(error) {
		const failure = this.interruptFailure(error);
		this.finalizationError = {
			message: failure.message,
			code: failure.code,
			cause: error
		};
		yield* this.pipeThroughMiddleware(this.buildInterruptRunErrorChunk(error));
	}
	async *emitActionableInterruptBoundary(finishEvent, approvals, clientRequests, genericRequests = []) {
		yield* this.emitSyntheticRunStarted(finishEvent);
		const genericInterruptIds = genericRequests.map(() => this.genericInterruptId());
		const terminal = this.completeEphemeralInterruptBindings(this.buildInterruptFinishedChunk(finishEvent, approvals, clientRequests, genericRequests, genericInterruptIds));
		let terminalOutputs;
		try {
			terminalOutputs = await this.middlewareRunner.runOnChunk(this.middlewareCtx, terminal);
		} catch (error) {
			yield* this.emitInterruptRunError(error);
			return false;
		}
		yield* this.pipeThroughMiddleware(this.buildMessagesSnapshotChunk());
		if (this.params.state !== void 0) yield* this.pipeThroughMiddleware({
			type: EventType.STATE_SNAPSHOT,
			timestamp: Date.now(),
			model: this.params.model,
			snapshot: this.params.state
		});
		for (const output of terminalOutputs) {
			yield this.publicInterruptTerminal(output);
			this.middlewareCtx.chunkIndex++;
		}
		return true;
	}
	async *emitBoundaryInterrupts(phase, finishEvent, toolCalls = [], requests) {
		this.middlewareCtx.phase = phase;
		const boundaryRequests = requests ?? await this.middlewareRunner.runOnInterruptBoundary(this.middlewareCtx);
		if (boundaryRequests.length === 0) return false;
		for (const request of boundaryRequests) if (this.interruptDefinitions.get(request.definition.id) !== request.definition) throw new Error(`Generic interrupt definition ${request.definition.id} is not registered on this chat.`);
		if (phase === "afterModel") {
			if (this.toolCallManager.hasToolCalls()) this.addAssistantToolCallMessage(this.toolCallManager.getToolCalls());
			else this.addAssistantTextMessageForInterrupt();
		}
		const actionable = this.getBoundaryActionableToolRequests(toolCalls);
		yield* this.emitActionableInterruptBoundary(finishEvent, actionable.approvals, actionable.clientRequests, boundaryRequests);
		return true;
	}
	addAssistantTextMessageForInterrupt() {
		if (this.accumulatedContent.length === 0) return;
		this.messages = [...this.messages, {
			role: "assistant",
			content: this.accumulatedContent
		}];
		this.middlewareCtx.messages = this.messages;
	}
	getBoundaryActionableToolRequests(toolCalls) {
		const { approvals, clientToolResults } = this.collectClientState();
		const approvalRequests = [];
		const clientRequests = [];
		for (const toolCall of toolCalls) {
			const tool = this.resolveExecutableTools([toolCall]).find((candidate) => candidate.name === toolCall.function.name);
			if (!tool) continue;
			let input = {};
			try {
				const parsed = JSON.parse(toolCall.function.arguments.trim() || "{}");
				input = parsed && typeof parsed === "object" ? parsed : {};
			} catch {
				input = {};
			}
			const approvalId = `approval_${toolCall.id}`;
			if (tool.needsApproval && !approvals.has(approvalId)) approvalRequests.push({
				toolCallId: toolCall.id,
				toolName: toolCall.function.name,
				input,
				approvalId
			});
			else if (!tool.execute && !clientToolResults.has(toolCall.id) && !this.resumeCancelledToolCallIds.has(toolCall.id)) clientRequests.push({
				toolCallId: toolCall.id,
				toolName: toolCall.function.name,
				input
			});
		}
		return {
			approvals: approvalRequests,
			clientRequests
		};
	}
	completeEphemeralInterruptBindings(chunk) {
		if (chunk.type !== EventType.RUN_FINISHED || chunk.outcome?.type !== "interrupt") return chunk;
		const interruptedRunId = this.runIdOverride ?? this.requestId;
		return {
			...chunk,
			outcome: {
				...chunk.outcome,
				interrupts: chunk.outcome.interrupts.map((interrupt) => {
					if (!interrupt.metadata || typeof interrupt.metadata !== "object" || Array.isArray(interrupt.metadata)) return interrupt;
					const metadata = { ...interrupt.metadata };
					const unopened = metadata[interruptBindingMetadataKey];
					if (unopened === null || typeof unopened !== "object" || Array.isArray(unopened)) return interrupt;
					metadata[interruptBindingMetadataKey] = {
						...unopened,
						interruptedRunId,
						generation: 0
					};
					return {
						...interrupt,
						metadata
					};
				})
			}
		};
	}
	buildToolResultChunks(results, finishEvent, argsMap) {
		const chunks = [];
		for (const result of results) {
			const content = normalizeToolResult(result.result);
			const wireContent = typeof content === "string" ? content : JSON.stringify(content);
			if (argsMap) {
				chunks.push({
					type: "TOOL_CALL_START",
					timestamp: Date.now(),
					model: finishEvent.model,
					toolCallId: result.toolCallId,
					toolCallName: result.toolName,
					toolName: result.toolName
				});
				const args = argsMap.get(result.toolCallId) ?? "{}";
				chunks.push({
					type: "TOOL_CALL_ARGS",
					timestamp: Date.now(),
					model: finishEvent.model,
					toolCallId: result.toolCallId,
					delta: args,
					args
				});
				chunks.push({
					type: "TOOL_CALL_END",
					timestamp: Date.now(),
					model: finishEvent.model,
					toolCallId: result.toolCallId,
					toolCallName: result.toolName,
					toolName: result.toolName,
					result: wireContent,
					...result.input !== void 0 && { input: result.input },
					...result.output !== void 0 && { output: result.output },
					...result.state !== void 0 && { state: result.state }
				});
			}
			chunks.push({
				type: "TOOL_CALL_RESULT",
				timestamp: Date.now(),
				model: finishEvent.model,
				messageId: this.createId("tool-result"),
				toolCallId: result.toolCallId,
				content: wireContent,
				role: "tool",
				...result.state !== void 0 && { state: result.state }
			});
			const placeholderIdx = this.messages.findIndex((m) => {
				if (m.role !== "tool" || m.toolCallId !== result.toolCallId) return false;
				if (typeof m.content !== "string") return false;
				try {
					return JSON.parse(m.content)?.pendingExecution === true;
				} catch {
					return false;
				}
			});
			const newToolMessage = {
				role: "tool",
				content,
				toolCallId: result.toolCallId
			};
			if (placeholderIdx >= 0) this.messages = [
				...this.messages.slice(0, placeholderIdx),
				newToolMessage,
				...this.messages.slice(placeholderIdx + 1)
			];
			else this.messages = [...this.messages, newToolMessage];
			this.middlewareCtx.messages = this.messages;
		}
		return chunks;
	}
	getPendingToolCallsFromMessages() {
		const completedToolIds = /* @__PURE__ */ new Set();
		for (const message of this.messages) if (message.role === "tool" && message.toolCallId) {
			let hasPendingExecution = false;
			if (typeof message.content === "string") try {
				if (JSON.parse(message.content).pendingExecution === true) hasPendingExecution = true;
			} catch {}
			if (!hasPendingExecution) completedToolIds.add(message.toolCallId);
		}
		const pending = [];
		for (const message of this.messages) if (message.role === "assistant" && message.toolCalls) for (const toolCall of message.toolCalls) {
			if (isProviderExecutedToolCall(toolCall)) continue;
			if (!completedToolIds.has(toolCall.id)) pending.push(toolCall);
		}
		return pending;
	}
	/**
	* Find a tool call by id in message history (including already-completed ones).
	* Used when the client has already attached a tool result for UI before resume.
	*/
	findToolCallInMessages(toolCallId) {
		for (const message of this.messages) {
			if (message.role !== "assistant" || !message.toolCalls) continue;
			for (const toolCall of message.toolCalls) if (toolCall.id === toolCallId) return toolCall;
		}
	}
	/**
	* Tool calls that must be reconstructed as interrupt pending for ephemeral
	* resume. Includes outstanding tools plus client tools that already have
	* results in history when the resume batch still carries `client_tool_*`
	* entries (the client writes local tool results before submitting resume).
	*/
	getToolCallsForEphemeralResume(resume) {
		const pending = this.getPendingToolCallsFromMessages();
		const byId = new Map(pending.map((toolCall) => [toolCall.id, toolCall]));
		for (const entry of resume ?? []) {
			let toolCallId;
			if (entry.interruptId.startsWith("client_tool_")) toolCallId = entry.interruptId.slice(12);
			else if (entry.interruptId.startsWith("approval_")) toolCallId = entry.interruptId.slice(9);
			if (toolCallId === void 0 || byId.has(toolCallId)) continue;
			const toolCall = this.findToolCallInMessages(toolCallId);
			if (toolCall && !isProviderExecutedToolCall(toolCall)) {
				pending.push(toolCall);
				byId.set(toolCallId, toolCall);
			}
		}
		return pending;
	}
	createSyntheticFinishedEvent() {
		return {
			type: "RUN_FINISHED",
			runId: this.runIdOverride ?? this.requestId,
			threadId: this.threadId,
			model: this.params.model,
			timestamp: Date.now(),
			finishReason: "tool_calls"
		};
	}
	async shouldContinue() {
		if (this.cyclePhase === "executeToolCalls") return true;
		const state = {
			iterationCount: this.iterationCount,
			messages: this.messages,
			finishReason: this.lastFinishReason,
			toolCallCount: this.toolCallCount,
			lastTurnToolCallCount: this.lastTurnToolCallCount
		};
		const strategyContinues = this.loopStrategy(state);
		const middlewareContinues = await this.middlewareRunner.runOnShouldContinue(this.middlewareCtx, state);
		return strategyContinues && middlewareContinues && this.toolPhase === "continue";
	}
	/**
	* Record tool calls (deduped by id) toward `toolCallCount` /
	* `lastTurnToolCallCount` for strategies and middleware `onShouldContinue`.
	*
	* Used for both live model turns and pending/resume batches. IDs already
	* counted in this run (e.g. wait→resume after a live turn) are not
	* re-added to `toolCallCount`. Per-turn execution caps are app middleware
	* (`onBeforeToolCall` skip), not engine policy.
	*/
	recordToolCalls(toolCalls) {
		this.lastTurnToolCallCount = toolCalls.length;
		let newlyCounted = 0;
		for (const tc of toolCalls) if (!this.countedToolCallIds.has(tc.id)) {
			this.countedToolCallIds.add(tc.id);
			newlyCounted++;
		}
		this.toolCallCount += newlyCounted;
	}
	isAborted() {
		return !!this.effectiveSignal?.aborted;
	}
	isMiddlewareAborted() {
		return !!this.middlewareAbortController?.signal.aborted;
	}
	isCancelled() {
		return this.isAborted() || this.isMiddlewareAborted();
	}
	/**
	* The reason to report on `AbortInfo` for a cancelled run.
	*
	* `this.abortReason` only ever holds a *middleware*-initiated reason
	* (`ctx.abort(reason)` / `MiddlewareAbortError`). A caller that aborts its own
	* controller — `abortController.abort(RUN_CANCEL_REASON)`, the in-process
	* cancel channel — never touches that field, so the reason has to be read back
	* off the caller's signal, which is the signal `isCancelled()` consults via
	* `isAborted()`. A signal aborted with no reason carries a DOMException rather
	* than a string, so non-string reasons are reported as absent.
	*/
	resolveAbortReason() {
		if (this.abortReason !== void 0) return this.abortReason;
		const signalReason = this.effectiveSignal?.reason;
		return typeof signalReason === "string" ? signalReason : void 0;
	}
	/**
	* Whether this run's teardown declared its abort a DETACH — see
	* {@link RunDetachedCapability}. Only `withSandbox`'s `onAbort` publishes it,
	* and only for a plain, intentless disconnect of a detachable run, so every
	* other exit path answers `false`.
	*
	* Surfaced on the engine (rather than the ctx being handed out) so the
	* capability read stays inside core, and so the delivery sink learns the
	* verdict through {@link publishRunDetachedSignal} instead of reaching into a
	* middleware context it has no business holding.
	*
	* @internal
	*/
	wasDetached() {
		return getRunDetached(this.middlewareCtx, { optional: true }) === true;
	}
	/**
	* The delivery socket closed while this run was still going.
	*
	* Notifies every subscriber (see {@link RunDisconnectCapability}) and RETURNS
	* IMMEDIATELY. Synchronous on purpose: it is called from
	* `ReadableStream.cancel()`, which must not be made to wait on a run-store
	* write, and the caller ({@link notifyRunDisconnected}) has no consumer left to
	* report to anyway.
	*
	* Subscribers therefore run CONCURRENTLY with the still-executing run — which is
	* the entire point. The run is typically suspended inside a slow middleware
	* `setup` at this moment, so anything dispatched from the run's own unwinding
	* would be minutes late. Nothing on this path aborts the run: a durable run
	* outlives its viewer.
	*
	* Each subscriber's promise is parked on `deferredPromises`, which the run awaits
	* in its `finally`, so bookkeeping cannot be lost to a race with the run's own
	* completion even though nothing awaits it here.
	*
	* IDEMPOTENT. A second cancel, or one arriving after a terminal hook already ran,
	* is ignored: the terminal hooks own the run's outcome, and re-stamping
	* `detachedSince` on a run that has already finished would hand a completed run
	* to the reaper as reclaimable work.
	*
	* @internal
	*/
	notifyDisconnected() {
		if (this.disconnected || this.terminalHookCalled) return;
		this.disconnected = true;
		for (const listener of this.disconnectListeners) this.runDisconnectListener(listener);
	}
	/**
	* Invoke one disconnect listener, isolated and with its failure SWALLOWED after
	* logging.
	*
	* There is no caller left to report to — the socket this would report on is the
	* one that just closed — and a rejection parked on `deferredPromises` would
	* surface as the run's failure, replacing a healthy outcome with a bookkeeping
	* error. Isolation matters for the usual reason too: one subscriber's failing
	* write must not skip the next one's.
	*/
	runDisconnectListener(listener) {
		let result;
		try {
			result = listener();
		} catch (error) {
			this.logger.errors("run disconnect listener failed", { error });
			return;
		}
		if (result === void 0) return;
		this.deferredPromises.push(result.catch((error) => {
			this.logger.errors("run disconnect listener failed", { error });
		}));
	}
	/**
	* Run the final structured-output adapter call through the middleware
	* pipeline. Yields chunks to the caller only when
	* `this.finalStructuredOutput.yieldChunks` is true; otherwise consumes
	* silently while still piping through middleware.
	*
	* On success, populates this.structuredOutputResult.
	* On failure, populates this.finalizationError.
	*/
	async *runStructuredFinalization() {
		if (!this.finalStructuredOutput) throw new Error("runStructuredFinalization called without finalStructuredOutput config");
		this.middlewareCtx.phase = "structuredOutput";
		const baseConfig = this.buildMiddlewareConfig();
		const { tools: _omitTools, ...baseWithoutTools } = baseConfig;
		let structuredConfig = {
			...baseWithoutTools,
			outputSchema: this.finalStructuredOutput.jsonSchema
		};
		structuredConfig = await this.middlewareRunner.runOnStructuredOutputConfig(this.middlewareCtx, structuredConfig);
		const { outputSchema: pinnedSchema, ...chatConfigSlice } = structuredConfig;
		const postOnConfig = await this.middlewareRunner.runOnConfig(this.middlewareCtx, {
			...chatConfigSlice,
			tools: baseConfig.tools
		});
		this.applyMiddlewareConfig(postOnConfig);
		const structuredCallOptions = {
			chatOptions: {
				model: this.params.model,
				messages: this.messages,
				metadata: postOnConfig.metadata,
				modelOptions: postOnConfig.modelOptions,
				systemPrompts: postOnConfig.systemPrompts,
				logger: this.logger,
				threadId: this.threadId,
				runId: this.runIdOverride,
				parentRunId: this.parentRunIdOverride,
				...this.effectiveRequest ? { request: this.effectiveRequest } : {}
			},
			outputSchema: pinnedSchema
		};
		let fallbackAdapterError = void 0;
		const providerStream = this.adapter.structuredOutputStream ? this.adapter.structuredOutputStream(structuredCallOptions) : fallbackStructuredOutputStream(this.adapter, structuredCallOptions, (err) => {
			fallbackAdapterError = err;
		});
		let startEmitted = false;
		let structuredMessageId = null;
		const extractMessageId = (c) => {
			if (c.type === EventType.TEXT_MESSAGE_START || c.type === EventType.TEXT_MESSAGE_CONTENT || c.type === EventType.TEXT_MESSAGE_END) return typeof c.messageId === "string" && c.messageId !== "" ? c.messageId : null;
			return null;
		};
		const buildSynthesizedStart = (timestamp = Date.now()) => {
			const idForStart = structuredMessageId ?? generateMessageId();
			structuredMessageId = idForStart;
			this.captureStructuredOutputMessageIdentity(idForStart);
			return {
				type: EventType.CUSTOM,
				name: "structured-output.start",
				value: { messageId: idForStart },
				model: this.params.model,
				timestamp,
				threadId: this.threadId,
				...this.runIdOverride ? { runId: this.runIdOverride } : {}
			};
		};
		const pipeThroughMiddleware = async (synthChunk) => this.middlewareRunner.runOnChunk(this.middlewareCtx, synthChunk);
		let runErrorYielded = false;
		for await (const chunk of providerStream) {
			if (this.isCancelled()) break;
			if (!startEmitted && chunk.type === EventType.CUSTOM && chunk.name === "structured-output.start") startEmitted = true;
			if (!structuredMessageId) {
				const extracted = extractMessageId(chunk);
				if (extracted) {
					structuredMessageId = extracted;
					this.captureStructuredOutputMessageIdentity(extracted);
				}
			}
			if (this.finalStructuredOutput.yieldChunks) {
				if (!startEmitted && (chunk.type === EventType.TEXT_MESSAGE_START || chunk.type === EventType.TEXT_MESSAGE_CONTENT || chunk.type === EventType.TEXT_MESSAGE_END)) {
					startEmitted = true;
					const synthOutputs = await pipeThroughMiddleware(buildSynthesizedStart(chunk.timestamp));
					for (const outputChunk of synthOutputs) {
						yield outputChunk;
						this.middlewareCtx.chunkIndex++;
					}
				}
				if (!startEmitted && chunk.type === EventType.RUN_ERROR) {
					startEmitted = true;
					const synthOutputs = await pipeThroughMiddleware(buildSynthesizedStart(chunk.timestamp));
					for (const outputChunk of synthOutputs) {
						yield outputChunk;
						this.middlewareCtx.chunkIndex++;
					}
				}
			}
			let outboundChunk = chunk;
			if (chunk.type === EventType.CUSTOM && chunk.name === "structured-output.complete") {
				const parsed = readStructuredOutputCompleteValue(chunk.value);
				if (parsed) {
					const object = this.finalStructuredOutput.normalize ? this.finalStructuredOutput.normalize(parsed.object) : parsed.object;
					this.structuredOutputResult = {
						data: object,
						rawText: parsed.raw,
						...parsed.reasoning !== void 0 ? { reasoning: parsed.reasoning } : {}
					};
					const value = chunk.value;
					if (object !== parsed.object && value && typeof value === "object") outboundChunk = {
						...chunk,
						value: {
							...value,
							object
						}
					};
				}
			}
			if (chunk.type === EventType.RUN_FINISHED && chunk.usage) await this.middlewareRunner.runOnUsage(this.middlewareCtx, chunk.usage);
			if (chunk.type === EventType.RUN_ERROR) this.finalizationError = {
				message: chunk.message,
				...chunk.code ? { code: chunk.code } : {},
				...fallbackAdapterError !== void 0 ? { cause: fallbackAdapterError } : {}
			};
			const outputChunks = await this.middlewareRunner.runOnChunk(this.middlewareCtx, outboundChunk);
			if (this.finalStructuredOutput.yieldChunks) for (const outputChunk of outputChunks) {
				if (outputChunk.type === EventType.RUN_ERROR) runErrorYielded = true;
				yield outputChunk;
				this.middlewareCtx.chunkIndex++;
			}
			if (this.finalizationError) break;
		}
		if (this.isCancelled()) return;
		if (!this.structuredOutputResult && !this.finalizationError) this.finalizationError = {
			message: "missing structured result",
			code: "structured-output-missing-result"
		};
		if (this.structuredOutputResult && !this.finalizationError && this.finalStructuredOutput.validate) try {
			const validated = this.finalStructuredOutput.validate(this.structuredOutputResult.data);
			this.validatedStructuredOutput = validated;
			this.hasValidatedStructuredOutput = true;
		} catch (err) {
			const message = err instanceof Error ? err.message : String(err);
			this.finalizationError = {
				message,
				code: "structured-output-validation-failed",
				cause: err
			};
		}
		if (this.finalizationError && this.finalStructuredOutput.yieldChunks && !runErrorYielded) {
			if (!startEmitted) {
				const startOutputs = await pipeThroughMiddleware(buildSynthesizedStart());
				for (const outputChunk of startOutputs) {
					yield outputChunk;
					this.middlewareCtx.chunkIndex++;
				}
				startEmitted = true;
			}
			const errChunk = {
				type: EventType.RUN_ERROR,
				runId: this.runIdOverride ?? this.requestId,
				model: this.params.model,
				timestamp: Date.now(),
				threadId: this.threadId,
				message: this.finalizationError.message,
				...this.finalizationError.code ? { code: this.finalizationError.code } : {},
				error: {
					message: this.finalizationError.message,
					...this.finalizationError.code ? { code: this.finalizationError.code } : {}
				}
			};
			const outputChunks = await this.middlewareRunner.runOnChunk(this.middlewareCtx, errChunk);
			for (const outputChunk of outputChunks) {
				yield outputChunk;
				this.middlewareCtx.chunkIndex++;
			}
		}
	}
	/**
	* Native combined mode: harvest the structured output from the agent
	* loop's accumulated final-turn text (no separate provider call).
	*
	* The adapter wired `outputSchema` into the regular `chatStream` request,
	* so the model's final-turn text is the schema-constrained JSON. We parse
	* `this.accumulatedContent`, populate `this.structuredOutputResult`, emit
	* a synthetic `structured-output.complete` (and a `structured-output.start`
	* if one wasn't emitted earlier — only happens on the streaming path when
	* the model returned no text at all), and run the validate callback when
	* present. Failures populate `this.finalizationError` so the engine's
	* terminal-hook chooser routes to `onError` (per spec §7.3).
	*
	* The `'structuredOutput'` middleware phase intentionally does NOT fire on
	* this path — middleware sees the run through `beforeModel` / `modelStream`
	* as usual. See PR #605 / issue #605 for the design rationale.
	*/
	async *harvestCombinedStructuredOutput() {
		if (!this.finalStructuredOutput) throw new Error("harvestCombinedStructuredOutput called without finalStructuredOutput config");
		const yieldChunks = this.finalStructuredOutput.yieldChunks;
		if ((this.finalStructuredOutput.source ?? "text") === "event") {
			if (!this.structuredOutputResult) this.finalizationError = {
				message: "missing structured result",
				code: "structured-output-missing-result"
			};
		} else {
			const rawText = this.accumulatedContent;
			if (rawText.length === 0) this.finalizationError = {
				message: "missing structured result",
				code: "structured-output-missing-result"
			};
			else try {
				const parsed = JSON.parse(rawText);
				const data = this.finalStructuredOutput.normalize ? this.finalStructuredOutput.normalize(parsed) : parsed;
				this.structuredOutputResult = {
					data,
					rawText
				};
			} catch (err) {
				const detail = rawText.slice(0, 200) + (rawText.length > 200 ? "..." : "");
				this.finalizationError = {
					message: `Failed to parse structured output as JSON. Content: ${detail}`,
					code: "structured-output-parse-failed",
					cause: err
				};
			}
		}
		if (this.structuredOutputResult && !this.finalizationError && this.finalStructuredOutput.validate) try {
			const validated = this.finalStructuredOutput.validate(this.structuredOutputResult.data);
			this.validatedStructuredOutput = validated;
			this.hasValidatedStructuredOutput = true;
		} catch (err) {
			const message = err instanceof Error ? err.message : String(err);
			this.finalizationError = {
				message,
				code: "structured-output-validation-failed",
				cause: err
			};
		}
		if (!yieldChunks) return;
		if (!this.combinedStartEmitted) {
			this.combinedStartEmitted = true;
			const messageId = this.combinedStructuredMessageId ?? generateMessageId();
			this.combinedStructuredMessageId = messageId;
			const synthStart = {
				type: EventType.CUSTOM,
				name: "structured-output.start",
				value: { messageId },
				model: this.params.model,
				timestamp: Date.now(),
				threadId: this.threadId,
				...this.runIdOverride ? { runId: this.runIdOverride } : {}
			};
			const startOutputs = await this.middlewareRunner.runOnChunk(this.middlewareCtx, synthStart);
			for (const outputChunk of startOutputs) {
				yield outputChunk;
				this.middlewareCtx.chunkIndex++;
			}
		}
		if (this.structuredOutputResult && !this.finalizationError && !this.combinedCompleteEmitted) {
			const completeChunk = {
				type: EventType.CUSTOM,
				name: "structured-output.complete",
				value: {
					object: this.structuredOutputResult.data,
					raw: this.structuredOutputResult.rawText,
					...this.combinedStructuredMessageId ? { messageId: this.combinedStructuredMessageId } : {}
				},
				model: this.params.model,
				timestamp: Date.now(),
				threadId: this.threadId,
				...this.runIdOverride ? { runId: this.runIdOverride } : {}
			};
			const completeOutputs = await this.middlewareRunner.runOnChunk(this.middlewareCtx, completeChunk);
			for (const outputChunk of completeOutputs) {
				yield outputChunk;
				this.middlewareCtx.chunkIndex++;
			}
		}
		if (this.finalizationError) {
			const errChunk = {
				type: EventType.RUN_ERROR,
				runId: this.runIdOverride ?? this.requestId,
				model: this.params.model,
				timestamp: Date.now(),
				threadId: this.threadId,
				message: this.finalizationError.message,
				...this.finalizationError.code ? { code: this.finalizationError.code } : {},
				error: {
					message: this.finalizationError.message,
					...this.finalizationError.code ? { code: this.finalizationError.code } : {}
				}
			};
			const errOutputs = await this.middlewareRunner.runOnChunk(this.middlewareCtx, errChunk);
			for (const outputChunk of errOutputs) {
				yield outputChunk;
				this.middlewareCtx.chunkIndex++;
			}
		}
	}
	buildMiddlewareConfig() {
		return {
			messages: this.messages,
			systemPrompts: [...this.systemPrompts],
			tools: [...this.tools],
			resume: this.params.resume,
			resumeToolState: {
				approvals: this.resumeApprovals,
				clientToolResults: this.resumeClientToolResults,
				deniedToolResults: this.resumeDeniedToolResults,
				cancelledToolCallIds: this.resumeCancelledToolCallIds
			},
			metadata: this.params.metadata,
			modelOptions: this.params.modelOptions
		};
	}
	async applyEphemeralInterruptResume(config) {
		if ((config.resume?.length ?? 0) === 0) return;
		const interruptedRunId = this.parentRunIdOverride;
		if (!interruptedRunId) throw new InterruptResumeValidationError([{
			scope: "batch",
			threadId: this.threadId,
			interruptedRunId: this.runIdOverride ?? this.requestId,
			generation: 0,
			interruptIds: config.resume?.map((entry) => entry.interruptId) ?? [],
			code: "stale",
			message: "Interrupt continuation requires parentRunId to identify the interrupted run.",
			source: "server",
			retryable: false
		}]);
		const approvalRequests = [];
		const clientRequests = [];
		const pendingToolCalls = this.getToolCallsForEphemeralResume(config.resume);
		const resumeInterruptIds = new Set(config.resume?.map((entry) => entry.interruptId));
		const toolInputs = /* @__PURE__ */ new Map();
		const toolsByCallId = /* @__PURE__ */ new Map();
		const clientExecutionCallIds = /* @__PURE__ */ new Set();
		for (const toolCall of pendingToolCalls) {
			const tool = this.tools.find((candidate) => candidate.name === toolCall.function.name);
			if (!tool) continue;
			toolsByCallId.set(toolCall.id, tool);
			let input = {};
			try {
				const parsed = JSON.parse(toolCall.function.arguments.trim() || "{}");
				input = parsed && typeof parsed === "object" ? parsed : {};
			} catch {
				input = {};
			}
			toolInputs.set(toolCall.id, input);
			if (!tool.execute && resumeInterruptIds.has(`client_tool_${toolCall.id}`)) clientExecutionCallIds.add(toolCall.id);
		}
		for (const toolCall of pendingToolCalls) if (toolsByCallId.get(toolCall.id)?.needsApproval && !clientExecutionCallIds.has(toolCall.id)) approvalRequests.push({
			toolCallId: toolCall.id,
			toolName: toolCall.function.name,
			input: toolInputs.get(toolCall.id) ?? {},
			approvalId: `approval_${toolCall.id}`
		});
		for (const toolCall of pendingToolCalls) {
			const tool = toolsByCallId.get(toolCall.id);
			if (tool !== void 0 && !tool.execute && (!tool.needsApproval || clientExecutionCallIds.has(toolCall.id))) clientRequests.push({
				toolCallId: toolCall.id,
				toolName: toolCall.function.name,
				input: toolInputs.get(toolCall.id) ?? {}
			});
		}
		const genericPending = this.getGenericContinuationPending(interruptedRunId);
		const pending = this.buildActionableInterrupts(approvalRequests, clientRequests).flatMap((descriptor) => {
			const unopened = readUnopenedInterruptBinding(descriptor);
			return unopened ? [{
				interruptId: descriptor.id,
				payload: descriptor,
				binding: {
					...unopened,
					interruptedRunId,
					generation: 0
				}
			}] : [];
		});
		pending.push(...genericPending);
		const validated = await validateInterruptResumeBatch({
			threadId: this.threadId,
			interruptedRunId,
			generation: 0,
			pending,
			resume: config.resume,
			tools: this.tools
		});
		if (validated.errors.length > 0 || !validated.resumeToolState) throw new InterruptResumeValidationError(validated.errors);
		const approvals = new Map(validated.resumeToolState.approvals);
		for (const request of clientRequests) if (toolsByCallId.get(request.toolCallId)?.needsApproval) approvals.set(request.toolCallId, true);
		this.applyResumeToolState({
			...validated.resumeToolState,
			approvals
		});
		const genericResolutions = validated.resumeToolState.genericInterrupts;
		if (genericPending.length > 0 && genericResolutions) {
			const resolutions = genericPending.sort((left, right) => {
				return (left.binding.kind === "generic" ? left.binding.batchIndex ?? 0 : 0) - (right.binding.kind === "generic" ? right.binding.batchIndex ?? 0 : 0);
			}).flatMap((record) => {
				const resolution = genericResolutions.get(record.interruptId);
				if (!resolution || !record.genericRequest) return [];
				return [resolution.status === "resolved" ? {
					request: record.genericRequest,
					status: "resolved",
					response: resolution.payload
				} : {
					request: record.genericRequest,
					status: "cancelled"
				}];
			});
			const policy = await this.middlewareRunner.runOnInterruptResolution(this.middlewareCtx, {
				for: (definition) => resolutions.filter((resolution) => resolution.request.definition === definition),
				all: (...definitions) => definitions.length === 0 ? resolutions : resolutions.filter((resolution) => definitions.includes(resolution.request.definition))
			});
			if (policy.toolResume === "stop") this.earlyTermination = true;
			else if (policy.toolResume === "cancel") for (const request of pendingToolCalls) this.resumeCancelledToolCallIds.add(request.id);
		}
	}
	getGenericContinuationPending(interruptedRunId) {
		const fail = (message) => {
			throw new InterruptResumeValidationError([{
				scope: "batch",
				threadId: this.threadId,
				interruptedRunId,
				generation: 0,
				interruptIds: [],
				code: "stale",
				message,
				source: "server",
				retryable: false
			}]);
		};
		const pending = [];
		const ids = /* @__PURE__ */ new Set();
		const batchIndexes = /* @__PURE__ */ new Set();
		for (const resumeItem of this.params.resume ?? []) {
			const parsed = readGenericInterruptContinuation(resumeItem.metadata);
			if (parsed.status === "absent") continue;
			if (parsed.status === "invalid") return fail(parsed.message);
			const entry = parsed.value;
			const id = resumeItem.interruptId;
			const definition = this.interruptDefinitions.get(entry.definitionId);
			if (!definition) return fail(`Generic interrupt definition ${entry.definitionId} is unavailable.`);
			if (ids.has(id) || batchIndexes.has(entry.batchIndex)) return fail("Generic interrupt continuation contains duplicate entries.");
			ids.add(id);
			batchIndexes.add(entry.batchIndex);
			let request;
			try {
				request = rehydrateInterruptRequest(definition, {
					key: entry.key,
					reason: entry.reason,
					message: entry.message,
					...typeof entry.expiresAt === "string" ? { expiresAt: entry.expiresAt } : {},
					...Object.prototype.hasOwnProperty.call(entry, "payload") ? { payload: entry.payload } : {}
				});
			} catch (error) {
				return fail(`Generic interrupt continuation ${id} is invalid: ${error instanceof Error ? error.message : String(error)}`);
			}
			const emitted = createInterruptBinding(request, { batchIndex: entry.batchIndex });
			if (entry.responseSchemaHash !== emitted.descriptor.responseSchemaHash || entry.payloadSchemaHash !== emitted.descriptor.payloadSchemaHash) return fail(`Generic interrupt continuation ${id} does not match its definition.`);
			pending.push({
				interruptId: id,
				payload: {
					id,
					...emitted.descriptor.responseSchemaCanonicalJson !== void 0 ? { responseSchema: JSON.parse(emitted.descriptor.responseSchemaCanonicalJson) } : {}
				},
				binding: {
					v: 1,
					kind: "generic",
					interruptId: id,
					interruptedRunId,
					generation: 0,
					definitionId: entry.definitionId,
					key: entry.key,
					batchIndex: entry.batchIndex,
					...typeof entry.expiresAt === "string" ? { expiresAt: entry.expiresAt } : {},
					...emitted.descriptor.payloadSchemaHash ? { payloadSchemaHash: emitted.descriptor.payloadSchemaHash } : {},
					...entry.responseSchemaHash !== void 0 ? { responseSchemaHash: entry.responseSchemaHash } : {}
				},
				genericRequest: request
			});
		}
		return pending;
	}
	applyResumeToolState(state) {
		if (state?.approvals) for (const [approvalId, resolution] of state.approvals) this.resumeApprovals.set(approvalId, resolution);
		if (state?.clientToolResults) for (const [toolCallId, result] of state.clientToolResults) this.resumeClientToolResults.set(toolCallId, result);
		if (state?.deniedToolResults) for (const [toolCallId, result] of state.deniedToolResults) this.resumeDeniedToolResults.set(toolCallId, result);
		if (state?.cancelledToolCallIds) for (const toolCallId of state.cancelledToolCallIds) this.resumeCancelledToolCallIds.add(toolCallId);
		if (state?.genericInterrupts) for (const [interruptId, resolution] of state.genericInterrupts) this.resumeGenericInterrupts.set(interruptId, resolution);
		if (state?.genericInterruptRequests) for (const [interruptId, request] of state.genericInterruptRequests) this.resumeGenericInterruptRequests.set(interruptId, request);
	}
	async applyDurableGenericInterruptResolution() {
		if (this.resumeGenericInterruptRequests.size === 0) return;
		const resolutions = [...this.resumeGenericInterruptRequests.entries()].flatMap(([interruptId, request]) => {
			const resolution = this.resumeGenericInterrupts.get(interruptId);
			if (!resolution) return [];
			return [resolution.status === "resolved" ? {
				request,
				status: "resolved",
				response: resolution.payload
			} : {
				request,
				status: "cancelled"
			}];
		});
		const policy = await this.middlewareRunner.runOnInterruptResolution(this.middlewareCtx, {
			for: (definition) => resolutions.filter((resolution) => resolution.request.definition === definition),
			all: (...definitions) => definitions.length === 0 ? resolutions : resolutions.filter((resolution) => definitions.includes(resolution.request.definition))
		});
		if (policy.toolResume === "stop") this.earlyTermination = true;
		else if (policy.toolResume === "cancel") for (const toolCall of this.getPendingToolCallsFromMessages()) this.resumeCancelledToolCallIds.add(toolCall.id);
	}
	applyMiddlewareConfig(config) {
		this.applyResumeToolState(config.resumeToolState);
		this.messages = config.messages;
		this.systemPrompts = config.systemPrompts;
		assertUniqueToolNames(config.tools);
		this.tools = config.tools;
		this.params = {
			...this.params,
			metadata: config.metadata,
			modelOptions: config.modelOptions
		};
		this.middlewareCtx.messages = this.messages;
		this.middlewareCtx.systemPrompts = this.systemPrompts;
		this.middlewareCtx.hasTools = this.tools.length > 0;
		this.middlewareCtx.toolNames = this.tools.map((t) => t.name);
		this.middlewareCtx.modelOptions = config.modelOptions;
	}
	setToolPhase(phase) {
		this.toolPhase = phase;
	}
	/**
	* Pipe a single chunk through the middleware pipeline (strip-to-spec, devtools, etc.)
	* and yield all resulting output chunks.
	*/
	async *pipeThroughMiddleware(chunk) {
		const outputChunks = await this.middlewareRunner.runOnChunk(this.middlewareCtx, chunk);
		for (const outputChunk of outputChunks) {
			if (outputChunk.type === EventType.RUN_STARTED) this.hasPublicRunStarted = true;
			yield outputChunk;
			this.middlewareCtx.chunkIndex++;
		}
	}
	/**
	* Drain queued `sandbox.file` chunks (emitted via the SandboxRuntime sink)
	* through the middleware pipeline and into the public stream.
	*/
	async *drainSandboxFileQueue() {
		while (this.sandboxFileQueue.length > 0) {
			const chunk = this.sandboxFileQueue.shift();
			if (chunk) yield* this.pipeThroughMiddleware(chunk);
		}
	}
	/**
	* Drain an executeToolCalls async generator, yielding any CustomEvent chunks
	* through the middleware pipeline and returning the final ExecuteToolCallsResult.
	*/
	async *drainToolCallGenerator(generator) {
		let next = await generator.next();
		while (!next.done) {
			yield* this.pipeThroughMiddleware(next.value);
			next = await generator.next();
		}
		return next.value;
	}
	createCustomEventChunk(eventName, value) {
		return {
			type: "CUSTOM",
			timestamp: Date.now(),
			model: this.params.model,
			name: eventName,
			value
		};
	}
	createId(prefix) {
		return `${prefix}-${Date.now()}-${Math.random().toString(36).slice(2, 9)}`;
	}
};
/**
* Text activity - handles agentic text generation, one-shot text generation, and agentic structured output.
*
* This activity supports four modes:
* 1. **Streaming agentic text**: Stream responses with automatic tool execution
* 2. **Streaming one-shot text**: Simple streaming request/response without tools
* 3. **Non-streaming text**: Returns collected text as a string (stream: false)
* 4. **Agentic structured output**: Run tools, then return structured data
*
* @example Full agentic text (streaming with tools)
* ```ts
* import { chat } from '@tanstack/ai'
* import { openaiText } from '@tanstack/ai-openai'
*
* for await (const chunk of chat({
*   adapter: openaiText('gpt-5.5'),
*   messages: [{ role: 'user', content: 'What is the weather?' }],
*   tools: [weatherTool]
* })) {
*   if (chunk.type === 'TEXT_MESSAGE_CONTENT') {
*     console.log(chunk.delta)
*   }
* }
* ```
*
* @example One-shot text (streaming without tools)
* ```ts
* for await (const chunk of chat({
*   adapter: openaiText('gpt-5.5'),
*   messages: [{ role: 'user', content: 'Hello!' }]
* })) {
*   console.log(chunk)
* }
* ```
*
* @example Non-streaming text (stream: false)
* ```ts
* const text = await chat({
*   adapter: openaiText('gpt-5.5'),
*   messages: [{ role: 'user', content: 'Hello!' }],
*   stream: false
* })
* // text is a string with the full response
* ```
*
* @example Agentic structured output (tools + structured response)
* ```ts
* import { z } from 'zod'
*
* const result = await chat({
*   adapter: openaiText('gpt-5.5'),
*   messages: [{ role: 'user', content: 'Research and summarize the topic' }],
*   tools: [researchTool, analyzeTool],
*   outputSchema: z.object({
*     summary: z.string(),
*     keyPoints: z.array(z.string())
*   })
* })
* // result is { summary: string, keyPoints: string[] }
* ```
*/
function chat(options) {
	validateInterruptDefinitions(options.interrupts);
	validateCapabilities(readRuntimeMiddleware(options.middleware) ?? [], options.adapter);
	if (options.tools) assertUniqueToolNames(options.tools);
	const { outputSchema, stream } = options;
	if (outputSchema && stream === true) return runStreamingStructuredOutput(toRuntimeTextActivityOptions(options, {
		outputSchema,
		stream: true
	}));
	if (outputSchema) return runAgenticStructuredOutput(toRuntimeTextActivityOptions(options, {
		outputSchema,
		stream: false
	}));
	if (stream === false) return runNonStreamingText(toRuntimeTextActivityOptions(options, {
		outputSchema: void 0,
		stream: false
	}));
	return runStreamingText(toRuntimeTextActivityOptions(options, {
		outputSchema: void 0,
		stream: true
	}));
}
function readRuntimeMiddleware(middleware) {
	if (middleware === void 0) return void 0;
	if (!Array.isArray(middleware)) throw new TypeError("Chat middleware must be an array.");
	return middleware;
}
function toRuntimeTextActivityOptions(options, overrides) {
	const { middleware, ...rest } = options;
	return {
		...rest,
		...overrides,
		...middleware === void 0 ? {} : { middleware: readRuntimeMiddleware(middleware) }
	};
}
function validateInterruptDefinitions(definitions) {
	if (!definitions) return;
	const seen = /* @__PURE__ */ new Set();
	for (const definition of definitions) {
		if (seen.has(definition.id)) throw new Error(`Duplicate interrupt definition id: ${definition.id}`);
		seen.add(definition.id);
	}
}
/**
* Publish both delivery-side seams for `stream`.
*
* Shared by the two streaming paths so they cannot drift apart — the
* structured-output path having been wired for one seam and not the other is
* exactly the bug `publishRunDetachedSignal` picked up last time (a durable
* `chat({ outputSchema, stream: true })` could never detach).
*/
function publishDeliverySeams(stream, engineRef) {
	publishRunDetachedSignal(stream, () => engineRef.current?.wasDetached() === true);
	publishRunDisconnectHandler(stream, () => {
		engineRef.current?.notifyDisconnected();
	});
}
/**
* Run streaming text (agentic or one-shot depending on tools).
*
* A thin, NON-generator wrapper, because the stream object is also the key the
* durable delivery sink looks the run's detach verdict up under (see
* `../../delivery-detach`) and delivers its disconnect notification through (see
* `../../delivery-disconnect`). A generator function cannot reach the generator it
* returns, so the identity has to be minted out here and the engine reached back
* through `engineRef`, which the body fills as soon as its engine exists.
*/
function runStreamingText(options) {
	const engineRef = {};
	const stream = streamTextChunks(options, engineRef);
	publishDeliverySeams(stream, engineRef);
	return stream;
}
async function* streamTextChunks(options, engineRef) {
	const { adapter, middleware, context, debug, mcp, ...textOptions } = options;
	const model = adapter.model;
	const logger = resolveDebugOption(debug);
	const mcpManager = MCPManager.from(mcp);
	const mcpTools = await mcpManager.discover();
	if (mcpTools.length > 0) textOptions.tools = [...textOptions.tools ?? [], ...mcpTools];
	const engine = new TextEngine({
		adapter,
		params: {
			...textOptions,
			model,
			logger
		},
		middleware,
		context
	}, logger);
	engineRef.current = engine;
	try {
		for await (const chunk of engine.run()) yield chunk;
	} finally {
		await mcpManager.dispose();
	}
}
/**
* Run non-streaming text - collects all content and returns as a string.
* Runs the full agentic loop (if tools are provided) but returns collected text.
*/
function runNonStreamingText(options) {
	const stream = runStreamingText({
		...options,
		stream: true
	});
	return streamToText(stream);
}
/**
* Run agentic structured output:
* 1. Execute the full agentic loop (with tools)
* 2. Once complete, call adapter.structuredOutput with the conversation context
* 3. Validate and return the structured result
*/
async function runAgenticStructuredOutput(options) {
	const { adapter, outputSchema, middleware, context, debug, mcp, ...textOptions } = options;
	const model = adapter.model;
	const logger = resolveDebugOption(debug);
	if (!outputSchema) throw new Error("outputSchema is required for structured output");
	const { jsonSchema, nullWideningMap } = convertSchemaForStructuredOutput(outputSchema);
	if (!jsonSchema) throw new Error("Failed to convert output schema to JSON Schema");
	const normalize = (data) => undoNullWidening(data, nullWideningMap);
	const validate = isStandardSchema(outputSchema) ? (data) => parseWithStandardSchema(outputSchema, data) : void 0;
	const nativeCombined = adapter.supportsCombinedToolsAndSchema?.(options.modelOptions) === true;
	const source = adapter.combinedStructuredOutputSource?.(options.modelOptions) ?? "text";
	const mcpManager = MCPManager.from(mcp);
	const mcpTools = await mcpManager.discover();
	if (mcpTools.length > 0) textOptions.tools = [...textOptions.tools ?? [], ...mcpTools];
	const engine = new TextEngine({
		adapter,
		params: {
			...textOptions,
			model,
			logger
		},
		middleware,
		context,
		finalStructuredOutput: {
			jsonSchema,
			yieldChunks: false,
			normalize,
			...validate ? { validate } : {},
			...nativeCombined ? { nativeCombined: true } : {},
			source
		}
	}, logger);
	try {
		for await (const _chunk of engine.run());
	} finally {
		await mcpManager.dispose();
	}
	const finalizationError = engine.getFinalizationError();
	if (finalizationError) {
		const err = new Error(finalizationError.message, finalizationError.cause !== void 0 ? { cause: finalizationError.cause } : void 0);
		if (finalizationError.code !== void 0) Object.defineProperty(err, "code", {
			value: finalizationError.code,
			enumerable: true
		});
		throw err;
	}
	const validated = engine.getValidatedStructuredOutput();
	if (validated) return validated.value;
	const result = engine.getStructuredOutputResult();
	if (!result) throw new Error("structured output finalization produced no result");
	return result.data;
}
/**
* Parse the `value` payload of a `structured-output.complete` CUSTOM event
* into a typed shape, returning `null` if the runtime payload doesn't match.
*
* Uses an `unknown`-input runtime check rather than `as` casts so the engine
* stays cast-free in its hot path.
*/
function readCustomEventMessageId(value) {
	if (typeof value !== "object" || value === null) return void 0;
	if (!("messageId" in value)) return void 0;
	const messageId = value.messageId;
	return typeof messageId === "string" && messageId !== "" ? messageId : void 0;
}
function readStructuredOutputCompleteValue(value) {
	if (typeof value !== "object" || value === null) return null;
	if (!("object" in value) || !("raw" in value)) return null;
	const raw = value.raw;
	if (typeof raw !== "string") return null;
	const reasoningField = value.reasoning;
	const reasoning = typeof reasoningField === "string" ? reasoningField : void 0;
	return {
		object: value.object,
		raw,
		...reasoning !== void 0 ? { reasoning } : {}
	};
}
/**
* Synthesize a streaming structured-output stream by wrapping a non-streaming
* `structuredOutput` call. Used when an adapter doesn't implement
* `structuredOutputStream` natively.
*
* `onAdapterError`, when provided, is invoked with the raw error from
* `adapter.structuredOutput` before the synthesized RUN_ERROR is yielded.
* The engine uses this to preserve the original error (stack, cause, custom
* properties like provider `status`/`code`) as `finalizationError.cause`,
* because the RUN_ERROR wire shape only carries `message` and `code`.
*/
async function* fallbackStructuredOutputStream(adapter, options, onAdapterError) {
	const { chatOptions } = options;
	const fallbackRand = Math.random().toString(36).slice(2);
	const runId = chatOptions.runId ?? `fallback-${Date.now()}-${fallbackRand}`;
	const threadId = chatOptions.threadId ?? `fallback-${Date.now()}-${fallbackRand}`;
	const messageId = `fallback-${Date.now()}-${fallbackRand}`;
	const model = chatOptions.model;
	const startedAt = Date.now();
	yield {
		type: EventType.RUN_STARTED,
		runId,
		threadId,
		model,
		timestamp: startedAt
	};
	let result;
	try {
		result = await adapter.structuredOutput(options);
	} catch (error) {
		onAdapterError?.(error);
		const message = error instanceof Error ? error.message : String(error);
		yield {
			type: EventType.RUN_ERROR,
			runId,
			threadId,
			model,
			timestamp: Date.now(),
			message,
			error: { message }
		};
		return;
	}
	yield {
		type: EventType.TEXT_MESSAGE_START,
		messageId,
		role: "assistant",
		model,
		timestamp: Date.now()
	};
	yield {
		type: EventType.TEXT_MESSAGE_CONTENT,
		messageId,
		delta: result.rawText,
		model,
		timestamp: Date.now()
	};
	yield {
		type: EventType.TEXT_MESSAGE_END,
		messageId,
		model,
		timestamp: Date.now()
	};
	yield {
		type: EventType.CUSTOM,
		name: "structured-output.complete",
		value: {
			object: result.data,
			raw: result.rawText
		},
		model,
		timestamp: Date.now()
	};
	yield {
		type: EventType.RUN_FINISHED,
		runId,
		threadId,
		model,
		timestamp: Date.now(),
		finishReason: "stop",
		...result.usage ? { usage: result.usage } : {}
	};
}
/**
* Run streaming structured output via the TextEngine, with the engine's
* `finalStructuredOutput.yieldChunks: true` mode. The agent loop's
* RUN_STARTED/RUN_FINISHED are suppressed; the structured-output finalization
* step's pair brackets the run for the consumer.
*
* Standard Schema *validation* is intentionally NOT run on this path — it is
* the consumer's responsibility. This is a deliberate asymmetry vs.
* `runAgenticStructuredOutput` (Promise<T> path), which DOES validate inside
* the engine and routes validation failures through `onError`. The reason:
* streaming consumers typically render partial JSON progressively (via
* `parsePartialJSON` or `useChat`'s `partial` slot) and validate downstream
* after assembly. Running validation server-side would force a hard error
* on partial-by-design payloads. See `docs/structured-outputs/overview.md`.
*
* Null-widening normalization, however, IS run on both paths: the
* `structured-output.complete` CUSTOM event is forwarded with its `value.object`
* already un-widened (synthesized strict-mode nulls dropped, genuine
* `.nullable()` nulls kept), so a consumer validating the assembled object
* against the original schema doesn't choke on a `null` for an `.optional()`
* field. Same `convertSchemaForStructuredOutput` pass and same
* `undoNullWidening` map as the Promise<T> path — the two must not diverge.
*
* Pre-flight validation (missing schema, unconvertible schema) throws
* synchronously at call time rather than as a yielded RUN_ERROR mid-stream —
* those are programmer errors, not runtime conditions.
*/
function runStreamingStructuredOutput(options) {
	const { outputSchema } = options;
	if (!outputSchema) throw new Error("outputSchema is required for streaming structured output");
	const { jsonSchema, nullWideningMap } = convertSchemaForStructuredOutput(outputSchema);
	if (!jsonSchema) throw new Error("Failed to convert output schema to JSON Schema");
	const normalize = (data) => undoNullWidening(data, nullWideningMap);
	const engineRef = {};
	const stream = runStreamingStructuredOutputImpl(options, jsonSchema, normalize, engineRef);
	publishDeliverySeams(stream, engineRef);
	return stream;
}
async function* runStreamingStructuredOutputImpl(options, jsonSchema, normalize, engineRef) {
	const { adapter, outputSchema, middleware, context, debug, mcp, ...textOptions } = options;
	const model = adapter.model;
	const logger = resolveDebugOption(debug);
	const nativeCombined = adapter.supportsCombinedToolsAndSchema?.(options.modelOptions) === true;
	const source = adapter.combinedStructuredOutputSource?.(options.modelOptions) ?? "text";
	const mcpManager = MCPManager.from(mcp);
	const mcpTools = await mcpManager.discover();
	if (mcpTools.length > 0) textOptions.tools = [...textOptions.tools ?? [], ...mcpTools];
	const engine = new TextEngine({
		adapter,
		params: {
			...textOptions,
			model,
			logger
		},
		middleware,
		context,
		finalStructuredOutput: {
			jsonSchema,
			yieldChunks: true,
			normalize,
			...nativeCombined ? { nativeCombined: true } : {},
			source
		}
	}, logger);
	engineRef.current = engine;
	try {
		for await (const chunk of engine.run()) yield chunk;
	} finally {
		await mcpManager.dispose();
	}
}
//#endregion
export { chat, createChatOptions, kind };

//# sourceMappingURL=index.js.map