* feat(io): add streaming API to Medium interface and optimize agentic context - Added ReadStream and WriteStream to io.Medium interface. - Implemented streaming methods in local and mock mediums. - Updated pkg/agentic/context.go to use streaming I/O with LimitReader. - Added 5000-byte truncation limit for all AI context file reads to reduce memory usage. - Documented when to use streaming vs full-file APIs in io.Medium. * feat(io): optimize streaming API and fix PR feedback - Fixed resource leak in agentic context by using defer for closing file streams. - Improved truncation logic in agentic context to handle multibyte characters correctly by checking byte length before string conversion. - Added comprehensive documentation to ReadStream and WriteStream in local medium. - Added unit tests for ReadStream and WriteStream in local medium. - Applied formatting and fixed auto-merge CI configuration. * feat(io): add streaming API and fix CI failures (syntax fix) - Introduced ReadStream and WriteStream to io.Medium interface. - Implemented streaming methods in local and mock mediums. - Optimized agentic context with streaming reads and truncation logic. - Fixed syntax error in local client tests by overwriting the file. - Fixed auto-merge CI by adding checkout and repository context. - Applied formatting fixes.
135 lines
3 KiB
Go
135 lines
3 KiB
Go
package agentic
|
|
|
|
import (
|
|
"context"
|
|
"os"
|
|
"os/exec"
|
|
"strings"
|
|
|
|
"github.com/host-uk/core/pkg/framework"
|
|
)
|
|
|
|
// Tasks for AI service
|
|
|
|
// TaskCommit requests Claude to create a commit.
|
|
type TaskCommit struct {
|
|
Path string
|
|
Name string
|
|
CanEdit bool // allow Write/Edit tools
|
|
}
|
|
|
|
// TaskPrompt sends a custom prompt to Claude.
|
|
type TaskPrompt struct {
|
|
Prompt string
|
|
WorkDir string
|
|
AllowedTools []string
|
|
|
|
taskID string
|
|
}
|
|
|
|
func (t *TaskPrompt) SetTaskID(id string) { t.taskID = id }
|
|
func (t *TaskPrompt) GetTaskID() string { return t.taskID }
|
|
|
|
// ServiceOptions for configuring the AI service.
|
|
type ServiceOptions struct {
|
|
DefaultTools []string
|
|
AllowEdit bool // global permission for Write/Edit tools
|
|
}
|
|
|
|
// DefaultServiceOptions returns sensible defaults.
|
|
func DefaultServiceOptions() ServiceOptions {
|
|
return ServiceOptions{
|
|
DefaultTools: []string{"Bash", "Read", "Glob", "Grep"},
|
|
AllowEdit: false,
|
|
}
|
|
}
|
|
|
|
// Service provides AI/Claude operations as a Core service.
|
|
type Service struct {
|
|
*framework.ServiceRuntime[ServiceOptions]
|
|
}
|
|
|
|
// NewService creates an AI service factory.
|
|
func NewService(opts ServiceOptions) func(*framework.Core) (any, error) {
|
|
return func(c *framework.Core) (any, error) {
|
|
return &Service{
|
|
ServiceRuntime: framework.NewServiceRuntime(c, opts),
|
|
}, nil
|
|
}
|
|
}
|
|
|
|
// OnStartup registers task handlers.
|
|
func (s *Service) OnStartup(ctx context.Context) error {
|
|
s.Core().RegisterTask(s.handleTask)
|
|
return nil
|
|
}
|
|
|
|
func (s *Service) handleTask(c *framework.Core, t framework.Task) (any, bool, error) {
|
|
switch m := t.(type) {
|
|
case TaskCommit:
|
|
err := s.doCommit(m)
|
|
return nil, true, err
|
|
|
|
case TaskPrompt:
|
|
err := s.doPrompt(m)
|
|
return nil, true, err
|
|
}
|
|
return nil, false, nil
|
|
}
|
|
|
|
func (s *Service) doCommit(task TaskCommit) error {
|
|
prompt := Prompt("commit")
|
|
|
|
tools := []string{"Bash", "Read", "Glob", "Grep"}
|
|
if task.CanEdit {
|
|
tools = []string{"Bash", "Read", "Write", "Edit", "Glob", "Grep"}
|
|
}
|
|
|
|
cmd := exec.CommandContext(context.Background(), "claude", "-p", prompt, "--allowedTools", strings.Join(tools, ","))
|
|
cmd.Dir = task.Path
|
|
cmd.Stdout = os.Stdout
|
|
cmd.Stderr = os.Stderr
|
|
cmd.Stdin = os.Stdin
|
|
|
|
return cmd.Run()
|
|
}
|
|
|
|
func (s *Service) doPrompt(task TaskPrompt) error {
|
|
if task.taskID != "" {
|
|
s.Core().Progress(task.taskID, 0.1, "Starting Claude...", &task)
|
|
}
|
|
|
|
opts := s.Opts()
|
|
tools := opts.DefaultTools
|
|
if len(tools) == 0 {
|
|
tools = []string{"Bash", "Read", "Glob", "Grep"}
|
|
}
|
|
|
|
if len(task.AllowedTools) > 0 {
|
|
tools = task.AllowedTools
|
|
}
|
|
|
|
cmd := exec.CommandContext(context.Background(), "claude", "-p", task.Prompt, "--allowedTools", strings.Join(tools, ","))
|
|
if task.WorkDir != "" {
|
|
cmd.Dir = task.WorkDir
|
|
}
|
|
cmd.Stdout = os.Stdout
|
|
cmd.Stderr = os.Stderr
|
|
cmd.Stdin = os.Stdin
|
|
|
|
if task.taskID != "" {
|
|
s.Core().Progress(task.taskID, 0.5, "Running Claude prompt...", &task)
|
|
}
|
|
|
|
err := cmd.Run()
|
|
|
|
if task.taskID != "" {
|
|
if err != nil {
|
|
s.Core().Progress(task.taskID, 1.0, "Failed: "+err.Error(), &task)
|
|
} else {
|
|
s.Core().Progress(task.taskID, 1.0, "Completed", &task)
|
|
}
|
|
}
|
|
|
|
return err
|
|
}
|