diff --git a/packages/opencode/src/agent/agent.ts b/packages/opencode/src/agent/agent.ts index 04f50b0316..442531e136 100644 --- a/packages/opencode/src/agent/agent.ts +++ b/packages/opencode/src/agent/agent.ts @@ -13,7 +13,7 @@ export namespace Agent { name: z.string(), description: z.string().optional(), mode: z.enum(["subagent", "primary", "all"]), - builtIn: z.boolean(), + internal: z.boolean(), topP: z.number().optional(), temperature: z.number().optional(), color: z.string().optional(), @@ -112,7 +112,7 @@ export namespace Agent { options: {}, permission: agentPermission, mode: "subagent", - builtIn: true, + internal: true, }, explore: { name: "explore", @@ -147,7 +147,7 @@ export namespace Agent { options: {}, permission: agentPermission, mode: "subagent", - builtIn: true, + internal: true, }, build: { name: "build", @@ -155,13 +155,13 @@ export namespace Agent { options: {}, permission: agentPermission, mode: "primary", - builtIn: true, + internal: true, }, summary: { name: "summary", mode: "subagent", options: {}, - builtIn: true, + internal: true, permission: agentPermission, prompt: `You are a title generator. You output ONLY a thread title. Nothing else. @@ -210,7 +210,7 @@ Your output must be: ...defaultTools, }, mode: "primary", - builtIn: true, + internal: true, }, } for (const [key, value] of Object.entries(cfg.agent ?? {})) { @@ -226,7 +226,7 @@ Your output must be: permission: agentPermission, options: {}, tools: {}, - builtIn: false, + internal: false, } const { name, diff --git a/packages/opencode/src/session/compaction.ts b/packages/opencode/src/session/compaction.ts index 602b7f77b6..821ca015e4 100644 --- a/packages/opencode/src/session/compaction.ts +++ b/packages/opencode/src/session/compaction.ts @@ -91,10 +91,10 @@ export namespace SessionCompaction { providerID: string modelID: string } - agent: string abort: AbortSignal auto: boolean }) { + const agent = await Agent.get("compaction") const model = await Provider.getModel(input.model.providerID, input.model.modelID) const system = [...SystemPrompt.compaction(model.providerID)] const msg = (await Session.updateMessage({ @@ -102,7 +102,8 @@ export namespace SessionCompaction { role: "assistant", parentID: input.parentID, sessionID: input.sessionID, - mode: input.agent, + mode: "compaction", + agent: "compaction", summary: true, path: { cwd: Instance.directory, @@ -127,7 +128,6 @@ export namespace SessionCompaction { model: model, abort: input.abort, }) - const agent = await Agent.get(input.agent) const result = await processor.process({ user: input.messages.findLast((m) => m.info.id === input.parentID)!.info as MessageV2.User, agent, diff --git a/packages/opencode/src/session/llm.ts b/packages/opencode/src/session/llm.ts index fdb011b518..f0f48ce667 100644 --- a/packages/opencode/src/session/llm.ts +++ b/packages/opencode/src/session/llm.ts @@ -9,6 +9,7 @@ import type { Agent } from "@/agent/agent" import type { MessageV2 } from "./message-v2" import { Plugin } from "@/plugin" import { SystemPrompt } from "./system" +import { ToolRegistry } from "@/tool/registry" export namespace LLM { const log = Log.create({ service: "llm" }) @@ -43,7 +44,12 @@ export namespace LLM { }) const [language, cfg] = await Promise.all([Provider.getLanguage(input.model), Config.get()]) - const [first, ...rest] = [...SystemPrompt.header(input.model.providerID), ...input.system] + const [first, ...rest] = [ + ...SystemPrompt.header(input.model.providerID), + ...(input.agent.prompt ?? SystemPrompt.provider(input.model)), + ...input.system, + ...(input.user.system ? [input.user.system] : []), + ] const system = [first, rest.join("\n")] const params = await Plugin.trigger( @@ -80,6 +86,8 @@ export namespace LLM { OUTPUT_TOKEN_MAX, ) + const tools = await resolveTools(input) + return streamText({ onError(error) { l.error("stream error", { @@ -88,7 +96,7 @@ export namespace LLM { }, async experimental_repairToolCall(failed) { const lower = failed.toolCall.toolName.toLowerCase() - if (lower !== failed.toolCall.toolName && input.tools[lower]) { + if (lower !== failed.toolCall.toolName && tools[lower]) { l.info("repairing tool call", { tool: failed.toolCall.toolName, repaired: lower, @@ -110,8 +118,8 @@ export namespace LLM { temperature: params.temperature, topP: params.topP, providerOptions: ProviderTransform.providerOptions(input.model, params.options), - activeTools: Object.keys(input.tools).filter((x) => x !== "invalid"), - tools: input.tools, + activeTools: Object.keys(tools).filter((x) => x !== "invalid"), + tools, maxOutputTokens, abortSignal: input.abort, headers: { @@ -151,4 +159,16 @@ export namespace LLM { experimental_telemetry: { isEnabled: cfg.experimental?.openTelemetry }, }) } + + async function resolveTools(input: Pick) { + const enabled = pipe( + input.agent.tools, + mergeDeep(await ToolRegistry.enabled(input.agent)), + mergeDeep(input.user.tools ?? {}), + ) + for (const [key, value] of Object.entries(enabled)) { + if (value === false) delete input.tools[key] + } + return input.tools + } } diff --git a/packages/opencode/src/session/message-v2.ts b/packages/opencode/src/session/message-v2.ts index 477abe9581..76162c7978 100644 --- a/packages/opencode/src/session/message-v2.ts +++ b/packages/opencode/src/session/message-v2.ts @@ -348,7 +348,11 @@ export namespace MessageV2 { parentID: z.string(), modelID: z.string(), providerID: z.string(), + /** + * @deprecated + */ mode: z.string(), + agent: z.string(), path: z.object({ cwd: z.string(), root: z.string(), diff --git a/packages/opencode/src/session/prompt.ts b/packages/opencode/src/session/prompt.ts index 2989cf9974..997366c6ca 100644 --- a/packages/opencode/src/session/prompt.ts +++ b/packages/opencode/src/session/prompt.ts @@ -5,24 +5,22 @@ import z from "zod" import { Identifier } from "../id/id" import { MessageV2 } from "./message-v2" import { Log } from "../util/log" -import { Flag } from "../flag/flag" import { SessionRevert } from "./revert" import { Session } from "." import { Agent } from "../agent/agent" import { Provider } from "../provider/provider" -import { generateText, type ModelMessage, type Tool as AITool, tool, jsonSchema } from "ai" +import { type Tool as AITool, tool, jsonSchema } from "ai" import { SessionCompaction } from "./compaction" import { Instance } from "../project/instance" import { Bus } from "../bus" import { ProviderTransform } from "../provider/transform" import { SystemPrompt } from "./system" import { Plugin } from "../plugin" - import PROMPT_PLAN from "../session/prompt/plan.txt" import BUILD_SWITCH from "../session/prompt/build-switch.txt" import MAX_STEPS from "../session/prompt/max-steps.txt" import { defer } from "../util/defer" -import { clone, mergeDeep, pipe } from "remeda" +import { mergeDeep, pipe } from "remeda" import { ToolRegistry } from "../tool/registry" import { Wildcard } from "../util/wildcard" import { MCP } from "../mcp" @@ -36,7 +34,6 @@ import { Command } from "../command" import { $, fileURLToPath } from "bun" import { ConfigMarkdown } from "../config/markdown" import { SessionSummary } from "./summary" -import { Config } from "../config/config" import { NamedError } from "@opencode-ai/util/error" import { fn } from "@/util/fn" import { SessionProcessor } from "./processor" @@ -89,8 +86,8 @@ export namespace SessionPrompt { .optional(), agent: z.string().optional(), noReply: z.boolean().optional(), - system: z.string().optional(), tools: z.record(z.string(), z.boolean()).optional(), + system: z.string().optional(), parts: z.array( z.discriminatedUnion("type", [ MessageV2.TextPart.omit({ @@ -138,6 +135,20 @@ export namespace SessionPrompt { }) export type PromptInput = z.infer + export const prompt = fn(PromptInput, async (input) => { + const session = await Session.get(input.sessionID) + await SessionRevert.cleanup(session) + + const message = await createUserMessage(input) + await Session.touch(input.sessionID) + + if (input.noReply === true) { + return message + } + + return loop(input.sessionID) + }) + export async function resolvePromptParts(template: string): Promise { const parts: PromptInput["parts"] = [ { @@ -189,20 +200,6 @@ export namespace SessionPrompt { return parts } - export const prompt = fn(PromptInput, async (input) => { - const session = await Session.get(input.sessionID) - await SessionRevert.cleanup(session) - - const message = await createUserMessage(input) - await Session.touch(input.sessionID) - - if (input.noReply === true) { - return message - } - - return loop(input.sessionID) - }) - function start(sessionID: string) { const s = state() if (s[sessionID]) return @@ -296,6 +293,7 @@ export namespace SessionPrompt { parentID: lastUser.id, sessionID, mode: task.agent, + agent: task.agent, path: { cwd: Instance.directory, root: Instance.worktree, @@ -406,7 +404,6 @@ export namespace SessionPrompt { messages: msgs, parentID: lastUser.id, abort, - agent: lastUser.agent, model: { providerID: model.providerID, modelID: model.id, @@ -448,6 +445,7 @@ export namespace SessionPrompt { parentID: lastUser.id, role: "assistant", mode: agent.name, + agent: agent.name, path: { cwd: Instance.directory, root: Instance.worktree, @@ -470,11 +468,6 @@ export namespace SessionPrompt { model, abort, }) - const system = await resolveSystemPrompt({ - model, - agent, - system: lastUser.system, - }) const tools = await resolveTools({ agent, sessionID, @@ -495,7 +488,7 @@ export namespace SessionPrompt { agent, abort, sessionID, - system, + system: [...(await SystemPrompt.environment()), ...(await SystemPrompt.custom())], messages: [ ...MessageV2.toModelMessage(msgs), ...(isLastStep @@ -532,21 +525,6 @@ export namespace SessionPrompt { return Provider.defaultModel() } - async function resolveSystemPrompt(input: { system?: string; agent: Agent.Info; model: Provider.Model }) { - using _ = log.time("system") - let system = [] - system.push( - ...(() => { - if (input.system) return [input.system] - if (input.agent.prompt) return [input.agent.prompt] - return SystemPrompt.provider(input.model) - })(), - ) - system.push(...(await SystemPrompt.environment())) - system.push(...(await SystemPrompt.custom())) - return system - } - async function resolveTools(input: { agent: Agent.Info model: Provider.Model @@ -561,7 +539,6 @@ export namespace SessionPrompt { mergeDeep(await ToolRegistry.enabled(input.agent)), mergeDeep(input.tools ?? {}), ) - for (const item of await ToolRegistry.tools(input.model.providerID)) { if (Wildcard.all(item.id, enabledTools) === false) continue const schema = ProviderTransform.schema(input.model, z.toJSONSchema(item.parameters)) @@ -625,7 +602,6 @@ export namespace SessionPrompt { }, }) } - for (const [key, item] of Object.entries(await MCP.tools())) { if (Wildcard.all(key, enabledTools) === false) continue const execute = item.execute @@ -704,7 +680,6 @@ export namespace SessionPrompt { created: Date.now(), }, tools: input.tools, - system: input.system, agent: agent.name, model: input.model ?? agent.model ?? (await lastModel(input.sessionID)), } @@ -995,7 +970,7 @@ export namespace SessionPrompt { synthetic: true, }) } - const wasPlan = input.messages.some((msg) => msg.info.role === "assistant" && msg.info.mode === "plan") + const wasPlan = input.messages.some((msg) => msg.info.role === "assistant" && msg.info.agent === "plan") if (wasPlan && input.agent.name === "build") { userMessage.parts.push({ id: Identifier.ascending("part"), @@ -1057,6 +1032,7 @@ export namespace SessionPrompt { sessionID: input.sessionID, parentID: userMsg.id, mode: input.agent, + agent: input.agent, cost: 0, path: { cwd: Instance.directory, diff --git a/packages/opencode/src/session/system.ts b/packages/opencode/src/session/system.ts index 3146110cf3..8485f35f44 100644 --- a/packages/opencode/src/session/system.ts +++ b/packages/opencode/src/session/system.ts @@ -122,7 +122,7 @@ export namespace SystemPrompt { export function compaction(providerID: string) { switch (providerID) { case "anthropic": - return [PROMPT_ANTHROPIC_SPOOF.trim(), PROMPT_COMPACTION] + return [PROMPT_COMPACTION] default: return [PROMPT_COMPACTION] } @@ -131,7 +131,7 @@ export namespace SystemPrompt { export function summarize(providerID: string) { switch (providerID) { case "anthropic": - return [PROMPT_ANTHROPIC_SPOOF.trim(), PROMPT_SUMMARIZE] + return [PROMPT_SUMMARIZE] default: return [PROMPT_SUMMARIZE] } @@ -140,7 +140,7 @@ export namespace SystemPrompt { export function title(providerID: string) { switch (providerID) { case "anthropic": - return [PROMPT_ANTHROPIC_SPOOF.trim(), PROMPT_TITLE] + return [PROMPT_TITLE] default: return [PROMPT_TITLE] }