fable-agent/src/examples/executors/grok-executor.ts

101 lines
3.2 KiB
TypeScript

/**
* Grok CLI Executor — PhaseExecutor using the `grok` CLI.
*
* Grok CLI is xAI's agentic coding tool (grok.exe at ~/.grok/bin/grok.exe).
* Routes through local proxy at :18901 (configured in ~/.grok/config.toml).
*
* Integration: shells out to `grok -m <model> -p "<prompt>"`.
*
* Usage:
* const grok = new GrokExecutor({ model: "grok-3" });
* const result = await feedbackLoop.run(task, grok);
*/
import { exec } from "node:child_process";
import * as path from "node:path";
import * as fs from "node:fs";
import type { LoopIteration } from "../../core/types.js";
import type { PhaseExecutor } from "../../tier2-primitives/loops/feedback-loop.js";
function findGrokBin(): string {
const candidates = [
path.join(process.env.HOME || "", ".grok", "bin", "grok.exe"),
path.join(process.env.USERPROFILE || "", ".grok", "bin", "grok.exe"),
"grok",
];
for (const c of candidates) {
try { if (fs.existsSync(c)) return c; } catch {}
}
return "grok";
}
export interface GrokConfig {
/** Grok CLI binary path */
bin?: string;
/** Model name */
model?: string;
/** Proxy URL */
proxyUrl?: string;
/** Timeout per phase in ms */
timeoutMs?: number;
}
export class GrokExecutor implements PhaseExecutor {
private config: GrokConfig;
constructor(config?: GrokConfig) {
this.config = {
bin: findGrokBin(),
model: process.env.GROK_MODEL ?? "grok-3",
proxyUrl: process.env.GROK_PROXY ?? "http://localhost:18901/v1",
timeoutMs: 120000,
...config,
};
}
async plan(task: string, previousIteration: LoopIteration | null): Promise<string> {
const prev = previousIteration ? `\nPrevious: ${previousIteration.reflection?.slice(0, 200)}` : "";
return this.callGrok(`You are in the PLAN phase.\nTask: ${task}${prev}`);
}
async execute(plan: string): Promise<string> {
return this.callGrok(`EXECUTE:\n${plan}`);
}
async observe(executed: string): Promise<string> {
return this.callGrok(`OBSERVE:\n${executed}`);
}
async reflect(observation: string, previousIteration: LoopIteration | null): Promise<string> {
const prev = previousIteration ? `\nExpected: ${previousIteration.refinement?.slice(0, 200)}` : "";
return this.callGrok(`REFLECT:\n${observation}${prev}`);
}
async refine(reflection: string): Promise<string> {
return this.callGrok(`REFINE:\n${reflection}`);
}
private async callGrok(prompt: string): Promise<string> {
const modelFlag = `-m "${this.config.model}"`;
const cmd = `"${this.config.bin}" ${modelFlag} -p "${prompt.replace(/"/g, '\\"')}" --output-format text 2>/dev/null`;
return new Promise((resolve) => {
exec(cmd, { timeout: this.config.timeoutMs, maxBuffer: 10 * 1024 * 1024 }, (err, stdout) => {
resolve(stdout.trim() || `[Grok error: ${err?.message?.slice(0, 200) ?? "unknown"}]`);
});
});
}
static describeConfig(): string {
return [
"Grok CLI Executor",
` Binary: ${findGrokBin()}`,
` Model: ${process.env.GROK_MODEL ?? "grok-3 (set GROK_MODEL)"}`,
` Proxy: ${process.env.GROK_PROXY ?? "http://localhost:18901/v1"}`,
" Mode: CLI -p (non-interactive)",
"",
"See ~/.grok/config.toml for proxy/model configuration.",
].join("\n");
}
}