Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
62 commits
Select commit Hold shift + click to select a range
cadd42d
feat(vscode): add Language Model Provider prototype
nikomatsakis Jan 1, 2026
759f02c
refactor(vscodelm): use sacp infrastructure for JSON-RPC
nikomatsakis Jan 1, 2026
fb174b4
refactor(vscodelm): remove unnecessary Arc<Mutex> from Eliza state
nikomatsakis Jan 1, 2026
6f89830
feat(vscodelm): implement Component trait and add tests
nikomatsakis Jan 1, 2026
7d24073
test(vscodelm): use expect-test for snapshot testing
nikomatsakis Jan 1, 2026
cfec082
chore: remove outdated content
nikomatsakis Jan 1, 2026
019a8be
refactor: cleanup the test to avoid mutex
nikomatsakis Jan 1, 2026
3f609b5
refactor: cleanup the method flow
nikomatsakis Jan 1, 2026
df48c03
refactor: cleanup logging a bit
nikomatsakis Jan 1, 2026
be61526
fix: always log commands to VSCode's output window
nikomatsakis Jan 1, 2026
82bad23
fix: handle all VS Code LM message part types correctly
nikomatsakis Jan 1, 2026
1c0fe17
feat: add session actor for VS Code LM provider
nikomatsakis Jan 1, 2026
9a0adfb
feat: add session UUID logging to vscodelm
nikomatsakis Jan 1, 2026
5c13627
feat: add configurable agent backend to vscodelm
nikomatsakis Jan 1, 2026
ffcd9be
fix: correct McpServer serialization format in TypeScript
nikomatsakis Jan 1, 2026
6b43669
docs: update lm-provider design doc with agent configuration protocol
nikomatsakis Jan 1, 2026
73849e0
feat: support AgentDefinition enum in vscodelm protocol
nikomatsakis Jan 1, 2026
a7c5365
refactor: resolve symposium builtins to embedded binary path
nikomatsakis Jan 1, 2026
1a565a6
docs: add Language Model Tool Bridging design and VS Code API references
nikomatsakis Jan 1, 2026
46fa579
feat: add cancellation support for vscodelm
nikomatsakis Jan 1, 2026
91417c1
refactor: use futures channels and merged streams for vscodelm cancel…
nikomatsakis Jan 1, 2026
692a1d4
feat: implement agent-internal tool permission bridging for vscodelm
nikomatsakis Jan 1, 2026
248847b
refactor: use MatchMessage in process_session_message
nikomatsakis Jan 2, 2026
ee46c6b
WIP: refactor session model and unify ContentPart type
nikomatsakis Jan 3, 2026
a168ddc
feat: introduce HistoryActor for centralized session state management
nikomatsakis Jan 5, 2026
2505fb4
refactor: apply edits from nikomatsakis review
nikomatsakis Jan 5, 2026
de34f3e
feat: pass chat request options from VS Code to Rust backend
nikomatsakis Jan 5, 2026
62323f0
chore: bump vscode extension version to 1.2.0
nikomatsakis Jan 5, 2026
f1851d4
fix: remove toolCallId from agent action input schema
nikomatsakis Jan 5, 2026
edbf752
fix: normalize messages for history matching
nikomatsakis Jan 5, 2026
3d798f0
feat: stream tool calls as markdown in VS Code LM provider
nikomatsakis Jan 5, 2026
24c72cb
feat: expose one language model per ACP agent
nikomatsakis Jan 5, 2026
f0c745d
feat: show all registry agents in LM picker with auto-install
nikomatsakis Jan 5, 2026
665c5d5
feat: show all registry agents in settings panel with auto-install
nikomatsakis Jan 5, 2026
6d511c4
chore: remove Codex and Gemini from built-in agents
nikomatsakis Jan 5, 2026
f553ff6
chore: sort agent lists alphabetically
nikomatsakis Jan 5, 2026
cf37b40
refactor: pass invocation_tx to VscodeToolsMcpServer constructor
nikomatsakis Jan 5, 2026
96415d0
refactor: replace tokio::select! with futures-concurrency race
nikomatsakis Jan 5, 2026
f5522b6
refactor: handle_vscode_tool_invocation takes ownership pattern
nikomatsakis Jan 5, 2026
f0729bf
feat: race peek against cancellation in handle_vscode_tool_invocation
nikomatsakis Jan 5, 2026
89114e5
refactor: add cancel_tool_invocation helper and clean up race formatting
nikomatsakis Jan 5, 2026
95502b4
refactor: add RequestState::on_cancel helper for racing cancellation
nikomatsakis Jan 5, 2026
6222118
fix: wrap agent in Conductor for MCP-over-ACP negotiation
nikomatsakis Jan 5, 2026
75c0fdb
fix: use VscodeToolsProxy in Conductor chain for MCP-over-ACP
nikomatsakis Jan 5, 2026
c46ba57
Revert "fix: use VscodeToolsProxy in Conductor chain for MCP-over-ACP"
nikomatsakis Jan 5, 2026
8a9243e
feat: accept RUST_LOG-style filter strings in --log argument
nikomatsakis Jan 5, 2026
996a109
fix: defer session creation until first request arrives
nikomatsakis Jan 5, 2026
1c59188
test: add vscodelm integration tests with expect_test assertions
nikomatsakis Jan 6, 2026
b9ae805
fix: use actual Eliza response in multi-turn test history
nikomatsakis Jan 6, 2026
10e69b1
fix: don't race against stale cancel_rx when waiting for tool result
nikomatsakis Jan 6, 2026
17b7d58
test: remove flaky vscodelm integration tests
nikomatsakis Jan 6, 2026
76d10f3
refactor: move vscodelm tests to separate module
nikomatsakis Jan 6, 2026
d7b5279
refactor: more DRY
nikomatsakis Jan 6, 2026
66966f3
fix: don't race against stale cancel_rx when waiting for tool result
nikomatsakis Jan 6, 2026
3c9d072
refactor: clarify why we are dropping request_state
nikomatsakis Jan 6, 2026
2e1cac5
refactor: rename mcp server
nikomatsakis Jan 6, 2026
7b4de6d
feat: add vscodelm_cli example for debugging tool invocation
nikomatsakis Jan 6, 2026
0cf6a74
feat: add --log-file option to vscodelm_cli example
nikomatsakis Jan 6, 2026
809b329
fix: auto-approve tool requests from vscode tools
nikomatsakis Jan 6, 2026
a113b9e
fix: strip mcp__ prefix in vscode_tools call_tool handler
nikomatsakis Jan 6, 2026
5359466
feat: move the LM stuff behind an experimental pref
nikomatsakis Jan 7, 2026
2dfcfae
docs: document experimental status of Language Model Provider
nikomatsakis Jan 7, 2026
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
25 changes: 25 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 3 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -56,3 +56,6 @@ clap = { version = "4.0", features = ["derive"] }
which = "6.0"
home = "0.5"
fxhash = "0.2.1"

