const require_sse = require("../../../utils/sse.cjs");
const require_reconnect = require("../../../utils/reconnect.cjs");
const require_stream = require("../../../utils/stream.cjs");
const require_queue = require("./queue.cjs");
const require_utils = require("./utils.cjs");
//#region src/client/stream/transport/http.ts
/**
* Transport adapter that speaks the thread-centric protocol over HTTP
* commands plus SSE event streams. Bound to a `threadId` at construction
* or later via {@link setThreadId}; request URLs derive from the
* currently-bound thread. Each {@link openEventStream} call opens an
* independent filtered SSE connection via
* `POST /threads/:thread_id/stream/events`.
*/
var ProtocolSseTransportAdapter = class {
	threadId;
	apiUrl;
	queue = new require_queue.AsyncQueue();
	fetchImpl;
	defaultHeaders;
	onRequest;
	fetchFactory;
	asyncCaller;
	maxReconnectAttempts;
	idleReconnect;
	onReconnect;
	reconnectDelayMs;
	paths;
	sessionAbortController = new AbortController();
	eventStreams = /* @__PURE__ */ new Set();
	closed = false;
	constructor(options) {
		this.fetchImpl = options.fetch ?? fetch;
		this.apiUrl = options.apiUrl;
		this.defaultHeaders = options.defaultHeaders ?? {};
		this.onRequest = options.onRequest;
		this.fetchFactory = options.fetchFactory;
		this.asyncCaller = options.asyncCaller;
		this.maxReconnectAttempts = options.maxReconnectAttempts ?? 5;
		this.idleReconnect = options.idleReconnect ?? "auto";
		this.onReconnect = options.onReconnect;
		this.reconnectDelayMs = options.reconnectDelayMs ?? require_reconnect.reconnectDelayMs;
		this.threadId = options.threadId ?? "";
		this.paths = options.paths;
	}
	/** {@inheritDoc TransportAdapter.setThreadId} */
	setThreadId(threadId) {
		this.threadId = threadId;
	}
	/**
	* Command/stream/state URLs derive from the currently-bound thread so a
	* single adapter can follow {@link setThreadId} re-binds. A fixed
	* `paths.*` string overrides the default and is used as-is.
	*/
	get commandsUrl() {
		return require_utils.resolveProtocolPath(this.paths?.commands, this.threadId, (id) => `/threads/${id}/commands`);
	}
	get streamUrl() {
		return require_utils.resolveProtocolPath(this.paths?.stream, this.threadId, (id) => `/threads/${id}/stream/events`);
	}
	get stateUrl() {
		return require_utils.resolveProtocolPath(this.paths?.state, this.threadId, (id) => `/threads/${id}/state`);
	}
	/**
	* Fetch checkpointed thread state for hydration.
	*
	* Uses `GET`, matching `client.threads.getState()` and both LangGraph
	* Platform and Agent Protocol custom backends (`POST` is reserved for
	* `updateState`).
	*/
	async getState() {
		const url = require_utils.toAbsoluteUrl(this.apiUrl, this.stateUrl);
		let requestInit = {
			method: "GET",
			headers: require_utils.mergeHeaders(this.defaultHeaders, {})
		};
		if (this.onRequest) requestInit = await this.onRequest(url, requestInit);
		const response = await (await this.resolveFetch())(url.toString(), requestInit);
		if (response.status === 404) return null;
		if (!response.ok) {
			const error = require_utils.toError(/* @__PURE__ */ new Error(`Thread state request failed: ${response.status} ${response.statusText}`));
			error.status = response.status;
			throw error;
		}
		return await response.json();
	}
	async resolveFetch() {
		if (this.fetchFactory) return await this.fetchFactory();
		return this.fetchImpl;
	}
	/**
	* HTTP/SSE transports have no handshake — connections are made
	* per-command and per-subscription.
	*/
	async open() {}
	async send(command) {
		const response = await this.request(this.commandsUrl, {
			method: "POST",
			headers: { "content-type": "application/json" },
			body: JSON.stringify(command),
			signal: this.sessionAbortController.signal
		});
		if (response.status === 202 || response.status === 204) return;
		const payload = await response.json();
		if (!require_utils.isProtocolResponse(payload)) throw new Error("Protocol command did not return a valid response.");
		return payload;
	}
	/**
	* WebSocket-style single event stream.
	* For the SSE transport this returns a dummy iterable; real event
	* delivery happens via {@link openEventStream}.
	*/
	events() {
		const queue = this.queue;
		return { [Symbol.asyncIterator]: () => ({
			next: async () => await queue.shift(),
			return: async () => {
				queue.close();
				return {
					done: true,
					value: void 0
				};
			}
		}) };
	}
	openEventStream(params) {
		if (this.closed) throw new Error("Protocol transport is closed.");
		const ac = new AbortController();
		this.eventStreams.add(ac);
		const streamQueue = new require_queue.AsyncQueue();
		const streamUrl = this.streamUrl;
		let resolveReady;
		let rejectReady;
		const ready = new Promise((resolve, reject) => {
			resolveReady = resolve;
			rejectReady = reject;
		});
		const initialSince = typeof params.since === "number" ? params.since : void 0;
		let readySettled = false;
		const startStream = async () => {
			let attempt = 0;
			while (!ac.signal.aborted && !this.closed) try {
				const response = await this.request(streamUrl, {
					method: "POST",
					headers: {
						"content-type": "application/json",
						accept: "text/event-stream"
					},
					body: JSON.stringify({
						channels: params.channels,
						...params.namespaces ? { namespaces: params.namespaces } : {},
						...params.depth != null ? { depth: params.depth } : {},
						...!readySettled && initialSince != null ? { since: initialSince } : {}
					}),
					signal: ac.signal
				}, { stream: true });
				if (!readySettled) {
					readySettled = true;
					resolveReady();
				}
				const readable = response.body ?? new ReadableStream({ start(controller) {
					controller.close();
				} });
				const enableIdle = this.idleReconnect === "auto" || typeof this.idleReconnect === "number" && this.idleReconnect > 0;
				const lines = readable.pipeThrough(require_sse.BytesLineDecoder());
				const stream = (enableIdle ? lines.pipeThrough(require_stream.idleReconnectStream({ mode: this.idleReconnect })) : lines).pipeThrough(require_sse.SSEDecoder());
				const iterable = require_stream.IterableReadableStream.fromReadableStream(stream);
				for await (const event of iterable) {
					if (ac.signal.aborted || this.closed) break;
					if (require_utils.isRecord(event.data)) streamQueue.push(event.data);
				}
				streamQueue.close();
				return;
			} catch (error) {
				if (ac.signal.aborted || this.closed) {
					if (!readySettled) rejectReady(error);
					streamQueue.close();
					return;
				}
				if (this.maxReconnectAttempts <= 0) {
					if (!readySettled) rejectReady(error);
					streamQueue.close(require_utils.toError(error));
					return;
				}
				attempt += 1;
				if (attempt > this.maxReconnectAttempts) {
					if (!readySettled) rejectReady(error);
					streamQueue.close(require_utils.toError(error));
					return;
				}
				this.onReconnect?.({
					attempt,
					cause: error
				});
				const delay = this.reconnectDelayMs(attempt);
				if (delay > 0) await new Promise((resolve) => {
					setTimeout(resolve, delay);
				});
			}
		};
		startStream();
		const cleanup = () => {
			this.eventStreams.delete(ac);
			ac.abort();
			streamQueue.close();
		};
		return {
			events: { [Symbol.asyncIterator]: () => ({
				next: async () => await streamQueue.shift(),
				return: async () => {
					cleanup();
					return {
						done: true,
						value: void 0
					};
				}
			}) },
			ready,
			close: cleanup
		};
	}
	async close() {
		if (this.closed) return;
		this.closed = true;
		this.sessionAbortController.abort();
		for (const ac of this.eventStreams) ac.abort();
		this.eventStreams.clear();
		this.queue.close();
	}
	async request(path, init, options) {
		const url = require_utils.toAbsoluteUrl(this.apiUrl, path);
		let requestInit = {
			...init,
			headers: require_utils.mergeHeaders(this.defaultHeaders, init.headers)
		};
		if (this.onRequest) requestInit = await this.onRequest(url, requestInit);
		const useAsyncCaller = this.asyncCaller != null && !options?.stream;
		const execute = async () => {
			const response = await (await this.resolveFetch())(url.toString(), requestInit);
			if (!response.ok) {
				if (useAsyncCaller) throw response;
				let detail = "";
				try {
					const body = await response.text();
					const parsed = JSON.parse(body);
					if (typeof parsed === "object" && parsed != null) detail = parsed.message ?? parsed.error ?? "";
					if (!detail) detail = body;
				} catch {}
				const message = detail ? `Protocol request failed: ${response.status} ${response.statusText} — ${detail}` : `Protocol request failed: ${response.status} ${response.statusText}`;
				throw new Error(message);
			}
			return response;
		};
		try {
			return useAsyncCaller ? await this.asyncCaller.call(execute) : await execute();
		} catch (error) {
			throw require_utils.toError(error);
		}
	}
};
//#endregion
exports.ProtocolSseTransportAdapter = ProtocolSseTransportAdapter;

//# sourceMappingURL=http.cjs.map