Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
125 changes: 118 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,9 +42,9 @@ code --install-extension opencode-for-devcontainers-*.vsix

## Usage

1. Open a workspace that has a `.devcontainer/devcontainer.json`
2. The extension activates automatically and shows a status bar item
3. Use the Command Palette (`Ctrl+Shift+P` / `Cmd+Shift+P`):
### Command Palette

Open a workspace that has a `.devcontainer/devcontainer.json`. The extension activates automatically and shows a status bar item. Use the Command Palette (`Ctrl+Shift+P` / `Cmd+Shift+P`):

| Command | Description |
|---------|-------------|
Expand All @@ -54,20 +54,115 @@ code --install-extension opencode-for-devcontainers-*.vsix
| **OpenCode: Stop Dev Container** | Stop the running devcontainer |
| **OpenCode: Show Dev Container Status** | Show a quick pick with status and actions |

### Chat Participant

The extension registers an `@opencode` chat participant in VS Code's chat panel with the following slash commands:

| Command | Description |
|---------|-------------|
| `@opencode /ask` | Send a prompt to the active OpenCode agent (default when no command is specified) |
| `@opencode /exec` | Execute a command directly in the devcontainer |
| `@opencode /status` | Show devcontainer and agent status |
| `@opencode /agents` | List configured agents grouped by primary and subagent |
| `@opencode /config` | Show current extension and agent configuration |

The chat participant supports file references — attach files from the editor to provide context with your prompts.

### Subagent Activity Tree

When a chat session is active, an **OpenCode Agents** tree view appears in the Explorer sidebar. It shows a real-time hierarchical view of subagent execution, including:

- Active and completed subagents with status icons
- Individual tool calls per subagent
- Duration and tool call counts

This can be toggled with the `chat.showSubagentTree` setting.

## Agent Configuration

Agents are loaded directly from your OpenCode configuration — no separate VS Code settings needed. The extension reads from the same sources as the OpenCode CLI:

### Search Order

The extension searches for `opencode.json` / `opencode.jsonc` in this order (first match wins):

1. **Explicit path** — set via the `opencodeConfigPath` VS Code setting
2. **Workspace root** — project-level config
3. **`~/.config/opencode/`** — global user config

### Config File Format

Agent definitions in `opencode.json` follow OpenCode's native format:

```jsonc
{
"agent": {
"build": {
"model": "anthropic/claude-sonnet-4-20250514",
"description": "Default coding agent with all tools enabled",
"mode": "primary"
},
"code-reviewer": {
"model": "anthropic/claude-sonnet-4-20250514",
"description": "Reviews code for best practices",
"mode": "subagent"
}
},
"default_agent": "build"
}
```

### Markdown-Based Agents

The extension also discovers agents defined as markdown files in `.opencode/agents/` within your workspace. The filename (minus `.md`) becomes the agent id, and the first line is used as the description.

### Built-in Agents

When no config file is found, the extension provides two built-in agents:

- **Build** — Default coding agent with all tools enabled (primary)
- **Plan** — Planning agent with restricted tool access (primary)

### Auto-Reload

The agent registry automatically reloads when:
- An `opencode.json` / `opencode.jsonc` file is created, modified, or deleted in the workspace
- The `opencodeConfigPath` VS Code setting is changed

## Configuration

All settings are under the `opencode-devcontainer` namespace:
All VS Code settings are under the `opencode-devcontainer` namespace:

### Paths

| Setting | Default | Description |
|---------|---------|-------------|
| `opencodePath` | `"opencode"` | Path to the OpenCode binary on the host |
| `opencodeConfigPath` | `""` | Path to `opencode.json` or `opencode.jsonc`. Supports `~` expansion. When empty, searches workspace root then `~/.config/opencode/` |
| `devcontainerPath` | `""` | Custom path to devcontainer config directory |
| `dockerPath` | `"docker"` | Path to the Docker CLI binary |
| `devcontainerCliPath` | `"devcontainer"` | Path to the devcontainer CLI binary |

### Execution

| Setting | Default | Description |
|---------|---------|-------------|
| `executionMode` | `"local-with-remote-exec"` | `"local-with-remote-exec"` or `"in-container"` |
| `containerWorkspaceFolder` | `""` | Workspace path inside the container (auto-detected) |
| `additionalEnvVars` | `{}` | Extra environment variables for OpenCode |
| `forwardEnvVars` | `["OPENAI_API_KEY", "ANTHROPIC_API_KEY", "OPENCODE_*"]` | Env vars to forward to the container |
| `containerWorkspaceFolder` | `""` | Workspace path inside the container (auto-detected from devcontainer.json) |