# Testing
expect-test = "1.5"
4 changes: 4 additions & 0 deletions md/SUMMARY.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,8 @@
- [Testing Implementation](./design/vscode-extension/testing-implementation.md)
- [Packaging](./design/vscode-extension/packaging.md)
- [Agent Registry](./design/vscode-extension/agent-registry.md)
- [Language Model Provider](./design/vscode-extension/lm-provider.md)
- [Language Model Tool Bridging](./design/vscode-extension/lm-tool-bridging.md)
- [Implementation Status](./design/vscode-extension/implementation-status.md)

# References
Expand All @@ -45,6 +47,8 @@

- [MynahUI GUI Capabilities](./references/mynah-ui-guide.md)
- [VSCode Webview Lifecycle](./references/vscode-webview-lifecycle.md)
- [VSCode Language Model Tool API](./references/vscode-lm-tool-api.md)
- [VSCode Language Model Tool Rejection](./references/vscode-lm-tool-rejection.md)
- [Language Server Protocol Overview](./research/lsp-overview/README.md)
- [Base Protocol](./research/lsp-overview/base-protocol.md)
- [Language Features](./research/lsp-overview/language-features.md)
Expand Down
24 changes: 24 additions & 0 deletions md/design/vscode-extension/implementation-status.md
Original file line number Diff line number Diff line change
Expand Up @@ -90,3 +90,27 @@ These allow protocol extensions beyond the ACP specification. Not currently need
- [ ] Session restoration after VSCode restart
- [ ] Workspace-specific state persistence
- [ ] Tab history and conversation export

## Language Model Provider (Experimental)

> Set `symposium.enableExperimentalLM: true` in VS Code settings to enable.

This feature exposes ACP agents via VS Code's `LanguageModelChatProvider` API, allowing them to appear in the model picker for use by Copilot and other extensions.

**Status:** Experimental, disabled by default. May not be the right approach.

- [x] TypeScript: LanguageModelChatProvider registration
- [x] TypeScript: JSON-RPC client over stdio
- [x] TypeScript: Progress callback integration
- [x] Rust: `vscodelm` subcommand
- [x] Rust: Session actor with history management
- [x] Rust: Tool bridging (symposium-agent-action for permissions)
- [x] Rust: VS Code tools via synthetic MCP server
- [x] Feature flag gating (`symposium.enableExperimentalLM`)
- [ ] Fix: Multiple MCP tools cause invocation failures

**Known issue:** Tool invocation works with a single isolated tool but fails when multiple VS Code-provided tools are bridged. Root cause unknown.

