Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions packages/opencode/src/acp/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,16 @@ opencode acp
opencode acp --cwd /path/to/project
```

### Question Tool Opt-In

ACP excludes `QuestionTool` by default.

```bash
OPENCODE_ENABLE_ACP_QUESTION_TOOL=1 opencode acp
```

Enable this only for ACP clients that support interactive question prompts.

### Programmatic

```typescript
Expand Down
12 changes: 12 additions & 0 deletions packages/opencode/src/flag/flag.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ export namespace Flag {
export declare const OPENCODE_DISABLE_PROJECT_CONFIG: boolean
export const OPENCODE_FAKE_VCS = process.env["OPENCODE_FAKE_VCS"]
export declare const OPENCODE_CLIENT: string
export declare const OPENCODE_ENABLE_ACP_QUESTION_TOOL: boolean
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This isnt really ACP specific tho so I'd change the tool name.

Also can u make it experimental? I imagine we'd have better config for this later.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

clarification on "make it experimental": should this be (1) explicit opt-in only via OPENCODE_EXPERIMENTAL_QUESTION_TOOL, or (2) also enabled when OPENCODE_EXPERIMENTAL=1? I implemented option 1 for now to keep ACP default unchanged, but I can switch to option 2 if you prefer.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@rekram1-node Thanks for the review. I updated everything you asked for. One thing I still do not fully understand, what did you mean by “This isnt really ACP specific”? In the current logic, this flag only affects ACP behavior, while app/cli/desktop keeps the question tool enabled as before. Since the flag only changes ACP behavior, an ACP in the name seemed logical to me

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The flag should be about enabling the question tool, it doesnt have anything to do w/ acp at all.

The cli, desktop, app are all clients, but we also have an sdk. So there are a lot of places or other clients that may not have question tool enabled by default. So this would be about enabling question tool, it shouldnt be tied to acp.

And uh Ig keep the flag separate from that other experimental one...

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ig thats kinda messy, so it doesnt have to be experimental but hopefully im making sense?

Copy link
Copy Markdown
Contributor Author

@ImmuneFOMO ImmuneFOMO Feb 15, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@rekram1-node Just to confirm I understood correctly: you want a generic flag like OPENCODE_ENABLE_QUESTION_TOOL (not ACP-specific and not tied to OPENCODE_EXPERIMENTAL), and the registry logic should be: keep question enabled by default for app/cli/desktop, plus enable it for any other client when this flag is set. Is that correct?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@rekram1-node just reminding, in case you lost this message

export const OPENCODE_SERVER_PASSWORD = process.env["OPENCODE_SERVER_PASSWORD"]
export const OPENCODE_SERVER_USERNAME = process.env["OPENCODE_SERVER_USERNAME"]

Expand Down Expand Up @@ -94,3 +95,14 @@ Object.defineProperty(Flag, "OPENCODE_CLIENT", {
enumerable: true,
configurable: false,
})

// Dynamic getter for OPENCODE_ENABLE_ACP_QUESTION_TOOL
// This must be evaluated at access time, not module load time,
// because ACP integrations may set this env var at runtime
Object.defineProperty(Flag, "OPENCODE_ENABLE_ACP_QUESTION_TOOL", {
get() {
return truthy("OPENCODE_ENABLE_ACP_QUESTION_TOOL")
},
enumerable: true,
configurable: false,
})
5 changes: 4 additions & 1 deletion packages/opencode/src/tool/registry.ts
Original file line number Diff line number Diff line change
Expand Up @@ -94,10 +94,13 @@ export namespace ToolRegistry {
async function all(): Promise<Tool.Info[]> {
const custom = await state().then((x) => x.custom)
const config = await Config.get()
const question =
["app", "cli", "desktop"].includes(Flag.OPENCODE_CLIENT) ||
(Flag.OPENCODE_CLIENT === "acp" && Flag.OPENCODE_ENABLE_ACP_QUESTION_TOOL)

return [
InvalidTool,
...(["app", "cli", "desktop"].includes(Flag.OPENCODE_CLIENT) ? [QuestionTool] : []),
...(question ? [QuestionTool] : []),
BashTool,
ReadTool,
GlobTool,
Expand Down
42 changes: 42 additions & 0 deletions packages/opencode/test/tool/registry.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,29 @@ import { tmpdir } from "../fixture/fixture"
import { Instance } from "../../src/project/instance"
import { ToolRegistry } from "../../src/tool/registry"

async function ids(client: string, flag?: string) {
const originalClient = process.env["OPENCODE_CLIENT"]
const originalFlag = process.env["OPENCODE_ENABLE_ACP_QUESTION_TOOL"]

try {
process.env["OPENCODE_CLIENT"] = client
if (flag === undefined) delete process.env["OPENCODE_ENABLE_ACP_QUESTION_TOOL"]
if (flag !== undefined) process.env["OPENCODE_ENABLE_ACP_QUESTION_TOOL"] = flag

await using tmp = await tmpdir()
return await Instance.provide({
directory: tmp.path,
fn: async () => ToolRegistry.ids(),
})
} finally {
if (originalClient === undefined) delete process.env["OPENCODE_CLIENT"]
if (originalClient !== undefined) process.env["OPENCODE_CLIENT"] = originalClient

if (originalFlag === undefined) delete process.env["OPENCODE_ENABLE_ACP_QUESTION_TOOL"]
if (originalFlag !== undefined) process.env["OPENCODE_ENABLE_ACP_QUESTION_TOOL"] = originalFlag
}
}

describe("tool.registry", () => {
test("loads tools from .opencode/tool (singular)", async () => {
await using tmp = await tmpdir({
Expand Down Expand Up @@ -119,4 +142,23 @@ describe("tool.registry", () => {
},
})
})

test("excludes question tool for acp when flag is unset", async () => {
expect(await ids("acp")).not.toContain("question")
})

test("excludes question tool for acp when flag is 0", async () => {
expect(await ids("acp", "0")).not.toContain("question")
})

test("includes question tool for acp when flag is enabled", async () => {
expect(await ids("acp", "1")).toContain("question")
expect(await ids("acp", "true")).toContain("question")
})

test("keeps question tool for app, cli, and desktop", async () => {
expect(await ids("app", "0")).toContain("question")
expect(await ids("cli", "0")).toContain("question")
expect(await ids("desktop", "0")).toContain("question")
})
})
Loading