einoacp

package module
v0.0.0-...-2607f61 Latest Latest
Warning

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

Go to latest
Published: Sep 16, 2026 License: Apache-2.0 Imports: 20 Imported by: 0

README

ACP Bridge

Utilities for bridging EINO ADK agents to the Agent Client Protocol (ACP). Provides two core functions:

  • AgentEventToSessionUpdate — Converts eino AgentEvent into ACP SessionUpdate notifications for streaming agent output to ACP clients.
  • NewClientToolsMiddleware — Bridges ACP client-side capabilities (filesystem, terminal) to eino's filesystem middleware, so the agent can read/write files and run commands on the client.

Installation

go get github.com/cloudwego/eino-ext/acp

API Reference

AgentEventToSessionUpdate

Converts an eino AgentEvent into a sequence of ACP SessionUpdate notifications.

func AgentEventToSessionUpdate(
    event *adk.AgentEvent,
    opt *EventConverterOption,
) iter.Seq2[acpproto.SessionUpdate, error]

Handles:

  • Assistant messagesAgentMessageChunk
  • Reasoning contentAgentThoughtChunk
  • User messagesUserMessageChunk
  • Tool callsToolCall
  • Tool resultsToolCallUpdate
  • InterruptsAgentMessageChunk with _meta["eino:interrupted"] (customizable)
Streaming Events to Client
iter := runner.Query(ctx, query)
for {
    event, ok := iter.Next()
    if !ok {
        break
    }
    for su, err := range einoacp.AgentEventToSessionUpdate(event, nil) {
        if err != nil {
            return acp.PromptResponse{}, err
        }
        conn.SessionUpdate(ctx, acp.SessionNotification{
            SessionID: sessionID,
            Update:    su,
        })
    }
}
return acp.PromptResponse{StopReason: acp.StopReasonEndTurn}, nil
Custom Interrupt Converter
opt := &einoacp.EventConverterOption{
    InterruptConverter: func(info *adk.InterruptInfo) iter.Seq2[acpproto.SessionUpdate, error] {
        return func(yield func(acpproto.SessionUpdate, error) bool) {
            // Custom interrupt handling logic
            yield(acpproto.NewSessionUpdateAgentMessageChunk(acpproto.ContentChunk{
                Content: acpproto.NewContentBlockText(acpproto.TextContent{
                    Text: fmt.Sprintf("Action required: %v", info.Data),
                }),
            }), nil)
        }
    },
}

for su, err := range einoacp.AgentEventToSessionUpdate(event, opt) {
    // ...
}
NewClientToolsMiddleware

Creates a ChatModelAgentMiddleware that bridges ACP client-side capabilities to eino's filesystem tools.

func NewClientToolsMiddleware(ctx context.Context, cfg *Config) (adk.ChatModelAgentMiddleware, error)

Config fields:

Field Description
SessionID ACP session ID (required)
Conn Agent-side ACP connection (required)
Capabilities Client capability set from initialization (required)
UseTerminalForFileTools Enable terminal-backed ls/glob/grep/edit (requires terminal capability)
Logger Optional structured logger; defaults to slog.Default()

Tools are enabled based on client-advertised capabilities:

Client Capability Enabled Tool
fs.readTextFile read_file
fs.writeTextFile write_file
terminal Shell command execution
terminal + UseTerminalForFileTools ls, glob, grep, edit
if clientCapabilities != nil {
    middleware, err := einoacp.NewClientToolsMiddleware(ctx, &einoacp.Config{
        SessionID:    sessionID,
        Conn:         conn,
        Capabilities: clientCapabilities,
    })
    if err != nil {
        return err
    }
    // Add to agent config
    agentConfig.Handlers = append(agentConfig.Handlers, middleware)
}

Examples

See example/main.go for a complete ACP server implementation that:

  1. Creates an eino ChatModelAgent per session
  2. Bridges client filesystem/terminal capabilities via NewClientToolsMiddleware
  3. Streams AgentEvents back as ACP SessionUpdate notifications

Documentation

Index

Constants

View Source
const (
	MetaKeyInterrupted       = "eino:interrupted"
	MetaKeyInterruptContexts = "eino:interruptContexts"
)

Metadata keys used on ACP SessionUpdate _meta to carry eino-specific context. These form a cross-process contract with clients; changing them is a breaking change.

Variables

View Source
var (
	// ErrShellNonZeroExit is returned when a shell command exits with a non-zero code.
	ErrShellNonZeroExit = errors.New("acp.shell: non-zero exit")
	// ErrCapabilityMissing is returned when the client does not advertise the required capability.
	ErrCapabilityMissing = errors.New("acp: client capability not supported")
	// ErrOldStringNotFound is returned when Edit cannot locate the oldString in the file.
	ErrOldStringNotFound = errors.New("acp.edit: oldString not found")
	// ErrAmbiguousReplace is returned when multiple occurrences exist but ReplaceAll is false.
	ErrAmbiguousReplace = errors.New("acp.edit: ambiguous replacement (set ReplaceAll)")
	// ErrFileTooLarge is returned when Edit is attempted on a file exceeding maxEditFileSize.
	ErrFileTooLarge = errors.New("acp.edit: file too large for in-memory edit")
)

Sentinel errors for structured error handling by callers.

Functions

func AgentEventToSessionUpdate

func AgentEventToSessionUpdate(
	event *adk.AgentEvent,
	opt *EventConverterOption,
) iter.Seq2[acpproto.SessionUpdate, error]

AgentEventToSessionUpdate converts an eino AgentEvent into a sequence of ACP SessionUpdate notifications. It handles message output (both streaming and non-streaming), tool calls, tool results, and interrupt events. For interrupt events, a custom InterruptConverter can be provided via opt; if nil, the default converter is used, which serializes the interrupt data as an AgentMessageChunk with interrupt metadata in _meta. When the upstream message output is a stream, tool-call argument chunks are concatenated into a single ToolCall update by default; set opt.PreserveToolCallStream to forward each chunk as its own update (see EventConverterOption.PreserveToolCallStream for the client-side reassembly contract).

func NewClientToolsMiddleware

func NewClientToolsMiddleware(ctx context.Context, cfg *Config) (adk.ChatModelAgentMiddleware, error)

NewClientToolsMiddleware creates a ChatModelAgentMiddleware that bridges ACP client-side capabilities (filesystem read/write, terminal execution) to eino's filesystem tools. The ACP protocol only exposes read_text_file, write_text_file and terminal capabilities, so read_file and write_file are enabled only when the client advertises the corresponding capability. ls/glob/grep/edit are disabled by default; they become available when cfg.UseTerminalForFileTools is true and the client advertises the terminal capability — in which case they run as shell commands.

Types

type ACPConn

ACPConn is the minimal interface for ACP client-side RPC calls used by Backend and shell. It is satisfied by *acpconn.AgentConnection (from the github.com/eino-contrib/acp/conn package). Callers may provide alternative implementations for testing or proxying.

type Backend

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

Backend implements the mfs.Backend interface on top of an ACP agent connection, bridging filesystem and shell tool calls to ACP client-side capabilities (read_text_file / write_text_file / terminal). It is exported so callers that build their own filesystem middleware (instead of using NewClientToolsMiddleware) can plug it directly into mfs.MiddlewareConfig.

func NewBackend

func NewBackend(cfg *Config) (*Backend, error)

NewBackend constructs a Backend from the same Config used by NewClientToolsMiddleware. The returned Backend reflects the capabilities advertised by the client:

  • Read/Write are enabled when cfg.Capabilities.FS.ReadTextFile / WriteTextFile are set; otherwise calling them returns an error from the underlying ACP RPC.
  • LsInfo/GlobInfo/GrepRaw (terminal-backed implementations) are only functional when cfg.Capabilities.Terminal is true AND cfg.UseTerminalForFileTools is set; otherwise they return an error.
  • Edit requires both ReadTextFile and WriteTextFile fs capabilities; it performs a read-modify-write cycle via the fs API.

func (*Backend) Edit

func (b *Backend) Edit(ctx context.Context, req *filesystem.EditRequest) error

func (*Backend) GlobInfo

func (*Backend) GrepRaw

func (*Backend) LsInfo

func (*Backend) Read

func (*Backend) Write

func (b *Backend) Write(ctx context.Context, req *filesystem.WriteRequest) error

type Config

type Config struct {
	// SessionID is the ACP session the middleware will operate on. Required.
	SessionID acpproto.SessionID
	// Conn is the agent-side ACP connection used to issue client requests. Required.
	// Any implementation of ACPConn is accepted; *conn.AgentConnection (from
	// github.com/eino-contrib/acp/conn) satisfies this interface and is the
	// typical production choice.
	Conn ACPConn
	// Capabilities is the client capability set advertised during initialization.
	// Required: tools are enabled based on what the client supports.
	Capabilities *acpproto.ClientCapabilities

	// UseTerminalForFileTools enables terminal-backed implementations of the
	// ls, glob, and grep tools. It only takes effect when the client
	// also advertises the terminal capability; otherwise those tools stay
	// disabled because the ACP protocol does not expose corresponding
	// filesystem methods.
	//
	// Note: edit always requires fs capability (ReadTextFile + WriteTextFile).
	//
	// Implementation: ls runs `ls -1A`, glob enumerates with `find` and matches
	// in-process via doublestar, grep prefers ripgrep (`rg --json`) and
	// transparently falls back to POSIX `grep -RnE` when `rg` is not
	// installed on the client side.
	UseTerminalForFileTools bool

	// Logger is an optional structured logger for non-fatal diagnostics (e.g.
	// ReleaseTerminal failures). If nil, slog.Default() is used.
	Logger *slog.Logger
}

Config configures NewClientToolsMiddleware.

type EventConverterOption

type EventConverterOption struct {
	// InterruptConverter is an optional custom converter for interrupt events.
	// If nil, the default conversion is used: the interrupt data is converted to
	// an AgentMessageChunk with the interrupt metadata in _meta.
	InterruptConverter InterruptConverter

	// PreserveToolCallStream controls how an upstream tool-call stream is mapped
	// into ACP SessionUpdates. ACP's ToolCall update has no native streaming
	// concept for arguments, so by default (false) the converter concatenates
	// every chunk into a single, complete ToolCall before yielding it. When set
	// to true, each upstream chunk is forwarded as its own ToolCall SessionUpdate
	// and the partial argument fragment is placed in RawInput as a JSON-encoded
	// string (so it stays valid JSON on the wire).
	//
	// Whether the upstream is a stream at all is determined by the eino event
	// (MessageVariant.IsStreaming), not by this flag — this flag only chooses
	// between "preserve the stream" and "concat into one update" when it is.
	//
	// When true, the converter guarantees that ToolCallIDs do not interleave:
	// once the emitted ToolCallID changes, the previous call is finalized and
	// no further chunks for it will appear. Clients reassemble fragments by
	// ToolCallID and treat an ID change as the end of the previous call.
	PreserveToolCallStream bool
}

EventConverterOption configures the behavior of AgentEventToSessionUpdate.

type InterruptConverter

type InterruptConverter func(info *adk.InterruptInfo) iter.Seq2[acpproto.SessionUpdate, error]

InterruptConverter converts an adk.InterruptInfo into a sequence of ACP SessionUpdates. Users can provide a custom implementation to control how interrupt events are presented to the client.

Directories

Path Synopsis
examples module

Jump to

Keyboard shortcuts

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