einoacp

package module
v0.0.0-...-0718863 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jun 26, 2026 License: MIT Imports: 17 Imported by: 0

README

eino-acp

Eino ChatModel provider that connects to any ACP (Agent Client Protocol) compatible coding agent.

Use Claude Code, Codex CLI, Gemini CLI, or any other ACP agent as an LLM backend for CloudWeGo Eino.

Install

go get github.com/strrl/eino-acp

Usage

package main

import (
	"context"
	"fmt"

	einoacp "github.com/strrl/eino-acp"
)

func main() {
	ctx := context.Background()

	cm, err := einoacp.NewChatModel(ctx, &einoacp.Config{
		Command: einoacp.ClaudeCommand(),
		// or: einoacp.CodexCommand()
		// or: einoacp.CopilotCommand()
		// or: einoacp.GeminiCommand()
	})
	if err != nil {
		panic(err)
	}

	// Generate (non-streaming)
	msg, err := cm.Generate(ctx, einoacp.UserMessages("Hello!"))
	if err != nil {
		panic(err)
	}
	fmt.Println(msg.Content)

	// Stream
	stream, err := cm.Stream(ctx, einoacp.UserMessages("Tell me a joke."))
	if err != nil {
		panic(err)
	}
	defer stream.Close()

	for {
		chunk, err := stream.Recv()
		if err != nil {
			break
		}
		fmt.Print(chunk.Content)
	}
	fmt.Println()
}

Config

Field Description
Command Command to launch the ACP agent. Use ClaudeCommand(), CodexCommand(), CopilotCommand(), or GeminiCommand() helpers, which auto-detect bunx, then pnpm dlx, then npx -y. Required.
Cwd Working directory for the agent session. Defaults to current directory.
Env Additional environment variables for the agent subprocess.
AutoApprove Auto-approve all permission requests from the agent. Default false.

How it works

  1. Launches the agent as a subprocess
  2. Communicates via ACP (JSON-RPC 2.0 over stdio)
  3. Maps ACP session updates to Eino's schema.Message streaming interface

Tool Call Mapping

eino-acp translates ACP tool_call and tool_call_update session updates into Eino assistant ToolCalls.

ACP field Eino field Notes
toolCallId ToolCall.ID Preserved as-is.
kind ToolCall.Function.Name Uses ACP tool kind such as read, edit, or execute. Falls back to other when ACP omits the kind.
rawInput ToolCall.Function.Arguments JSON-marshaled into a stable string. Defaults to {} when missing.
title, status, rawOutput, locations, content, _meta ToolCall.Extra Preserved under acp_* keys so observability consumers can inspect ACP-specific metadata.

For Generate, the final assistant message includes the accumulated ToolCalls.

For Stream, each ACP tool event is emitted as an assistant chunk with empty Content and a single ToolCall, so consumers such as tape, Langfuse-style handlers, or ADK can observe tool activity as it happens.

agent_thought_chunk, plan, and other non-message ACP session updates are still ignored.

Built on coder/acp-go-sdk.

License

MIT

Documentation

Overview

Package einoacp provides an ACP-backed chat model implementation for Eino.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func ClaudeCommand

func ClaudeCommand() []string

ClaudeCommand returns the command to launch Claude Code via ACP.

func CodexCommand

func CodexCommand() []string

CodexCommand returns the command to launch OpenAI Codex CLI via ACP. Uses the dedicated ACP adapter package from the ACP registry.

func CopilotCommand

func CopilotCommand() []string

CopilotCommand returns the command to launch GitHub Copilot via ACP.

func GeminiCommand deprecated

func GeminiCommand() []string

GeminiCommand returns the command to launch Google Gemini CLI via ACP.

NOTE: Gemini CLI in ACP mode currently does not work with OAuth authentication when launched as a subprocess. See upstream issue: https://github.com/google-gemini/gemini-cli/issues/12042

Deprecated: Use ClaudeCommand or CodexCommand instead until this is resolved.

func UserMessages

func UserMessages(content string) []*schema.Message

UserMessages is a convenience function to create a single user message slice.

Types

type ChatModel

type ChatModel struct {
	// contains filtered or unexported fields
}

ChatModel implements eino's model.ChatModel by communicating with any ACP-compatible coding agent (Claude Code, Codex CLI, etc.) over the Agent Client Protocol.

func NewChatModel

func NewChatModel(_ context.Context, config *Config) (*ChatModel, error)

NewChatModel creates a new ACP chat model.

func (*ChatModel) BindTools

func (cm *ChatModel) BindTools(_ []*schema.ToolInfo) error

BindTools is a no-op; ACP agents manage their own tools.

func (*ChatModel) Generate

func (cm *ChatModel) Generate(_ context.Context, _ []*schema.Message, _ ...model.Option) (*schema.Message, error)

Generate is not supported for ACP-based chat models.

ACP is a streaming-only protocol: the agent emits ordered session updates (tool_call → tool_call_update → agent_message_chunk) that must be consumed in real time. Collapsing them into a single *schema.Message via Generate destroys event ordering and streaming granularity.

Use Stream instead.

func (*ChatModel) GetType

func (cm *ChatModel) GetType() string

GetType returns the component type name used by Eino callbacks.

func (*ChatModel) IsCallbacksEnabled

