import { getLogger as getLogger$1 } from "../../../logger.js";
import runDbClient_default from "../../../data/db/runDbClient.js";
import { errorOp } from "../utils/agent-operations.js";
import { createBufferingStreamHelper, createVercelStreamHelper } from "../utils/stream-helpers.js";
import { ExecutionHandler } from "../handlers/executionHandler.js";
import { contextValidationMiddleware } from "../context/validation.js";
import { handleContextResolution } from "../context/context.js";
import "../context/index.js";
import { pendingToolApprovalManager } from "../services/PendingToolApprovalManager.js";
import { toolApprovalUiBus } from "../services/ToolApprovalUiBus.js";
import { OpenAPIHono, createRoute, z } from "@hono/zod-openapi";
import { commonGetErrorResponses, createApiError, createMessage, generateId, getActiveAgentForConversation, getConversation, getConversationId, loggerFactory, setActiveAgentForConversation } from "@inkeep/agents-core";
import { JsonToSseTransformStream, createUIMessageStream } from "ai";
import { context, propagation, trace } from "@opentelemetry/api";
import { stream } from "hono/streaming";

//#region src/domains/run/routes/chatDataStream.ts
const app = new OpenAPIHono();
const logger = getLogger$1("chatDataStream");
const chatDataStreamRoute = createRoute({
	method: "post",
	path: "/chat",
	tags: ["Chat"],
	summary: "Chat (Vercel Streaming Protocol)",
	description: "Chat completion endpoint streaming with Vercel data stream protocol.",
	security: [{ bearerAuth: [] }],
	request: { body: { content: { "application/json": { schema: z.object({
		model: z.string().optional(),
		messages: z.array(z.object({
			role: z.enum([
				"system",
				"user",
				"assistant",
				"function",
				"tool"
			]),
			content: z.any(),
			parts: z.array(z.union([
				z.object({
					type: z.union([z.enum([
						"text",
						"image",
						"audio",
						"video",
						"file"
					]), z.string().regex(/^data-/, "Type must start with \"data-\"")]),
					text: z.string().optional()
				}),
				z.object({
					type: z.string().regex(/^tool-/, "Type must start with \"tool-\""),
					toolCallId: z.string(),
					state: z.any(),
					approval: z.object({
						id: z.string(),
						approved: z.boolean().optional(),
						reason: z.string().optional()
					}).optional(),
					input: z.any().optional(),
					callProviderMetadata: z.any().optional()
				}),
				z.object({ type: z.literal("step-start") })
			])).optional()
		})),
		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")
	}) } } } },
	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.get("requestBody") || {};
		const approvalPart = (body.messages || []).flatMap((m) => m?.parts || []).find((p) => p?.state === "approval-responded" && typeof p?.toolCallId === "string");
		const isApprovalResponse = !!approvalPart;
		const conversationId = isApprovalResponse ? body.conversationId : body.conversationId || getConversationId();
		if (isApprovalResponse) {
			if (!conversationId) return c.json({
				success: false,
				error: "conversationId is required for approval response"
			}, 400);
			const toolCallId = approvalPart.toolCallId;
			const approved = !!approvalPart.approval?.approved;
			const reason = approvalPart.approval?.reason;
			if (!await getConversation(runDbClient_default)({
				scopes: {
					tenantId,
					projectId
				},
				conversationId
			})) return c.json({
				success: false,
				error: "Conversation not found"
			}, 404);
			if (!(approved ? pendingToolApprovalManager.approveToolCall(toolCallId) : pendingToolApprovalManager.denyToolCall(toolCallId, reason))) return c.json({
				success: true,
				toolCallId,
				approved,
				alreadyProcessed: true
			});
			return c.json({
				success: true,
				toolCallId,
				approved
			});
		}
		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 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) await setActiveAgentForConversation(runDbClient_default)({
				scopes: {
					tenantId,
					projectId
				},
				conversationId,
				subAgentId: defaultSubAgentId,
				ref: executionContext.resolvedRef,
				agentId
			});
			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 userText = typeof lastUserMessage?.content === "string" ? lastUserMessage.content : lastUserMessage?.parts?.map((p) => p.text).join("") || "";
			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
				});
				if (executionContext.metadata?.initiatedBy) {
					messageSpan.setAttribute("user.type", executionContext.metadata.initiatedBy.type);
					messageSpan.setAttribute("user.id", executionContext.metadata.initiatedBy.id);
				}
			}
			await createMessage(runDbClient_default)({
				id: generateId(),
				tenantId,
				projectId,
				conversationId,
				role: "user",
				content: { text: userText },
				visibility: "user-facing",
				messageType: "chat"
			});
			if (messageSpan) messageSpan.addEvent("user.message.stored", {
				"message.id": conversationId,
				"database.operation": "insert"
			});
			if (!(body.stream !== false)) {
				const emitOperations = c.req.header("x-emit-operations") === "true";
				const bufferingHelper = createBufferingStreamHelper();
				const result = await new ExecutionHandler().execute({
					executionContext,
					conversationId,
					userMessage: userText,
					initialAgentId: subAgentId,
					requestId: `chat-${Date.now()}`,
					sseHelper: bufferingHelper,
					emitOperations,
					forwardedHeaders
				});
				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 dataStream = createUIMessageStream({ execute: async ({ writer }) => {
				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
							});
						} 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,
						initialAgentId: subAgentId,
						requestId,
						sseHelper: streamHelper,
						emitOperations,
						datasetRunId: datasetRunId || void 0,
						forwardedHeaders
					})).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();
				}
			} });
			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");
			return stream(c, (stream$1) => stream$1.pipe(dataStream.pipeThrough(new JsonToSseTransformStream()).pipeThrough(new TextEncoderStream())));
		});
	} catch (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"
		});
	}
});
const toolApprovalRoute = createRoute({
	method: "post",
	path: "/tool-approvals",
	tags: ["Chat"],
	summary: "Approve or deny tool execution",
	description: "Handle user approval/denial of tool execution requests during conversations",
	security: [{ bearerAuth: [] }],
	request: { body: { content: { "application/json": { schema: z.object({
		conversationId: z.string().describe("The conversation ID"),
		toolCallId: z.string().describe("The tool call ID to respond to"),
		approved: z.boolean().describe("Whether the tool execution is approved"),
		reason: z.string().optional().describe("Optional reason for the decision")
	}) } } } },
	responses: {
		200: {
			description: "Tool approval response processed successfully",
			content: { "application/json": { schema: z.object({
				success: z.boolean(),
				message: z.string().optional()
			}) } }
		},
		400: {
			description: "Bad request - invalid tool call ID or conversation ID",
			content: { "application/json": { schema: z.object({ error: z.string() }) } }
		},
		404: {
			description: "Tool call not found or already processed",
			content: { "application/json": { schema: z.object({ error: z.string() }) } }
		},
		500: {
			description: "Internal server error",
			content: { "application/json": { schema: z.object({
				error: z.string(),
				message: z.string()
			}) } }
		}
	}
});
app.openapi(toolApprovalRoute, async (c) => {
	return trace.getTracer("tool-approval-handler").startActiveSpan("tool_approval_request", async (span) => {
		try {
			const { tenantId, projectId } = c.get("executionContext");
			const { conversationId, toolCallId, approved, reason } = await c.req.json();
			logger.info({
				conversationId,
				toolCallId,
				approved,
				reason,
				tenantId,
				projectId
			}, "Processing tool approval request");
			if (!await getConversation(runDbClient_default)({
				scopes: {
					tenantId,
					projectId
				},
				conversationId
			})) {
				span.setStatus({
					code: 1,
					message: "Conversation not found"
				});
				return c.json({ error: "Conversation not found" }, 404);
			}
			let success = false;
			if (approved) success = pendingToolApprovalManager.approveToolCall(toolCallId);
			else success = pendingToolApprovalManager.denyToolCall(toolCallId, reason);
			if (!success) {
				span.setStatus({
					code: 1,
					message: "Tool call not found"
				});
				return c.json({ error: "Tool call not found or already processed" }, 404);
			}
			logger.info({
				conversationId,
				toolCallId,
				approved
			}, "Tool approval processed successfully");
			span.setStatus({
				code: 1,
				message: "Success"
			});
			return c.json({
				success: true,
				message: approved ? "Tool execution approved" : "Tool execution denied"
			});
		} catch (error) {
			const errorMessage = error instanceof Error ? error.message : "Unknown error";
			logger.error({
				error: errorMessage,
				stack: error instanceof Error ? error.stack : void 0
			}, "Failed to process tool approval");
			span.setStatus({
				code: 2,
				message: errorMessage
			});
			return c.json({
				error: "Internal server error",
				message: errorMessage
			}, 500);
		} finally {
			span.end();
		}
	});
});
var chatDataStream_default = app;

//#endregion
export { chatDataStream_default as default };