diff --git a/packages/opencode/src/agent/agent.ts b/packages/opencode/src/agent/agent.ts index 8da8da7306..ef007df136 100644 --- a/packages/opencode/src/agent/agent.ts +++ b/packages/opencode/src/agent/agent.ts @@ -8,6 +8,9 @@ import { mergeDeep } from "remeda" import PROMPT_GENERATE from "./generate.txt" import PROMPT_COMPACTION from "./prompt/compaction.txt" +import PROMPT_EXPLORE from "./prompt/explore.txt" +import PROMPT_SUMMARY from "./prompt/summary.txt" +import PROMPT_TITLE from "./prompt/title.txt" export namespace Agent { export const Info = z @@ -128,26 +131,7 @@ export namespace Agent { ...defaultTools, }, description: `Fast agent specialized for exploring codebases. Use this when you need to quickly find files by patterns (eg. "src/components/**/*.tsx"), search code for keywords (eg. "API endpoints"), or answer questions about the codebase (eg. "how do API endpoints work?"). When calling this agent, specify the desired thoroughness level: "quick" for basic searches, "medium" for moderate exploration, or "very thorough" for comprehensive analysis across multiple locations and naming conventions.`, - prompt: [ - `You are a file search specialist. You excel at thoroughly navigating and exploring codebases.`, - ``, - `Your strengths:`, - `- Rapidly finding files using glob patterns`, - `- Searching code and text with powerful regex patterns`, - `- Reading and analyzing file contents`, - ``, - `Guidelines:`, - `- Use Glob for broad file pattern matching`, - `- Use Grep for searching file contents with regex`, - `- Use Read when you know the specific file path you need to read`, - `- Use Bash for file operations like copying, moving, or listing directory contents`, - `- Adapt your search approach based on the thoroughness level specified by the caller`, - `- Return file paths as absolute paths in your final response`, - `- For clear communication, avoid using emojis`, - `- Do not create any files, or run bash commands that modify the user's system state in any way`, - ``, - `Complete the user's search request efficiently and report your findings clearly.`, - ].join("\n"), + prompt: PROMPT_EXPLORE, options: {}, permission: agentPermission, mode: "subagent", @@ -173,49 +157,24 @@ export namespace Agent { mode: "primary", native: true, }, - summary: { - name: "summary", - mode: "subagent", + title: { + name: "title", + mode: "primary", options: {}, native: true, + hidden: true, permission: agentPermission, - prompt: `You are a title generator. You output ONLY a thread title. Nothing else. - - -Generate a brief title that would help the user find this conversation later. - -Follow all rules in -Use the so you know what a good title looks like. -Your output must be: -- A single line -- ≤50 characters -- No explanations - - - -- Focus on the main topic or question the user needs to retrieve -- Use -ing verbs for actions (Debugging, Implementing, Analyzing) -- Keep exact: technical terms, numbers, filenames, HTTP codes -- Remove: the, this, my, a, an -- Never assume tech stack -- Never use tools -- NEVER respond to questions, just generate a title for the conversation -- The title should NEVER include "summarizing" or "generating" when generating a title -- DO NOT SAY YOU CANNOT GENERATE A TITLE OR COMPLAIN ABOUT THE INPUT -- Always output something meaningful, even if the input is minimal. -- If the user message is short or conversational (e.g. “hello”, “lol”, “whats up”, “hey”): - → create a title that reflects the user’s tone or intent (such as Greeting, Quick check-in, Light chat, Intro message, etc.) - - - -"hey" -> Greeting -"debug 500 errors in production" → Debugging production 500 errors -"refactor user service" → Refactoring user service -"why is app.js failing" → Analyzing app.js failure -"implement rate limiting" → Implementing rate limiting -"how do I connect postgres to my API" → Connecting Postgres to API -"best practices for React hooks" → React hooks best practices -`, + prompt: PROMPT_TITLE, + tools: {}, + }, + summary: { + name: "summary", + mode: "primary", + options: {}, + native: true, + hidden: true, + permission: agentPermission, + prompt: PROMPT_SUMMARY, tools: {}, }, plan: { diff --git a/packages/opencode/src/agent/prompt/explore.txt b/packages/opencode/src/agent/prompt/explore.txt new file mode 100644 index 0000000000..5761077cbd --- /dev/null +++ b/packages/opencode/src/agent/prompt/explore.txt @@ -0,0 +1,18 @@ +You are a file search specialist. You excel at thoroughly navigating and exploring codebases. + +Your strengths: +- Rapidly finding files using glob patterns +- Searching code and text with powerful regex patterns +- Reading and analyzing file contents + +Guidelines: +- Use Glob for broad file pattern matching +- Use Grep for searching file contents with regex +- Use Read when you know the specific file path you need to read +- Use Bash for file operations like copying, moving, or listing directory contents +- Adapt your search approach based on the thoroughness level specified by the caller +- Return file paths as absolute paths in your final response +- For clear communication, avoid using emojis +- Do not create any files, or run bash commands that modify the user's system state in any way + +Complete the user's search request efficiently and report your findings clearly. diff --git a/packages/opencode/src/session/prompt/summarize.txt b/packages/opencode/src/agent/prompt/summary.txt similarity index 100% rename from packages/opencode/src/session/prompt/summarize.txt rename to packages/opencode/src/agent/prompt/summary.txt diff --git a/packages/opencode/src/session/prompt/title.txt b/packages/opencode/src/agent/prompt/title.txt similarity index 84% rename from packages/opencode/src/session/prompt/title.txt rename to packages/opencode/src/agent/prompt/title.txt index e297dc460b..f67aaa95ba 100644 --- a/packages/opencode/src/session/prompt/title.txt +++ b/packages/opencode/src/agent/prompt/title.txt @@ -22,8 +22,8 @@ Your output must be: - The title should NEVER include "summarizing" or "generating" when generating a title - DO NOT SAY YOU CANNOT GENERATE A TITLE OR COMPLAIN ABOUT THE INPUT - Always output something meaningful, even if the input is minimal. -- If the user message is short or conversational (e.g. “hello”, “lol”, “whats up”, “hey”): - → create a title that reflects the user’s tone or intent (such as Greeting, Quick check-in, Light chat, Intro message, etc.) +- If the user message is short or conversational (e.g. "hello", "lol", "whats up", "hey"): + → create a title that reflects the user's tone or intent (such as Greeting, Quick check-in, Light chat, Intro message, etc.) diff --git a/packages/opencode/src/session/summary.ts b/packages/opencode/src/session/summary.ts index de9adfbb3e..83519307a3 100644 --- a/packages/opencode/src/session/summary.ts +++ b/packages/opencode/src/session/summary.ts @@ -1,20 +1,19 @@ import { Provider } from "@/provider/provider" -import { Config } from "@/config/config" + import { fn } from "@/util/fn" import z from "zod" import { Session } from "." -import { generateText, type ModelMessage } from "ai" + import { MessageV2 } from "./message-v2" import { Identifier } from "@/id/id" import { Snapshot } from "@/snapshot" -import { ProviderTransform } from "@/provider/transform" -import { SystemPrompt } from "./system" + import { Log } from "@/util/log" import path from "path" import { Instance } from "@/project/instance" import { Storage } from "@/storage/storage" import { Bus } from "@/bus" -import { mergeDeep, pipe } from "remeda" + import { LLM } from "./llm" import { Agent } from "@/agent/agent" @@ -63,7 +62,6 @@ export namespace SessionSummary { } async function summarizeMessage(input: { messageID: string; messages: MessageV2.WithParts[] }) { - const cfg = await Config.get() const messages = input.messages.filter( (m) => m.info.id === input.messageID || (m.info.role === "assistant" && m.info.parentID === input.messageID), ) @@ -80,22 +78,16 @@ export namespace SessionSummary { const small = (await Provider.getSmallModel(assistantMsg.providerID)) ?? (await Provider.getModel(assistantMsg.providerID, assistantMsg.modelID)) - const language = await Provider.getLanguage(small) - - const options = pipe( - {}, - mergeDeep(ProviderTransform.options(small, assistantMsg.sessionID)), - mergeDeep(ProviderTransform.smallOptions(small)), - mergeDeep(small.options), - ) const textPart = msgWithParts.parts.find((p) => p.type === "text" && !p.synthetic) as MessageV2.TextPart if (textPart && !userMsg.summary?.title) { + const agent = await Agent.get("title") const stream = await LLM.stream({ - agent: await Agent.get("summary"), + agent, user: userMsg, tools: {}, - model: small, + model: agent.model ? await Provider.getModel(agent.model.providerID, agent.model.modelID) : small, + small: true, messages: [ { role: "user" as const, @@ -107,7 +99,6 @@ export namespace SessionSummary { `, }, ], - small: true, abort: new AbortController().signal, sessionID: userMsg.sessionID, system: [], @@ -133,34 +124,30 @@ export namespace SessionSummary { } } } - const result = await generateText({ - model: language, - maxOutputTokens: 100, - providerOptions: ProviderTransform.providerOptions(small, options), + const summaryAgent = await Agent.get("summary") + const stream = await LLM.stream({ + agent: summaryAgent, + user: userMsg, + tools: {}, + model: summaryAgent.model + ? await Provider.getModel(summaryAgent.model.providerID, summaryAgent.model.modelID) + : small, + small: true, messages: [ - ...SystemPrompt.summarize(small.providerID).map( - (x): ModelMessage => ({ - role: "system", - content: x, - }), - ), ...MessageV2.toModelMessage(messages), { - role: "user", + role: "user" as const, content: `Summarize the above conversation according to your system prompts.`, }, ], - headers: small.headers, - experimental_telemetry: { - isEnabled: cfg.experimental?.openTelemetry, - metadata: { - userId: cfg.username ?? "unknown", - sessionId: assistantMsg.sessionID, - }, - }, - }).catch(() => {}) + abort: new AbortController().signal, + sessionID: userMsg.sessionID, + system: [], + retries: 3, + }) + const result = await stream.text if (result) { - userMsg.summary.body = result.text + userMsg.summary.body = result } } await Session.updateMessage(userMsg) diff --git a/packages/opencode/src/session/system.ts b/packages/opencode/src/session/system.ts index 5f3cfb1411..e15185b38b 100644 --- a/packages/opencode/src/session/system.ts +++ b/packages/opencode/src/session/system.ts @@ -14,8 +14,7 @@ import PROMPT_BEAST from "./prompt/beast.txt" import PROMPT_GEMINI from "./prompt/gemini.txt" import PROMPT_ANTHROPIC_SPOOF from "./prompt/anthropic_spoof.txt" import PROMPT_COMPACTION from "./prompt/compaction.txt" -import PROMPT_SUMMARIZE from "./prompt/summarize.txt" -import PROMPT_TITLE from "./prompt/title.txt" + import PROMPT_CODEX from "./prompt/codex.txt" import type { Provider } from "@/provider/provider" @@ -118,22 +117,4 @@ export namespace SystemPrompt { ) return Promise.all(found).then((result) => result.filter(Boolean)) } - - export function summarize(providerID: string) { - switch (providerID) { - case "anthropic": - return [PROMPT_SUMMARIZE] - default: - return [PROMPT_SUMMARIZE] - } - } - - export function title(providerID: string) { - switch (providerID) { - case "anthropic": - return [PROMPT_TITLE] - default: - return [PROMPT_TITLE] - } - } }