UNPKG

@deep-assistant/agent

Version:

A minimal, public domain AI CLI agent compatible with OpenCode's JSON interface. Bun-only runtime.

127 lines (118 loc) 4.51 kB
import { Tool } from "./tool" import DESCRIPTION from "./task.txt" import z from "zod" import { Session } from "../session" import { Bus } from "../bus" import { MessageV2 } from "../session/message-v2" import { Identifier } from "../id/id" import { Agent } from "../agent/agent" import { SessionPrompt } from "../session/prompt" import { iife } from "../util/iife" import { defer } from "../util/defer" export const TaskTool = Tool.define("task", async () => { const agents = await Agent.list().then((x) => x.filter((a) => a.mode !== "primary")) const description = DESCRIPTION.replace( "{agents}", agents .map((a) => `- ${a.name}: ${a.description ?? "This subagent should only be called manually by the user."}`) .join("\n"), ) return { description, parameters: z.object({ description: z.string().describe("A short (3-5 words) description of the task"), prompt: z.string().describe("The task for the agent to perform"), subagent_type: z.string().describe("The type of specialized agent to use for this task"), session_id: z.string().describe("Existing Task session to continue").optional(), }), async execute(params, ctx) { const agent = await Agent.get(params.subagent_type) if (!agent) throw new Error(`Unknown agent type: ${params.subagent_type} is not a valid agent type`) const session = await iife(async () => { if (params.session_id) { const found = await Session.get(params.session_id).catch(() => {}) if (found) return found } return await Session.create({ parentID: ctx.sessionID, title: params.description + ` (@${agent.name} subagent)`, }) }) // Try to get parent message for model info, but use defaults if not available let parentModel: { modelID: string; providerID: string } | undefined try { const msg = await MessageV2.get({ sessionID: ctx.sessionID, messageID: ctx.messageID }) if (msg.info.role === "assistant") { parentModel = { modelID: msg.info.modelID, providerID: msg.info.providerID, } } } catch (e) { // Parent message not in storage, will use agent model or default } ctx.metadata({ title: params.description, metadata: { sessionId: session.id, }, }) const messageID = Identifier.ascending("message") const parts: Record<string, MessageV2.ToolPart> = {} const unsub = Bus.subscribe(MessageV2.Event.PartUpdated, async (evt) => { if (evt.properties.part.sessionID !== session.id) return if (evt.properties.part.messageID === messageID) return if (evt.properties.part.type !== "tool") return parts[evt.properties.part.id] = evt.properties.part ctx.metadata({ title: params.description, metadata: { summary: Object.values(parts).sort((a, b) => a.id?.localeCompare(b.id)), sessionId: session.id, }, }) }) const model = agent.model ?? parentModel ?? { modelID: "grok-code", providerID: "opencode", } function cancel() { SessionPrompt.cancel(session.id) } ctx.abort.addEventListener("abort", cancel) using _ = defer(() => ctx.abort.removeEventListener("abort", cancel)) const promptParts = await SessionPrompt.resolvePromptParts(params.prompt) const result = await SessionPrompt.prompt({ messageID, sessionID: session.id, model: { modelID: model.modelID, providerID: model.providerID, }, agent: agent.name, tools: { todowrite: false, todoread: false, task: false, ...agent.tools, }, parts: promptParts, }) unsub() let all all = await Session.messages({ sessionID: session.id }) all = all.filter((x) => x.info.role === "assistant") all = all.flatMap((msg) => msg.parts.filter((x: any) => x.type === "tool") as MessageV2.ToolPart[]) const text = result.parts.findLast((x) => x.type === "text")?.text ?? "" const output = text + "\n\n" + ["<task_metadata>", `session_id: ${session.id}`, "</task_metadata>"].join("\n") return { title: params.description, metadata: { summary: all, sessionId: session.id, }, output, } }, } })