Go API server + Preact UI + Claude Code adapter. - App-centric model (ideas, not repos) - AgentProvider interface for multi-agent support - K8s pod lifecycle for sandboxed agent sessions - Gitea integration (create repos, push branches) - WebSocket streaming for live session output - Woodpecker CI/CD pipelines (kaniko build + kubectl deploy)
123 lines
3.3 KiB
Go
123 lines
3.3 KiB
Go
package claudecode
|
|
|
|
import (
|
|
"fmt"
|
|
|
|
"github.com/agentsphere/agent-mgr/internal/provider"
|
|
corev1 "k8s.io/api/core/v1"
|
|
"k8s.io/apimachinery/pkg/api/resource"
|
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
|
)
|
|
|
|
const (
|
|
Namespace = "agent-mgr"
|
|
RunnerImage = "git.asp.now/platform/claude-code-runner:latest"
|
|
GitImage = "alpine/git:latest"
|
|
)
|
|
|
|
func buildPod(cfg provider.SessionConfig, opts *Config) *corev1.Pod {
|
|
podName := fmt.Sprintf("agent-%s", cfg.SessionID.String()[:8])
|
|
branch := cfg.Branch
|
|
|
|
cloneScript := fmt.Sprintf(`
|
|
set -eu
|
|
git clone %s /workspace
|
|
cd /workspace
|
|
git checkout -b %s
|
|
git config user.name "agent-mgr-bot"
|
|
git config user.email "bot@asp.now"
|
|
`, cfg.RepoClone, branch)
|
|
|
|
claudeArgs := []string{"--output-format", "stream-json", "--permission-mode", "auto-accept-only"}
|
|
if opts != nil {
|
|
if opts.Model != "" {
|
|
claudeArgs = append(claudeArgs, "--model", opts.Model)
|
|
}
|
|
if opts.MaxTurns > 0 {
|
|
claudeArgs = append(claudeArgs, "--max-turns", fmt.Sprintf("%d", opts.MaxTurns))
|
|
}
|
|
}
|
|
|
|
return &corev1.Pod{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: podName,
|
|
Namespace: Namespace,
|
|
Labels: map[string]string{
|
|
"app": "agent-session",
|
|
"session-id": cfg.SessionID.String(),
|
|
},
|
|
},
|
|
Spec: corev1.PodSpec{
|
|
RestartPolicy: corev1.RestartPolicyNever,
|
|
ServiceAccountName: "agent-runner",
|
|
InitContainers: []corev1.Container{
|
|
{
|
|
Name: "git-clone",
|
|
Image: GitImage,
|
|
Command: []string{"sh", "-c", cloneScript},
|
|
VolumeMounts: []corev1.VolumeMount{
|
|
{Name: "workspace", MountPath: "/workspace"},
|
|
},
|
|
Resources: corev1.ResourceRequirements{
|
|
Requests: corev1.ResourceList{
|
|
corev1.ResourceCPU: resource.MustParse("50m"),
|
|
corev1.ResourceMemory: resource.MustParse("64Mi"),
|
|
},
|
|
Limits: corev1.ResourceList{
|
|
corev1.ResourceCPU: resource.MustParse("200m"),
|
|
corev1.ResourceMemory: resource.MustParse("128Mi"),
|
|
},
|
|
},
|
|
},
|
|
},
|
|
Containers: []corev1.Container{
|
|
{
|
|
Name: "claude",
|
|
Image: RunnerImage,
|
|
Args: claudeArgs,
|
|
Stdin: true,
|
|
TTY: false,
|
|
WorkingDir: "/workspace",
|
|
Env: []corev1.EnvVar{
|
|
{
|
|
Name: "ANTHROPIC_API_KEY",
|
|
ValueFrom: &corev1.EnvVarSource{
|
|
SecretKeyRef: &corev1.SecretKeySelector{
|
|
LocalObjectReference: corev1.LocalObjectReference{Name: "agent-mgr-secrets"},
|
|
Key: "anthropic-api-key",
|
|
},
|
|
},
|
|
},
|
|
{Name: "SESSION_ID", Value: cfg.SessionID.String()},
|
|
},
|
|
VolumeMounts: []corev1.VolumeMount{
|
|
{Name: "workspace", MountPath: "/workspace"},
|
|
},
|
|
Resources: corev1.ResourceRequirements{
|
|
Requests: corev1.ResourceList{
|
|
corev1.ResourceCPU: resource.MustParse("200m"),
|
|
corev1.ResourceMemory: resource.MustParse("256Mi"),
|
|
},
|
|
Limits: corev1.ResourceList{
|
|
corev1.ResourceCPU: resource.MustParse("500m"),
|
|
corev1.ResourceMemory: resource.MustParse("512Mi"),
|
|
},
|
|
},
|
|
},
|
|
},
|
|
Volumes: []corev1.Volume{
|
|
{
|
|
Name: "workspace",
|
|
VolumeSource: corev1.VolumeSource{
|
|
EmptyDir: &corev1.EmptyDirVolumeSource{
|
|
SizeLimit: resourcePtr(resource.MustParse("1Gi")),
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
}
|
|
}
|
|
|
|
func resourcePtr(q resource.Quantity) *resource.Quantity { return &q }
|