FN-7712: fix Grok CLI model list parsing for real grok models output
Fixes the Grok CLI model picker showing raw prompt/preamble text instead of real model names by rewriting parseModelLines to match the actual verified `grok models` output shape. - Rewrote parseModelLines in process-manager.ts to strip the login/"Default model:"/"Available models:" preamble - Strip `*`/`-` bullet markers and the `(default)` annotation from each model line - Preserve existing legacy `id - Label`, columnar, and JSON parsing paths - Added regression tests covering the real grok models output shape - Added changeset (patch) documenting the fix Files changed: .changeset/fn-7712-grok-model-parse.md | 7 ++++ .../src/__tests__/process-manager.test.ts | 39 ++++++++++++++++++++++ .../src/process-manager.ts | 34 ++++++++++++------- 3 files changed, 68 insertions(+), 12 deletions(-) Fusion-Task-Id: FN-7712 Fusion-Task-Lineage: 93e34513-07b9-41b3-8b8b-ecdb763b4208 Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
This commit is contained in:
7
.changeset/fn-7712-grok-model-parse.md
Normal file
7
.changeset/fn-7712-grok-model-parse.md
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
---
|
||||||
|
"@runfusion/fusion": patch
|
||||||
|
---
|
||||||
|
|
||||||
|
summary: Fix Grok CLI model picker showing prompt text instead of real model names.
|
||||||
|
category: fix
|
||||||
|
dev: Rewrote parseModelLines in fusion-plugin-grok-runtime/process-manager.ts to strip the login/"Default model:"/"Available models:" preamble and `*`/`-` bullet markers plus the `(default)` annotation from verified `grok models` output; legacy `id - Label`, columnar, and JSON paths preserved.
|
||||||
@@ -16,6 +16,20 @@ const DASH_MODELS_OUTPUT = [
|
|||||||
|
|
||||||
const COLUMN_MODELS_OUTPUT = ["grok-4 $5.00/M in", "grok-4-fast $0.20/M in"].join("\n");
|
const COLUMN_MODELS_OUTPUT = ["grok-4 $5.00/M in", "grok-4-fast $0.20/M in"].join("\n");
|
||||||
|
|
||||||
|
// FN-7712: verified real `grok models` output shape (attachment 1871.png) —
|
||||||
|
// login/session preamble, "Default model:" line, "Available models:" header,
|
||||||
|
// then a bulleted list with `* <id> (default)` for the active model and
|
||||||
|
// `- <id>` for the rest.
|
||||||
|
const REAL_BULLETED_OUTPUT = [
|
||||||
|
"You are logged in with grok-cli v1.2.3",
|
||||||
|
"Default model: grok-4.5",
|
||||||
|
"Available models:",
|
||||||
|
"* grok-4.5 (default)",
|
||||||
|
"- grok-composer-2.5-fast",
|
||||||
|
"- grok-4-fast-reasoning",
|
||||||
|
"- grok-3-mini",
|
||||||
|
].join("\n");
|
||||||
|
|
||||||
describe("discoverGrokModels", () => {
|
describe("discoverGrokModels", () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
vi.clearAllMocks();
|
vi.clearAllMocks();
|
||||||
@@ -39,6 +53,31 @@ describe("discoverGrokModels", () => {
|
|||||||
expect(result.fallbackUsed).toBe(false);
|
expect(result.fallbackUsed).toBe(false);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("extracts clean model ids from the verified real bulleted output, dropping preamble and markers", async () => {
|
||||||
|
vi.mocked(runGrokCommand).mockResolvedValueOnce({ code: 0, stdout: REAL_BULLETED_OUTPUT, stderr: "" });
|
||||||
|
const result = await discoverGrokModels("grok");
|
||||||
|
|
||||||
|
expect(result.models).toEqual(["grok-4.5", "grok-composer-2.5-fast", "grok-4-fast-reasoning", "grok-3-mini"]);
|
||||||
|
expect(result.source).toBe("models-text");
|
||||||
|
expect(result.fallbackUsed).toBe(false);
|
||||||
|
for (const model of result.models) {
|
||||||
|
expect(model).not.toMatch(/^[*-]/);
|
||||||
|
expect(model).not.toMatch(/\(default\)/i);
|
||||||
|
}
|
||||||
|
expect(result.models.some((m) => /logged in|default model|available models/i.test(m))).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("strips the ` (default)` suffix marker from a bulleted default-model line", async () => {
|
||||||
|
vi.mocked(runGrokCommand).mockResolvedValueOnce({
|
||||||
|
code: 0,
|
||||||
|
stdout: ["Available models:", "* grok-4.5 (default)"].join("\n"),
|
||||||
|
stderr: "",
|
||||||
|
});
|
||||||
|
const result = await discoverGrokModels("grok");
|
||||||
|
|
||||||
|
expect(result.models).toEqual(["grok-4.5"]);
|
||||||
|
});
|
||||||
|
|
||||||
it("extracts bare ids from columnar/pricing-separated output", async () => {
|
it("extracts bare ids from columnar/pricing-separated output", async () => {
|
||||||
vi.mocked(runGrokCommand).mockResolvedValueOnce({ code: 0, stdout: COLUMN_MODELS_OUTPUT, stderr: "" });
|
vi.mocked(runGrokCommand).mockResolvedValueOnce({ code: 0, stdout: COLUMN_MODELS_OUTPUT, stderr: "" });
|
||||||
const result = await discoverGrokModels("grok");
|
const result = await discoverGrokModels("grok");
|
||||||
|
|||||||
@@ -1,28 +1,38 @@
|
|||||||
import { runGrokCommand } from "./cli-spawn.js";
|
import { runGrokCommand } from "./cli-spawn.js";
|
||||||
|
|
||||||
/*
|
/*
|
||||||
FNXC:GrokCli 2026-07-08-00:00:
|
FNXC:GrokCli 2026-07-08-19:30:
|
||||||
FN-7705: the exact `grok models` output line shape is
|
FN-7712: the `grok models` output shape is now VERIFIED from a real capture
|
||||||
`upstream-pending-verification` — the upstream README documents the command
|
(attachment 1871.png in FN-7712): the CLI prints a login/session preamble
|
||||||
lists available Grok models "with pricing hints" but does not pin an exact
|
("You are logged in with grok..."), a "Default model: <id>" line, an
|
||||||
column/separator format. We parse CONSERVATIVELY: strip obvious header/tip/
|
"Available models:" header, then a bulleted list — `* <id> (default)` for
|
||||||
empty-state lines, then take the leading token before a ` - ` label
|
the active model and `- <id>` for the rest. We drop the preamble/header
|
||||||
separator (mirroring the Cursor CLI's `<id> - <Label>` shape) or, absent
|
lines (case-insensitively, tolerating a trailing colon and leading/trailing
|
||||||
that, before the first run of 2+ spaces (a common columnar pricing-hint
|
whitespace), strip a leading `* `/`- ` bullet marker from each remaining
|
||||||
layout), falling back to the whole trimmed line as a single-token id.
|
candidate line, then strip a trailing ` (default)`/`(default)` annotation
|
||||||
Defensive JSON-tolerant parsing is attempted first (mirroring Cursor's
|
before id extraction so the picker only ever sees bare model ids. Legacy
|
||||||
process-manager.ts), even though the real CLI is not known to emit JSON.
|
extraction (leading token before a ` - ` label separator, else before the
|
||||||
|
first run of 2+ spaces for columnar/pricing layouts, else the whole trimmed
|
||||||
|
token) is preserved unchanged so older `id - Label (pricing)` and columnar
|
||||||
|
fixtures still parse. Defensive JSON-tolerant parsing is attempted first
|
||||||
|
(mirroring Cursor's process-manager.ts), even though the real CLI is not
|
||||||
|
known to emit JSON.
|
||||||
*/
|
*/
|
||||||
function parseModelLines(raw: string): string[] {
|
function parseModelLines(raw: string): string[] {
|
||||||
const ids = raw
|
const ids = raw
|
||||||
.split(/\r?\n/)
|
.split(/\r?\n/)
|
||||||
.map((line) => line.trim())
|
.map((line) => line.trim())
|
||||||
.filter(Boolean)
|
.filter(Boolean)
|
||||||
.filter((line) => !/^available models$/i.test(line))
|
.filter((line) => !/^available models:?$/i.test(line))
|
||||||
|
.filter((line) => !/^you are logged in\b/i.test(line))
|
||||||
|
.filter((line) => !/^default model:?/i.test(line))
|
||||||
.filter((line) => !/^models?:?$/i.test(line))
|
.filter((line) => !/^models?:?$/i.test(line))
|
||||||
.filter((line) => !/^no models? available/i.test(line))
|
.filter((line) => !/^no models? available/i.test(line))
|
||||||
.filter((line) => !/^tip:/i.test(line))
|
.filter((line) => !/^tip:/i.test(line))
|
||||||
.filter((line) => !/^usage/i.test(line))
|
.filter((line) => !/^usage/i.test(line))
|
||||||
|
.map((line) => line.replace(/^[*-]\s+/, ""))
|
||||||
|
.map((line) => line.replace(/\s*\(default\)\s*$/i, ""))
|
||||||
|
.map((line) => line.trim())
|
||||||
.map((line) => {
|
.map((line) => {
|
||||||
const dashIndex = line.indexOf(" - ");
|
const dashIndex = line.indexOf(" - ");
|
||||||
if (dashIndex !== -1) return line.slice(0, dashIndex).trim();
|
if (dashIndex !== -1) return line.slice(0, dashIndex).trim();
|
||||||
|
|||||||
Reference in New Issue
Block a user