Files
kandev/apps/backend/internal/agent/agents/gemini.go
T
2026-09-09 07:50:08 +01:00

153 lines
4.3 KiB
Go

package agents
import (
"context"
_ "embed"
"time"
"github.com/kandev/kandev/internal/agent/usage"
"github.com/kandev/kandev/pkg/agent"
)
//go:embed logos/gemini_light.svg
var geminiLogoLight []byte
//go:embed logos/gemini_dark.svg
var geminiLogoDark []byte
const geminiPackage = "@google/gemini-cli"
var (
_ Agent = (*Gemini)(nil)
_ PassthroughAgent = (*Gemini)(nil)
_ InferenceAgent = (*Gemini)(nil)
_ ManagedNPMRuntimeAgent = (*Gemini)(nil)
)
type Gemini struct {
StandardPassthrough
}
func NewGemini() *Gemini {
return &Gemini{
StandardPassthrough: StandardPassthrough{
PermSettings: emptyPermSettings,
Cfg: PassthroughConfig{
Supported: true,
Label: "CLI Passthrough",
Description: "Show terminal directly instead of chat interface",
PassthroughCmd: NewCommand("npx", "--yes", "--prefer-offline", geminiPackage),
ModelFlag: NewParam("--model", "{model}"),
PromptFlag: NewParam("--prompt-interactive", "{prompt}"),
IdleTimeout: 3 * time.Second,
BufferMaxBytes: DefaultBufferMaxBytes,
ResumeFlag: NewParam("--resume", "latest"),
},
},
}
}
func (a *Gemini) ID() string { return "gemini" }
func (a *Gemini) Name() string { return "Google Gemini CLI Agent" }
func (a *Gemini) DisplayName() string { return "Gemini" }
func (a *Gemini) Description() string {
return "Google Gemini CLI-powered autonomous coding agent using ACP protocol."
}
func (a *Gemini) Enabled() bool { return true }
func (a *Gemini) DisplayOrder() int { return 5 }
func (a *Gemini) Logo(v LogoVariant) []byte {
if v == LogoDark {
return geminiLogoDark
}
return geminiLogoLight
}
func (a *Gemini) IsInstalled(ctx context.Context) (*DiscoveryResult, error) {
// Check for the gemini CLI on PATH. Auth state is surfaced later by the
// ACP probe, not by scanning ~/.gemini.
result, err := Detect(ctx, WithCommand("gemini"))
if err != nil {
return result, err
}
result.SupportsMCP = true
result.Capabilities = DiscoveryCapabilities{
SupportsSessionResume: true,
}
return result, nil
}
func (a *Gemini) BuildCommand(opts CommandOptions) Command {
return a.ManagedNPMRuntime().ACPCommand(opts.ManagedRuntimeVersion)
}
func (a *Gemini) ManagedNPMRuntime() ManagedNPMRuntimeSpec {
return newManagedNPMRuntimeSpec(geminiPackage, "--acp")
}
func (a *Gemini) Runtime() *RuntimeConfig {
canRecover := false
return &RuntimeConfig{
Cmd: a.ManagedNPMRuntime().CachedACPCommand(),
WorkingDir: "{workspace}",
Env: map[string]string{},
ResourceLimits: ResourceLimits{MemoryMB: 4096, CPUCores: 2.0, Timeout: time.Hour},
Protocol: agent.ProtocolACP,
ProjectSkillDir: ".agents/skills",
SessionConfig: SessionConfig{
CanRecover: &canRecover,
SessionDirTemplate: "{home}/.gemini",
},
}
}
func (a *Gemini) RemoteAuth() *RemoteAuth {
return &RemoteAuth{
Methods: []RemoteAuthMethod{
{
Type: remoteAuthMethodTypeFiles,
Label: remoteAuthLabelCopyFiles,
SourceFiles: map[string][]string{
"darwin": {".gemini/oauth_creds.json", ".gemini/settings.json", ".gemini/google_accounts.json"},
"linux": {".gemini/oauth_creds.json", ".gemini/settings.json", ".gemini/google_accounts.json"},
},
TargetRelDir: ".gemini",
},
{
Type: "env",
EnvVar: "GEMINI_API_KEY",
},
},
}
}
// Gemini has no dedicated login subcommand — the first run of `gemini` drops
// the user into the TUI's "Sign in with Google" flow. Spawn the bare CLI
// under the PTY so the user can complete that flow inline.
func (a *Gemini) LoginCommand() *LoginCommand {
return &LoginCommand{
Cmd: []string{"gemini"},
Description: "Sign in with your Google account (use the TUI's Sign in option, then quit).",
}
}
func (a *Gemini) InstallScript() string {
return "npm install -g " + geminiPackage
}
func (a *Gemini) BillingType() usage.BillingType { return defaultBillingType() }
func (a *Gemini) PermissionSettings() map[string]PermissionSetting {
return emptyPermSettings
}
// InferenceConfig returns configuration for one-shot inference using ACP.
// Gemini CLI speaks ACP natively via the --acp flag, so there's no separate
// _acp variant.
func (a *Gemini) InferenceConfig() *InferenceConfig {
return &InferenceConfig{
Supported: true,
Command: a.ManagedNPMRuntime().CachedACPCommand(),
}
}