"use server"; import { openai } from "@/lib/openai-client"; import { hf } from "@/lib/hf-client"; import { z } from "zod"; import { startMcpSandbox } from "@/lib/mcp-sandbox"; // Use a global map to store active sandbox instances across requests const activeSandboxes = (global as any).activeSandboxes || new Map(); (global as any).activeSandboxes = activeSandboxes; // Helper to extract text content from a message regardless of format function getMessageText(message: any): string { // Check if the message has parts (new format) if (message.parts && Array.isArray(message.parts)) { const textParts = message.parts.filter( (p: any) => p.type === "text" && p.text ); if (textParts.length > 0) { return textParts.map((p: any) => p.text).join("\n"); } } // Fallback to content (old format) if (typeof message.content === "string") { return message.content; } // If content is an array (potentially of parts), try to extract text if (Array.isArray(message.content)) { const textItems = message.content.filter( (item: any) => typeof item === "string" || (item.type === "text" && item.text) ); if (textItems.length > 0) { return textItems .map((item: any) => (typeof item === "string" ? item : item.text)) .join("\n"); } } return ""; } export async function generateTitle(messages: any[]): Promise { const normalized = messages.map((m) => ({ role: m.role as "user" | "assistant" | "system", content: Array.isArray(m.content) ? m.content.join("\n") : m.content, })); const response = await hf.chat.completions.create({ model: "meta-llama/Llama-3.1-8B-Instruct", max_tokens: 30, temperature: 0.4, messages: [ { role: "system", content: "You are a helpful assistant that writes very short, unique titles (≤30 characters) for chat conversations.", }, ...normalized, { role: "user", content: "Write the title only." }, ], }); const title = response.choices[0]?.message.content?.trim() ?? "New Chat"; // basic schema check return z.string().min(1).max(100).parse(title); } export interface KeyValuePair { key: string; value: string; } /** * Server action to start a sandbox */ export async function startSandbox(params: { id: string; command: string; args: string[]; env?: KeyValuePair[]; }): Promise<{ url: string }> { const { id, command, args, env } = params; console.log(`[startSandbox] Starting sandbox for ID: ${id}`); // Validate required fields if (!id || !command || !args) { throw new Error("Missing required fields"); } // Check if we already have a sandbox for this ID if (activeSandboxes.has(id)) { // If we do, get the URL and return it without creating a new sandbox const existingSandbox = activeSandboxes.get(id); console.log( `[startSandbox] Reusing existing sandbox for ${id}, URL: ${existingSandbox.url}` ); // Re-fetch the URL to make sure it's current try { const freshUrl = await existingSandbox.sandbox.getUrl(); console.log(`[startSandbox] Updated sandbox URL for ${id}: ${freshUrl}`); // Update the URL in the map activeSandboxes.set(id, { sandbox: existingSandbox.sandbox, url: freshUrl, }); return { url: freshUrl }; } catch (error) { console.error( `[startSandbox] Error refreshing sandbox URL for ${id}:`, error ); // Fall through to create a new sandbox if we couldn't refresh the URL activeSandboxes.delete(id); console.log( `[startSandbox] Removed stale sandbox for ${id}, will create a new one` ); } } // Build the command string let cmd: string; // Prepare the command based on the type of executable if (command === "uvx") { // For uvx, use the direct format const toolName = args[0]; cmd = `uvx ${toolName} ${args.slice(1).join(" ")}`; } else if (command.includes("python")) { // For python commands cmd = `${command} ${args.join(" ")}`; } else { // For node or other commands cmd = `${command} ${args.join(" ")}`; } // Convert env array to object if needed const envs: Record = {}; if (env && env.length > 0) { env.forEach((envVar) => { if (envVar.key) envs[envVar.key] = envVar.value || ""; }); } // Start the sandbox console.log( `[startSandbox] Creating new sandbox for ${id} with command: ${cmd}` ); const sandbox = await startMcpSandbox({ cmd, envs }); const url = await sandbox.getUrl(); console.log(`[startSandbox] Sandbox created for ${id}, URL: ${url}`); // Store the sandbox in our map activeSandboxes.set(id, { sandbox, url }); return { url }; } /** * Server action to stop a sandbox */ export async function stopSandbox(id: string): Promise<{ success: boolean }> { if (!id) { throw new Error("Missing sandbox ID"); } // Check if we have a sandbox with this ID if (!activeSandboxes.has(id)) { throw new Error(`No active sandbox found with ID: ${id}`); } // Stop the sandbox const { sandbox } = activeSandboxes.get(id); try { await sandbox.stop(); console.log(`Stopped sandbox with ID: ${id}`); } catch (stopError) { console.error(`Error stopping sandbox ${id}:`, stopError); // Continue to remove from the map even if stop fails } // Remove from our map activeSandboxes.delete(id); return { success: true }; }