### Environment Variables

| Setting | Default | Description |
|---------|---------|-------------|
| `additionalEnvVars` | `{}` | Extra environment variables to pass to OpenCode |
| `forwardEnvVars` | `["OPENAI_API_KEY", "ANTHROPIC_API_KEY", "OPENCODE_*"]` | Env var names or patterns to forward to the container |

### Chat Display

| Setting | Default | Description |
|---------|---------|-------------|
| `chat.showToolCalls` | `true` | Show individual tool call details in chat responses |
| `chat.showSubagentTree` | `true` | Show the subagent activity tree view in the Explorer |

## Architecture

Expand All @@ -86,6 +181,22 @@ Host Machine Dev Container
└──────────────────┘ └──────────────────┘
```

### Internal Communication

The extension communicates with the OpenCode process via a line-delimited JSON protocol over stdin/stdout. Events from OpenCode include text output, tool call start/end, subagent lifecycle events, and completion/error signals.

## Development

```sh
npm install # Install dependencies
npm run build # Build for production
npm run watch # Build in watch mode
npm run lint # Run ESLint
npm test # Run tests (vitest)
npm run test:watch # Run tests in watch mode
npx vsce package # Package as .vsix
```

## License

MIT
35 changes: 6 additions & 29 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
"name": "opencode-for-devcontainers",
"displayName": "OpenCode for Dev Containers",
"description": "Run OpenCode locally but execute commands within a devcontainer",
"version": "0.1.0",
"version": "0.1.2",
"publisher": "marchingphoenix",
"license": "MIT",
"repository": {
Expand Down Expand Up @@ -100,6 +100,11 @@
"default": "devcontainer",
"description": "Path to the devcontainer CLI binary"
},
"opencode-devcontainer.opencodeConfigPath": {
"type": "string",
"default": "",
"description": "Path to the OpenCode configuration file (opencode.json or opencode.jsonc). When empty, searches the workspace root and ~/.config/opencode/ automatically."
},
"opencode-devcontainer.executionMode": {
"type": "string",
"enum": [
Expand Down Expand Up @@ -135,34 +140,6 @@
},
"description": "Environment variable names (or patterns with *) to forward from the host to the container"
},
"opencode-devcontainer.agents": {
"type": "array",
"default": [
{
"id": "default",
"name": "Default Agent",
"provider": "anthropic",
"model": "claude-sonnet-4-5-20250929"
}
],
"items": {
"type": "object",
"properties": {
"id": { "type": "string" },
"name": { "type": "string" },
"provider": { "type": "string" },
"model": { "type": "string" },
"description": { "type": "string" }
},
"required": ["id", "name", "provider", "model"]
},
"description": "Configured OpenCode agents (AI provider/model combinations)"
},
"opencode-devcontainer.defaultAgent": {
"type": "string",
"default": "default",
"description": "ID of the default agent to use in the chat window"
},
"opencode-devcontainer.chat.showToolCalls": {
"type": "boolean",
"default": true,
Expand Down
16 changes: 16 additions & 0 deletions src/__mocks__/vscode.ts
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,15 @@ function makeConfigProxy(section: string) {
};
}

function createMockFileSystemWatcher() {
return {
onDidChange: vi.fn((_cb: unknown) => ({ dispose: vi.fn() })),
onDidCreate: vi.fn((_cb: unknown) => ({ dispose: vi.fn() })),
onDidDelete: vi.fn((_cb: unknown) => ({ dispose: vi.fn() })),
dispose: vi.fn(),
};
}

export const workspace = {
getConfiguration: vi.fn((section?: string) => makeConfigProxy(section ?? "")),
get workspaceFolders() {
Expand All @@ -164,6 +173,9 @@ export const workspace = {
onDidChangeConfiguration: vi.fn((_cb: unknown) => ({
dispose: vi.fn(),
})),
createFileSystemWatcher: vi.fn((_pattern: string) =>
createMockFileSystemWatcher()
),
};

// ---------------------------------------------------------------------------
Expand Down Expand Up @@ -266,6 +278,10 @@ export function __resetMocks(): void {
// Reset all vi.fn() mocks
workspace.getConfiguration.mockClear();
workspace.onDidChangeConfiguration.mockClear();
workspace.createFileSystemWatcher.mockClear();
workspace.createFileSystemWatcher.mockImplementation(
(_pattern: string) => createMockFileSystemWatcher()
);
window.createStatusBarItem.mockClear();
window.createTerminal.mockClear();
window.showErrorMessage.mockClear();
Expand Down
Loading