Skip to content
Draft
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
1 change: 0 additions & 1 deletion bun.lock

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

2 changes: 1 addition & 1 deletion packages/opencode/script/build.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
#!/usr/bin/env bun

import solidPlugin from "../node_modules/@opentui/solid/scripts/solid-plugin"
import solidPlugin from "@opentui/solid/bun-plugin"
import path from "path"
import fs from "fs"
import { $ } from "bun"
Expand Down
146 changes: 146 additions & 0 deletions packages/opencode/src/a2a/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
# Agent2Agent (A2A) Protocol Implementation

This directory implements the Agent2Agent (A2A) protocol for OpenCode, allowing OpenCode to expose all its capabilities as an A2A-compliant agent server.

## Overview

The A2A protocol enables autonomous agents to communicate, collaborate, and delegate tasks to each other. This implementation exposes OpenCode's development tools and capabilities through standardized A2A interfaces.

## Architecture

### Components

- **`types.ts`** - Type definitions and Zod schemas for A2A entities
- **`card.ts`** - Agent card generation describing OpenCode's capabilities
- **`executor.ts`** - Agent executor implementing task execution logic
- **`index.ts`** - Main entry point and exports

### Endpoints

The A2A implementation provides the following HTTP endpoints:

#### 1. Agent Card Endpoint
- **Path**: `/a2a/.well-known/agent.json`
- **Method**: GET
- **Description**: Returns the agent card describing OpenCode's capabilities, supported skills, and available transports

#### 2. JSON-RPC Endpoint
- **Path**: `/a2a/jsonrpc`
- **Method**: POST
- **Description**: JSON-RPC 2.0 interface for agent-to-agent communication
- **Supported Methods**:
- `agent/execute` - Execute a task with messages
- `agent/card` - Get the agent card

#### 3. REST Endpoint
- **Path**: `/a2a/rest/execute`
- **Method**: POST
- **Description**: HTTP+JSON/REST interface for task execution

## Exposed Capabilities

OpenCode exposes the following capabilities as A2A skills:

- **bash** - Execute bash commands
- **read** - Read file contents
- **write** - Write files
- **edit** - Edit files
- **grep** - Search file contents
- **glob** - Find files by pattern
- **task** - Delegate to sub-agents
- **webfetch** - Fetch web content
- **websearch** - Search the web
- **codesearch** - Search code
- **skill** - Execute custom skills
- **lsp** - Language server protocol operations
- And all other tools from the tool registry

## Usage

### Starting the A2A Server

The A2A endpoints are automatically exposed when the OpenCode server starts. By default, they are available at:

- JSON-RPC: `http://localhost:4096/a2a/jsonrpc`
- REST: `http://localhost:4096/a2a/rest/execute`
- Agent Card: `http://localhost:4096/a2a/.well-known/agent.json`

### Connecting from Another Agent

Other A2A-compliant agents can discover and communicate with OpenCode using the A2A client SDK:

```typescript
import { ClientFactory } from "@a2a-js/sdk/client"

// Discover the agent
const client = await ClientFactory.create("http://localhost:4096/a2a/jsonrpc")

// Send a message
const response = await client.execute({
messages: [
{
kind: "message",
role: "user",
parts: [{ kind: "text", text: "List files in the current directory" }],
},
],
})
```

### Example Request (JSON-RPC)

```json
{
"jsonrpc": "2.0",
"method": "agent/execute",
"params": {
"contextId": "task-123",
"messages": [
{
"kind": "message",
"role": "user",
"parts": [
{
"kind": "text",
"text": "Create a new file called test.txt with 'Hello World'"
}
]
}
]
},
"id": 1
}
```

## Implementation Details

### Task Execution Flow

1. **Request Reception**: A2A request is received via JSON-RPC or REST
2. **Context Creation**: An OpenCode session is created with the task context
3. **Message Processing**: User message is converted to OpenCode's internal format
4. **Agent Execution**: OpenCode's build agent processes the request
5. **Response Streaming**: Agent responses are streamed and collected
6. **Result Return**: Final result is returned in A2A message format

### Session Management

Each A2A task creates an isolated OpenCode session with:
- Unique session ID (`a2a-{taskId}`)
- Working directory from context or current directory
- Full access to OpenCode's tool registry
- Abort capability for task cancellation

## Integration with OpenCode

The A2A implementation integrates with existing OpenCode components:

- **Tool Registry**: Exposes all registered tools as A2A skills
- **Session Management**: Uses OpenCode's session system for state
- **Agent System**: Leverages the "build" agent for execution
- **Server Framework**: Integrated with Hono HTTP server

## Protocol Specification

This implementation follows the A2A Protocol Specification v0.3.0:
https://a2a-protocol.org/v0.3.0/specification
79 changes: 79 additions & 0 deletions packages/opencode/src/a2a/card.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
import { Installation } from "../installation"
import { ToolRegistry } from "../tool/registry"
import { Log } from "../util/log"
import { A2A as A2ATypes } from "./types"

// AgentCard interface matching A2A protocol spec
interface AgentCard {
name: string
description: string
protocolVersion: string
version: string
url: string
skills: Array<{
id: string
name: string
description: string
tags: string[]
}>
capabilities: {
pushNotifications: boolean
}
defaultInputModes: string[]
defaultOutputModes: string[]
additionalInterfaces: Array<{
url: string
transport: string
}>
}

export namespace A2ACard {
const log = Log.create({ service: "a2a-card" })

export async function generate(serverUrl: string): Promise<AgentCard> {
const version = Installation.VERSION
const tools = await ToolRegistry.ids()

const skills = tools.map((toolId) => ({
id: toolId,
name: toolId.charAt(0).toUpperCase() + toolId.slice(1),
description: `Execute ${toolId} tool`,
tags: ["development", "coding"],
}))

const card: AgentCard = {
name: "OpenCode",
description:
"OpenCode is an AI-powered development tool with capabilities for code editing, file operations, bash execution, and project management.",
protocolVersion: A2ATypes.PROTOCOL_VERSION,
version: version ?? "1.0.0",
url: serverUrl,
skills,
capabilities: {
pushNotifications: false,
},
defaultInputModes: ["text"],
defaultOutputModes: ["text"],
additionalInterfaces: [
{
url: (() => {
const url = new URL(serverUrl)
// Replace the last '/jsonrpc' segment with '/rest'
const parts = url.pathname.split("/")
const lastIndex = parts.lastIndexOf("jsonrpc")
if (lastIndex !== -1) {
parts[lastIndex] = "rest"
url.pathname = parts.join("/")
}
return url.toString()
})(),
transport: "HTTP+JSON",
},
{ url: serverUrl, transport: "JSONRPC" },
],
}

log.info("generated agent card", { tools: tools.length })
return card
}
}
Loading