import { tool, type PluginInput } from "@opencode-ai/plugin" import type { BackgroundManager, BackgroundTask } from "../../features/background-agent" import type { BackgroundTaskArgs, BackgroundOutputArgs, BackgroundCancelArgs } from "./types" import { BACKGROUND_TASK_DESCRIPTION, BACKGROUND_OUTPUT_DESCRIPTION, BACKGROUND_CANCEL_DESCRIPTION } from "./constants" type OpencodeClient = PluginInput["client"] function formatDuration(start: Date, end?: Date): string { const duration = (end ?? new Date()).getTime() - start.getTime() const seconds = Math.floor(duration / 1000) const minutes = Math.floor(seconds / 60) const hours = Math.floor(minutes / 60) if (hours > 0) { return `${hours}h ${minutes % 60}m ${seconds % 60}s` } else if (minutes > 0) { return `${minutes}m ${seconds % 60}s` } else { return `${seconds}s` } } export function createBackgroundTask(manager: BackgroundManager) { return tool({ description: BACKGROUND_TASK_DESCRIPTION, args: { description: tool.schema.string().describe("Short task description (shown in status)"), prompt: tool.schema.string().describe("Full detailed prompt for the agent"), agent: tool.schema.string().describe("Agent type to use (any agent allowed)"), }, async execute(args: BackgroundTaskArgs, toolContext) { try { const task = await manager.launch({ description: args.description, prompt: args.prompt, agent: args.agent, parentSessionID: toolContext.sessionID, parentMessageID: toolContext.messageID, }) return `Background task launched successfully. Task ID: ${task.id} Session ID: ${task.sessionID} Description: ${task.description} Agent: ${task.agent} Status: ${task.status} Use \`background_output\` tool with task_id="${task.id}" to check progress or retrieve results. - block=false: Check status without waiting - block=true (default): Wait for completion and get result` } catch (error) { const message = error instanceof Error ? error.message : String(error) return `❌ Failed to launch background task: ${message}` } }, }) } function delay(ms: number): Promise { return new Promise(resolve => setTimeout(resolve, ms)) } function formatTaskStatus(task: BackgroundTask): string { const duration = formatDuration(task.startedAt, task.completedAt) const progress = task.progress ? `\nTool calls: ${task.progress.toolCalls}\nLast tool: ${task.progress.lastTool ?? "N/A"}` : "" return `Task Status Task ID: ${task.id} Description: ${task.description} Agent: ${task.agent} Status: ${task.status} Duration: ${duration}${progress} Session ID: ${task.sessionID}` } async function formatTaskResult(task: BackgroundTask, client: OpencodeClient): Promise { const messagesResult = await client.session.messages({ path: { id: task.sessionID }, }) if (messagesResult.error) { return `Error fetching messages: ${messagesResult.error}` } const messages = messagesResult.data const assistantMessages = messages.filter( (m: any) => m.info?.role === "assistant" ) const lastMessage = assistantMessages[assistantMessages.length - 1] const textParts = lastMessage?.parts?.filter( (p: any) => p.type === "text" ) ?? [] const textContent = textParts.map((p: any) => p.text).join("\n") const duration = formatDuration(task.startedAt, task.completedAt) return `Task Result Task ID: ${task.id} Description: ${task.description} Duration: ${duration} Session ID: ${task.sessionID} --- ${textContent || "(No output)"}` } export function createBackgroundOutput(manager: BackgroundManager, client: OpencodeClient) { return tool({ description: BACKGROUND_OUTPUT_DESCRIPTION, args: { task_id: tool.schema.string().describe("Task ID to get output from"), block: tool.schema.boolean().optional().describe("Wait for completion (default: true)"), timeout: tool.schema.number().optional().describe("Max wait time in ms (default: 60000, max: 600000)"), }, async execute(args: BackgroundOutputArgs) { try { const task = manager.getTask(args.task_id) if (!task) { return `Task not found: ${args.task_id}` } const shouldBlock = args.block !== false const timeoutMs = Math.min(args.timeout ?? 60000, 600000) // Non-blocking: return status immediately if (!shouldBlock) { return formatTaskStatus(task) } // Already completed: return result immediately if (task.status === "completed") { return await formatTaskResult(task, client) } // Error or cancelled: return status immediately if (task.status === "error" || task.status === "cancelled") { return formatTaskStatus(task) } // Blocking: poll until completion or timeout const startTime = Date.now() while (Date.now() - startTime < timeoutMs) { await delay(1000) const currentTask = manager.getTask(args.task_id) if (!currentTask) { return `Task was deleted: ${args.task_id}` } if (currentTask.status === "completed") { return await formatTaskResult(currentTask, client) } if (currentTask.status === "error" || currentTask.status === "cancelled") { return formatTaskStatus(currentTask) } } // Timeout exceeded: return current status const finalTask = manager.getTask(args.task_id) if (!finalTask) { return `Task was deleted: ${args.task_id}` } return `Timeout exceeded (${timeoutMs}ms). Task still ${finalTask.status}.\n\n${formatTaskStatus(finalTask)}` } catch (error) { return `Error getting output: ${error instanceof Error ? error.message : String(error)}` } }, }) } export function createBackgroundCancel(manager: BackgroundManager, client: OpencodeClient) { return tool({ description: BACKGROUND_CANCEL_DESCRIPTION, args: { taskId: tool.schema.string().describe("Task ID to cancel"), }, async execute(args: BackgroundCancelArgs) { try { const task = manager.getTask(args.taskId) if (!task) { return `❌ Task not found: ${args.taskId}` } if (task.status !== "running") { return `❌ Cannot cancel task: current status is "${task.status}". Only running tasks can be cancelled.` } const abortResult = await client.session.abort({ path: { id: task.sessionID }, }) if (abortResult.error) { return `❌ Failed to abort session: ${(abortResult.error as any).message || String(abortResult.error)}` } task.status = "cancelled" task.completedAt = new Date() return `✅ Task cancelled successfully Task ID: ${task.id} Description: ${task.description} Session ID: ${task.sessionID} Status: ${task.status}` } catch (error) { return `❌ Error cancelling task: ${error instanceof Error ? error.message : String(error)}` } }, }) }