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
cmd/entire/cli
agent/pi
Amodels.go+49
Amodels_test.go+35
review
Mcmd_test.go+6/-2
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