7b796b1eaa
All four templates were confirmed against the shipped CLI, not inferred
from the agent name:
- pi-acp -> {home}/.pi (pi-acp@0.0.32 package source)
- qoder-acp -> {home}/.qoder (qodercli 1.1.7, --config-dir proof)
- kiro-acp -> {home}/.kiro (Kiro CLI 2.15.0, ACP session/new)
- trae-acp -> {home}/.cache/trae-cli (Trae CLI 0.120.48, ACP session/new)
Kiro and Trae deliberately break the "{home}/.<agent>" shape: Kiro keeps
its login token in ~/.local/share/kiro-cli/data.sqlite3 while sessions
live in ~/.kiro, and Trae keeps config in ~/.trae but session state in
the cache dir with auth in the OS keyring. Each Runtime() records the
evidence and the residual gaps.
Extends the shared ACP agent spec table with sessionDirTemplate so every
agent's session root is pinned, including the ones already set.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
119 lines
3.3 KiB
Go
119 lines
3.3 KiB
Go
//nolint:dupl // Native-binary ACP agents (Cursor, Kimi, Kiro, Qoder, Trae) follow the same minimal scaffold; differences are the binary name and subcommand.
|
|
package agents
|
|
|
|
import (
|
|
"context"
|
|
_ "embed"
|
|
"time"
|
|
|
|
"github.com/kandev/kandev/internal/agent/usage"
|
|
"github.com/kandev/kandev/pkg/agent"
|
|
)
|
|
|
|
//go:embed logos/qoder_acp_light.svg
|
|
var qoderACPLogoLight []byte
|
|
|
|
//go:embed logos/qoder_acp_dark.svg
|
|
var qoderACPLogoDark []byte
|
|
|
|
const qoderACPBin = "qodercli"
|
|
|
|
var (
|
|
_ Agent = (*QoderACP)(nil)
|
|
_ PassthroughAgent = (*QoderACP)(nil)
|
|
_ InferenceAgent = (*QoderACP)(nil)
|
|
)
|
|
|
|
// QoderACP implements Agent for the Qoder CLI using ACP.
|
|
type QoderACP struct {
|
|
StandardPassthrough
|
|
}
|
|
|
|
func NewQoderACP() *QoderACP {
|
|
return &QoderACP{
|
|
StandardPassthrough: StandardPassthrough{
|
|
PermSettings: emptyPermSettings,
|
|
Cfg: PassthroughConfig{
|
|
Supported: true,
|
|
Label: "CLI Passthrough",
|
|
Description: "Show terminal directly instead of chat interface",
|
|
PassthroughCmd: NewCommand(qoderACPBin),
|
|
ModelFlag: NewParam("--model", "{model}"),
|
|
IdleTimeout: 3 * time.Second,
|
|
BufferMaxBytes: DefaultBufferMaxBytes,
|
|
},
|
|
},
|
|
}
|
|
}
|
|
|
|
func (a *QoderACP) ID() string { return "qoder-acp" }
|
|
func (a *QoderACP) Name() string { return "Qoder ACP Agent" }
|
|
func (a *QoderACP) DisplayName() string { return "Qoder" }
|
|
func (a *QoderACP) Description() string {
|
|
return "Qoder coding agent using the ACP protocol via qodercli --acp."
|
|
}
|
|
func (a *QoderACP) Enabled() bool { return true }
|
|
func (a *QoderACP) DisplayOrder() int { return 16 }
|
|
|
|
func (a *QoderACP) Logo(v LogoVariant) []byte {
|
|
if v == LogoDark {
|
|
return qoderACPLogoDark
|
|
}
|
|
return qoderACPLogoLight
|
|
}
|
|
|
|
func (a *QoderACP) IsInstalled(ctx context.Context) (*DiscoveryResult, error) {
|
|
result, err := Detect(ctx, WithCommand(qoderACPBin))
|
|
if err != nil {
|
|
return result, err
|
|
}
|
|
result.SupportsMCP = true
|
|
result.Capabilities = DiscoveryCapabilities{
|
|
SupportsSessionResume: true,
|
|
}
|
|
return result, nil
|
|
}
|
|
|
|
func (a *QoderACP) BuildCommand(opts CommandOptions) Command {
|
|
return Cmd(qoderACPBin, "--acp").Build()
|
|
}
|
|
|
|
func (a *QoderACP) Runtime() *RuntimeConfig {
|
|
canRecover := true
|
|
return &RuntimeConfig{
|
|
Cmd: Cmd(qoderACPBin, "--acp").Build(),
|
|
WorkingDir: "{workspace}",
|
|
Env: map[string]string{},
|
|
ResourceLimits: ResourceLimits{MemoryMB: 4096, CPUCores: 2.0, Timeout: time.Hour},
|
|
Protocol: agent.ProtocolACP,
|
|
SessionConfig: SessionConfig{
|
|
// Verified against qodercli 1.1.7: every user-level artifact
|
|
// (settings.json, .auth/, logs/sessions/<project>) is written
|
|
// under ~/.qoder, and the --config-dir flag relocates that single
|
|
// root wholesale — running with it leaves nothing else in $HOME.
|
|
SessionDirTemplate: "{home}/.qoder",
|
|
NativeSessionResume: true,
|
|
CanRecover: &canRecover,
|
|
},
|
|
}
|
|
}
|
|
|
|
func (a *QoderACP) RemoteAuth() *RemoteAuth { return nil }
|
|
|
|
func (a *QoderACP) InstallScript() string {
|
|
return "Install Qoder CLI from https://qoder.com"
|
|
}
|
|
|
|
func (a *QoderACP) PermissionSettings() map[string]PermissionSetting {
|
|
return emptyPermSettings
|
|
}
|
|
|
|
func (a *QoderACP) InferenceConfig() *InferenceConfig {
|
|
return &InferenceConfig{
|
|
Supported: true,
|
|
Command: NewCommand(qoderACPBin, "--acp"),
|
|
}
|
|
}
|
|
|
|
func (a *QoderACP) BillingType() usage.BillingType { return defaultBillingType() }
|