Skip to content

[Enhancement]: Go SDK - Import CPE as a Library Module #168

@spachava753

Description

@spachava753

Enhancement Category

Developer experience

Problem or Use Case

Currently, CPE is only usable as a CLI tool. Developers who want to build applications with AI-powered code analysis, conversation management, or MCP integration must either:

  1. Shell out to the cpe binary (brittle, hard to integrate, loses type safety)
  2. Reimplement CPE's core functionality from scratch
  3. Fork and modify CPE directly (maintenance burden)

This limits CPE's usefulness in several scenarios:

  • Building custom AI assistants - developers building their own CLI tools or applications want CPE's conversation management, storage, and model abstraction
  • Embedding in larger applications - web servers, desktop apps, or other Go programs that want to leverage CPE's capabilities
  • Creating specialized tooling - custom CI/CD integrations, code review bots, or development workflow automation
  • Testing and experimentation - programmatic access makes it easier to build benchmarks, test suites, or research tools

Proposed Solution

Expose CPE's core functionality as a public Go SDK that can be imported as a module. The SDK should support all current CLI operations programmatically:

Core API Surface

import "github.com/spachava753/cpe/sdk"

// Configuration - two approaches
// Option 1: Load from file (like CLI)
cfg, err := sdk.LoadConfig("./cpe.yaml")

// Option 2: Programmatic configuration
cfg := &sdk.Config{
    Models: []sdk.ModelConfig{
        {
            Ref:       "sonnet",
            ID:        "claude-3-5-sonnet-20241022",
            Type:      "anthropic",
            ApiKeyEnv: "ANTHROPIC_API_KEY",
        },
    },
    MCPServers: map[string]sdk.MCPServerConfig{...},
    Defaults: sdk.Defaults{
        Model: "sonnet",
        CodeMode: &sdk.CodeModeConfig{Enabled: true},
    },
}

Conversation & Storage Management

// Initialize storage (SQLite-backed, like .cpeconvo)
storage, err := sdk.NewDialogStorage(ctx, "./my-conversations.db")
defer storage.Close()

// Create a new conversation
conv := sdk.NewConversation(storage)

// Or resume from a specific message ID (branching/forking)
conv, err := sdk.ResumeConversation(storage, "abc123")

// List all conversation trees
trees, err := storage.ListConversations(ctx)

// Get full dialog history for a branch
dialog, msgIDs, err := storage.GetDialogForMessage(ctx, "abc123")

Generation & Agent Execution

// Create a generator from config
gen, err := sdk.NewGenerator(ctx, cfg, sdk.GeneratorOptions{
    ModelRef:     "sonnet",
    SystemPrompt: customPrompt,  // or use cfg.Defaults.SystemPromptPath
})
defer gen.Close()

// Single-turn generation (no storage)
response, err := gen.Generate(ctx, dialog, &sdk.GenOpts{
    Temperature: 0.7,
    MaxTokens:   4096,
})

// Multi-turn with automatic storage
result, err := conv.Send(ctx, gen, sdk.UserMessage{
    Text:   "Analyze this code",
    Files:  []string{"main.go"},         // Multimodal: files
    Images: [][]byte{screenshotBytes},   // Multimodal: images
})

// Access the response
fmt.Println(result.Text)
fmt.Println(result.MessageID)  // For continuation/forking

// Incognito mode (no storage)
result, err := sdk.GenerateIncognito(ctx, gen, dialog)

MCP Integration

// Initialize MCP connections from config
mcpState, err := sdk.InitializeMCP(ctx, cfg.MCPServers)
defer mcpState.Close()

// Create generator with MCP tools
gen, err := sdk.NewGenerator(ctx, cfg, sdk.GeneratorOptions{
    MCPState: mcpState,
    CodeMode: true,  // Enable code mode for tool execution
})

// Or manually call MCP tools
result, err := mcpState.CallTool(ctx, "server_name", "tool_name", map[string]any{
    "param": "value",
})

Streaming Support

// Stream responses with callbacks
err := gen.GenerateStream(ctx, dialog, opts, func(chunk sdk.StreamChunk) error {
    switch chunk.Type {
    case sdk.ChunkText:
        fmt.Print(chunk.Text)
    case sdk.ChunkToolCall:
        fmt.Printf("Calling tool: %s\n", chunk.ToolName)
    case sdk.ChunkToolResult:
        fmt.Printf("Tool result: %s\n", chunk.Result)
    }
    return nil
})

Package Structure

github.com/spachava753/cpe/
├── sdk/                          # Public SDK package
│   ├── config.go                 # Configuration types and loading
│   ├── storage.go                # DialogStorage interface and implementation  
│   ├── conversation.go           # High-level conversation management
│   ├── generator.go              # Generator creation and options
│   ├── mcp.go                    # MCP state and tool calling
│   └── types.go                  # Public types (Message, Dialog, etc.)
├── internal/                     # Internal implementation (existing)
│   ├── agent/
│   ├── config/
│   ├── storage/
│   └── ...
└── cmd/                          # CLI (uses sdk internally)

Alternatives Considered

  1. Keep CLI-only: Users can shell out to cpe, but this is slow, lossy (no type safety), and hard to integrate properly.

  2. Export internal packages directly: Making internal/ packages public would work but violates Go conventions and exposes implementation details that may change.

  3. Separate repository: A separate cpe-sdk repo would add maintenance burden and versioning complexity.

  4. gRPC/HTTP server mode: Adding a server mode would enable language-agnostic access but adds network overhead and deployment complexity for Go users.

The proposed SDK approach provides the cleanest API while reusing existing internal implementations.

Impact Scope

Power users / advanced workflows

Additional Context

Key Design Considerations

  1. Configuration flexibility: Support both file-based config (for CLI parity) and programmatic config (for embedded use). The internal/config package already has good separation that can be exposed.

  2. Storage abstraction: The current DialogStorage interface in internal/commands/generate.go is already well-abstracted. Expose this with perhaps a factory function for SQLite (default) or allow users to implement their own.

  3. Conversation forking: The tree-based message storage already supports this perfectly. The SDK should make it easy to:

    • List conversation trees
    • Resume from any message ID
    • Create branches by continuing from historical messages
  4. Multimodal support: The gai.Block system already handles text, images, audio, video. The SDK should provide helpers for common cases (file paths, byte arrays).

  5. Interruption handling: Consider exposing the partial-save behavior for interrupted generations.

  6. Middleware/hooks: Allow users to add custom middleware (logging, metrics, etc.) to the generator pipeline.

Migration Path

The CLI (cmd/) would be refactored to use the SDK internally, ensuring feature parity and that the SDK is truly complete. This also serves as a reference implementation.

Related Components

  • internal/storage/dialog_storage.go - conversation storage (expose as SDK interface)
  • internal/config/ - configuration loading (expose config types)
  • internal/agent/generator.go - generator creation (expose as SDK factory)
  • internal/mcp/ - MCP integration (expose state management)
  • internal/commands/generate.go - generation flow (reference for SDK conversation API)

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions