Files
multica/server/pkg/agent/devin_model_test.go
e8fdcfd226 MUL-6623 feat(agent): add host-local Devin CLI over ACP (#7485)
* feat(agent): add host-local Devin CLI over ACP

Spawn `devin acp` via the shared ACP client. Local CLI only — not
cloud Devin VMs/Playbooks/Secrets. Model via `devin acp --model`.
Catalog from `devin models list --format json` (families/variants).

* fix: skip Devin ACP handshake when browser auth is also offered

* fix: close dim case string so Devin compiles

Missing quote after dim broke runtime_config.go and every Go CI job.

* fix: stop runtimeConfigPath recursion on Devin

Devin is a builtin whose ProtocolFamily is itself. Recursing on that
overflowed the stack and killed backend-tests.

* test(agent): cover Devin model failures and final-answer delivery

Co-authored-by: multica-agent <[email protected]>

* fix(agent): honor Devin model aliases and stored login (MUL-6623)

Co-authored-by: multica-agent <[email protected]>

* fix(agent): drain Kiro panic details before terminating runtime (MUL-6623)

Co-authored-by: multica-agent <[email protected]>

---------

Co-authored-by: Sol-Boy <[email protected]>
Co-authored-by: multica-agent <[email protected]>
2026-10-26 15:11:05 +08:00

59 lines
3.7 KiB
Go

package agent
import (
"context"
"encoding/json"
"fmt"
"testing"
)
func TestDevinModelNamesAndAliases(t *testing.T) {
const catalog = `{"families":[{"slug":"claude-opus-5","family_label":"Claude Opus 5","aliases":["opus"],"variants":[{"model_uid":"opus-high","label":"Opus High"},{"model_uid":"opus-low","label":"Opus Low"}]},{"slug":"swe-2","aliases":["swe"],"variants":[{"model_uid":"swe-2"}]}]}`
for _, tc := range []struct {
name, requested, current, options, catalog, want, set string
wantError bool
}{
{name: "canonical", requested: "opus-high", current: "opus-high", want: "opus-high"},
{name: "default", current: "swe-2", want: "swe-2"},
{name: "alias-resolved", requested: "opus", current: "opus-high", catalog: catalog, want: "opus-high"},
{name: "slug-resolved", requested: "claude-opus-5", current: "opus-low", catalog: catalog, want: "opus-low"},
{name: "family-name", requested: "Claude Opus 5", current: "opus-high", catalog: catalog, want: "opus-high"},
{name: "session-name", requested: "Opus High", current: "opus-high", options: `[{"value":"opus-high","name":"Opus High"}]`, want: "opus-high"},
{name: "session-name-normalized", requested: "OPUS-HIGH", current: "opus-high", options: `[{"value":"opus-high","name":"Opus High"}]`, want: "opus-high"},
{name: "catalog-variant-name", requested: "Opus High", current: "opus-high", catalog: catalog, want: "opus-high"},
{name: "resume-alias", requested: "opus", current: "swe-2", options: `[{"value":"opus-high","name":"Opus High"}]`, catalog: catalog, want: "opus-high", set: "opus-high"},
{name: "unavailable-family-fallback", requested: "opus", current: "swe-2", catalog: catalog, wantError: true},
{name: "ambiguous-variant", requested: "opus", current: "swe-2", options: `[{"value":"opus-high"},{"value":"opus-low"}]`, catalog: catalog, wantError: true},
{name: "ambiguous-session-name", requested: "Opus", current: "opus-high", options: `[{"value":"opus-high","name":"Opus"},{"value":"opus-low","name":"Opus"}]`, wantError: true},
{name: "ambiguous-family", requested: "opus", current: "opus-high", catalog: `{"families":[{"aliases":["opus"],"variants":[{"model_uid":"opus-high"}]},{"aliases":["opus"],"variants":[{"model_uid":"old-opus"}]}]}`, wantError: true},
{name: "unknown-partial", requested: "op", current: "swe-2", catalog: catalog, wantError: true},
{name: "punctuation-not-an-alias", requested: ".", current: "swe-2", catalog: catalog, wantError: true},
{name: "opaque-id-not-guessed", requested: "MODEL_PRIVATE_2", current: "swe-2", catalog: catalog, wantError: true},
{name: "bad-catalog", requested: "opus", current: "swe-2", catalog: `invalid`, wantError: true},
} {
t.Run(tc.name, func(t *testing.T) {
options := tc.options
if options == "" {
options = "[]"
}
raw := json.RawMessage(fmt.Sprintf(`{"configOptions":[{"id":"model","currentValue":%q,"options":%s}]}`, tc.current, options))
set := ""
actual, err := confirmDevinModel(context.Background(), func(_ context.Context, method string, params any) (json.RawMessage, error) {
if method != "session/set_config_option" {
t.Fatalf("unexpected RPC %s", method)
}
set = params.(map[string]any)["value"].(string)
return json.RawMessage(fmt.Sprintf(`{"configOptions":[{"id":"model","currentValue":%q}]}`, set)), nil
}, "session", tc.requested, raw, func(context.Context) ([]byte, error) {
if tc.catalog == "" {
t.Fatal("exact session choice unexpectedly required a catalog process")
}
return []byte(tc.catalog), nil
})
if (err != nil) != tc.wantError || actual != tc.want || set != tc.set {
t.Fatalf("actual=%q set=%q err=%v; want model=%q set=%q error=%v", actual, set, err, tc.want, tc.set, tc.wantError)
}
})
}
}