export async function endpointOai()

in src/lib/server/endpoints/openai/endpointOai.ts [123:323]


export async function endpointOai(
	input: z.input<typeof endpointOAIParametersSchema>
): Promise<Endpoint> {
	const {
		baseURL,
		apiKey,
		completion,
		model,
		defaultHeaders,
		defaultQuery,
		multimodal,
		extraBody,
		useCompletionTokens,
		streamingSupported,
	} = endpointOAIParametersSchema.parse(input);

	let OpenAI;
	try {
		OpenAI = (await import("openai")).OpenAI;
	} catch (e) {
		throw new Error("Failed to import OpenAI", { cause: e });
	}

	const openai = new OpenAI({
		apiKey: apiKey || "sk-",
		baseURL,
		defaultHeaders,
		defaultQuery,
	});

	const imageProcessor = makeImageProcessor(multimodal.image);

	if (completion === "completions") {
		if (model.tools) {
			throw new Error(
				"Tools are not supported for 'completions' mode, switch to 'chat_completions' instead"
			);
		}
		return async ({ messages, preprompt, continueMessage, generateSettings, conversationId }) => {
			const prompt = await buildPrompt({
				messages,
				continueMessage,
				preprompt,
				model,
			});

			const parameters = { ...model.parameters, ...generateSettings };
			const body: CompletionCreateParamsStreaming = {
				model: model.id ?? model.name,
				prompt,
				stream: true,
				max_tokens: parameters?.max_new_tokens,
				stop: parameters?.stop,
				temperature: parameters?.temperature,
				top_p: parameters?.top_p,
				frequency_penalty: parameters?.repetition_penalty,
				presence_penalty: parameters?.presence_penalty,
			};

			const openAICompletion = await openai.completions.create(body, {
				body: { ...body, ...extraBody },
				headers: {
					"ChatUI-Conversation-ID": conversationId?.toString() ?? "",
					"X-use-cache": "false",
				},
			});

			return openAICompletionToTextGenerationStream(openAICompletion);
		};
	} else if (completion === "chat_completions") {
		return async ({
			messages,
			preprompt,
			generateSettings,
			tools,
			toolResults,
			conversationId,
		}) => {
			// Format messages for the chat API, handling multimodal content if supported
			let messagesOpenAI: OpenAI.Chat.Completions.ChatCompletionMessageParam[] =
				await prepareMessages(messages, imageProcessor, !model.tools && model.multimodal);

			// Check if a system message already exists as the first message
			const hasSystemMessage = messagesOpenAI.length > 0 && messagesOpenAI[0]?.role === "system";

			if (hasSystemMessage) {
				// System message exists - preserve user configuration
				if (preprompt !== undefined) {
					// Prepend preprompt to existing system message if preprompt exists
					const userSystemPrompt = messagesOpenAI[0].content || "";
					messagesOpenAI[0].content =
						preprompt + (userSystemPrompt ? "\n\n" + userSystemPrompt : "");
				}
				// If no preprompt, user's system message remains unchanged
			} else {
				// No system message exists - create a new one with preprompt or empty string
				messagesOpenAI = [{ role: "system", content: preprompt ?? "" }, ...messagesOpenAI];
			}

			// Handle models that don't support system role by converting to user message
			// This maintains compatibility with older or non-standard models
			if (
				!model.systemRoleSupported &&
				messagesOpenAI.length > 0 &&
				messagesOpenAI[0]?.role === "system"
			) {
				messagesOpenAI[0] = {
					...messagesOpenAI[0],
					role: "user",
				};
			}

			// Format tool results for the API to provide context for follow-up tool calls
			// This creates the full conversation flow needed for multi-step tool interactions
			if (toolResults && toolResults.length > 0) {
				const toolCallRequests: OpenAI.Chat.Completions.ChatCompletionAssistantMessageParam = {
					role: "assistant",
					content: null,
					tool_calls: [],
				};

				const responses: Array<OpenAI.Chat.Completions.ChatCompletionToolMessageParam> = [];

				for (const result of toolResults) {
					const id = result?.call?.toolId || uuidv4();

					const toolCallResult: OpenAI.Chat.Completions.ChatCompletionMessageToolCall = {
						type: "function",
						function: {
							name: result.call.name,
							arguments: JSON.stringify(result.call.parameters),
						},
						id,
					};

					toolCallRequests.tool_calls?.push(toolCallResult);
					const toolCallResponse: OpenAI.Chat.Completions.ChatCompletionToolMessageParam = {
						role: "tool",
						content: "",
						tool_call_id: id,
					};
					if ("outputs" in result) {
						toolCallResponse.content = JSON.stringify(result.outputs);
					}
					responses.push(toolCallResponse);
				}
				messagesOpenAI.push(toolCallRequests);
				messagesOpenAI.push(...responses);
			}

			// Combine model defaults with request-specific parameters
			const parameters = { ...model.parameters, ...generateSettings };
			const toolCallChoices = createChatCompletionToolsArray(tools);
			const body = {
				model: model.id ?? model.name,
				messages: messagesOpenAI,
				stream: streamingSupported,
				// Support two different ways of specifying token limits depending on the model
				...(useCompletionTokens
					? { max_completion_tokens: parameters?.max_new_tokens }
					: { max_tokens: parameters?.max_new_tokens }),
				stop: parameters?.stop,
				temperature: parameters?.temperature,
				top_p: parameters?.top_p,
				frequency_penalty: parameters?.repetition_penalty,
				presence_penalty: parameters?.presence_penalty,
				// Only include tool configuration if tools are provided
				...(toolCallChoices.length > 0 ? { tools: toolCallChoices, tool_choice: "auto" } : {}),
			};

			// Handle both streaming and non-streaming responses with appropriate processors
			if (streamingSupported) {
				const openChatAICompletion = await openai.chat.completions.create(
					body as ChatCompletionCreateParamsStreaming,
					{
						body: { ...body, ...extraBody },
						headers: {
							"ChatUI-Conversation-ID": conversationId?.toString() ?? "",
							"X-use-cache": "false",
						},
					}
				);
				return openAIChatToTextGenerationStream(openChatAICompletion);
			} else {
				const openChatAICompletion = await openai.chat.completions.create(
					body as ChatCompletionCreateParamsNonStreaming,
					{
						body: { ...body, ...extraBody },
						headers: {
							"ChatUI-Conversation-ID": conversationId?.toString() ?? "",
							"X-use-cache": "false",
						},
					}
				);
				return openAIChatToTextGenerationSingle(openChatAICompletion);
			}
		};
	} else {
		throw new Error("Invalid completion type");
	}
}