From 67e0081c6d573b701ed931f96e14dbe5b4258a17 Mon Sep 17 00:00:00 2001 From: Claude Sonnet 5 Date: Fri, 3 Jul 2026 08:49:43 +0000 Subject: refactor(executor): extract provider-neutral tool-use loop (Phase 1) Splits LocalRunner's OpenAI-specific agentic loop into reusable, provider- agnostic pieces so later phases can add native Anthropic/OpenAI/Google/Groq/ OpenRouter adapters without duplicating the control flow: - internal/provider: neutral Provider/ChatRequest/ChatResponse types, plus an openaicompat adapter wrapping the existing internal/llm.Client unchanged - internal/sandbox: Sandbox interface + HostSandbox (git clone/push/cleanup, read_file/write_file/run_bash/glob), lifted verbatim from local.go/localtools.go - internal/agentloop: the extracted tool-use loop (request/response/tool- dispatch/loop, ask_user blocking, stream-json envelope, summary fallback) - internal/agentchannel: AgentChannel/SubtaskSpec/BlockedError/ErrAgentBlocked moved out of internal/executor so agentloop can use them without an import cycle; internal/executor re-exports via type aliases, so no call site changes - internal/executor/nativerunner.go: NativeRunner replaces LocalRunner, wiring agentloop.Loop + openaicompat + HostSandbox together - config.Providers map[string]ProviderConfig added (unused until Phase 2+) Zero intended behavior change: go test -race ./... passes across all packages, and end-to-end stream-json/summary/changestats output was verified byte-compatible against a fake OpenAI-compatible server. Adds test coverage for sandbox tool-dispatch (git clone/push, read/write/bash/glob) that LocalRunner never had. Co-Authored-By: Claude Sonnet 5 Claude-Session: https://claude.ai/code/session_01V1moSNCJRcP6kykA4tyUSs --- internal/cli/run.go | 6 ++++-- internal/cli/serve.go | 6 ++++-- 2 files changed, 8 insertions(+), 4 deletions(-) (limited to 'internal/cli') diff --git a/internal/cli/run.go b/internal/cli/run.go index 771c7b7..1c53b1a 100644 --- a/internal/cli/run.go +++ b/internal/cli/run.go @@ -8,6 +8,7 @@ import ( "syscall" "github.com/thepeterstone/claudomator/internal/executor" + "github.com/thepeterstone/claudomator/internal/provider/openaicompat" "github.com/thepeterstone/claudomator/internal/storage" "github.com/thepeterstone/claudomator/internal/task" "github.com/spf13/cobra" @@ -106,11 +107,12 @@ func runTasks(file string, parallel int, dryRun bool) error { localClient := buildLocalLLMClient(cfg.LocalModel, logger) if localClient != nil && cfg.Runners.LocalEnabled() { - runners["local"] = &executor.LocalRunner{ - Client: localClient, + runners["local"] = &executor.NativeRunner{ + Provider: openaicompat.New(localClient), Logger: logger, LogDir: cfg.LogDir, DefaultTemperature: cfg.LocalModel.DefaultTemperature, + DefaultModel: cfg.LocalModel.Model, } } diff --git a/internal/cli/serve.go b/internal/cli/serve.go index 19db2f8..d8ec6a1 100644 --- a/internal/cli/serve.go +++ b/internal/cli/serve.go @@ -15,6 +15,7 @@ import ( "github.com/thepeterstone/claudomator/internal/config" "github.com/thepeterstone/claudomator/internal/executor" "github.com/thepeterstone/claudomator/internal/notify" + "github.com/thepeterstone/claudomator/internal/provider/openaicompat" "github.com/thepeterstone/claudomator/internal/storage" "github.com/thepeterstone/claudomator/internal/version" "github.com/spf13/cobra" @@ -139,11 +140,12 @@ func serve(addr, basePath string) error { localClient := buildLocalLLMClient(cfg.LocalModel, logger) if localClient != nil && cfg.Runners.LocalEnabled() { - runners["local"] = &executor.LocalRunner{ - Client: localClient, + runners["local"] = &executor.NativeRunner{ + Provider: openaicompat.New(localClient), Logger: logger, LogDir: cfg.LogDir, DefaultTemperature: cfg.LocalModel.DefaultTemperature, + DefaultModel: cfg.LocalModel.Model, } logger.Info("local runner registered", "endpoint", cfg.LocalModel.Endpoint, "model", cfg.LocalModel.Model) } -- cgit v1.2.3