Files
tutti/packages/agent/daemon/runtime/standard_acp_launch_settings_test.go
agent-radar 2ed3415fb7
Publish Claude Code Binaries / Stage and publish binaries (push) Canceled after 0s
Publish Tutti App Runtime / Build darwin-arm64 (push) Canceled after 0s
Publish Tutti App Runtime / Build darwin-amd64 (push) Canceled after 0s
Publish Tutti App Runtime / Build linux-amd64 (push) Canceled after 0s
Publish Tutti App Runtime / Build windows-amd64 (push) Canceled after 0s
Sync Tutti Agent Skills / sync (push) Canceled after 0s
Windows Agent Adapters / Test Windows agent adapters (push) Canceled after 0s
Windows Daemon Adapters / Test Windows daemon adapters (push) Canceled after 0s
Publish Tutti App Runtime / Publish runtime catalog (push) Canceled after 0s
源码快照 @ 2026-09-01 (source-only, 剔除非源码素材)
2026-09-01 20:17:27 +08:00

756 lines
31 KiB
Go

package agentruntime
import (
"context"
"encoding/json"
"errors"
"reflect"
"strings"
"testing"
)
func TestNexightSpawnCommandCarriesModelSettings(t *testing.T) {
t.Parallel()
cases := []struct {
name string
settings *SessionSettings
want []string
}{
{
name: "spark model adds reasoning summary override",
settings: &SessionSettings{Model: "gpt-5.3-codex-spark", ReasoningEffort: "high"},
want: []string{
nexightACPCommand,
"--config", "model=gpt-5.3-codex-spark",
"--config", "model_reasoning_summary=none",
"--config", "model_reasoning_effort=high",
},
},
{
name: "plain model omits reasoning summary override",
settings: &SessionSettings{Model: "gpt-5.1-codex", ReasoningEffort: "medium"},
want: []string{
nexightACPCommand,
"--config", "model=gpt-5.1-codex",
"--config", "model_reasoning_effort=medium",
},
},
}
for _, tc := range cases {
tc := tc
t.Run(tc.name, func(t *testing.T) {
t.Parallel()
transport := newStandardACPTransport("Nexight", "nexight-session-1")
adapter := NewNexightAdapter(transport)
session := standardTestSession(ProviderNexight)
session.Settings = tc.settings
if _, err := adapter.Start(context.Background(), session); err != nil {
t.Fatalf("Start: %v", err)
}
transport.mu.Lock()
specs := append([]ProcessSpec(nil), transport.specs...)
transport.mu.Unlock()
if len(specs) != 1 {
t.Fatalf("specs = %#v, want one process spawn", specs)
}
if !reflect.DeepEqual(specs[0].Command, tc.want) {
t.Fatalf("spawn command = %#v, want %#v", specs[0].Command, tc.want)
}
})
}
}
func TestNexightRequiresNewSessionWhenReasoningSummaryOverrideChanges(t *testing.T) {
t.Parallel()
adapter := NewNexightAdapter(nil)
session := standardTestSession(ProviderNexight)
session.Settings = &SessionSettings{Model: "gpt-5.1-codex"}
sparkModel := "gpt-5.3-codex-spark"
if !adapter.RequiresNewSessionForSettings(session, SessionSettingsPatch{Model: &sparkModel}) {
t.Fatal("switching to a spark-family model must force a new session (spawn-time model_reasoning_summary override)")
}
plainModel := "gpt-5.2-codex"
if adapter.RequiresNewSessionForSettings(session, SessionSettingsPatch{Model: &plainModel}) {
t.Fatal("plain-to-plain model change must not force a new session")
}
if adapter.RequiresNewSessionForSettings(session, SessionSettingsPatch{}) {
t.Fatal("empty patch must not force a new session")
}
}
func TestStandardACPSpawnCommandUnchangedForOtherProviders(t *testing.T) {
t.Parallel()
transport := newStandardACPTransport("Hermes Agent", "hermes-session-1")
adapter := newHermesExtensionTestAdapter(transport)
session := standardTestSession(hermesExtensionTestProvider)
session.Settings = &SessionSettings{Model: "gpt-5.3-codex-spark", ReasoningEffort: "high"}
if _, err := adapter.Start(context.Background(), session); err != nil {
t.Fatalf("Start: %v", err)
}
transport.mu.Lock()
specs := append([]ProcessSpec(nil), transport.specs...)
transport.mu.Unlock()
if len(specs) != 1 || !reflect.DeepEqual(specs[0].Command, []string{"hermes", "acp"}) {
t.Fatalf("spawn command = %#v, want bare hermes command", specs)
}
sparkModel := "gpt-5.3-codex-spark"
if adapter.RequiresNewSessionForSettings(session, SessionSettingsPatch{Model: &sparkModel}) {
t.Fatal("non-nexight providers must not force new sessions for model changes")
}
}
func TestStandardACPLaunchPermissionBuildsExactArgvAndDoesNotSetWorkflowMode(t *testing.T) {
t.Parallel()
tests := []struct {
name string
semantic string
want string
}{
{name: "default", want: "default"},
{name: "ask", semantic: "ask-before-write", want: "default"},
{name: "auto", semantic: "auto", want: "auto"},
{name: "full access", semantic: "full-access", want: "bypassPermissions"},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
transport := newStandardACPTransport("Spawn ACP", "spawn-session-1")
adapterRaw, err := NewStandardACPAdapter(StandardACPAdapterConfig{
Provider: "acp:spawn",
Name: "spawn-acp",
DisplayName: "Spawn ACP",
Command: []string{"grok", "--no-auto-update", "--permission-mode", "${permissionMode}", "agent", "stdio"},
PermissionModes: map[string]string{"ask-before-write": "default", "auto": "auto", "full-access": "bypassPermissions"},
LaunchPermission: &StandardACPLaunchPermissionSetting{
Placeholder: "${permissionMode}",
Values: map[string]string{"ask-before-write": "default", "auto": "auto", "full-access": "bypassPermissions"},
},
}, transport, LegacyHostMetadata())
if err != nil {
t.Fatalf("NewStandardACPAdapter: %v", err)
}
adapter := adapterRaw.(*standardACPAdapter)
session := standardTestSession("acp:spawn")
session.PermissionModeID = tt.semantic
if _, err := adapter.Start(context.Background(), session); err != nil {
t.Fatalf("Start: %v", err)
}
transport.mu.Lock()
specs := append([]ProcessSpec(nil), transport.specs...)
transport.mu.Unlock()
want := []string{"grok", "--no-auto-update", "--permission-mode", tt.want, "agent", "stdio"}
if len(specs) != 1 || !reflect.DeepEqual(specs[0].Command, want) {
t.Fatalf("spawn command = %#v, want %#v", specs, want)
}
if got := transport.conn.lastSetModeParams(); got != nil {
t.Fatalf("session/set_mode params = %#v, want none for spawn-time permission", got)
}
})
}
}
func TestStandardACPLaunchPermissionKeepsPlanAsIndependentWorkflowMode(t *testing.T) {
t.Parallel()
transport := newStandardACPTransport("Spawn ACP", "spawn-plan-session")
transport.conn.modes = map[string]any{
"currentModeId": "default",
"availableModes": []any{
map[string]any{"id": "default", "name": "Default"},
map[string]any{"id": "plan", "name": "Plan"},
},
}
adapterRaw, err := NewStandardACPAdapter(StandardACPAdapterConfig{
Provider: "acp:spawn-plan",
Name: "spawn-plan-acp",
DisplayName: "Spawn Plan ACP",
Command: []string{"grok", "--no-auto-update", "--permission-mode", "${permissionMode}", "agent", "stdio"},
PermissionModes: map[string]string{"ask-before-write": "default", "auto": "auto", "full-access": "bypassPermissions"},
PlanModeRuntimeID: "plan",
PlanModeDisabledRuntimeID: "default",
LaunchPermission: &StandardACPLaunchPermissionSetting{
Placeholder: "${permissionMode}",
Values: map[string]string{"ask-before-write": "default", "auto": "auto", "full-access": "bypassPermissions"},
},
}, transport, LegacyHostMetadata())
if err != nil {
t.Fatalf("NewStandardACPAdapter: %v", err)
}
adapter := adapterRaw.(*standardACPAdapter)
session := standardTestSession("acp:spawn-plan")
session.PermissionModeID = "full-access"
session.Settings = &SessionSettings{PermissionModeID: "full-access"}
if _, err := adapter.Start(context.Background(), session); err != nil {
t.Fatalf("Start: %v", err)
}
if got := transport.conn.lastSetModeParams(); got != nil {
t.Fatalf("startup session/set_mode = %#v, want none", got)
}
for _, enabled := range []bool{true, false} {
session.Settings.PlanMode = enabled
if err := adapter.ValidateSessionSettings(session, SessionSettingsPatch{PlanMode: &enabled}); err != nil {
t.Fatalf("ValidateSessionSettings(%v): %v", enabled, err)
}
if err := adapter.ApplySessionSettings(context.Background(), session, SessionSettingsPatch{PlanMode: &enabled}); err != nil {
t.Fatalf("ApplySessionSettings(%v): %v", enabled, err)
}
want := "default"
if enabled {
want = "plan"
}
if got := transport.conn.lastModeID(); got != want {
t.Fatalf("plan=%v mode = %q, want %q", enabled, got, want)
}
}
changed := "auto"
if !adapter.RequiresNewSessionForSettings(session, SessionSettingsPatch{PermissionModeID: &changed}) {
t.Fatal("spawn-time permission update must require a new session")
}
if err := adapter.ApplySessionSettings(context.Background(), session, SessionSettingsPatch{PermissionModeID: &changed}); !errors.Is(err, ErrSessionSettingsRequireNewSession) {
t.Fatalf("ApplySessionSettings permission error = %v, want ErrSessionSettingsRequireNewSession", err)
}
}
func TestStandardACPLaunchPermissionWithoutPlanWorkflowNeverSendsPermissionAsMode(t *testing.T) {
t.Parallel()
transport := newStandardACPTransport("Spawn ACP", "spawn-without-plan-session")
transport.conn.modes = map[string]any{
"currentModeId": "default",
"availableModes": []any{
map[string]any{"id": "default", "name": "Default"},
map[string]any{"id": "plan", "name": "Plan"},
},
}
adapterRaw, err := NewStandardACPAdapter(StandardACPAdapterConfig{
Provider: "acp:spawn-without-plan",
Name: "spawn-without-plan-acp",
DisplayName: "Spawn Without Plan ACP",
Command: []string{"spawn-acp", "--permission-mode", "${permissionMode}", "agent", "stdio"},
PermissionModes: map[string]string{"ask-before-write": "default", "auto": "auto", "full-access": "bypassPermissions"},
LaunchPermission: &StandardACPLaunchPermissionSetting{
Placeholder: "${permissionMode}",
Values: map[string]string{"ask-before-write": "default", "auto": "auto", "full-access": "bypassPermissions"},
},
}, transport, LegacyHostMetadata())
if err != nil {
t.Fatalf("NewStandardACPAdapter: %v", err)
}
adapter := adapterRaw.(*standardACPAdapter)
session := standardTestSession("acp:spawn-without-plan")
session.PermissionModeID = "ask-before-write"
session.Settings = &SessionSettings{PermissionModeID: "ask-before-write", PlanMode: true}
if _, err := adapter.Start(context.Background(), session); err != nil {
t.Fatalf("Start: %v", err)
}
if got := transport.conn.lastSetModeParams(); got != nil {
t.Fatalf("startup session/set_mode = %#v, want none", got)
}
enabled := true
if err := adapter.ValidateSessionSettings(session, SessionSettingsPatch{PlanMode: &enabled}); err == nil {
t.Fatal("ValidateSessionSettings error = nil, want missing signed Plan mapping")
}
}
func TestStandardACPDeclaredPlanModesDoNotRequireRuntimeModeCatalog(t *testing.T) {
t.Parallel()
transport := newStandardACPTransport("Declared Plan ACP", "declared-plan-session")
adapterRaw, err := NewStandardACPAdapter(StandardACPAdapterConfig{
Provider: "acp:declared-plan",
Name: "declared-plan-acp",
DisplayName: "Declared Plan ACP",
Command: []string{"declared-plan", "agent", "stdio"},
PermissionModes: map[string]string{"ask-before-write": "default"},
PlanModeRuntimeID: "plan",
PlanModeDisabledRuntimeID: "default",
}, transport, LegacyHostMetadata())
if err != nil {
t.Fatalf("NewStandardACPAdapter: %v", err)
}
adapter := adapterRaw.(*standardACPAdapter)
session := standardTestSession("acp:declared-plan")
session.Settings = &SessionSettings{PermissionModeID: "ask-before-write"}
if _, err := adapter.Start(context.Background(), session); err != nil {
t.Fatalf("Start: %v", err)
}
for _, enabled := range []bool{true, false} {
if err := adapter.ValidateSessionSettings(session, SessionSettingsPatch{PlanMode: &enabled}); err != nil {
t.Fatalf("ValidateSessionSettings(%v): %v", enabled, err)
}
session.Settings.PlanMode = enabled
if err := adapter.ApplySessionSettings(context.Background(), session, SessionSettingsPatch{PlanMode: &enabled}); err != nil {
t.Fatalf("ApplySessionSettings(%v): %v", enabled, err)
}
want := "default"
if enabled {
want = "plan"
}
if got := transport.conn.lastModeID(); got != want {
t.Fatalf("plan=%v mode = %q, want %q", enabled, got, want)
}
}
}
func TestStandardACPDeclaredPlanModeSurfacesRuntimeRejection(t *testing.T) {
t.Parallel()
transport := newStandardACPTransport("Rejected Plan ACP", "rejected-plan-session")
adapterRaw, err := NewStandardACPAdapter(StandardACPAdapterConfig{
Provider: "acp:rejected-plan",
Name: "rejected-plan-acp",
DisplayName: "Rejected Plan ACP",
Command: []string{"rejected-plan", "agent", "stdio"},
PermissionModes: map[string]string{"ask-before-write": "default"},
PlanModeRuntimeID: "plan",
PlanModeDisabledRuntimeID: "default",
}, transport, LegacyHostMetadata())
if err != nil {
t.Fatalf("NewStandardACPAdapter: %v", err)
}
adapter := adapterRaw.(*standardACPAdapter)
session := standardTestSession("acp:rejected-plan")
session.Settings = &SessionSettings{PermissionModeID: "ask-before-write"}
if _, err := adapter.Start(context.Background(), session); err != nil {
t.Fatalf("Start: %v", err)
}
transport.conn.setModeError = &acpError{Code: -32602, Message: "unsupported mode"}
enabled := true
session.Settings.PlanMode = enabled
if err := adapter.ApplySessionSettings(context.Background(), session, SessionSettingsPatch{PlanMode: &enabled}); err == nil {
t.Fatal("ApplySessionSettings error = nil, want runtime rejection")
}
}
func TestStandardACPLaunchPermissionPlanRestartsAndLoadsSameSession(t *testing.T) {
t.Parallel()
transport := &multiProcStandardACPTransport{
agentTitle: "Grok Build",
sessionID: "grok-provider-session",
supportsLoadSession: true,
}
adapterRaw, err := NewStandardACPAdapter(StandardACPAdapterConfig{
Provider: "acp:grok-launch-plan",
Name: "grok-launch-plan-acp",
DisplayName: "Grok Build",
Command: []string{"grok", "--permission-mode", "${permissionMode}", "agent", "stdio"},
PermissionModes: map[string]string{"ask-before-write": "default", "auto": "auto", "full-access": "bypassPermissions"},
PlanModeRuntimeID: "plan",
PlanModeDisabledRuntimeID: "default",
PlanModeUsesLaunchPermission: true,
LaunchPermission: &StandardACPLaunchPermissionSetting{
Placeholder: "${permissionMode}",
DefaultSemantic: "ask-before-write",
Values: map[string]string{"ask-before-write": "default", "auto": "auto", "full-access": "bypassPermissions"},
},
}, transport, LegacyHostMetadata())
if err != nil {
t.Fatalf("NewStandardACPAdapter: %v", err)
}
adapter := adapterRaw.(*standardACPAdapter)
session := standardTestSession("acp:grok-launch-plan")
session.PermissionModeID = "auto"
session.Settings = &SessionSettings{PermissionModeID: "auto"}
events, err := adapter.Start(context.Background(), session)
if err != nil {
t.Fatalf("Start: %v", err)
}
session.ProviderSessionID = events[0].ProviderSessionID
for _, enabled := range []bool{true, false} {
if err := adapter.ValidateSessionSettings(session, SessionSettingsPatch{PlanMode: &enabled}); err != nil {
t.Fatalf("ValidateSessionSettings(%v): %v", enabled, err)
}
session.Settings.PlanMode = enabled
if err := adapter.ApplySessionSettings(context.Background(), session, SessionSettingsPatch{PlanMode: &enabled}); err != nil {
t.Fatalf("ApplySessionSettings(%v): %v", enabled, err)
}
wantDecision := ""
if enabled {
wantDecision = "denied"
}
if got := adapter.automaticPermissionDecision(session.AgentSessionID); got != wantDecision {
t.Fatalf("plan=%v automatic permission decision = %q, want %q", enabled, got, wantDecision)
}
}
transport.mu.Lock()
specs := append([]ProcessSpec(nil), transport.specs...)
connections := append([]*standardACPConnection(nil), transport.conns...)
transport.mu.Unlock()
if len(specs) != 3 {
t.Fatalf("process starts = %d, want initial plus two Plan restarts", len(specs))
}
wantModes := []string{"auto", "plan", "auto"}
for index, want := range wantModes {
if got := specs[index].Command[2]; got != want {
t.Fatalf("process %d permission mode = %q, want %q", index, got, want)
}
}
for index := 1; index < len(connections); index++ {
if got := asString(connections[index].lastLoadSessionParams["sessionId"]); got != "grok-provider-session" {
t.Fatalf("restart %d loaded session = %q, want grok-provider-session", index, got)
}
}
spawned, live := transport.snapshot()
if spawned != 3 || len(live) != 1 {
t.Fatalf("spawned/live processes = %d/%d, want 3/1", spawned, len(live))
}
}
func TestStandardACPLaunchPermissionPlanPersistsWithoutSpawnAfterReleaseFailure(t *testing.T) {
t.Parallel()
transport := &multiProcStandardACPTransport{
agentTitle: "Grok Build",
sessionID: "grok-provider-session-deferred-plan",
supportsLoadSession: true,
}
adapterRaw, err := NewStandardACPAdapter(StandardACPAdapterConfig{
Provider: "acp:grok-launch-plan-deferred",
Name: "grok-launch-plan-deferred-acp",
DisplayName: "Grok Build",
Command: []string{"grok", "--permission-mode", "${permissionMode}", "agent", "stdio"},
PermissionModes: map[string]string{"ask-before-write": "default", "auto": "auto", "full-access": "bypassPermissions"},
PlanModeRuntimeID: "plan",
PlanModeDisabledRuntimeID: "default",
PlanModeUsesLaunchPermission: true,
LaunchPermission: &StandardACPLaunchPermissionSetting{
Placeholder: "${permissionMode}",
DefaultSemantic: "ask-before-write",
Values: map[string]string{"ask-before-write": "default", "auto": "auto", "full-access": "bypassPermissions"},
},
}, transport, LegacyHostMetadata())
if err != nil {
t.Fatalf("NewStandardACPAdapter: %v", err)
}
adapter := adapterRaw.(*standardACPAdapter)
session := standardTestSession("acp:grok-launch-plan-deferred")
session.PermissionModeID = "auto"
session.Settings = &SessionSettings{PermissionModeID: "auto"}
events, err := adapter.Start(context.Background(), session)
if err != nil {
t.Fatalf("Start: %v", err)
}
session.ProviderSessionID = events[0].ProviderSessionID
transport.mu.Lock()
firstConnection := transport.conns[0]
transport.mu.Unlock()
firstConnection.mu.Lock()
firstConnection.closeFailures = 1
firstConnection.mu.Unlock()
if err := adapter.ReleaseLiveSession(context.Background(), session); err == nil {
t.Fatal("ReleaseLiveSession error = nil, want injected close failure")
}
enabled := true
session.Settings.PlanMode = enabled
if err := adapter.ApplySessionSettings(context.Background(), session, SessionSettingsPatch{PlanMode: &enabled}); err != nil {
t.Fatalf("ApplySessionSettings: %v", err)
}
spawned, _ := transport.snapshot()
if spawned != 1 {
t.Fatalf("process starts after settings persistence = %d, want 1", spawned)
}
if err := adapter.Resume(context.Background(), session); err != nil {
t.Fatalf("Resume: %v", err)
}
transport.mu.Lock()
specs := append([]ProcessSpec(nil), transport.specs...)
connections := append([]*standardACPConnection(nil), transport.conns...)
transport.mu.Unlock()
if len(specs) != 2 || specs[1].Command[2] != "plan" {
t.Fatalf("resume command = %#v, want persisted Plan launch", specs)
}
if got := asString(connections[1].lastLoadSessionParams["sessionId"]); got != session.ProviderSessionID {
t.Fatalf("resumed provider session = %q, want %q", got, session.ProviderSessionID)
}
}
func TestStandardACPLaunchPermissionRejectsUnknownSessionSemantic(t *testing.T) {
t.Parallel()
transport := newStandardACPTransport("Spawn ACP", "spawn-invalid-session")
adapterRaw, err := NewStandardACPAdapter(StandardACPAdapterConfig{
Provider: "acp:spawn-invalid",
Name: "spawn-invalid-acp",
DisplayName: "Spawn Invalid ACP",
Command: []string{"spawn-acp", "${permissionMode}", "stdio"},
PermissionModes: map[string]string{"ask-before-write": "ask", "auto": "auto", "full-access": "always-approve"},
LaunchPermission: &StandardACPLaunchPermissionSetting{
Placeholder: "${permissionMode}",
Values: map[string]string{"ask-before-write": "ask", "auto": "auto", "full-access": "always-approve"},
},
}, transport, LegacyHostMetadata())
if err != nil {
t.Fatalf("NewStandardACPAdapter: %v", err)
}
session := standardTestSession("acp:spawn-invalid")
session.PermissionModeID = "unknown"
if _, err := adapterRaw.Start(context.Background(), session); err == nil {
t.Fatal("Start unknown permission semantic error = nil")
}
transport.mu.Lock()
defer transport.mu.Unlock()
if len(transport.specs) != 0 {
t.Fatalf("spawn specs = %#v, want fail before process start", transport.specs)
}
}
func TestStandardACPSessionNewAndLoadPassStandardClientMCPConfig(t *testing.T) {
t.Parallel()
newTransport := newStandardACPTransport("MCP ACP", "mcp-new-session")
newAdapterRaw, err := NewStandardACPAdapter(StandardACPAdapterConfig{
Provider: "acp:mcp-new", Name: "mcp-new-acp", DisplayName: "MCP ACP", Command: []string{"mcp-acp", "stdio"},
}, newTransport, LegacyHostMetadata())
if err != nil {
t.Fatalf("NewStandardACPAdapter new: %v", err)
}
newSession := standardTestSession("acp:mcp-new")
newSession.MCPServers = []MCPServerBinding{{Name: "connector", Type: "http", URL: "http://127.0.0.1:1234/mcp/connector",
Headers: map[string]string{"Authorization": "Bearer test-token"}}}
if _, err := newAdapterRaw.Start(context.Background(), newSession); err != nil {
t.Fatalf("Start: %v", err)
}
if servers, ok := newTransport.conn.lastNewSessionParams["mcpServers"].([]any); !ok || len(servers) != 1 {
t.Fatalf("session/new mcpServers = %#v, want connector binding", newTransport.conn.lastNewSessionParams["mcpServers"])
}
loadTransport := newStandardACPTransport("MCP ACP", "mcp-load-session")
loadTransport.conn.supportsLoadSession = true
loadAdapterRaw, err := NewStandardACPAdapter(StandardACPAdapterConfig{
Provider: "acp:mcp-load", Name: "mcp-load-acp", DisplayName: "MCP ACP", Command: []string{"mcp-acp", "stdio"},
}, loadTransport, LegacyHostMetadata())
if err != nil {
t.Fatalf("NewStandardACPAdapter load: %v", err)
}
loadSession := standardTestSession("acp:mcp-load")
loadSession.ProviderSessionID = "mcp-load-session"
loadSession.MCPServers = cloneMCPServerBindings(newSession.MCPServers)
if err := loadAdapterRaw.Resume(context.Background(), loadSession); err != nil {
t.Fatalf("Resume: %v", err)
}
if servers, ok := loadTransport.conn.lastLoadSessionParams["mcpServers"].([]any); !ok || len(servers) != 1 {
t.Fatalf("session/load mcpServers = %#v, want connector binding", loadTransport.conn.lastLoadSessionParams["mcpServers"])
}
}
func TestStandardACPSessionFallsBackWithoutHTTPMCPWhenAgentDoesNotAdvertiseCapability(t *testing.T) {
t.Parallel()
newTransport := newStandardACPTransport("MCP Unsupported ACP", "mcp-unsupported-session")
newTransport.conn.supportsHTTPMCP = false
newAdapter, err := NewStandardACPAdapter(StandardACPAdapterConfig{
Provider: "acp:mcp-unsupported", Name: "mcp-unsupported-acp", DisplayName: "MCP Unsupported ACP", Command: []string{"mcp-acp", "stdio"},
}, newTransport, LegacyHostMetadata())
if err != nil {
t.Fatalf("NewStandardACPAdapter: %v", err)
}
session := standardTestSession("acp:mcp-unsupported")
session.MCPServers = []MCPServerBinding{{Name: "connector", Type: "http", URL: "http://127.0.0.1:1234/mcp/connector"}}
if _, err := newAdapter.Start(context.Background(), session); err != nil {
t.Fatalf("Start error = %v", err)
}
if servers, ok := newTransport.conn.lastNewSessionParams["mcpServers"].([]any); ok && len(servers) != 0 {
t.Fatalf("session/new mcpServers = %#v, want empty fallback", newTransport.conn.lastNewSessionParams["mcpServers"])
}
loadTransport := newStandardACPTransport("MCP Unsupported ACP", "mcp-unsupported-load-session")
loadTransport.conn.supportsHTTPMCP = false
loadTransport.conn.supportsLoadSession = true
loadAdapter, err := NewStandardACPAdapter(StandardACPAdapterConfig{
Provider: "acp:mcp-unsupported", Name: "mcp-unsupported-acp", DisplayName: "MCP Unsupported ACP", Command: []string{"mcp-acp", "stdio"},
}, loadTransport, LegacyHostMetadata())
if err != nil {
t.Fatalf("NewStandardACPAdapter load: %v", err)
}
loadSession := standardTestSession("acp:mcp-unsupported")
loadSession.ProviderSessionID = "mcp-unsupported-load-session"
loadSession.MCPServers = cloneMCPServerBindings(session.MCPServers)
if err := loadAdapter.Resume(context.Background(), loadSession); err != nil {
t.Fatalf("Resume error = %v", err)
}
if servers, ok := loadTransport.conn.lastLoadSessionParams["mcpServers"].([]any); ok && len(servers) != 0 {
t.Fatalf("session/load mcpServers = %#v, want empty fallback", loadTransport.conn.lastLoadSessionParams["mcpServers"])
}
}
func TestStandardACPConnectorCapabilitiesRequireExplicitHTTPDeclaration(t *testing.T) {
t.Parallel()
for _, test := range []struct {
name string
supported bool
}{
{name: "declared", supported: true},
{name: "missing", supported: false},
} {
t.Run(test.name, func(t *testing.T) {
t.Parallel()
transport := newStandardACPTransport("Capability ACP", "capability-session")
transport.conn.supportsHTTPMCP = test.supported
adapter, err := NewStandardACPAdapter(StandardACPAdapterConfig{
Provider: "acp:capability", Name: "capability-acp", DisplayName: "Capability ACP", Command: []string{"capability-acp", "stdio"},
}, transport, LegacyHostMetadata())
if err != nil {
t.Fatalf("NewStandardACPAdapter: %v", err)
}
capabilities, err := adapter.(ConnectorCapabilityAdapter).ConnectorCapabilities(t.Context(), standardTestSession("acp:capability"))
if err != nil {
t.Fatalf("ConnectorCapabilities: %v", err)
}
if capabilities.HTTPMCP != test.supported {
t.Fatalf("HTTPMCP = %v, want %v", capabilities.HTTPMCP, test.supported)
}
if transport.conn.lastNewSessionParams != nil {
t.Fatalf("capability probe created session: %#v", transport.conn.lastNewSessionParams)
}
})
}
}
func TestStandardACPSetModelCarriesAdvertisedPerModelReasoningMetadata(t *testing.T) {
t.Parallel()
transport := newStandardACPTransport("Model ACP", "model-session")
transport.conn.models = map[string]any{
"currentModelId": "reasoning-model",
"availableModels": []any{
map[string]any{
"modelId": "reasoning-model", "name": "Reasoning Model",
"supportsReasoningEffort": true, "reasoningEffort": "deep",
"reasoningEfforts": []any{
map[string]any{"value": "brief", "label": "Brief"},
map[string]any{"value": "deep", "label": "Deep", "default": true},
},
},
map[string]any{"modelId": "plain-model", "name": "Plain Model", "supportsReasoningEffort": false, "reasoningEfforts": []any{}},
},
}
adapterRaw, err := NewStandardACPAdapter(StandardACPAdapterConfig{
Provider: "acp:model-meta",
Name: "model-meta-acp",
DisplayName: "Model ACP",
Command: []string{"model-acp", "stdio"},
SetModelReasoningEffortMeta: true,
}, transport, LegacyHostMetadata())
if err != nil {
t.Fatalf("NewStandardACPAdapter: %v", err)
}
adapter := adapterRaw.(*standardACPAdapter)
session := standardTestSession("acp:model-meta")
if _, err := adapter.Start(context.Background(), session); err != nil {
t.Fatalf("Start: %v", err)
}
session.Settings = &SessionSettings{Model: "reasoning-model", ReasoningEffort: "brief"}
brief := "brief"
if err := adapter.ValidateSessionSettings(session, SessionSettingsPatch{ReasoningEffort: &brief}); err != nil {
t.Fatalf("ValidateSessionSettings reasoning: %v", err)
}
if err := adapter.ApplySessionSettings(context.Background(), session, SessionSettingsPatch{ReasoningEffort: &brief}); err != nil {
t.Fatalf("ApplySessionSettings reasoning: %v", err)
}
calls := transport.conn.setModelCalls()
if len(calls) != 1 || calls[0]["modelId"] != "reasoning-model" {
t.Fatalf("set_model calls = %#v, want reasoning-model", calls)
}
if meta := payloadObject(calls[0]["_meta"]); meta["reasoningEffort"] != "brief" {
t.Fatalf("set_model _meta = %#v, want brief", meta)
}
plain := "plain-model"
session.Settings = &SessionSettings{Model: plain, ReasoningEffort: "brief"}
if err := adapter.ValidateSessionSettings(session, SessionSettingsPatch{Model: &plain}); err != nil {
t.Fatalf("ValidateSessionSettings plain model: %v", err)
}
if err := adapter.ApplySessionSettings(context.Background(), session, SessionSettingsPatch{Model: &plain}); err != nil {
t.Fatalf("ApplySessionSettings plain model: %v", err)
}
calls = transport.conn.setModelCalls()
if len(calls) != 2 || calls[1]["modelId"] != plain {
t.Fatalf("set_model calls = %#v, want plain-model second", calls)
}
if _, exists := calls[1]["_meta"]; exists {
t.Fatalf("plain model set_model = %#v, want no reasoning metadata", calls[1])
}
unsupported := "brief"
if err := adapter.ValidateSessionSettings(session, SessionSettingsPatch{ReasoningEffort: &unsupported}); err == nil {
t.Fatal("unsupported model reasoning validation error = nil")
}
if calls := transport.conn.setConfigOptionCalls(); len(calls) != 0 {
t.Fatalf("config option calls = %#v, want standard session/set_model only", calls)
}
}
func TestStandardACPStartupFailsWhenRequestedModelIsRejected(t *testing.T) {
t.Parallel()
transport := newStandardACPTransport("Model ACP", "model-rejected-session")
transport.conn.models = map[string]any{
"currentModelId": "provider-default",
"availableModels": []any{
map[string]any{"modelId": "requested-model", "name": "Requested Model"},
},
}
transport.conn.setModelError = &acpError{
Code: -32603,
Message: "Internal error",
Data: json.RawMessage(`{"details":"requested model is unavailable"}`),
}
adapterRaw, err := NewStandardACPAdapter(StandardACPAdapterConfig{
Provider: "acp:model-rejected", Name: "model-rejected-acp", DisplayName: "Model ACP", Command: []string{"model-acp", "stdio"},
}, transport, LegacyHostMetadata())
if err != nil {
t.Fatalf("NewStandardACPAdapter: %v", err)
}
session := standardTestSession("acp:model-rejected")
session.Settings = &SessionSettings{Model: "requested-model"}
if _, err := adapterRaw.Start(context.Background(), session); err == nil {
t.Fatal("Start error = nil, want rejected requested model to abort startup")
} else if !strings.Contains(err.Error(), "model configuration failed") {
t.Fatalf("Start error = %v, want model configuration failure", err)
}
}
func TestStandardACPStartupDoesNotReapplyCurrentRequestedModel(t *testing.T) {
t.Parallel()
transport := newStandardACPTransport("Model ACP", "model-current-session")
transport.conn.models = map[string]any{
"currentModelId": "requested-model",
"availableModels": []any{
map[string]any{"modelId": "requested-model", "name": "Requested Model"},
},
}
transport.conn.setModelError = &acpError{
Code: -32603, Message: "duplicate model application rejected",
}
adapterRaw, err := NewStandardACPAdapter(StandardACPAdapterConfig{
Provider: "acp:model-current", Name: "model-current-acp", DisplayName: "Model ACP", Command: []string{"model-acp", "stdio"},
}, transport, LegacyHostMetadata())
if err != nil {
t.Fatalf("NewStandardACPAdapter: %v", err)
}
session := standardTestSession("acp:model-current")
session.Settings = &SessionSettings{Model: "requested-model"}
if _, err := adapterRaw.Start(context.Background(), session); err != nil {
t.Fatalf("Start: %v", err)
}
if calls := transport.conn.setModelCalls(); len(calls) != 0 {
t.Fatalf("set_model calls = %#v, want current model left unchanged", calls)
}
}