**Open question:** VS Code LM consumers inject their own context (project details, editor state, etc.) into requests. ACP agents like Claude Code also inject context. These competing context layers may confuse the model, making the LM API better suited for raw model access than wrapping full agents.

See [Language Model Provider](./lm-provider.md) and [Tool Bridging](./lm-tool-bridging.md) for architecture details.
290 changes: 290 additions & 0 deletions md/design/vscode-extension/lm-provider.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,290 @@
# Language Model Provider

> **Experimental:** This feature is disabled by default. Set `symposium.enableExperimentalLM: true` in VS Code settings to enable it.

This chapter describes the architecture for exposing ACP agents as VS Code Language Models via the `LanguageModelChatProvider` API (introduced in VS Code 1.104). This allows ACP agents to appear in VS Code's model picker and be used by any extension that consumes the Language Model API.

## Current Status

The Language Model Provider is experimental and may not be the right approach for Symposium.

**What works:**
- Basic message flow between VS Code LM API and ACP agents
- Session management with committed/provisional history model
- Tool bridging architecture (both directions)

**Known issues:**
- Tool invocation fails when multiple VS Code-provided tools are bridged to the agent. A single isolated tool works correctly, but when multiple tools are available, the model doesn't invoke them properly. The root cause is not yet understood.

**Open question:** VS Code LM consumers (like GitHub Copilot) inject their own context into requests - project details, file contents, editor state, etc. ACP agents like Claude Code also inject their own context. When both layers add context, they may "fight" each other, confusing the model. The LM API may be better suited for raw model access rather than wrapping agents that have their own context management.

## Overview

The Language Model Provider bridges VS Code's stateless Language Model API to ACP's stateful session model. When users select "Symposium" in the model picker, requests are routed through Symposium to the configured ACP agent.

```
┌─────────────────────────────────────────────────────────────────┐
│ VS Code │
│ ┌───────────────────────────────────────────────────────────┐ │
│ │ Language Model Consumer │ │
│ │ (Copilot, other extensions, etc.) │ │
│ └─────────────────────────┬─────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌───────────────────────────────────────────────────────────┐ │
│ │ LanguageModelChatProvider (TypeScript) │ │
│ │ │ │
│ │ - Thin adapter layer │ │
│ │ - Serializes VS Code API calls to JSON-RPC │ │
│ │ - Forwards to Rust process │ │
│ │ - Deserializes responses, streams back via progress │ │
│ └─────────────────────────┬─────────────────────────────────┘ │
└────────────────────────────┼────────────────────────────────────┘
│ JSON-RPC (stdio)
┌─────────────────────────────────────────────────────────────────┐
│ symposium-acp-agent vscodelm │
│ │
│ - Receives serialized VS Code LM API calls │
│ - Manages session state │
│ - Routes to ACP agent (or Eliza for prototype) │
│ - Streams responses back │
└─────────────────────────────────────────────────────────────────┘
```

## Design Decisions

### TypeScript/Rust Split

The TypeScript extension is a thin adapter:
- Registers as `LanguageModelChatProvider`
- Serializes `provideLanguageModelChatResponse` calls to JSON-RPC
- Sends to Rust process over stdio
- Deserializes responses and streams back via `progress` callback

The Rust process handles all logic:
- Session management
- Message history tracking
- ACP protocol (future)
- Response streaming

This keeps the interesting logic in Rust where it's testable and maintainable.

### Session Management

VS Code's Language Model API is stateless: each request includes the full message history. ACP sessions are stateful. The Rust backend bridges this gap using a **History Actor** that tracks session state.

#### Architecture

```mermaid
graph LR
VSCode[VS Code] <--> HA[History Actor]
HA <--> SA[Session Actor]
SA <--> Agent[ACP Agent]
```

- **History Actor**: Receives requests from VS Code, tracks message history, identifies new messages
- **Session Actor**: Manages the ACP agent connection, handles streaming responses

#### Committed and Provisional History

The History Actor maintains two pieces of state:

- **Committed**: Complete `(User, Assistant)*` message pairs that VS Code has acknowledged. Always ends with an assistant message (or is empty).
- **Provisional**: The current in-flight exchange: one user message `U` and the assistant response parts `A` we've sent so far (possibly empty).

#### Commit Flow

When we receive a new request, we compare its history against `committed + provisional`:

```mermaid
sequenceDiagram
participant VSCode as VS Code
participant HA as History Actor
participant SA as Session Actor

Note over HA: committed = [], provisional = (U1, [])

SA->>HA: stream parts P1, P2, P3
Note over HA: provisional = (U1, [P1, P2, P3])
HA->>VSCode: stream P1, P2, P3

SA->>HA: done streaming
HA->>VSCode: response complete

VSCode->>HA: new request with history [U1, A1, U2]
Note over HA: matches committed + provisional + new user msg
Note over HA: commit: committed = [U1, A1]
Note over HA: provisional = (U2, [])
HA->>SA: new_messages = [U2], canceled = false
```

