import runDbClient_default from "../../../data/db/runDbClient.js";
import { getLogger as getLogger$1 } from "../../../logger.js";
import { flushBatchProcessor } from "../../../instrumentation.js";
import { createBufferingStreamHelper, createVercelStreamHelper } from "../stream/stream-helpers.js";
import { errorOp } from "../utils/agent-operations.js";
import { ExecutionHandler } from "../handlers/executionHandler.js";
import { getUserIdFromContext } from "../types/executionContext.js";
import { contextValidationMiddleware } from "../context/validation.js";
import { handleContextResolution } from "../context/context.js";
import "../context/index.js";
import { FileSecurityError, PdfUrlIngestionError } from "../services/blob-storage/file-security-errors.js";
import { VercelMessageSchema } from "../types/chat.js";
import { extractTextFromParts, getMessagePartsFromVercelContent } from "../utils/message-parts.js";
import { buildPersistedMessageContent, inlineExternalPdfUrlParts } from "../services/blob-storage/file-upload-helpers.js";
import { pendingToolApprovalManager } from "../session/PendingToolApprovalManager.js";
import { toolApprovalUiBus } from "../session/ToolApprovalUiBus.js";
import { agentExecutionWorkflow, toolApprovalHook } from "../workflow/functions/agentExecution.js";
import { streamBufferRegistry } from "../stream/stream-buffer-registry.js";
import { OpenAPIHono, z } from "@hono/zod-openapi";
import { PartSchema, buildConversationMetadata, commonGetErrorResponses, createApiError, createMessage, generateId, getActiveAgentForConversation, getConversation, getConversationId, getWorkflowExecutionByConversation, loggerFactory, setActiveAgentForConversation } from "@inkeep/agents-core";
import { createProtectedRoute, inheritedRunApiKeyAuth } from "@inkeep/agents-core/middleware";
import { getRun, start } from "workflow/api";
import { HTTPException } from "hono/http-exception";
import { context, propagation, trace } from "@opentelemetry/api";
import { JsonToSseTransformStream, createUIMessageStream } from "ai";
import { stream } from "hono/streaming";

