diff --git a/packages/opencode/src/session/mcp-tools.ts b/packages/opencode/src/session/mcp-tools.ts index c1a7b4f0c0..ee32958d55 100644 --- a/packages/opencode/src/session/mcp-tools.ts +++ b/packages/opencode/src/session/mcp-tools.ts @@ -61,6 +61,11 @@ interface DeferredToolDescriptor { searchText: string } +interface Output { + tools: Record + deferredSystemPrompt?: string +} + const SearchDeferredToolsParameters = Schema.Struct({ query: Schema.String.annotate({ description: @@ -229,9 +234,8 @@ export const resolve = Effect.fn("SessionMcpTools.resolve")(function* (input: In return output }) - const hasMcpResourceServer = Object.values(yield* mcp.clients()).some( - (client) => !!client.getServerCapabilities()?.resources, - ) + const clients = yield* mcp.clients() + const hasMcpResourceServer = Object.values(clients).some((client) => !!client.getServerCapabilities()?.resources) if (hasMcpResourceServer) addResourceTools(tools, { input, run, mcp, plugin, truncate, context }) const mcpTools = yield* mcp.tools() @@ -261,7 +265,10 @@ export const resolve = Effect.fn("SessionMcpTools.resolve")(function* (input: In allowedMcpTools, executeMcpTool, }) - return tools + return { + tools, + deferredSystemPrompt: deferredSystemPrompt(allowedMcpTools, clients), + } satisfies Output } for (const [key, item] of Object.entries(mcpTools)) { @@ -275,35 +282,11 @@ export const resolve = Effect.fn("SessionMcpTools.resolve")(function* (input: In tools[key] = item } - return tools + return { tools } satisfies Output }) -export const systemPrompt = Effect.fn("SessionMcpTools.systemPrompt")(function* (input: { - agent: Agent.Info - session: Session.Info - messages: SessionV1.WithParts[] -}) { - const mcp = yield* MCP.Service - const flags = yield* RuntimeFlags.Service - if (!flags.experimentalToolSearch) return undefined - - const mcpTools = yield* mcp.tools() - const mcpDisabled = Permission.disabled( - Object.keys(mcpTools), - Permission.merge(input.agent.permission, input.session.permission ?? []), - ) - const userTools = currentUserToolOverrides(input.messages) - const allowedTools = Object.fromEntries( - Object.entries(mcpTools).filter(([key]) => userTools?.[key] !== false && !mcpDisabled.has(key)), - ) - if (Object.keys(allowedTools).length === 0) return undefined - - const deferredDescriptors = yield* deferredToolDescriptors(allowedTools) - if (deferredToolSchemaTokens(deferredDescriptors) < MIN_DEFERRED_MCP_SCHEMA_TOKENS) { - return undefined - } - - const servers = deferredServerSummaries(Object.keys(allowedTools), Object.keys(yield* mcp.clients())) +function deferredSystemPrompt(allowedMcpTools: Record, clients: Record) { + const servers = deferredServerSummaries(Object.keys(allowedMcpTools), Object.keys(clients)) return [ DEFERRED_TOOL_SYSTEM_PROMPT, servers.length === 0 @@ -315,7 +298,7 @@ export const systemPrompt = Effect.fn("SessionMcpTools.systemPrompt")(function* ] .filter((part): part is string => part !== undefined) .join("\n\n") -}) +} function addResourceTools( tools: Record, diff --git a/packages/opencode/src/session/prompt.ts b/packages/opencode/src/session/prompt.ts index dbc35babad..142f24462c 100644 --- a/packages/opencode/src/session/prompt.ts +++ b/packages/opencode/src/session/prompt.ts @@ -1223,7 +1223,7 @@ export const layer = Layer.effect( const bypassAgentCheck = lastUserMsg?.parts.some((p) => p.type === "agent") ?? false const promptOps = yield* ops() - const tools = yield* SessionTools.resolve({ + const resolvedTools = yield* SessionTools.resolve({ agent, session, model, @@ -1240,6 +1240,7 @@ export const layer = Layer.effect( Effect.provideService(Truncate.Service, truncate), Effect.provideService(RuntimeFlags.Service, flags), ) + const tools = resolvedTools.tools if (lastUser.format?.type === "json_schema") { tools["StructuredOutput"] = createStructuredOutputTool({ @@ -1260,12 +1261,7 @@ export const layer = Layer.effect( sys.environment(model), instruction.system().pipe(Effect.orDie), sys.mcp(agent, session.permission), - tools.search_deferred_tools && tools.call_deferred_tool - ? SessionTools.deferredSystemPrompt({ agent, session, messages: msgs }).pipe( - Effect.provideService(MCP.Service, mcp), - Effect.provideService(RuntimeFlags.Service, flags), - ) - : Effect.succeed(undefined), + Effect.succeed(resolvedTools.deferredSystemPrompt), MessageV2.toModelMessagesEffect(msgs, model), ]) const system = [ diff --git a/packages/opencode/src/session/tools.ts b/packages/opencode/src/session/tools.ts index 49374f24e6..073d54fad5 100644 --- a/packages/opencode/src/session/tools.ts +++ b/packages/opencode/src/session/tools.ts @@ -17,9 +17,6 @@ import { SessionProcessor } from "./processor" import { Session } from "./session" import { PartID } from "./schema" -export const DEFERRED_TOOL_SYSTEM_PROMPT = SessionMcpTools.DEFERRED_TOOL_SYSTEM_PROMPT -export const deferredSystemPrompt = SessionMcpTools.systemPrompt - export const resolve = Effect.fn("SessionTools.resolve")(function* (input: { agent: Agent.Info model: Provider.Model @@ -112,8 +109,12 @@ export const resolve = Effect.fn("SessionTools.resolve")(function* (input: { }) } - Object.assign(tools, yield* SessionMcpTools.resolve(input)) - return tools + const mcp = yield* SessionMcpTools.resolve(input) + Object.assign(tools, mcp.tools) + return { + tools, + deferredSystemPrompt: mcp.deferredSystemPrompt, + } }) export * as SessionTools from "./tools" diff --git a/packages/opencode/test/session/tools.test.ts b/packages/opencode/test/session/tools.test.ts index 37646d3794..acd261808d 100644 --- a/packages/opencode/test/session/tools.test.ts +++ b/packages/opencode/test/session/tools.test.ts @@ -132,7 +132,7 @@ const belowThresholdIt = makeIt({ queryDescription: "Natural language analytics query", }) -function resolveTools(input: { messages?: SessionV1.WithParts[]; canDeferMcpTools?: boolean } = {}) { +function resolveToolResult(input: { messages?: SessionV1.WithParts[]; canDeferMcpTools?: boolean } = {}) { return SessionTools.resolve({ agent, model, @@ -145,6 +145,10 @@ function resolveTools(input: { messages?: SessionV1.WithParts[]; canDeferMcpTool }) } +function resolveTools(input: { messages?: SessionV1.WithParts[]; canDeferMcpTools?: boolean } = {}) { + return resolveToolResult(input).pipe(Effect.map((result) => result.tools)) +} + describe("session.tools", () => { deferredIt.instance("defers MCP tools behind fixed search and call tools", () => Effect.gen(function* () { @@ -181,7 +185,7 @@ describe("session.tools", () => { deferredIt.instance("lists deferred MCP servers in the system prompt", () => Effect.gen(function* () { - const prompt = yield* SessionTools.deferredSystemPrompt({ agent, session, messages: [] }) + const prompt = (yield* resolveToolResult()).deferredSystemPrompt expect(prompt).toContain("Deferred MCP servers available through `search_deferred_tools`:") expect(prompt).toContain("- posthog: 2 tools")