import runDbClient_default from "../../../data/db/runDbClient.js";
import { getLogger as getLogger$1 } from "../../../logger.js";
import { flushBatchProcessor } from "../../../instrumentation.js";
import { createSSEStreamHelper } from "../stream/stream-helpers.js";
import { errorOp } from "../utils/agent-operations.js";
import { ExecutionHandler } from "../handlers/executionHandler.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 { FileContentItemSchema, ImageContentItemSchema } from "../types/chat.js";
import { extractTextFromParts, getMessagePartsFromOpenAIContent } from "../utils/message-parts.js";
import { buildPersistedMessageContent, inlineExternalPdfUrlParts } from "../services/blob-storage/file-upload-helpers.js";
import { toolApprovalUiBus } from "../session/ToolApprovalUiBus.js";
import { agentExecutionWorkflow } from "../workflow/functions/agentExecution.js";
import { OpenAPIHono, z } from "@hono/zod-openapi";
import { PartSchema, buildConversationMetadata, createApiError, createMessage, createOrGetConversation, generateId, getActiveAgentForConversation, getConversationId, setActiveAgentForConversation } from "@inkeep/agents-core";
import { createProtectedRoute, inheritedRunApiKeyAuth } from "@inkeep/agents-core/middleware";
import { start } from "workflow/api";
import { HTTPException } from "hono/http-exception";
import { context, propagation, trace } from "@opentelemetry/api";
import { stream, streamSSE } from "hono/streaming";

