import { MultiCursorBuffer } from "./multi-cursor-buffer.js";
import { AIMessage } from "@langchain/core/messages";
//#region src/client/stream/messages.ts
function applyCoreContentDelta(target, delta) {
	if (target.type !== delta.type) return structuredClone(delta);
	switch (delta.type) {
		case "text": return {
			...target,
			...delta,
			text: `${"text" in target ? target.text : ""}${delta.text}`
		};
		case "reasoning": return {
			...target,
			...delta,
			reasoning: `${"reasoning" in target ? target.reasoning : ""}${delta.reasoning}`
		};
		case "tool_call_chunk":
		case "server_tool_call_chunk": {
			const merged = {
				...target,
				...delta
			};
			if (delta.id == null && "id" in target && target.id != null) merged.id = target.id;
			if (delta.name == null && "name" in target && target.name != null) merged.name = target.name;
			merged.args = `${("args" in target ? target.args : "") ?? ""}${delta.args ?? ""}`;
			return merged;
		}
		default: return {
			...target,
			...delta
		};
	}
}
function coreContentBlockFromDelta(delta, current) {
	switch (delta.type) {
		case "text-delta": return {
			type: "text",
			text: delta.text
		};
		case "reasoning-delta": return {
			type: "reasoning",
			reasoning: delta.reasoning
		};
		case "data-delta": {
			const merged = {
				...current ?? {},
				data: delta.data
			};
			if (delta.encoding) merged.encoding = delta.encoding;
			return merged;
		}
		case "block-delta": return delta.fields;
	}
}
function applyCoreEventDelta(current, event) {
	if (event.content) return current ? applyCoreContentDelta(current, event.content) : event.content;
	switch (event.delta.type) {
		case "text-delta":
			if (current?.type === "text") return {
				...current,
				text: `${"text" in current ? current.text : ""}${event.delta.text}`
			};
			return coreContentBlockFromDelta(event.delta, current);
		case "reasoning-delta":
			if (current?.type === "reasoning") return {
				...current,
				reasoning: `${"reasoning" in current ? current.reasoning : ""}${event.delta.reasoning}`
			};
			return coreContentBlockFromDelta(event.delta, current);
		case "data-delta": {
			const merged = { ...current ?? {} };
			merged.data = `${merged.data ?? ""}${event.delta.data}`;
			if (event.delta.encoding) merged.encoding = event.delta.encoding;
			return merged;
		}
		case "block-delta": return {
			...current ?? {},
			...event.delta.fields
		};
	}
}
function normalizeUsage(usage) {
	if (!usage) return void 0;
	return {
		...usage,
		input_tokens: usage.input_tokens ?? 0,
		output_tokens: usage.output_tokens ?? 0,
		total_tokens: usage.total_tokens ?? 0
	};
}
/**
* Symbol keys for assembler → StreamingMessage communication.
* Module-private: invisible to external consumers, accessible to
* {@link StreamingMessageAssembler} within this file.
*/
const PUSH_TEXT = Symbol("pushText");
const PUSH_REASONING = Symbol("pushReasoning");
const PUSH_EVENT = Symbol("pushEvent");
const UPDATE_CONTEXT = Symbol("updateContext");
const FINISH = Symbol("finish");
const ERROR = Symbol("error");
/**
* Live streaming view of a single message lifecycle, matching the
* in-process `ChatModelStream` dual-interface pattern.
*
* - `text` / `reasoning`: iterate for streaming deltas, or await for
*   the full concatenated string after the message completes.
* - `usage`: promise that resolves with token usage on message-finish.
* - `blocks`: the assembled content blocks (updated as deltas arrive).
*
* Created by {@link StreamingMessageAssembler} and yielded by
* the `session.messages` lazy getter.
*/
var StreamingMessage = class {
	id;
	namespace;
	node;
	metadata;
	assembled;
	#events = new MultiCursorBuffer();
	#textChunks = [];
	#reasoningChunks = [];
	#textWaiters = [];
	#reasoningWaiters = [];
	#textDone = false;
	#reasoningDone = false;
	#resolveText;
	#resolveReasoning;
	#textPromise;
	#reasoningPromise;
	constructor(assembled) {
		this.id = assembled.id;
		this.assembled = assembled;
		this.namespace = assembled.namespace;
		this.node = assembled.node;
		this.metadata = assembled.metadata;
		this.#textPromise = new Promise((r) => {
			this.#resolveText = r;
		});
		this.#reasoningPromise = new Promise((r) => {
			this.#resolveReasoning = r;
		});
	}
	get text() {
		const chunks = this.#textChunks;
		const waiters = this.#textWaiters;
		const getDone = () => this.#textDone;
		let cursor = 0;
		return {
			[Symbol.asyncIterator]() {
				return { async next() {
					while (true) {
						if (cursor < chunks.length) return {
							done: false,
							value: chunks[cursor++]
						};
						if (getDone()) return {
							done: true,
							value: void 0
						};
						await new Promise((resolve) => {
							waiters.push(resolve);
						});
					}
				} };
			},
			then: this.#textPromise.then.bind(this.#textPromise),
			full: { async *[Symbol.asyncIterator]() {
				let accumulated = "";
				for await (const chunk of { [Symbol.asyncIterator]: () => ({ next: async () => {
					while (true) {
						if (cursor < chunks.length) return {
							done: false,
							value: chunks[cursor++]
						};
						if (getDone()) return {
							done: true,
							value: void 0
						};
						await new Promise((resolve) => {
							waiters.push(resolve);
						});
					}
				} }) }) {
					accumulated += chunk;
					yield accumulated;
				}
			} }
		};
	}
	get reasoning() {
		const chunks = this.#reasoningChunks;
		const waiters = this.#reasoningWaiters;
		const getDone = () => this.#reasoningDone;
		let cursor = 0;
		return {
			[Symbol.asyncIterator]() {
				return { async next() {
					while (true) {
						if (cursor < chunks.length) return {
							done: false,
							value: chunks[cursor++]
						};
						if (getDone()) return {
							done: true,
							value: void 0
						};
						await new Promise((resolve) => {
							waiters.push(resolve);
						});
					}
				} };
			},
			then: this.#reasoningPromise.then.bind(this.#reasoningPromise),
			full: { async *[Symbol.asyncIterator]() {
				let accumulated = "";
				for await (const chunk of { [Symbol.asyncIterator]: () => ({ next: async () => {
					while (true) {
						if (cursor < chunks.length) return {
							done: false,
							value: chunks[cursor++]
						};
						if (getDone()) return {
							done: true,
							value: void 0
						};
						await new Promise((resolve) => {
							waiters.push(resolve);
						});
					}
				} }) }) {
					accumulated += chunk;
					yield accumulated;
				}
			} }
		};
	}
	get usage() {
		const promise = (async () => {
			let usage;
			for await (const snapshot of this.#usageIterator()) usage = snapshot;
			return usage;
		})();
		return {
			[Symbol.asyncIterator]: () => this.#usageIterator(),
			then: promise.then.bind(promise)
		};
	}
	get toolCalls() {
		const events = this.#events;
		const iterator = async function* () {
			for await (const event of events) if (event.event === "content-block-finish" && event.content.type === "tool_call") yield event.content;
		};
		return {
			[Symbol.asyncIterator]: iterator,
			then: async (onfulfilled, onrejected) => {
				try {
					const calls = [];
					for await (const call of iterator()) calls.push(call);
					return onfulfilled ? onfulfilled(calls) : calls;
				} catch (err) {
					if (onrejected) return onrejected(err);
					throw err;
				}
			},
			full: { async *[Symbol.asyncIterator]() {
				const calls = [];
				for await (const call of iterator()) {
					calls.push(call);
					yield [...calls];
				}
			} }
		};
	}
	get output() {
		return { then: (onf, onr) => this.#assembleMessage().then(onf, onr) };
	}
	get blocks() {
		return this.assembled.blocks;
	}
	[Symbol.asyncIterator]() {
		return this.#events[Symbol.asyncIterator]();
	}
	then(onfulfilled, onrejected) {
		return this.#assembleMessage().then(onfulfilled, onrejected);
	}
	async *#usageIterator() {
		for await (const event of this.#events) if (event.event === "message-start" && event.usage) yield normalizeUsage(event.usage);
		else if (event.event === "message-finish" && event.usage) yield normalizeUsage(event.usage);
	}
	async #assembleMessage() {
		const contentBlocks = [];
		let id;
		let usage;
		let metadata = {};
		let finishReason;
		for await (const event of this.#events) switch (event.event) {
			case "message-start":
				id = event.id ?? id;
				if (event.usage) usage = normalizeUsage(event.usage);
				break;
			case "content-block-start":
				contentBlocks[event.index] = event.content;
				break;
			case "content-block-delta": {
				const current = contentBlocks[event.index];
				contentBlocks[event.index] = applyCoreEventDelta(current, event);
				break;
			}
			case "content-block-finish":
				contentBlocks[event.index] = event.content;
				break;
			case "message-finish":
				finishReason = event.reason;
				if (event.usage) usage = normalizeUsage(event.usage);
				if (event.responseMetadata) metadata = {
					...metadata,
					...event.responseMetadata
				};
				break;
			default: break;
		}
		return new AIMessage({
			id,
			content: contentBlocks.filter((block) => block != null),
			usage_metadata: usage,
			response_metadata: {
				...metadata,
				...finishReason ? { finish_reason: finishReason } : {},
				output_version: "v1"
			}
		});
	}
	[PUSH_EVENT](event) {
		this.#events.push(event);
	}
	[UPDATE_CONTEXT](event) {
		this.node = event.params.node ?? this.node;
	}
	[PUSH_TEXT](delta) {
		this.#textChunks.push(delta);
		const pending = this.#textWaiters.splice(0, this.#textWaiters.length);
		for (const waiter of pending) waiter();
	}
	[PUSH_REASONING](delta) {
		this.#reasoningChunks.push(delta);
		const pending = this.#reasoningWaiters.splice(0, this.#reasoningWaiters.length);
		for (const waiter of pending) waiter();
	}
	[FINISH]() {
		this.#textDone = true;
		this.#reasoningDone = true;
		this.#resolveText(this.#textChunks.join(""));
		this.#resolveReasoning(this.#reasoningChunks.join(""));
		const textPending = this.#textWaiters.splice(0, this.#textWaiters.length);
		for (const waiter of textPending) waiter();
		const reasoningPending = this.#reasoningWaiters.splice(0, this.#reasoningWaiters.length);
		for (const waiter of reasoningPending) waiter();
		this.#events.close();
	}
	[ERROR]() {
		this[FINISH]();
	}
};
function toStreamingMessageHandle(message) {
	return new Proxy(message, {
		get(target, prop) {
			if (prop === "then") return void 0;
			const value = Reflect.get(target, prop, target);
			return typeof value === "function" ? value.bind(target) : value;
		},
		has(target, prop) {
			if (prop === "then") return false;
			return prop in target;
		}
	});
}
function cloneBlock(block) {
	return structuredClone(block);
}
function blockFromDelta(delta, current) {
	return coreContentBlockFromDelta(delta, current);
}
function applyContentDelta(target, delta) {
	if (target.type !== delta.type) return cloneBlock(delta);
	switch (delta.type) {
		case "text": return {
			...target,
			...delta,
			text: `${"text" in target ? target.text : ""}${delta.text}`
		};
		case "reasoning": return {
			...target,
			...delta,
			reasoning: `${"reasoning" in target ? target.reasoning : ""}${delta.reasoning}`
		};
		case "tool_call_chunk":
		case "server_tool_call_chunk": {
			const merged = {
				...target,
				...delta
			};
			if (delta.id == null && "id" in target && target.id != null) merged.id = target.id;
			if (delta.name == null && "name" in target && target.name != null) merged.name = target.name;
			merged.args = `${("args" in target ? target.args : "") ?? ""}${delta.args ?? ""}`;
			return merged;
		}
		default: return {
			...target,
			...delta
		};
	}
}
function messageKeyFor(event) {
	const { namespace, node, data } = event.params;
	const namespaceKey = namespace.join("/");
	const messageId = data.event === "message-start" ? data.id ?? "" : "";
	return `${namespaceKey}::${node ?? ""}::${messageId}`;
}
function toChatModelStreamEvent(event) {
	return event.params.data;
}
/**
* Incrementally assembles `messages` events into complete message objects.
*/
var MessageAssembler = class {
	activeMessages = /* @__PURE__ */ new Map();
	activeByNamespaceNode = /* @__PURE__ */ new Map();
	blockIndexByProtocolIndexAndType = /* @__PURE__ */ new Map();
	/**
	* Applies a single message event and returns the resulting assembly update.
	*
	* @param event - Incoming `messages` event to fold into the assembler state.
	*/
	consume(event) {
		const data = event.params.data;
		const namespaceNodeKey = `${event.params.namespace.join("/")}::${event.params.node ?? ""}`;
		if (data.event === "message-start") {
			const key = messageKeyFor(event);
			this.activeByNamespaceNode.set(namespaceNodeKey, key);
			const message = {
				id: data.id,
				namespace: [...event.params.namespace],
				node: event.params.node,
				metadata: data.metadata,
				blocks: []
			};
			this.activeMessages.set(key, message);
			return {
				kind: "message-start",
				key,
				message,
				event
			};
		}
		const activeKey = this.activeByNamespaceNode.get(namespaceNodeKey);
		if (!activeKey) {
			const syntheticKey = `${namespaceNodeKey}::`;
			this.activeByNamespaceNode.set(namespaceNodeKey, syntheticKey);
			const synthetic = {
				id: data.id,
				namespace: [...event.params.namespace],
				node: event.params.node,
				blocks: []
			};
			this.activeMessages.set(syntheticKey, synthetic);
			return this.consume(event);
		}
		const message = this.activeMessages.get(activeKey);
		if (!message) throw new Error(`No active message state found for key ${activeKey}`);
		if (data.event === "usage") {
			message.usage = data.usage;
			return {
				kind: "usage",
				key: activeKey,
				message,
				event
			};
		}
		switch (data.event) {
			case "content-block-start":
				message.blocks[data.index] = cloneBlock(data.content);
				this.blockIndexByProtocolIndexAndType.set(blockIndexKey(activeKey, data.index, data.content.type), data.index);
				return {
					kind: "content-block-start",
					key: activeKey,
					message,
					index: data.index,
					block: data.content,
					event
				};
			case "content-block-delta": {
				const deltaEvent = data;
				const deltaBlock = deltaEvent.content ?? (deltaEvent.delta != null ? blockFromDelta(deltaEvent.delta, message.blocks[data.index]) : void 0);
				if (deltaBlock == null) throw new Error("Received content-block-delta without content");
				const targetIndex = this.resolveBlockIndex(activeKey, message.blocks, data.index, deltaBlock.type);
				const current = message.blocks[targetIndex];
				message.blocks[targetIndex] = deltaEvent.content != null ? current == null ? cloneBlock(deltaEvent.content) : applyContentDelta(current, deltaEvent.content) : applyCoreEventDelta(current, data);
				return {
					kind: "content-block-delta",
					key: activeKey,
					message,
					index: targetIndex,
					block: deltaBlock,
					event
				};
			}
			case "content-block-finish": {
				const targetIndex = this.resolveFinishBlockIndex(activeKey, data.index, data.content.type);
				message.blocks[targetIndex] = cloneBlock(data.content);
				return {
					kind: "content-block-finish",
					key: activeKey,
					message,
					index: targetIndex,
					block: data.content,
					event
				};
			}
			case "message-finish":
				message.usage = data.usage;
				message.finishMetadata = data.responseMetadata;
				this.activeMessages.delete(activeKey);
				this.activeByNamespaceNode.delete(namespaceNodeKey);
				this.clearBlockIndexAliases(activeKey);
				return {
					kind: "message-finish",
					key: activeKey,
					message: structuredClone(message),
					event
				};
			case "error":
				message.error = {
					message: data.message,
					code: data.code
				};
				this.activeMessages.delete(activeKey);
				this.activeByNamespaceNode.delete(namespaceNodeKey);
				this.clearBlockIndexAliases(activeKey);
				return {
					kind: "message-error",
					key: activeKey,
					message: structuredClone(message),
					event
				};
		}
	}
	resolveBlockIndex(activeKey, blocks, protocolIndex, blockType) {
		const current = blocks[protocolIndex];
		if (current == null || current.type === blockType || areCompatibleBlockTypes(current.type, blockType)) {
			this.blockIndexByProtocolIndexAndType.set(blockIndexKey(activeKey, protocolIndex, blockType), protocolIndex);
			return protocolIndex;
		}
		const key = blockIndexKey(activeKey, protocolIndex, blockType);
		const existing = this.blockIndexByProtocolIndexAndType.get(key);
		if (existing != null) return existing;
		const nextIndex = blocks.length;
		this.blockIndexByProtocolIndexAndType.set(key, nextIndex);
		return nextIndex;
	}
	resolveFinishBlockIndex(activeKey, protocolIndex, blockType) {
		const key = blockIndexKey(activeKey, protocolIndex, blockType);
		const existing = this.blockIndexByProtocolIndexAndType.get(key);
		if (existing != null) return existing;
		this.blockIndexByProtocolIndexAndType.set(key, protocolIndex);
		return protocolIndex;
	}
	clearBlockIndexAliases(activeKey) {
		const prefix = `${activeKey}::`;
		for (const key of this.blockIndexByProtocolIndexAndType.keys()) if (key.startsWith(prefix)) this.blockIndexByProtocolIndexAndType.delete(key);
	}
};
function blockIndexKey(activeKey, protocolIndex, blockType) {
	return `${activeKey}::${protocolIndex}::${blockType}`;
}
function areCompatibleBlockTypes(currentType, nextType) {
	const toolCallTypes = new Set([
		"tool_call",
		"tool_call_chunk",
		"tool_use",
		"input_json_delta"
	]);
	const serverToolCallTypes = new Set(["server_tool_call", "server_tool_call_chunk"]);
	return toolCallTypes.has(currentType) && toolCallTypes.has(nextType) || serverToolCallTypes.has(currentType) && serverToolCallTypes.has(nextType);
}
/**
* Assembles `messages` events into {@link StreamingMessage} instances
* with live text/reasoning delta streams, matching the in-process
* `ChatModelStream` dual-interface pattern.
*/
var StreamingMessageAssembler = class {
	#assembler = new MessageAssembler();
	#activeStreaming = /* @__PURE__ */ new Map();
	/**
	* Folds a single event and returns a new {@link StreamingMessage}
	* when a `message-start` is seen, or `undefined` for continuation
	* events (deltas, finish, error).
	*/
	consume(event) {
		const update = this.#assembler.consume(event);
		if (update == null) return void 0;
		switch (update.kind) {
			case "message-start": {
				const streaming = new StreamingMessage(update.message);
				streaming[UPDATE_CONTEXT](update.event);
				streaming[PUSH_EVENT](toChatModelStreamEvent(update.event));
				this.#activeStreaming.set(update.key, streaming);
				return streaming;
			}
			case "content-block-start": {
				const streaming = this.#activeStreaming.get(update.key);
				if (streaming) {
					streaming[UPDATE_CONTEXT](update.event);
					streaming[PUSH_EVENT](toChatModelStreamEvent(update.event));
				}
				if (streaming && update.block.type === "text" && "text" in update.block && update.block.text) streaming[PUSH_TEXT](update.block.text);
				if (streaming && update.block.type === "reasoning" && "reasoning" in update.block && update.block.reasoning) streaming[PUSH_REASONING](update.block.reasoning);
				return;
			}
			case "content-block-delta": {
				const streaming = this.#activeStreaming.get(update.key);
				if (!streaming) return void 0;
				streaming[UPDATE_CONTEXT](update.event);
				streaming[PUSH_EVENT](toChatModelStreamEvent(update.event));
				if (update.block.type === "text" && "text" in update.block) streaming[PUSH_TEXT](update.block.text);
				if (update.block.type === "reasoning" && "reasoning" in update.block) streaming[PUSH_REASONING](update.block.reasoning);
				return;
			}
			case "content-block-finish": {
				const streaming = this.#activeStreaming.get(update.key);
				if (streaming) {
					streaming[UPDATE_CONTEXT](update.event);
					streaming[PUSH_EVENT](toChatModelStreamEvent(update.event));
				}
				return;
			}
			case "usage": {
				const streaming = this.#activeStreaming.get(update.key);
				if (streaming) {
					streaming[UPDATE_CONTEXT](update.event);
					streaming[PUSH_EVENT](toChatModelStreamEvent(update.event));
				}
				return;
			}
			case "message-finish": {
				const streaming = this.#activeStreaming.get(update.key);
				if (streaming) {
					streaming[UPDATE_CONTEXT](update.event);
					streaming[PUSH_EVENT](toChatModelStreamEvent(update.event));
					streaming[FINISH]();
					this.#activeStreaming.delete(update.key);
				}
				return;
			}
			case "message-error": {
				const streaming = this.#activeStreaming.get(update.key);
				if (streaming) {
					streaming[UPDATE_CONTEXT](update.event);
					streaming[PUSH_EVENT](toChatModelStreamEvent(update.event));
					streaming[ERROR]();
					this.#activeStreaming.delete(update.key);
				}
				return;
			}
		}
	}
};
//#endregion
export { MessageAssembler, StreamingMessage, StreamingMessageAssembler, toStreamingMessageHandle };

//# sourceMappingURL=messages.js.map