//#region src/domains/run/routes/chatDataStream.ts
const app = new OpenAPIHono();
const logger = getLogger$1("chatDataStream");
const chatDataStreamRoute = createProtectedRoute({
	method: "post",
	path: "/chat",
	tags: ["Chat"],
	summary: "Chat (Vercel Streaming Protocol)",
	description: "Chat completion endpoint streaming with Vercel data stream protocol.",
	security: [{ bearerAuth: [] }],
	permission: inheritedRunApiKeyAuth(),
	request: { body: { content: { "application/json": { schema: z.object({
		model: z.string().optional(),
		messages: z.array(VercelMessageSchema),
		id: z.string().optional(),
		conversationId: z.string().optional(),
		stream: z.boolean().optional().describe("Whether to stream the response").default(true),
		max_tokens: z.number().optional().describe("Maximum tokens to generate"),
		headers: z.record(z.string(), z.unknown()).optional().describe("Headers data for template processing"),
		runConfig: z.record(z.string(), z.unknown()).optional().describe("Run configuration"),
		executionMode: z.enum(["classic", "durable"]).optional().describe("Override the agent execution mode for this request. Takes precedence over the agent config default. Falls back to classic if unset."),
		userProperties: z.record(z.string(), z.unknown()).optional().describe("User properties to associate with the conversation")
	}) } } } },
	responses: {
		200: {
			description: "Streamed chat completion",
			headers: z.object({
				"Content-Type": z.string().default("text/plain; charset=utf-8"),
				"x-vercel-ai-data-stream": z.string().default("v1")
			})
		},
		...commonGetErrorResponses
	}
});
app.use("/chat", contextValidationMiddleware);
app.openapi(chatDataStreamRoute, async (c) => {
	try {
		const executionContext = c.get("executionContext");
		const { tenantId, projectId, agentId } = executionContext;
		loggerFactory.getLogger("chatDataStream").debug({
			tenantId,
			projectId,
			agentId
		}, "Extracted chatDataStream parameters");
		const body = c.req.valid("json");
		const approvalParts = (body.messages || []).flatMap((m) => m?.parts || []).filter((p) => p?.state === "approval-responded" && typeof p?.toolCallId === "string");
		if (approvalParts.length > 0) {
			const conversationId$1 = body.conversationId;
			if (!conversationId$1) return c.json({
				success: false,
				error: "conversationId is required for approval response"
			}, 400);
			if (!await getConversation(runDbClient_default)({
				scopes: {
					tenantId,
					projectId
				},
				conversationId: conversationId$1
			})) return c.json({
				success: false,
				error: "Conversation not found"
			}, 404);
			const durableExecution = await getWorkflowExecutionByConversation(runDbClient_default)({
				tenantId,
				projectId,
				conversationId: conversationId$1
			});
			if (durableExecution?.status === "suspended" && durableExecution) {
				await Promise.allSettled(approvalParts.map(async (approvalPart) => {
					const toolCallId = approvalPart.toolCallId;
					const approved = !!approvalPart.approval?.approved;
					const reason = approvalPart.approval?.reason;
					const token = `tool-approval:${conversationId$1}:${durableExecution.id}:${toolCallId}`;
					try {
						await toolApprovalHook.resume(token, {
							approved,
							reason: approved ? void 0 : reason
						});
					} catch (error) {
						const message = error instanceof Error ? error.message : String(error);
						if (!message.includes("not found") && !message.includes("already")) throw error;
					}
				}));
				const namespace = durableExecution.metadata?.continuationStreamNamespace;
				const run = getRun(durableExecution.id);
				c.header("Content-Type", "text/event-stream");
				c.header("Cache-Control", "no-cache");
				c.header("Connection", "keep-alive");
				return stream(c, async (s) => {
					try {
						const reader = run.getReadable({ namespace }).getReader();
						while (true) {
							const { done, value } = await reader.read();
							if (done) break;
							await s.write(value);
						}
					} catch (error) {
						logger.error({
							error,
							executionId: durableExecution.id
						}, "Error streaming approval continuation");
						await s.write(`event: error\ndata: ${JSON.stringify({ error: "Stream error" })}\n\n`);
					}
				});
			}
			const results = approvalParts.map((approvalPart) => {
				const toolCallId = approvalPart.toolCallId;
				const approved = !!approvalPart.approval?.approved;
				const reason = approvalPart.approval?.reason;
				return {
					toolCallId,
					approved,
					alreadyProcessed: !(approved ? pendingToolApprovalManager.approveToolCall(toolCallId) : pendingToolApprovalManager.denyToolCall(toolCallId, reason))
				};
			});
			return c.json({
				success: true,
				results
			});
		}
		const targetTenantId = c.req.header("x-target-tenant-id");
		const targetProjectId = c.req.header("x-target-project-id");
		const targetAgentId = c.req.header("x-target-agent-id");
		const forwardedHeaders = {};
		const xForwardedCookie = c.req.header("x-forwarded-cookie");
		const cookie = c.req.header("cookie");
		const clientTimezone = c.req.header("x-inkeep-client-timezone");
		const clientTimestamp = c.req.header("x-inkeep-client-timestamp");
		if (xForwardedCookie) forwardedHeaders["x-forwarded-cookie"] = xForwardedCookie;
		else if (cookie) forwardedHeaders["x-forwarded-cookie"] = cookie;
		if (clientTimezone && clientTimestamp) {
			const isValidTimezone = clientTimezone.length < 100 && /^[A-Za-z0-9_/\-+]+$/.test(clientTimezone);
			const isValidTimestamp = clientTimestamp.length < 50 && /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(\.\d{3})?Z$/.test(clientTimestamp);
			if (isValidTimezone && isValidTimestamp) {
				forwardedHeaders["x-inkeep-client-timezone"] = clientTimezone;
				forwardedHeaders["x-inkeep-client-timestamp"] = clientTimestamp;
			} else logger.warn({
				clientTimezone: isValidTimezone ? clientTimezone : clientTimezone.substring(0, 100),
				clientTimestamp: isValidTimestamp ? clientTimestamp : clientTimestamp.substring(0, 50),
				isValidTimezone,
				isValidTimestamp
			}, "Invalid client timezone or timestamp format, ignoring both");
		} else if (clientTimezone || clientTimestamp) logger.warn({
			hasTimezone: !!clientTimezone,
			hasTimestamp: !!clientTimestamp
		}, "Client timezone and timestamp must both be present, ignoring");
		const conversationId = body.conversationId ?? getConversationId();
		const activeSpan = trace.getActiveSpan();
		if (activeSpan) activeSpan.setAttributes({
			"conversation.id": conversationId,
			"tenant.id": tenantId,
			"agent.id": agentId,
			"project.id": projectId,
			...targetTenantId && { "target.tenant.id": targetTenantId },
			...targetProjectId && { "target.project.id": targetProjectId },
			...targetAgentId && { "target.agent.id": targetAgentId }
		});
		let currentBag = propagation.getBaggage(context.active());
		if (!currentBag) currentBag = propagation.createBaggage();
		currentBag = currentBag.setEntry("conversation.id", { value: conversationId });
		const ctxWithBaggage = propagation.setBaggage(context.active(), currentBag);
		return await context.with(ctxWithBaggage, async () => {
			const agent = executionContext.project.agents[agentId];
			if (!agent) throw createApiError({
				code: "not_found",
				message: "Agent not found"
			});
			const defaultSubAgentId = agent.defaultSubAgentId;
			const agentName = agent.name;
			if (!defaultSubAgentId) throw createApiError({
				code: "bad_request",
				message: "Agent does not have a default agent configured"
			});
			const activeAgent = await getActiveAgentForConversation(runDbClient_default)({
				scopes: {
					tenantId,
					projectId
				},
				conversationId
			});
			if (!activeAgent) {
				const conversationMeta = buildConversationMetadata(executionContext, body.userProperties);
				await setActiveAgentForConversation(runDbClient_default)({
					scopes: {
						tenantId,
						projectId
					},
					conversationId,
					subAgentId: defaultSubAgentId,
					ref: executionContext.resolvedRef,
					agentId,
					userId: executionContext.metadata?.endUserId,
					...conversationMeta ? { metadata: conversationMeta } : {}
				});
			}
			const subAgentId = activeAgent?.activeSubAgentId || defaultSubAgentId;
			logger.info({ subAgentId }, "subAgentId");
			if (!executionContext.project.agents[agentId]?.subAgents[subAgentId]) {
				logger.error({ subAgentId }, "subAgentId not found");
				throw createApiError({
					code: "not_found",
					message: "Agent not found"
				});
			}
			await handleContextResolution({
				executionContext,
				conversationId,
				headers: c.get("validatedContext") || body.headers || {},
				credentialStores: c.get("credentialStores")
			});
			const lastUserMessage = body.messages.filter((m) => m.role === "user").slice(-1)[0];
			const messageParts = await inlineExternalPdfUrlParts(z.array(PartSchema).parse(getMessagePartsFromVercelContent(lastUserMessage?.content, lastUserMessage?.parts)));
			const userText = extractTextFromParts(messageParts) || "";
			logger.info({
				userText,
				lastUserMessage
			}, "userText");
			const messageSpan = trace.getActiveSpan();
			if (messageSpan) {
				messageSpan.setAttributes({
					"message.timestamp": (/* @__PURE__ */ new Date()).toISOString(),
					"message.content": userText,
					"agent.name": agentName
				});
				const invocationType = c.req.header("x-inkeep-invocation-type");
				if (invocationType) messageSpan.setAttribute("invocation.type", invocationType);
				const invocationEntryPoint = c.req.header("x-inkeep-invocation-entry-point");
				if (invocationEntryPoint) messageSpan.setAttribute("invocation.entryPoint", invocationEntryPoint);
				if (executionContext.metadata?.initiatedBy) {
					messageSpan.setAttribute("user.type", executionContext.metadata.initiatedBy.type);
					messageSpan.setAttribute("user.id", executionContext.metadata.initiatedBy.id);
				}
			}
			const userMessageId = generateId();
			if (messageSpan) messageSpan.setAttribute("message.id", userMessageId);
			const messageContent = await buildPersistedMessageContent(userText, messageParts, {
				tenantId,
				projectId,
				conversationId,
				messageId: userMessageId
			});
			await createMessage(runDbClient_default)({
				scopes: {
					tenantId,
					projectId
				},
				data: {
					id: userMessageId,
					conversationId,
					role: "user",
					content: messageContent,
					visibility: "user-facing",
					messageType: "chat"
				}
			});
			if (messageSpan) messageSpan.addEvent("user.message.stored", {
				"message.id": userMessageId,
				"database.operation": "insert"
			});
			if ((body.executionMode ?? agent.executionMode ?? "classic") === "durable") {
				const requestId = `chatds-${Date.now()}`;
				const userId = getUserIdFromContext(executionContext);
				const run = await start(agentExecutionWorkflow, [{
					tenantId,
					projectId,
					agentId,
					conversationId,
					userMessage: userText,
					messageParts: messageParts.length > 0 ? messageParts : void 0,
					requestId,
					resolvedRef: executionContext.resolvedRef,
					forwardedHeaders: Object.keys(forwardedHeaders).length > 0 ? forwardedHeaders : void 0,
					outputFormat: "vercel",
					userId
				}]);
				logger.info({
					runId: run.runId,
					conversationId,
					agentId
				}, "Durable execution started via /chat");
				c.header("x-workflow-run-id", run.runId);
				c.header("content-type", "text/event-stream");
				c.header("cache-control", "no-cache");
				c.header("connection", "keep-alive");
				c.header("x-vercel-ai-data-stream", "v2");
				c.header("x-accel-buffering", "no");
				streamBufferRegistry.register({
					tenantId,
					projectId,
					conversationId
				});
				return stream(c, async (s) => {
					try {
						const encoder = new TextEncoder();
						const reader = run.readable.getReader();
						while (true) {
							const { done, value } = await reader.read();
							if (done) break;
							const encoded = typeof value === "string" ? encoder.encode(value) : value;
							streamBufferRegistry.push({
								tenantId,
								projectId,
								conversationId
							}, encoded);
							await s.write(value);
						}
					} catch (error) {
						logger.error({
							error,
							runId: run.runId
						}, "Error streaming durable execution via /chat");
						await s.write(`event: error\ndata: ${JSON.stringify({ error: "Stream error" })}\n\n`);
					} finally {
						await streamBufferRegistry.complete({
							tenantId,
							projectId,
							conversationId
						});
					}
				});
			}
			if (!(body.stream !== false)) {
				const emitOperations = c.req.header("x-emit-operations") === "true";
				const bufferingHelper = createBufferingStreamHelper();
				const responseMessageId$1 = generateId();
				const result = await new ExecutionHandler().execute({
					executionContext,
					conversationId,
					userMessage: userText,
					messageParts: messageParts.length > 0 ? messageParts : void 0,
					initialAgentId: subAgentId,
					requestId: `chat-${Date.now()}`,
					sseHelper: bufferingHelper,
					emitOperations,
					forwardedHeaders,
					responseMessageId: responseMessageId$1
				});
				const captured = bufferingHelper.getCapturedResponse();
				return c.json({
					id: `chat-${Date.now()}`,
					object: "chat.completion",
					created: Math.floor(Date.now() / 1e3),
					model: agentName,
					choices: [{
						index: 0,
						message: {
							role: "assistant",
							content: captured.hasError ? captured.errorMessage : captured.text
						},
						finish_reason: result.success && !captured.hasError ? "stop" : "error"
					}],
					usage: {
						prompt_tokens: 0,
						completion_tokens: 0,
						total_tokens: 0
					}
				});
			}
			const responseMessageId = generateId();
			const dataStream = createUIMessageStream({ execute: async ({ writer }) => {
				writer.write({
					type: "start",
					messageId: responseMessageId
				});
				const streamHelper = createVercelStreamHelper(writer);
				let unsubscribe;
				try {
					const emitOperations = c.req.header("x-emit-operations") === "true";
					const executionHandler = new ExecutionHandler();
					const datasetRunId = c.req.header("x-inkeep-dataset-run-id");
					const requestId = `chatds-${Date.now()}`;
					const chunkString = (s, size = 16) => {
						const out = [];
						for (let i = 0; i < s.length; i += size) out.push(s.slice(i, i + size));
						return out;
					};
					const seenToolCalls = /* @__PURE__ */ new Set();
					const seenOutputs = /* @__PURE__ */ new Set();
					unsubscribe = toolApprovalUiBus.subscribe(requestId, async (event) => {
						if (event.type === "approval-needed") {
							if (seenToolCalls.has(event.toolCallId)) return;
							seenToolCalls.add(event.toolCallId);
							await streamHelper.writeToolInputStart({
								toolCallId: event.toolCallId,
								toolName: event.toolName
							});
							const inputText = JSON.stringify(event.input ?? {});
							for (const part of chunkString(inputText, 16)) await streamHelper.writeToolInputDelta({
								toolCallId: event.toolCallId,
								inputTextDelta: part
							});
							await streamHelper.writeToolInputAvailable({
								toolCallId: event.toolCallId,
								toolName: event.toolName,
								input: event.input ?? {},
								providerMetadata: event.providerMetadata
							});
							await streamHelper.writeToolApprovalRequest({
								approvalId: event.approvalId,
								toolCallId: event.toolCallId,
								toolName: event.toolName,
								input: event.input
							});
						} else if (event.type === "approval-resolved") {
							if (seenOutputs.has(event.toolCallId)) return;
							seenOutputs.add(event.toolCallId);
							if (event.approved) await streamHelper.writeToolOutputAvailable({
								toolCallId: event.toolCallId,
								output: { status: "approved" }
							});
							else await streamHelper.writeToolOutputDenied({ toolCallId: event.toolCallId });
						}
					});
					if (!(await executionHandler.execute({
						executionContext,
						conversationId,
						userMessage: userText,
						messageParts: messageParts.length > 0 ? messageParts : void 0,
						initialAgentId: subAgentId,
						requestId,
						sseHelper: streamHelper,
						emitOperations,
						datasetRunId: datasetRunId || void 0,
						forwardedHeaders,
						responseMessageId
					})).success) await streamHelper.writeOperation(errorOp("Unable to process request", "system"));
				} catch (err) {
					logger.error({ err }, "Streaming error");
					await streamHelper.writeOperation(errorOp("Internal server error", "system"));
				} finally {
					try {
						unsubscribe?.();
					} catch (_e) {}
					if ("cleanup" in streamHelper && typeof streamHelper.cleanup === "function") streamHelper.cleanup();
					await flushBatchProcessor();
				}
			} });
			c.header("content-type", "text/event-stream");
			c.header("cache-control", "no-cache");
			c.header("connection", "keep-alive");
			c.header("x-vercel-ai-data-stream", "v2");
			c.header("x-accel-buffering", "no");
			const [clientStream, bufferStream] = dataStream.pipeThrough(new JsonToSseTransformStream()).pipeThrough(new TextEncoderStream()).tee();
			streamBufferRegistry.register({
				tenantId,
				projectId,
				conversationId
			});
			(async () => {
				const reader = bufferStream.getReader();
				try {
					while (true) {
						const { done, value } = await reader.read();
						if (done) break;
						streamBufferRegistry.push({
							tenantId,
							projectId,
							conversationId
						}, value);
					}
				} catch (error) {
					logger.error({
						error,
						conversationId
					}, "Error buffering stream for resumption");
				} finally {
					await streamBufferRegistry.complete({
						tenantId,
						projectId,
						conversationId
					});
				}
			})();
			return stream(c, (s) => s.pipe(clientStream));
		});
	} catch (error) {
		if (error instanceof FileSecurityError) throw createApiError({
			code: "bad_request",
			message: error.message
		});
		if (error instanceof PdfUrlIngestionError) throw createApiError({
			code: "bad_request",
			message: error.message
		});
		if (error instanceof HTTPException) throw error;
		logger.error({
			error,
			errorMessage: error instanceof Error ? error.message : String(error),
			errorStack: error instanceof Error ? error.stack : void 0,
			errorType: error?.constructor?.name
		}, "chatDataStream error - DETAILED");
		throw createApiError({
			code: "internal_server_error",
			message: "Failed to process chat completion"
		});
	}
});
var chatDataStream_default = app;

//#endregion
export { chatDataStream_default as default };