import type { Message } from "../types/message" import { resolvePastedPlaceholders } from "../lib/prompt-placeholders" import { instances } from "./instances" import { addRecentModelPreference, preferences, setAgentModelPreference, } from "./preferences" import { sessions, withSession } from "./session-state" import { getDefaultModel, isModelValid } from "./session-models" import { computeDisplayParts, getSessionIndex, initializePartVersion, updateSessionInfo, } from "./session-messages" const ID_LENGTH = 26 const BASE62_CHARS = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz" let lastTimestamp = 0 let localCounter = 0 function randomBase62(length: number): string { let result = "" const cryptoObj = (globalThis as unknown as { crypto?: Crypto }).crypto if (cryptoObj && typeof cryptoObj.getRandomValues === "function") { const bytes = new Uint8Array(length) cryptoObj.getRandomValues(bytes) for (let i = 0; i < length; i++) { result += BASE62_CHARS[bytes[i] % BASE62_CHARS.length] } } else { for (let i = 0; i < length; i++) { const idx = Math.floor(Math.random() * BASE62_CHARS.length) result += BASE62_CHARS[idx] } } return result } function createId(prefix: string): string { const timestamp = Date.now() if (timestamp !== lastTimestamp) { lastTimestamp = timestamp localCounter = 0 } localCounter++ const value = (BigInt(timestamp) << BigInt(12)) + BigInt(localCounter) const bytes = new Array(6) for (let i = 0; i < 6; i++) { const shift = BigInt(8 * (5 - i)) bytes[i] = Number((value >> shift) & BigInt(0xff)) } const hex = bytes.map((b) => b.toString(16).padStart(2, "0")).join("") const random = randomBase62(ID_LENGTH - 12) return `${prefix}_${hex}${random}` } async function sendMessage( instanceId: string, sessionId: string, prompt: string, attachments: any[] = [], ): Promise { const instance = instances().get(instanceId) if (!instance || !instance.client) { throw new Error("Instance not ready") } const instanceSessions = sessions().get(instanceId) const session = instanceSessions?.get(sessionId) if (!session) { throw new Error("Session not found") } const messageId = createId("msg") const textPartId = createId("part") const resolvedPrompt = resolvePastedPlaceholders(prompt, attachments) const optimisticParts: any[] = [ { id: textPartId, type: "text" as const, text: resolvedPrompt, synthetic: true, renderCache: undefined, }, ] const optimisticMessage: Message = { id: messageId, sessionId, type: "user", parts: optimisticParts, timestamp: Date.now(), status: "sending", version: 0, } optimisticParts.forEach((part: any) => initializePartVersion(part)) optimisticMessage.displayParts = computeDisplayParts(optimisticMessage, preferences().showThinkingBlocks) withSession(instanceId, sessionId, (session) => { session.messages.push(optimisticMessage) const index = getSessionIndex(instanceId, sessionId) index.messageIndex.set(optimisticMessage.id, session.messages.length - 1) }) const requestParts: any[] = [ { id: textPartId, type: "text" as const, text: resolvedPrompt, }, ] if (attachments.length > 0) { for (const att of attachments) { const source = att.source if (source.type === "file") { const partId = createId("part") requestParts.push({ id: partId, type: "file" as const, url: att.url, mime: source.mime, filename: att.filename, }) optimisticParts.push({ id: partId, type: "file" as const, url: att.url, mime: source.mime, filename: att.filename, synthetic: true, }) } else if (source.type === "text") { const display: string | undefined = att.display const value: unknown = source.value const isPastedPlaceholder = typeof display === "string" && /^pasted #\d+/.test(display) if (isPastedPlaceholder || typeof value !== "string") { continue } const partId = createId("part") requestParts.push({ id: partId, type: "text" as const, text: value, }) optimisticParts.push({ id: partId, type: "text" as const, text: value, synthetic: true, renderCache: undefined, }) } } } const requestBody = { messageID: messageId, parts: requestParts, ...(session.agent && { agent: session.agent }), ...(session.model.providerId && session.model.modelId && { model: { providerID: session.model.providerId, modelID: session.model.modelId, }, }), } console.log("[sendMessage] Sending prompt:", { sessionId, requestBody, }) try { console.log(`[HTTP] POST /session.prompt for instance ${instanceId}`, { sessionId, requestBody }) const response = await instance.client.session.prompt({ path: { id: sessionId }, body: requestBody, }) console.log("[sendMessage] Response:", response) if (response.error) { console.error("[sendMessage] Server returned error:", response.error) throw new Error(JSON.stringify(response.error) || "Failed to send message") } } catch (error) { console.error("[sendMessage] Failed to send prompt:", error) throw error } } async function executeCustomCommand( instanceId: string, sessionId: string, commandName: string, args: string, ): Promise { const instance = instances().get(instanceId) if (!instance || !instance.client) { throw new Error("Instance not ready") } const session = sessions().get(instanceId)?.get(sessionId) if (!session) { throw new Error("Session not found") } const body: { command: string arguments: string messageID: string agent?: string model?: string } = { command: commandName, arguments: args, messageID: createId("msg"), } if (session.agent) { body.agent = session.agent } if (session.model.providerId && session.model.modelId) { body.model = `${session.model.providerId}/${session.model.modelId}` } await instance.client.session.command({ path: { id: sessionId }, body, }) } async function runShellCommand(instanceId: string, sessionId: string, command: string): Promise { const instance = instances().get(instanceId) if (!instance || !instance.client) { throw new Error("Instance not ready") } const session = sessions().get(instanceId)?.get(sessionId) if (!session) { throw new Error("Session not found") } const agent = session.agent || "build" await instance.client.session.shell({ path: { id: sessionId }, body: { agent, command, }, }) } async function abortSession(instanceId: string, sessionId: string): Promise { const instance = instances().get(instanceId) if (!instance || !instance.client) { throw new Error("Instance not ready") } console.log("[abortSession] Aborting session:", { instanceId, sessionId }) try { console.log(`[HTTP] POST /session.abort for instance ${instanceId}`, { sessionId }) await instance.client.session.abort({ path: { id: sessionId }, }) console.log("[abortSession] Session aborted successfully") } catch (error) { console.error("[abortSession] Failed to abort session:", error) throw error } } async function updateSessionAgent(instanceId: string, sessionId: string, agent: string): Promise { const instanceSessions = sessions().get(instanceId) const session = instanceSessions?.get(sessionId) if (!session) { throw new Error("Session not found") } const nextModel = await getDefaultModel(instanceId, agent) const shouldApplyModel = isModelValid(instanceId, nextModel) withSession(instanceId, sessionId, (current) => { current.agent = agent if (shouldApplyModel) { current.model = nextModel } }) if (agent && shouldApplyModel) { setAgentModelPreference(instanceId, agent, nextModel) } if (shouldApplyModel) { updateSessionInfo(instanceId, sessionId) } } async function updateSessionModel( instanceId: string, sessionId: string, model: { providerId: string; modelId: string }, ): Promise { const instanceSessions = sessions().get(instanceId) const session = instanceSessions?.get(sessionId) if (!session) { throw new Error("Session not found") } if (!isModelValid(instanceId, model)) { console.warn("Invalid model selection", model) return } withSession(instanceId, sessionId, (current) => { current.model = model }) if (session.agent) { setAgentModelPreference(instanceId, session.agent, model) } addRecentModelPreference(model) updateSessionInfo(instanceId, sessionId) } export { abortSession, executeCustomCommand, runShellCommand, sendMessage, updateSessionAgent, updateSessionModel, }