bolt.diy/app/routes/api.chat.ts
Chris Toshok 4795d02150
instrument whether an api key is the user's or ours (#33)
introduce an `AnthropicApiKey` type so we aren't passing a string/boolean everywhere.
2025-02-19 13:58:40 -08:00

101 lines
3.3 KiB
TypeScript

import { type ActionFunctionArgs } from '@remix-run/cloudflare';
import { ChatStreamController } from '~/utils/chatStreamController';
import { assert } from '~/lib/replay/ReplayProtocolClient';
import { getStreamTextArguments, type FileMap, type Messages } from '~/lib/.server/llm/stream-text';
import { chatAnthropic, type AnthropicApiKey } from '~/lib/.server/llm/chat-anthropic';
import { ensureOpenTelemetryInitialized } from '~/lib/.server/otel';
export async function action(args: ActionFunctionArgs) {
return chatAction(args);
}
// Directions given to the LLM when we have an enhanced prompt describing the bug to fix.
const EnhancedPromptPrefix = `
ULTRA IMPORTANT: Below is a detailed description of the bug.
Focus specifically on fixing this bug. Do not guess about other problems.
`;
async function chatAction({ context, request }: ActionFunctionArgs) {
ensureOpenTelemetryInitialized(context);
const { messages, files, promptId, simulationEnhancedPrompt, anthropicApiKey: clientAnthropicApiKey } = await request.json<{
messages: Messages;
files: FileMap;
promptId?: string;
simulationEnhancedPrompt?: string;
anthropicApiKey?: string;
}>();
let finished: (v?: any) => void;
context.cloudflare.ctx.waitUntil(new Promise((resolve) => finished = resolve));
console.log("SimulationEnhancedPrompt", simulationEnhancedPrompt);
try {
const { system, messages: coreMessages } = await getStreamTextArguments({
messages,
env: context.cloudflare.env,
apiKeys: {},
files,
providerSettings: undefined,
promptId,
});
const apiKey = clientAnthropicApiKey ?? context.cloudflare.env.ANTHROPIC_API_KEY;
if (!apiKey) {
throw new Error("Anthropic API key is not set");
}
const anthropicApiKey: AnthropicApiKey = {
key: apiKey,
isUser: !!clientAnthropicApiKey,
};
const resultStream = new ReadableStream({
async start(controller) {
const chatController = new ChatStreamController(controller);
if (simulationEnhancedPrompt) {
const lastMessage = coreMessages[coreMessages.length - 1];
assert(lastMessage.role == "user", "Last message must be a user message");
assert(lastMessage.content.length > 0, "Last message must have content");
const lastContent = lastMessage.content[0];
assert(typeof lastContent == "object" && lastContent.type == "text", "Last message content must be text");
lastContent.text += `\n\n${EnhancedPromptPrefix}\n\n${simulationEnhancedPrompt}`;
}
try {
await chatAnthropic(chatController, files, anthropicApiKey, system, coreMessages);
} catch (e) {
console.error(e);
chatController.writeText(`Error chatting with Anthropic: ${e}`);
}
controller.close();
setTimeout(finished, 1000);
},
});
return new Response(resultStream, {
status: 200,
headers: {
contentType: 'text/plain; charset=utf-8',
},
});
} catch (error: any) {
console.error(error);
if (error.message?.includes('API key')) {
throw new Response('Invalid or missing API key', {
status: 401,
statusText: 'Unauthorized',
});
}
throw new Response(null, {
status: 500,
statusText: 'Internal Server Error',
});
}
}