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
541 lines
19 KiB
Go
541 lines
19 KiB
Go
package agentruntime
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/tutti-os/tutti/packages/agent/daemon/providerregistry"
|
|
)
|
|
|
|
func newOpenCodeTestAdapter(transport ProcessTransport) *standardACPAdapter {
|
|
descriptor, ok := providerregistry.Find(ProviderOpenCode)
|
|
if !ok {
|
|
panic("opencode provider descriptor is missing")
|
|
}
|
|
return newOpenCodeAdapterFromProviderDescriptor(
|
|
descriptor,
|
|
transport,
|
|
LegacyHostMetadata(),
|
|
nil,
|
|
)
|
|
}
|
|
|
|
func TestOpenCodeAdapterUsesOfficialACPCommand(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
adapter := newOpenCodeTestAdapter(nil)
|
|
if adapter.config.provider != ProviderOpenCode {
|
|
t.Fatalf("provider = %q, want %q", adapter.config.provider, ProviderOpenCode)
|
|
}
|
|
if len(adapter.config.command) != 2 || adapter.config.command[0] != "opencode" || adapter.config.command[1] != "acp" {
|
|
t.Fatalf("command = %#v, want opencode acp", adapter.config.command)
|
|
}
|
|
if adapter.config.planModeRuntimeID != "plan" || adapter.config.planModeDisabledRuntimeID != "build" {
|
|
t.Fatalf("plan mode ids = %q/%q, want plan/build", adapter.config.planModeRuntimeID, adapter.config.planModeDisabledRuntimeID)
|
|
}
|
|
for _, permissionModeID := range []string{"read-only", "ask", "full-access"} {
|
|
if got := adapter.config.permissionModeID(permissionModeID); got != "" {
|
|
t.Fatalf("permission mode %q mapped to ACP mode %q", permissionModeID, got)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestOpenCodeAdapterRejectsSparkNoneBeforePrompt(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
transport := newStandardACPTransport("OpenCode", "opencode-spark-session")
|
|
transport.conn.configOptions = []map[string]any{
|
|
{
|
|
"id": "model",
|
|
"options": []any{
|
|
map[string]any{"name": "GPT-5.3 Codex Spark", "value": openCodeSparkModelID},
|
|
},
|
|
},
|
|
{
|
|
"id": "effort",
|
|
"currentValue": "none",
|
|
"options": []any{
|
|
map[string]any{"name": "Off", "value": "none"},
|
|
map[string]any{"name": "Low", "value": "low"},
|
|
},
|
|
},
|
|
}
|
|
adapter := newOpenCodeTestAdapter(transport)
|
|
session := standardTestSession(ProviderOpenCode)
|
|
session.Settings = &SessionSettings{Model: openCodeSparkModelID, ReasoningEffort: "none"}
|
|
|
|
if _, err := adapter.Start(context.Background(), session); err == nil ||
|
|
!strings.Contains(err.Error(), `does not support reasoning effort "none"`) {
|
|
t.Fatalf("Start error = %v, want actionable Spark capability error", err)
|
|
}
|
|
if calls := transport.conn.setConfigOptionCalls(); len(calls) != 0 {
|
|
t.Fatalf("startup config calls = %#v, want none after preflight rejection", calls)
|
|
}
|
|
if transport.conn.promptCallCount != 0 {
|
|
t.Fatalf("prompt call count = %d, want no prompt after preflight rejection", transport.conn.promptCallCount)
|
|
}
|
|
}
|
|
|
|
func TestOpenCodeACPEnvInjectsModelConfigContent(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
session := standardTestSession(ProviderOpenCode)
|
|
session.Settings = &SessionSettings{Model: "anthropic/claude-sonnet-4-5"}
|
|
|
|
adapter := newOpenCodeTestAdapter(nil)
|
|
env, err := adapter.config.finalizeEnv(adapter.config.env(session), session)
|
|
if err != nil {
|
|
t.Fatalf("finalize OpenCode env: %v", err)
|
|
}
|
|
found := false
|
|
for _, item := range env {
|
|
if strings.HasPrefix(item, "OPENCODE_CONFIG_CONTENT=") {
|
|
found = true
|
|
var config map[string]any
|
|
if err := json.Unmarshal([]byte(strings.TrimPrefix(item, "OPENCODE_CONFIG_CONTENT=")), &config); err != nil {
|
|
t.Fatalf("decode OPENCODE_CONFIG_CONTENT: %v", err)
|
|
}
|
|
if config["model"] != "anthropic/claude-sonnet-4-5" {
|
|
t.Fatalf("model config = %#v", config["model"])
|
|
}
|
|
permission, _ := config["permission"].(map[string]any)
|
|
if permission["*"] != "ask" || permission["glob"] != "allow" {
|
|
t.Fatalf("permission config = %#v", permission)
|
|
}
|
|
read, _ := permission["read"].(map[string]any)
|
|
if read["*.env"] != "deny" || read["*.env.example"] != "allow" {
|
|
t.Fatalf("read permission config = %#v", read)
|
|
}
|
|
agents, _ := config["agent"].(map[string]any)
|
|
plan, _ := agents["plan"].(map[string]any)
|
|
planPermission, _ := plan["permission"].(map[string]any)
|
|
if planPermission["edit"] != "deny" {
|
|
t.Fatalf("plan permission config = %#v", planPermission)
|
|
}
|
|
}
|
|
}
|
|
if !found {
|
|
t.Fatalf("env = %#v, want OPENCODE_CONFIG_CONTENT", env)
|
|
}
|
|
}
|
|
|
|
func TestOpenCodeACPEnvMergesUserConfigBeforeApplyingAuthoritativePermissions(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
descriptor, ok := providerregistry.Find(ProviderOpenCode)
|
|
if !ok {
|
|
t.Fatal("opencode provider descriptor is missing")
|
|
}
|
|
session := standardTestSession(ProviderOpenCode)
|
|
session.Settings = &SessionSettings{Model: "openai/gpt-5"}
|
|
base := `{"theme":"system","model":"user/model","permission":{"bash":"allow"},"agent":{"plan":{"temperature":0.2,"permission":{"bash":"ask"}}}}`
|
|
env, err := openCodeFinalEnv(
|
|
descriptor.Runtime.StandardACP.SettingsEnvironment,
|
|
session,
|
|
`{"theme":"inherited"}`,
|
|
[]string{"KEEP=1", `OPENCODE_PERMISSION={"bash":"allow"}`, "OPENCODE_CONFIG_CONTENT=" + base},
|
|
)
|
|
if err != nil {
|
|
t.Fatalf("openCodeFinalEnv: %v", err)
|
|
}
|
|
if len(env) != 3 || env[0] != "KEEP=1" || env[1] != "OPENCODE_PERMISSION={}" || !strings.HasPrefix(env[2], "OPENCODE_CONFIG_CONTENT=") {
|
|
t.Fatalf("env = %#v, want neutral permission override and one final OpenCode config entry", env)
|
|
}
|
|
var config map[string]any
|
|
if err := json.Unmarshal([]byte(strings.TrimPrefix(env[2], "OPENCODE_CONFIG_CONTENT=")), &config); err != nil {
|
|
t.Fatalf("decode final OpenCode config: %v", err)
|
|
}
|
|
if config["theme"] != "system" || config["model"] != "openai/gpt-5" {
|
|
t.Fatalf("preserved/generated config = %#v", config)
|
|
}
|
|
permission, _ := config["permission"].(map[string]any)
|
|
if permission["*"] != "ask" || permission["bash"] == "allow" {
|
|
t.Fatalf("authoritative permission config = %#v", permission)
|
|
}
|
|
agents, _ := config["agent"].(map[string]any)
|
|
plan, _ := agents["plan"].(map[string]any)
|
|
planPermission, _ := plan["permission"].(map[string]any)
|
|
if plan["temperature"] != 0.2 || planPermission["bash"] != "ask" || planPermission["edit"] != "deny" {
|
|
t.Fatalf("merged plan config = %#v", plan)
|
|
}
|
|
}
|
|
|
|
func TestOpenCodeACPEnvRejectsInvalidUserConfig(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
descriptor, ok := providerregistry.Find(ProviderOpenCode)
|
|
if !ok {
|
|
t.Fatal("opencode provider descriptor is missing")
|
|
}
|
|
_, err := openCodeFinalEnv(
|
|
descriptor.Runtime.StandardACP.SettingsEnvironment,
|
|
standardTestSession(ProviderOpenCode),
|
|
"",
|
|
[]string{"OPENCODE_CONFIG_CONTENT={invalid"},
|
|
)
|
|
if err == nil || !strings.Contains(err.Error(), "decode OPENCODE_CONFIG_CONTENT") {
|
|
t.Fatalf("invalid config error = %v", err)
|
|
}
|
|
}
|
|
|
|
func TestOpenCodePermissionTiersResolveACPRequestsIndependentlyFromPlanMode(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
adapter := newOpenCodeTestAdapter(nil)
|
|
if got := adapter.config.automaticPermissionDecision("read-only"); got != "denied" {
|
|
t.Fatalf("read-only decision = %q, want denied", got)
|
|
}
|
|
if got := adapter.config.automaticPermissionDecision("ask"); got != "" {
|
|
t.Fatalf("ask decision = %q, want prompt", got)
|
|
}
|
|
if got := adapter.config.automaticPermissionDecision("full-access"); got != "approved" {
|
|
t.Fatalf("full-access decision = %q, want approved", got)
|
|
}
|
|
if got := adapter.effectiveModeID(Session{PermissionModeID: "full-access", Settings: &SessionSettings{PlanMode: false}}); got != "build" {
|
|
t.Fatalf("build workflow mode = %q, want build", got)
|
|
}
|
|
if got := adapter.effectiveModeID(Session{PermissionModeID: "read-only", Settings: &SessionSettings{PlanMode: true}}); got != "plan" {
|
|
t.Fatalf("plan workflow mode = %q, want plan", got)
|
|
}
|
|
}
|
|
|
|
func TestOpenCodeAskPermissionOptionsExcludeIrrevocableAlwaysAllow(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
options := openCodePermissionOptions([]map[string]any{
|
|
{"optionId": "once", "kind": "allow_once"},
|
|
{"optionId": "always", "kind": "allow_always"},
|
|
{"optionId": "reject", "kind": "reject_once"},
|
|
})
|
|
if len(options) != 2 || options[0]["optionId"] != "once" || options[1]["optionId"] != "reject" {
|
|
t.Fatalf("permission options = %#v, want once/reject", options)
|
|
}
|
|
}
|
|
|
|
func TestOpenCodeFullAccessSelectsOneShotApproval(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
raw := json.RawMessage(`{"options":[{"optionId":"always","kind":"allow_always"},{"optionId":"once","kind":"allow_once"}]}`)
|
|
optionID, ok := acpPermissionRequestDecisionOptionID(raw, "approved", openCodePermissionOptions)
|
|
if !ok || optionID != "once" {
|
|
t.Fatalf("automatic approval = %q/%t, want once/true", optionID, ok)
|
|
}
|
|
}
|
|
|
|
func TestOpenCodeAutomaticPermissionTiersResolveWithoutPrompt(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
for _, testCase := range []struct {
|
|
name string
|
|
tier string
|
|
optionID string
|
|
}{
|
|
{name: "read-only denies", tier: "read-only", optionID: "reject"},
|
|
{name: "full-access approves", tier: "full-access", optionID: "allow"},
|
|
} {
|
|
t.Run(testCase.name, func(t *testing.T) {
|
|
transport := newStandardACPTransport("OpenCode", "opencode-session-automatic-permission")
|
|
transport.conn.promptPermission = true
|
|
adapter := newOpenCodeTestAdapter(transport)
|
|
session := standardTestSession(ProviderOpenCode)
|
|
session.PermissionModeID = testCase.tier
|
|
if _, err := adapter.Start(context.Background(), session); err != nil {
|
|
t.Fatalf("Start: %v", err)
|
|
}
|
|
session.ProviderSessionID = "opencode-session-automatic-permission"
|
|
|
|
execDone := make(chan error, 1)
|
|
go func() {
|
|
_, err := adapter.Exec(context.Background(), session, textPrompt("run the build"), "", "turn-1", nil, nil)
|
|
execDone <- err
|
|
}()
|
|
select {
|
|
case err := <-execDone:
|
|
if err != nil {
|
|
t.Fatalf("Exec: %v", err)
|
|
}
|
|
case <-time.After(2 * time.Second):
|
|
t.Fatalf("Exec did not finish for %s", testCase.tier)
|
|
}
|
|
if got := transport.conn.permissionOptionID(); got != testCase.optionID {
|
|
t.Fatalf("permission option id = %q, want %q", got, testCase.optionID)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestOpenCodeDoesNotRequireNewSessionForModelSettings(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
adapter := newOpenCodeTestAdapter(nil)
|
|
model := "openai/gpt-5"
|
|
if adapter.RequiresNewSessionForSettings(Session{}, SessionSettingsPatch{Model: &model}) {
|
|
t.Fatal("model patch required a new session")
|
|
}
|
|
if adapter.RequiresNewSessionForSettings(Session{}, SessionSettingsPatch{}) {
|
|
t.Fatal("empty patch required a new session")
|
|
}
|
|
}
|
|
|
|
func TestOpenCodeAdapterStartAppliesPlanMode(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
transport := newStandardACPTransport("OpenCode", "opencode-session-plan")
|
|
adapter := newOpenCodeTestAdapter(transport)
|
|
session := standardTestSession(ProviderOpenCode)
|
|
session.Settings = &SessionSettings{PlanMode: true}
|
|
|
|
if _, err := adapter.Start(context.Background(), session); err != nil {
|
|
t.Fatalf("Start: %v", err)
|
|
}
|
|
if transport.conn.lastModeID() != "plan" {
|
|
t.Fatalf("mode id = %q, want plan", transport.conn.lastModeID())
|
|
}
|
|
}
|
|
|
|
func TestOpenCodeAdapterApplySessionSettingsTogglesPlanMode(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
transport := newStandardACPTransport("OpenCode", "opencode-session-plan-toggle")
|
|
adapter := newOpenCodeTestAdapter(transport)
|
|
session := standardTestSession(ProviderOpenCode)
|
|
if _, err := adapter.Start(context.Background(), session); err != nil {
|
|
t.Fatalf("Start: %v", err)
|
|
}
|
|
if transport.conn.lastModeID() != "build" {
|
|
t.Fatalf("initial mode id = %q, want build", transport.conn.lastModeID())
|
|
}
|
|
|
|
planMode := true
|
|
session.ProviderSessionID = "opencode-session-plan-toggle"
|
|
session.Settings = &SessionSettings{PlanMode: planMode}
|
|
if err := adapter.ApplySessionSettings(context.Background(), session, SessionSettingsPatch{
|
|
PlanMode: &planMode,
|
|
}); err != nil {
|
|
t.Fatalf("ApplySessionSettings plan on: %v", err)
|
|
}
|
|
if transport.conn.lastModeID() != "plan" {
|
|
t.Fatalf("mode id = %q, want plan", transport.conn.lastModeID())
|
|
}
|
|
|
|
planMode = false
|
|
session.Settings.PlanMode = planMode
|
|
if err := adapter.ApplySessionSettings(context.Background(), session, SessionSettingsPatch{
|
|
PlanMode: &planMode,
|
|
}); err != nil {
|
|
t.Fatalf("ApplySessionSettings plan off: %v", err)
|
|
}
|
|
if transport.conn.lastModeID() != "build" {
|
|
t.Fatalf("mode id = %q, want build", transport.conn.lastModeID())
|
|
}
|
|
}
|
|
|
|
func TestOpenCodePermissionChangeDoesNotChangeACPWorkflowMode(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
transport := newStandardACPTransport("OpenCode", "opencode-session-permission")
|
|
adapter := newOpenCodeTestAdapter(transport)
|
|
session := standardTestSession(ProviderOpenCode)
|
|
session.PermissionModeID = "ask"
|
|
if _, err := adapter.Start(context.Background(), session); err != nil {
|
|
t.Fatalf("Start: %v", err)
|
|
}
|
|
if transport.conn.lastModeID() != "build" {
|
|
t.Fatalf("initial mode id = %q, want build", transport.conn.lastModeID())
|
|
}
|
|
|
|
transport.conn.setModeError = &acpError{Code: -32000, Message: "session/set_mode must not be called"}
|
|
session.ProviderSessionID = "opencode-session-permission"
|
|
session.PermissionModeID = "full-access"
|
|
if err := adapter.ApplyPermissionMode(context.Background(), session); err != nil {
|
|
t.Fatalf("ApplyPermissionMode called ACP session/set_mode: %v", err)
|
|
}
|
|
if got := adapter.automaticPermissionDecision(session.AgentSessionID); got != "approved" {
|
|
t.Fatalf("live permission decision = %q, want approved", got)
|
|
}
|
|
if transport.conn.lastModeID() != "build" {
|
|
t.Fatalf("workflow mode changed to %q, want build", transport.conn.lastModeID())
|
|
}
|
|
}
|
|
|
|
func TestOpenCodeApplySessionSettingsSendsLiveModelAndEffortConfigOptions(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
transport := newStandardACPTransport("OpenCode", "opencode-session-1")
|
|
transport.conn.configOptions = []map[string]any{
|
|
{
|
|
"id": "model",
|
|
"options": []any{
|
|
map[string]any{"name": "GPT-5.3 Codex Spark", "value": "openai/gpt-5.3-codex-spark"},
|
|
},
|
|
},
|
|
{
|
|
"id": "effort",
|
|
"options": []any{
|
|
map[string]any{"name": "High", "value": "high"},
|
|
},
|
|
},
|
|
}
|
|
adapter := newOpenCodeTestAdapter(transport)
|
|
session := standardTestSession(ProviderOpenCode)
|
|
|
|
if _, err := adapter.Start(context.Background(), session); err != nil {
|
|
t.Fatalf("Start: %v", err)
|
|
}
|
|
|
|
session.Settings = &SessionSettings{
|
|
Model: "openai/gpt-5.3-codex-spark",
|
|
ReasoningEffort: "high",
|
|
}
|
|
if err := adapter.ApplySessionSettings(context.Background(), session, SessionSettingsPatch{
|
|
Model: stringPtr("openai/gpt-5.3-codex-spark"),
|
|
ReasoningEffort: stringPtr("high"),
|
|
}); err != nil {
|
|
t.Fatalf("ApplySessionSettings: %v", err)
|
|
}
|
|
|
|
calls := transport.conn.setConfigOptionCalls()
|
|
if len(calls) != 2 {
|
|
t.Fatalf("config option calls = %#v, want model + effort", calls)
|
|
}
|
|
if got, _ := calls[0]["configId"].(string); got != "model" {
|
|
t.Fatalf("first config id = %q, want model", got)
|
|
}
|
|
if got, _ := calls[0]["value"].(string); got != "openai/gpt-5.3-codex-spark" {
|
|
t.Fatalf("first config value = %q, want openai/gpt-5.3-codex-spark", got)
|
|
}
|
|
if got, _ := calls[1]["configId"].(string); got != "effort" {
|
|
t.Fatalf("second config id = %q, want effort", got)
|
|
}
|
|
if got, _ := calls[1]["value"].(string); got != "high" {
|
|
t.Fatalf("second config value = %q, want high", got)
|
|
}
|
|
}
|
|
|
|
func TestOpenCodeApplySessionSettingsRejectsUnadvertisedEffortBeforeACPCall(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
transport := newStandardACPTransport("OpenCode", "opencode-big-pickle-session")
|
|
transport.conn.configOptions = []map[string]any{{
|
|
"id": "model",
|
|
"options": []any{
|
|
map[string]any{"name": "Big Pickle", "value": "opencode/big-pickle"},
|
|
},
|
|
}}
|
|
adapter := newOpenCodeTestAdapter(transport)
|
|
session := standardTestSession(ProviderOpenCode)
|
|
if _, err := adapter.Start(context.Background(), session); err != nil {
|
|
t.Fatalf("Start: %v", err)
|
|
}
|
|
|
|
err := adapter.ApplySessionSettings(context.Background(), session, SessionSettingsPatch{
|
|
ReasoningEffort: stringPtr("high"),
|
|
})
|
|
if err == nil || !strings.Contains(err.Error(), `effort "high" is not advertised`) {
|
|
t.Fatalf("ApplySessionSettings error = %v", err)
|
|
}
|
|
if calls := transport.conn.setConfigOptionCalls(); len(calls) != 0 {
|
|
t.Fatalf("config option calls = %#v, want none", calls)
|
|
}
|
|
}
|
|
|
|
func TestOpenCodeApplySessionSettingsRejectsSparkNoneBeforeACPCall(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
transport := newStandardACPTransport("OpenCode", "opencode-spark-live-session")
|
|
transport.conn.configOptions = []map[string]any{
|
|
{
|
|
"id": "model",
|
|
"options": []any{
|
|
map[string]any{"name": "GPT-5.3 Codex Spark", "value": openCodeSparkModelID},
|
|
},
|
|
},
|
|
{
|
|
"id": "effort",
|
|
"options": []any{
|
|
map[string]any{"name": "Off", "value": "none"},
|
|
map[string]any{"name": "Low", "value": "low"},
|
|
},
|
|
},
|
|
}
|
|
adapter := newOpenCodeTestAdapter(transport)
|
|
session := standardTestSession(ProviderOpenCode)
|
|
if _, err := adapter.Start(context.Background(), session); err != nil {
|
|
t.Fatalf("Start: %v", err)
|
|
}
|
|
|
|
model := openCodeSparkModelID
|
|
effort := "none"
|
|
patch := SessionSettingsPatch{Model: &model, ReasoningEffort: &effort}
|
|
if err := adapter.ValidateSessionSettings(session, patch); err == nil || !strings.Contains(err.Error(), "does not support") {
|
|
t.Fatalf("ValidateSessionSettings error = %v, want Spark none rejection", err)
|
|
}
|
|
if err := adapter.ApplySessionSettings(context.Background(), session, patch); err == nil || !strings.Contains(err.Error(), "does not support") {
|
|
t.Fatalf("ApplySessionSettings error = %v, want Spark none rejection", err)
|
|
}
|
|
if calls := transport.conn.setConfigOptionCalls(); len(calls) != 0 {
|
|
t.Fatalf("config option calls = %#v, want none", calls)
|
|
}
|
|
}
|
|
|
|
func TestOpenCodeSessionStateFiltersSparkNoneFromRuntimeContext(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
transport := newStandardACPTransport("OpenCode", "opencode-spark-context-session")
|
|
transport.conn.configOptions = []map[string]any{
|
|
{
|
|
"id": "model",
|
|
"currentValue": openCodeSparkModelID,
|
|
"options": []any{
|
|
map[string]any{
|
|
"name": "GPT-5.3 Codex Spark",
|
|
"value": openCodeSparkModelID,
|
|
"reasoningEffort": "none",
|
|
"reasoningEfforts": []any{map[string]any{"value": "none"}, map[string]any{"value": "low"}},
|
|
},
|
|
},
|
|
},
|
|
{
|
|
"id": "effort",
|
|
"currentValue": "none",
|
|
"options": []any{
|
|
map[string]any{"name": "Off", "value": "none"},
|
|
map[string]any{"name": "Low", "value": "low"},
|
|
},
|
|
},
|
|
}
|
|
adapter := newOpenCodeTestAdapter(transport)
|
|
session := standardTestSession(ProviderOpenCode)
|
|
session.Settings = &SessionSettings{Model: openCodeSparkModelID, ReasoningEffort: "low"}
|
|
if _, err := adapter.Start(context.Background(), session); err != nil {
|
|
t.Fatalf("Start: %v", err)
|
|
}
|
|
|
|
state := adapter.SessionState(session)
|
|
descriptors, ok := state.RuntimeContext["configOptions"].([]map[string]any)
|
|
if !ok {
|
|
t.Fatalf("runtime configOptions = %#v, want descriptors", state.RuntimeContext["configOptions"])
|
|
}
|
|
for _, descriptor := range descriptors {
|
|
for _, option := range configOptionEntries(descriptor["options"]) {
|
|
if strings.EqualFold(asString(option["value"]), "none") {
|
|
t.Fatalf("runtime descriptor %q exposes Spark-incompatible none: %#v", descriptor["id"], descriptor)
|
|
}
|
|
for _, effort := range configOptionEntries(option["reasoningEfforts"]) {
|
|
if strings.EqualFold(asString(effort["value"]), "none") {
|
|
t.Fatalf("runtime model descriptor exposes Spark-incompatible none: %#v", descriptor)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if got := state.RuntimeContext["reasoningEffort"]; strings.EqualFold(asString(got), "none") {
|
|
t.Fatalf("runtime context exposes Spark-incompatible reasoning effort: %#v", state.RuntimeContext)
|
|
}
|
|
if state.Settings != nil && strings.EqualFold(state.Settings.ReasoningEffort, "none") {
|
|
t.Fatalf("session settings expose Spark-incompatible reasoning effort: %#v", state.Settings)
|
|
}
|
|
}
|