The new user message `U2` confirms that VS Code received and accepted our assistant response `A1`. We commit the exchange and start fresh with `U2`.

#### Cancellation via History Mismatch

If VS Code sends a request that doesn't include our provisional content, the provisional work was rejected:

```mermaid
sequenceDiagram
participant VSCode as VS Code
participant HA as History Actor
participant SA as Session Actor

Note over HA: committed = [U1, A1], provisional = (U2, [P1, P2])

VSCode->>HA: new request with history [U1, A1, U3]
Note over HA: doesn't match committed + provisional
Note over HA: discard provisional
Note over HA: provisional = (U3, [])
HA->>SA: new_messages = [U3], canceled = true

SA->>SA: cancel downstream agent
```

This happens when:
- User cancels the chat in VS Code
- User rejects a tool confirmation
- User sends a different message while we were responding

The Session Actor receives `canceled = true` and propagates cancellation to the downstream ACP agent.

### Agent Configuration

The agent to use is specified per-request via the `agent` field in the JSON-RPC protocol. This is an `AgentDefinition` enum:

```typescript
type AgentDefinition =
| { eliza: { deterministic?: boolean } }
| { mcp_server: McpServerStdio };

interface McpServerStdio {
name: string;
command: string;
args: string[];
env: Array<{ name: string; value: string }>;
}
```

The TypeScript extension reads the agent configuration from VS Code settings via the agent registry, resolves the distribution to get the actual command, and includes it in each request. The Rust backend dispatches based on the variant:

- **`eliza`**: Uses the in-process Eliza chatbot (useful for testing)
- **`mcp_server`**: Spawns an external ACP agent process and manages sessions

## JSON-RPC Protocol

The protocol between TypeScript and Rust mirrors the `LanguageModelChatProvider` interface.

### Requests (TypeScript → Rust)

**`lm/provideLanguageModelChatResponse`**

Each request includes the agent configuration via the `agent` field, which is an `AgentDefinition` enum with two variants:

**External ACP agent (mcp_server)**:
```json
{
"jsonrpc": "2.0",
"id": 1,
"method": "lm/provideLanguageModelChatResponse",
"params": {
"modelId": "symposium",
"messages": [
{ "role": "user", "content": [{ "type": "text", "value": "Hello" }] }
],
"agent": {
"mcp_server": {
"name": "my-agent",
"command": "/path/to/agent",
"args": ["--flag"],
"env": [{ "name": "KEY", "value": "value" }]
}
}
}
}
```

**Built-in Eliza (for testing)**:
```json
{
"jsonrpc": "2.0",
"id": 1,
"method": "lm/provideLanguageModelChatResponse",
"params": {
"modelId": "symposium-eliza",
"messages": [
{ "role": "user", "content": [{ "type": "text", "value": "Hello" }] }
],
"agent": {
"eliza": { "deterministic": true }
}
}
}
```

The Rust backend dispatches based on the variant - spawning an external process for `mcp_server` or using the in-process Eliza for `eliza`.

### Notifications (Rust → TypeScript)

**`lm/responsePart`** - Streams response chunks
```json
{
"jsonrpc": "2.0",
"method": "lm/responsePart",
"params": {
"requestId": 1,
"part": { "type": "text", "value": "How " }
}
}
```

**`lm/responseComplete`** - Signals end of response
```json
{
"jsonrpc": "2.0",
"method": "lm/responseComplete",
"params": {
"requestId": 1
}
}
```

### Response

After all parts are streamed, the request completes:
```json
{
"jsonrpc": "2.0",
"id": 1,
"result": {}
}
```

## Implementation Status

- [x] Rust: `vscodelm` subcommand in symposium-acp-agent
- [x] Rust: JSON-RPC message parsing
- [x] Rust: Eliza integration for testing
- [x] Rust: Response streaming
- [x] Rust: Configurable agent backend (McpServer support)
- [x] Rust: Session actor with ACP session management
- [x] TypeScript: LanguageModelChatProvider registration
- [x] TypeScript: JSON-RPC client over stdio
- [x] TypeScript: Progress callback integration
- [x] TypeScript: Agent configuration from settings
- [ ] End-to-end test with real ACP agent

## Tool Bridging

See [Language Model Tool Bridging](./lm-tool-bridging.md) for the design of how tools flow between VS Code and ACP agents. This covers:

- VS Code-provided tools (shuttled to agent via synthetic MCP server)
- Agent-internal tools (permission requests surfaced via `symposium-agent-action`)
- Handle state management across requests
- Cancellation and history matching

## Future Work

- Session caching with message history diffing
- Token counting heuristics
- Model metadata from agent capabilities
Loading