//#region src/domains/run/routes/chat.ts
const app = new OpenAPIHono();
const logger = getLogger$1("completionsHandler");
const chatCompletionsRoute = createProtectedRoute({
	method: "post",
	path: "/completions",
	tags: ["Chat"],
	summary: "Create chat completion",
	description: "Creates a new chat completion with streaming SSE response using the configured agent",
	security: [{ bearerAuth: [] }],
	permission: inheritedRunApiKeyAuth(),
	request: { body: { content: { "application/json": { schema: z.object({
		model: z.string().describe("The model to use for the completion"),
		messages: z.array(z.object({
			role: z.enum([
				"system",
				"user",
				"assistant",
				"function",
				"tool"
			]).describe("The role of the message"),
			content: z.union([z.string(), z.array(z.discriminatedUnion("type", [
				z.object({
					type: z.literal("text"),
					text: z.string()
				}),
				ImageContentItemSchema,
				FileContentItemSchema
			]))]).describe("The message content"),
			name: z.string().optional().describe("The name of the message sender")
		})).describe("The conversation messages"),
		temperature: z.number().optional().describe("Controls randomness (0-1)"),
		top_p: z.number().optional().describe("Controls nucleus sampling"),
		n: z.number().optional().describe("Number of completions to generate"),
		stream: z.boolean().optional().describe("Whether to stream the response"),
		max_tokens: z.number().optional().describe("Maximum tokens to generate"),
		presence_penalty: z.number().optional().describe("Presence penalty (-2 to 2)"),
		frequency_penalty: z.number().optional().describe("Frequency penalty (-2 to 2)"),
		logit_bias: z.record(z.string(), z.number()).optional().describe("Token logit bias"),
		user: z.string().optional().describe("User identifier"),
		conversationId: z.string().optional().describe("Conversation ID for multi-turn chat"),
		tools: z.array(z.string()).optional().describe("Available tools"),
		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."),
		headers: z.record(z.string(), z.unknown()).optional().describe("Headers data for template processing (validated against context config schema)"),
		userProperties: z.record(z.string(), z.unknown()).optional().describe("User properties to associate with the conversation")
	}) } } } },
	responses: {
		200: {
			description: "Streaming chat completion response in Server-Sent Events format",
			headers: z.object({
				"Content-Type": z.string().default("text/event-stream"),
				"Cache-Control": z.string().default("no-cache"),
				Connection: z.string().default("keep-alive")
			}),
			content: { "text/event-stream": { schema: z.string().describe("Server-Sent Events stream with chat completion chunks") } }
		},
		400: {
			description: "Invalid request context or parameters",
			content: { "application/json": { schema: z.object({
				error: z.string(),
				details: z.array(z.object({
					field: z.string(),
					message: z.string(),
					value: z.unknown().optional()
				})).optional()
			}) } }
		},
		404: {
			description: "Agent or agent not found",
			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.use("/completions", contextValidationMiddleware);
app.openapi(chatCompletionsRoute, async (c) => {
	getLogger$1("chat").info({
		path: c.req.path,
		method: c.req.method,
		params: c.req.param()
	}, "Chat route accessed");
	const otelHeaders = {
		traceparent: c.req.header("traceparent"),
		tracestate: c.req.header("tracestate"),
		baggage: c.req.header("baggage")
	};
	logger.info({
		otelHeaders,
		path: c.req.path,
		method: c.req.method
	}, "OpenTelemetry headers: chat");
	try {
		const executionContext = c.get("executionContext");
		const { tenantId, projectId, agentId } = executionContext;
		getLogger$1("chat").debug({
			tenantId,
			agentId
		}, "Extracted chat parameters from API key context");
		const body = c.get("requestBody") || {};
		const conversationId = body.conversationId || getConversationId();
		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 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 fullAgent = executionContext.project.agents[agentId];
			if (!fullAgent) throw createApiError({
				code: "not_found",
				message: "Agent not found"
			});
			const agent = fullAgent;
			let defaultSubAgentId;
			const agentKeys = Object.keys(fullAgent.subAgents || {});
			const firstAgentId = agentKeys.length > 0 ? agentKeys[0] : "";
			defaultSubAgentId = fullAgent.defaultSubAgentId || firstAgentId;
			if (!defaultSubAgentId) throw createApiError({
				code: "not_found",
				message: "No default agent found in agent"
			});
			const conversationMeta = buildConversationMetadata(executionContext, body.userProperties);
			await createOrGetConversation(runDbClient_default)({
				tenantId,
				projectId,
				id: conversationId,
				agentId,
				activeSubAgentId: defaultSubAgentId,
				ref: executionContext.resolvedRef,
				userId: executionContext.metadata?.endUserId,
				...conversationMeta ? { metadata: conversationMeta } : {}
			});
			const activeAgent = await getActiveAgentForConversation(runDbClient_default)({
				scopes: {
					tenantId,
					projectId
				},
				conversationId
			});
			if (!activeAgent) await setActiveAgentForConversation(runDbClient_default)({
				scopes: {
					tenantId,
					projectId
				},
				conversationId,
				agentId,
				subAgentId: defaultSubAgentId,
				ref: executionContext.resolvedRef
			});
			const subAgentId = activeAgent?.activeSubAgentId || defaultSubAgentId;
			if (!executionContext.project.agents[agentId]?.subAgents[subAgentId]) throw createApiError({
				code: "not_found",
				message: "Agent not found"
			});
			const validatedContext = c.get("validatedContext") || body.headers || {};
			await handleContextResolution({
				executionContext,
				conversationId,
				headers: validatedContext,
				credentialStores: c.get("credentialStores")
			});
			logger.info({
				tenantId,
				projectId,
				agentId,
				conversationId,
				defaultSubAgentId,
				activeSubAgentId: activeAgent?.activeSubAgentId || "none",
				hasContextConfig: !!agent.contextConfigId,
				hasHeaders: !!body.headers,
				hasValidatedContext: !!validatedContext,
				validatedContextKeys: Object.keys(validatedContext)
			}, "parameters");
			const requestId = `chatcmpl-${Date.now()}`;
			const timestamp = Math.floor(Date.now() / 1e3);
			const lastUserMessage = body.messages.filter((msg) => msg.role === "user").slice(-1)[0];
			const messageParts = await inlineExternalPdfUrlParts(z.array(PartSchema).parse(lastUserMessage ? getMessagePartsFromOpenAIContent(lastUserMessage.content) : []));
			const userMessage = extractTextFromParts(messageParts);
			const agentName = agent.name;
			const messageSpan = trace.getActiveSpan();
			if (messageSpan) {
				messageSpan.setAttributes({
					"message.content": userMessage,
					"message.timestamp": (/* @__PURE__ */ new Date()).toISOString(),
					"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();
			const messageContent = await buildPersistedMessageContent(userMessage, 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": conversationId,
				"database.operation": "insert"
			});
			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");
			if ((body.executionMode ?? agent.executionMode ?? "classic") === "durable") {
				const emitOperations = c.req.header("x-emit-operations") === "true";
				const run = await start(agentExecutionWorkflow, [{
					tenantId,
					projectId,
					agentId,
					conversationId,
					userMessage,
					messageParts: messageParts.length > 0 ? messageParts : void 0,
					requestId,
					resolvedRef: executionContext.resolvedRef,
					forwardedHeaders: Object.keys(forwardedHeaders).length > 0 ? forwardedHeaders : void 0,
					emitOperations: emitOperations || void 0
				}]);
				logger.info({
					runId: run.runId,
					conversationId,
					agentId
				}, "Durable execution started");
				c.header("Content-Type", "text/event-stream");
				c.header("Cache-Control", "no-cache");
				c.header("Connection", "keep-alive");
				c.header("x-workflow-run-id", run.runId);
				return stream(c, async (s) => {
					try {
						const reader = run.readable.getReader();
						while (true) {
							const { done, value } = await reader.read();
							if (done) break;
							await s.write(value);
						}
					} catch (error) {
						logger.error({
							error,
							runId: run.runId
						}, "Error streaming durable execution");
						await s.write(`event: error\ndata: ${JSON.stringify({ error: "Stream error" })}\n\n`);
					}
				});
			}
			return streamSSE(c, async (stream$1) => {
				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;
				};
				let unsubscribe;
				try {
					const sseHelper = createSSEStreamHelper(stream$1, requestId, timestamp);
					await sseHelper.writeRole();
					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 sseHelper.writeToolInputStart({
								toolCallId: event.toolCallId,
								toolName: event.toolName
							});
							const inputText = JSON.stringify(event.input ?? {});
							for (const part of chunkString(inputText, 16)) await sseHelper.writeToolInputDelta({
								toolCallId: event.toolCallId,
								inputTextDelta: part
							});
							await sseHelper.writeToolInputAvailable({
								toolCallId: event.toolCallId,
								toolName: event.toolName,
								input: event.input ?? {},
								providerMetadata: event.providerMetadata
							});
							await sseHelper.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 sseHelper.writeToolOutputAvailable({
								toolCallId: event.toolCallId,
								output: { status: "approved" }
							});
							else await sseHelper.writeToolOutputDenied({ toolCallId: event.toolCallId });
						}
					});
					logger.info({ subAgentId }, "Starting execution");
					const emitOperations = c.req.header("x-emit-operations") === "true";
					const result = await new ExecutionHandler().execute({
						executionContext,
						conversationId,
						userMessage,
						messageParts: messageParts.length > 0 ? messageParts : void 0,
						initialAgentId: subAgentId,
						requestId,
						sseHelper,
						emitOperations,
						forwardedHeaders
					});
					logger.info({ result }, `Execution completed: ${result.success ? "success" : "failed"} after ${result.iterations} iterations`);
					if (!result.success) await sseHelper.writeOperation(errorOp("Sorry, I was unable to process your request at this time. Please try again.", "system"));
					await sseHelper.complete();
				} catch (error) {
					logger.error({
						error: error instanceof Error ? error.message : error,
						stack: error instanceof Error ? error.stack : void 0
					}, "Error during streaming execution");
					try {
						const sseHelper = createSSEStreamHelper(stream$1, requestId, timestamp);
						await sseHelper.writeOperation(errorOp("Sorry, I was unable to process your request at this time. Please try again.", "system"));
						await sseHelper.complete();
					} catch (streamError) {
						logger.error({ streamError }, "Failed to write error to stream");
					}
				} finally {
					try {
						unsubscribe?.();
					} catch (_e) {}
					await flushBatchProcessor();
				}
			});
		});
	} 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: error instanceof Error ? error.message : error,
			stack: error instanceof Error ? error.stack : void 0
		}, "Error in chat completions endpoint before streaming");
		throw createApiError({
			code: "internal_server_error",
			message: "Failed to process chat completion"
		});
	}
});
var chat_default = app;

//#endregion
export { chat_default as default };