Implement Pi live model listing for entire review --models · Entire

Implement Pi live model listing for entire review --models

d91d995→main·

dipree·1mo ago·3 files·+90 added/-2 removed

Pi has a real model-enumeration command, so ListModels shells out to pi --list-models and parses the table into provider/model entries. parsePiModelList is split out and unit-tested (no pi binary needed).

Sessions

ebcc924da0dfView transcript

Changes

3

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49

package pi

import (
    "bufio"
    "context"
    "fmt"
    "strings"

"github.com/entireio/cli/cmd/entire/cli/agent"
)

var _ agent.ModelLister = (*PiAgent)(nil)

// ListModels returns Pi's live model catalog by shelling out to
// `pi --list-models`. Unlike the curated lists for claude-code/codex/gemini,
// Pi has a real enumeration command spanning every configured provider, so the
// result reflects what this machine/account can actually use.
func (a *PiAgent) ListModels(ctx context.Context) ([]agent.ModelInfo, error) {
    out, err := agent.RunIsolatedTextGeneratorCLI(ctx, nil, "pi", "pi", []string{"--list-models"}, "")
    if err != nil {
        return nil, fmt.Errorf("pi --list-models: %w", err)
    }
    return parsePiModelList(out), nil
}

// parsePiModelList parses the tabular `pi --list-models` output. Each non-header
// row is "<provider> <model> <context> <max-out> <thinking> <images>"; the model
// ID is rendered as "provider/model" (the unambiguous form Pi's --model accepts)
// with the context window kept as a note.
func parsePiModelList(raw string) []agent.ModelInfo {
    var models []agent.ModelInfo
    scanner := bufio.NewScanner(strings.NewReader(raw))
    for scanner.Scan() {
        fields := strings.Fields(scanner.Text())
        if len(fields) < 2 {
            continue
        }
        provider, model := fields[0], fields[1]
        if provider == "provider" && model == "model" {
            continue // header row
        }
        note := ""
        if len(fields) >= 3 {
            note = fields[2] + " ctx"
        }
        models = append(models, agent.ModelInfo{ID: provider + "/" + model, Note: note})
    }
    return models
}

Acmd/entire/cli/agent/pi/models.go+49

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35

package pi

import "testing"

func TestParsePiModelList(t *testing.T) {
    raw := "provider      model                       context  max-out  thinking  images\n" +
        "anthropic     claude-opus-4-0             200K     32K      yes       yes   \n" +
        "openai        gpt-5                       400K     128K     yes       no    \n" +
        "\n" +
        "google        gemini-2.5-pro              1M       64K      yes       yes   \n"

got := parsePiModelList(raw)
    if len(got) != 3 {
        t.Fatalf("parsed %d models, want 3: %#v", len(got), got)
    }
    want := []struct{id, note string}{
        {"anthropic/claude-opus-4-0", "200K ctx"},
        {"openai/gpt-5", "400K ctx"},
        {"google/gemini-2.5-pro", "1M ctx"},
    }
    for i, w := range want {
        if got[i].ID != w.id {
            t.Errorf("model[%d].ID = %q, want %q", i, got[i].ID, w.id)
        }
        if got[i].Note != w.note {
            t.Errorf("model[%d].Note = %q, want %q", i, got[i].Note, w.note)
        }
    }
}

func TestParsePiModelList_HeaderAndBlanksSkipped(t *testing.T) {
    if got := parsePiModelList("provider model\n\n   \n"); len(got) != 0 {
        t.Fatalf("expected no models, got %#v", got)
    }
}

Acmd/entire/cli/agent/pi/models_test.go+35

142 unmodified lines

143
144
145
146
147
146
147
148
149
150
151
152
153
154

142 unmodified lines

t.Errorf("--models output missing %q:\n%s", want, out)
    }
}
if strings.Contains(out, "gpt-5-codex") {
        t.Errorf("--models should not invent example codex models:\n%s", out)
    // codex has no enumeration command, so its own section must show the
    // no-advertised-models note rather than invented examples. (A substring
    // check would false-positive on Pi's live list, which legitimately
    // includes openai/gpt-5-codex.)
    if !strings.Contains(out, "codex:\n  (no advertised models") {
        t.Errorf("codex section should show no advertised models:\n%s", out)
    }
}

Mcmd/entire/cli/review/cmd_test.go+6/-2