func (cm *ChatModel) IsCallbacksEnabled() bool

IsCallbacksEnabled reports whether callback hooks are enabled for this model.

func (*ChatModel) Stream

func (cm *ChatModel) Stream(ctx context.Context, input []*schema.Message, _ ...model.Option) (outStream *schema.StreamReader[*schema.Message], err error)

Stream runs the ACP agent and yields assistant output chunks as they arrive.

func (*ChatModel) WithTools

func (cm *ChatModel) WithTools(_ []*schema.ToolInfo) (model.ToolCallingChatModel, error)

WithTools is a no-op; ACP agents manage their own tools.

type Config

type Config struct {
	// Command is the full command to launch the ACP agent.
	// The first element is the binary, the rest are arguments.
	// Use the provided helpers: ClaudeCommand(), CodexCommand().
	// Required, must have at least one element.
	Command []string

	// Cwd is the working directory for the agent session.
	// Defaults to the current working directory.
	Cwd string

	// Env sets additional environment variables for the agent subprocess.
	// These are merged with the current process environment.
	Env []string

	// AutoApprove automatically approves all permission requests from the agent.
	// When false, permission requests are denied.
	AutoApprove bool

	// OnPermission is called for every ACP permission request. When set, it
	// takes precedence over AutoApprove.
	OnPermission func(context.Context, acp.RequestPermissionRequest) (acp.RequestPermissionResponse, error)

	// OnSessionUpdate is called for every ACP SessionUpdate received during
	// execution. This fires in real-time regardless of whether Generate() or
	// Stream() is used, giving consumers access to tool calls, text chunks,
	// and other ACP events as they happen.
	OnSessionUpdate func(acp.SessionUpdate)

	// SelectModel is the ACP model id to switch to after each NewSession.
	// Empty = use the agent's default. Passing --model on the spawn
	// command line is unreliable: Claude Code ignores it in --acp mode and
	// the session stays on its default regardless.
	//
	// Model selection is done via the session config option mechanism:
	// the agent advertises a category="model" select option at session
	// creation and we switch by calling session/set_config_option. (The
	// old, unstable session/set_model RPC was removed from the protocol.)
	//
	// Per-CLI valid ids:
	//   Claude Code: "default" / "sonnet" / "haiku"
	//   GitHub Copilot: "auto" / "gpt-5.4" / "gpt-5.3-codex" / ...
	// Discoverable via the OnSessionInfo callback below. When SelectModel
	// is non-empty but not advertised by the agent, NewSession fails fast.
	SelectModel string

	// OnSessionInfo fires once per NewSession with the model + session id
	// information extracted from the agent's "model" config option. The
	// ModelConfig carries the *resolved* CurrentModelID (after applying
	// SelectModel, or the agent's default) and the full list of available
	// models with human-readable names + descriptions. nil when the agent
	// advertises no model selector.
	//
	// Use this to (a) surface the actual running model to a UI rather
	// than the alias the user picked, and (b) populate a model picker
	// dynamically without hardcoding a per-CLI list.
	//
	// Called BEFORE the first prompt is sent on the new session, so
	// callers can react to the model state synchronously if needed.
	OnSessionInfo func(sessionId acp.SessionId, models *ModelConfig)

	// McpServers is the list of MCP servers to attach to each ACP session.
	// Claude Code will discover and use tools from these servers.
	McpServers []acp.McpServer
}

Config for the ACP-based chat model.

type ModelConfig

type ModelConfig struct {
	// ConfigID is the SessionConfigId to target in a set_config_option call.
	ConfigID string
	// CurrentModelID is the value id the agent currently has selected.
	CurrentModelID string
	// Available is the flattened (grouped + ungrouped) list of selectable
	// models in advertised order.
	Available []ModelOption
}

ModelConfig is the agent's model-selection config option for a session, extracted from NewSessionResponse.ConfigOptions.

Model selection is no longer a dedicated RPC. The (unstable, now removed) session/set_model method was folded into the generic session config option mechanism: agents advertise a `select` option with category "model" at session creation, and the client switches models by calling session/set_config_option against that option's id. See https://agentclientprotocol.com/protocol/session-config-options

func ExtractModelConfig

func ExtractModelConfig(opts []acp.SessionConfigOption) *ModelConfig

ExtractModelConfig returns the category=="model" select option from a NewSession response's config options, or nil when the agent advertises no model selector. Boolean options and non-model categories are ignored.

func (*ModelConfig) ResolveValue

func (mc *ModelConfig) ResolveValue(requested string) (string, bool)

ResolveValue maps a user-requested model id/alias to a concrete value id from the available options. It matches by exact value id first, then case-insensitively by human-readable name. Returns "", false when the requested model is not advertised — callers fail fast rather than silently falling back to a default.

type ModelOption

type ModelOption struct {
	ID          string
	Name        string
	Description string
	// Meta carries the agent's per-option `_meta` extension payload (e.g.
	// Copilot's usage metadata), passed through verbatim for callers that
	// synthesize richer descriptions. nil when the agent reports none.
	Meta map[string]any
}

ModelOption is one selectable model advertised by an ACP agent's "model" session config option.

Directories

Path Synopsis
Example demonstrates how to use the ACP chat model with Eino.
Example demonstrates how to use the ACP chat model with Eino.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL