dify/dify-agent-runtime/internal/stubclient/client.go
Yunlu Wen 302d7b1e1b
feat(agent): shellctl rewritten in go (#38841)
Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
2026-07-15 01:58:36 +00:00

144 lines
3.9 KiB
Go

// Package stubclient provides a gRPC client for the Dify Agent Stub service.
//
// The Agent Stub service runs on the host (dify-agent backend) and exposes
// Connect, FileUpload, and FileDownload RPCs to sandbox-resident processes.
// This client is used by the dify-agent CLI binary inside the sandbox container.
package stubclient
import (
"context"
"fmt"
"time"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials/insecure"
stubv1 "github.com/langgenius/dify/dify-agent-runtime/gen/dify/agent/stub/v1"
)
// Client wraps the gRPC AgentStubService client with connection lifecycle management.
type Client struct {
conn *grpc.ClientConn
stub stubv1.AgentStubServiceClient
timeout time.Duration
}
// Option configures a Client.
type Option func(*Client)
// WithTimeout sets the default per-call timeout. Default is 30s.
func WithTimeout(d time.Duration) Option {
return func(c *Client) {
c.timeout = d
}
}
// Dial creates a new Client connected to the given target address (host:port).
func Dial(target string, opts ...Option) (*Client, error) {
c := &Client{timeout: 30 * time.Second}
for _, o := range opts {
o(c)
}
conn, err := grpc.NewClient(target, grpc.WithTransportCredentials(insecure.NewCredentials()))
if err != nil {
return nil, fmt.Errorf("stubclient: dial %s: %w", target, err)
}
c.conn = conn
c.stub = stubv1.NewAgentStubServiceClient(conn)
return c, nil
}
// Close closes the underlying gRPC connection.
func (c *Client) Close() error {
if c.conn != nil {
return c.conn.Close()
}
return nil
}
// ConnectResult holds the response from a Connect RPC.
type ConnectResult struct {
ConnectionID string
Status string
}
// Connect establishes a logical connection with the Agent Stub server.
func (c *Client) Connect(ctx context.Context, protocolVersion int32, argv []string, metadataJSON string) (*ConnectResult, error) {
ctx, cancel := context.WithTimeout(ctx, c.timeout)
defer cancel()
resp, err := c.stub.Connect(ctx, &stubv1.ConnectRequest{
ProtocolVersion: protocolVersion,
Argv: argv,
MetadataJson: metadataJSON,
})
if err != nil {
return nil, fmt.Errorf("stubclient: connect: %w", err)
}
return &ConnectResult{
ConnectionID: resp.GetConnectionId(),
Status: resp.GetStatus(),
}, nil
}
// FileUploadResult holds the response from a CreateFileUploadRequest RPC.
type FileUploadResult struct {
UploadURL string
}
// CreateFileUpload requests an upload URL from the Agent Stub server.
func (c *Client) CreateFileUpload(ctx context.Context, filename, mimetype string) (*FileUploadResult, error) {
ctx, cancel := context.WithTimeout(ctx, c.timeout)
defer cancel()
resp, err := c.stub.CreateFileUploadRequest(ctx, &stubv1.FileUploadRequest{
Filename: filename,
Mimetype: mimetype,
})
if err != nil {
return nil, fmt.Errorf("stubclient: file upload: %w", err)
}
return &FileUploadResult{
UploadURL: resp.GetUploadUrl(),
}, nil
}
// FileDownloadResult holds the response from a CreateFileDownloadRequest RPC.
type FileDownloadResult struct {
Filename string
MimeType string
Size int64
DownloadURL string
}
// CreateFileDownload requests a download URL from the Agent Stub server.
func (c *Client) CreateFileDownload(ctx context.Context, transferMethod string, reference, url *string, forExternal bool) (*FileDownloadResult, error) {
ctx, cancel := context.WithTimeout(ctx, c.timeout)
defer cancel()
fileMapping := &stubv1.FileMapping{
TransferMethod: transferMethod,
}
if reference != nil {
fileMapping.Reference = reference
}
if url != nil {
fileMapping.Url = url
}
resp, err := c.stub.CreateFileDownloadRequest(ctx, &stubv1.FileDownloadRequest{
File: fileMapping,
ForExternal: &forExternal,
})
if err != nil {
return nil, fmt.Errorf("stubclient: file download: %w", err)
}
return &FileDownloadResult{
Filename: resp.GetFilename(),
MimeType: resp.GetMimeType(),
Size: resp.GetSize(),
DownloadURL: resp.GetDownloadUrl(),
}, nil
}