summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorJason Quense <[email protected]>2026-03-16 00:01:46 -0400
committerGitHub <[email protected]>2026-03-15 23:01:46 -0500
commitc2ca1494e5f2b21655982d694b6bafd4526f147e (patch)
treef44824f3527b5a7c78ff8ff75d5c85578290b7f4
parent4ee426ba549131c4903a71dfb6259200467aca81 (diff)
downloadopencode-c2ca1494e5f2b21655982d694b6bafd4526f147e.tar.gz
opencode-c2ca1494e5f2b21655982d694b6bafd4526f147e.zip
fix(opencode): preserve prompt tool enables with empty agent permissions (#17064)
Co-authored-by: jquense <[email protected]>
-rw-r--r--packages/opencode/src/session/llm.ts8
-rw-r--r--packages/opencode/src/session/prompt.ts1
-rw-r--r--packages/opencode/test/session/llm.test.ts92
3 files changed, 98 insertions, 3 deletions
diff --git a/packages/opencode/src/session/llm.ts b/packages/opencode/src/session/llm.ts
index 4e42fb0d2..88841a30a 100644
--- a/packages/opencode/src/session/llm.ts
+++ b/packages/opencode/src/session/llm.ts
@@ -32,6 +32,7 @@ export namespace LLM {
sessionID: string
model: Provider.Model
agent: Agent.Info
+ permission?: PermissionNext.Ruleset
system: string[]
abort: AbortSignal
messages: ModelMessage[]
@@ -255,8 +256,11 @@ export namespace LLM {
})
}
- async function resolveTools(input: Pick<StreamInput, "tools" | "agent" | "user">) {
- const disabled = PermissionNext.disabled(Object.keys(input.tools), input.agent.permission)
+ async function resolveTools(input: Pick<StreamInput, "tools" | "agent" | "permission" | "user">) {
+ const disabled = PermissionNext.disabled(
+ Object.keys(input.tools),
+ PermissionNext.merge(input.agent.permission, input.permission ?? []),
+ )
for (const tool of Object.keys(input.tools)) {
if (input.user.tools?.[tool] === false || disabled.has(tool)) {
delete input.tools[tool]
diff --git a/packages/opencode/src/session/prompt.ts b/packages/opencode/src/session/prompt.ts
index 743537f59..5bde2608f 100644
--- a/packages/opencode/src/session/prompt.ts
+++ b/packages/opencode/src/session/prompt.ts
@@ -666,6 +666,7 @@ export namespace SessionPrompt {
const result = await processor.process({
user: lastUser,
agent,
+ permission: session.permission,
abort,
sessionID,
system,
diff --git a/packages/opencode/test/session/llm.test.ts b/packages/opencode/test/session/llm.test.ts
index 64e73e0de..b44191b6b 100644
--- a/packages/opencode/test/session/llm.test.ts
+++ b/packages/opencode/test/session/llm.test.ts
@@ -1,6 +1,7 @@
import { afterAll, beforeAll, beforeEach, describe, expect, test } from "bun:test"
import path from "path"
-import type { ModelMessage } from "ai"
+import { tool, type ModelMessage } from "ai"
+import z from "zod"
import { LLM } from "../../src/session/llm"
import { Global } from "../../src/global"
import { Instance } from "../../src/project/instance"
@@ -325,6 +326,95 @@ describe("session.llm.stream", () => {
})
})
+ test("keeps tools enabled by prompt permissions", async () => {
+ const server = state.server
+ if (!server) {
+ throw new Error("Server not initialized")
+ }
+
+ const providerID = "alibaba"
+ const modelID = "qwen-plus"
+ const fixture = await loadFixture(providerID, modelID)
+ const model = fixture.model
+
+ const request = waitRequest(
+ "/chat/completions",
+ new Response(createChatStream("Hello"), {
+ status: 200,
+ headers: { "Content-Type": "text/event-stream" },
+ }),
+ )
+
+ await using tmp = await tmpdir({
+ init: async (dir) => {
+ await Bun.write(
+ path.join(dir, "opencode.json"),
+ JSON.stringify({
+ $schema: "https://opencode.ai/config.json",
+ enabled_providers: [providerID],
+ provider: {
+ [providerID]: {
+ options: {
+ apiKey: "test-key",
+ baseURL: `${server.url.origin}/v1`,
+ },
+ },
+ },
+ }),
+ )
+ },
+ })
+
+ await Instance.provide({
+ directory: tmp.path,
+ fn: async () => {
+ const resolved = await Provider.getModel(providerID, model.id)
+ const sessionID = "session-test-tools"
+ const agent = {
+ name: "test",
+ mode: "primary",
+ options: {},
+ permission: [{ permission: "question", pattern: "*", action: "deny" }],
+ } satisfies Agent.Info
+
+ const user = {
+ id: "user-tools",
+ sessionID,
+ role: "user",
+ time: { created: Date.now() },
+ agent: agent.name,
+ model: { providerID, modelID: resolved.id },
+ tools: { question: true },
+ } satisfies MessageV2.User
+
+ const stream = await LLM.stream({
+ user,
+ sessionID,
+ model: resolved,
+ agent,
+ permission: [{ permission: "question", pattern: "*", action: "allow" }],
+ system: ["You are a helpful assistant."],
+ abort: new AbortController().signal,
+ messages: [{ role: "user", content: "Hello" }],
+ tools: {
+ question: tool({
+ description: "Ask a question",
+ inputSchema: z.object({}),
+ execute: async () => ({ output: "" }),
+ }),
+ },
+ })
+
+ for await (const _ of stream.fullStream) {
+ }
+
+ const capture = await request
+ const tools = capture.body.tools as Array<{ function?: { name?: string } }> | undefined
+ expect(tools?.some((item) => item.function?.name === "question")).toBe(true)
+ },
+ })
+ })
+
test("sends responses API payload for OpenAI models", async () => {
const server = state.server
if (!server) {