1/**
2 * Optional integration check against a real `grok agent stdio` install.
3 * Enable with: T3_GROK_ACP_PROBE=1 vp test run GrokAcpCliProbe
4 * Set T3_GROK_LIVE_TURN=1 to also send a small prompt to the real model.
5 *
6 * The probe assumes either `XAI_API_KEY` is set in the environment or
7 * the user has previously run `grok login`. Without credentials the
8 * agent's `authenticate` request will fail and the test will surface
9 * the error.
10 */
11import * as NodeServices from "@effect/platform-node/NodeServices";
12import { it } from "@effect/vitest";
13import * as Effect from "effect/Effect";
14import * as Deferred from "effect/Deferred";
15import * as Fiber from "effect/Fiber";
16import * as FileSystem from "effect/FileSystem";
17import * as Stream from "effect/Stream";
18import { describe, expect } from "vite-plus/test";
19
20import { makeGrokAcpRuntime } from "./acpSupport.ts";
21
22const makeProbeRuntime = makeGrokAcpRuntime({
23 grokSettings: { binaryPath: "grok" },
24 environment: process.env,
25 cwd: process.cwd(),
26 clientInfo: { name: "t3-grok-probe", version: "0.0.0" },
27});
28
29describe.runIf(process.env.T3_GROK_ACP_PROBE === "1")("Grok ACP CLI probe", () => {
30 it.effect("initialize and authenticate against real grok agent stdio", () =>
31 Effect.gen(function* () {
32 const runtime = yield* makeProbeRuntime;
33 const started = yield* runtime.start();
34 expect(started.initializeResult).toBeDefined();
35 }).pipe(Effect.scoped, Effect.provide(NodeServices.layer)),
36 );
37
38 it.effect("completes a prompt on the agent's default model", () =>
39 Effect.gen(function* () {
40 const runtime = yield* makeProbeRuntime;
41 const started = yield* runtime.start();
42 expect(typeof started.sessionId).toBe("string");
43
44 const result = yield* runtime
45 .prompt({ prompt: [{ type: "text", text: "Respond with exactly: grok switch ok" }] })
46 .pipe(Effect.timeout("60 seconds"));
47 expect(result.stopReason).toBe("end_turn");
48 }).pipe(Effect.scoped, Effect.provide(NodeServices.layer)),
49 );
50 it.effect("session/set_model accepts advertised reasoning effort metadata", () =>
51 Effect.gen(function* () {
52 const runtime = yield* makeProbeRuntime;
53 const started = yield* runtime.start();
54 const modelState = started.sessionSetupResult.models;
55 const currentModelId = modelState?.currentModelId.trim();
56 expect(currentModelId).toBeDefined();
57 if (!currentModelId) return;
58
59 const currentModel = modelState?.availableModels.find(
60 (model) => model.modelId.trim() === currentModelId,
61 );
62 const reasoningEffort = currentModel?._meta?.reasoningEffort;
63 expect(typeof reasoningEffort).toBe("string");
64 if (typeof reasoningEffort !== "string") return;
65
66 yield* runtime.setSessionModel(currentModelId, { reasoningEffort });
67 }).pipe(Effect.scoped, Effect.provide(NodeServices.layer)),
68 );
69
70 it.effect.skipIf(process.env.T3_GROK_LIVE_TURN !== "1")(
71 "finishes a real Grok turn and streams its answer",
72 () =>
73 Effect.gen(function* () {
74 const fileSystem = yield* FileSystem.FileSystem;
75 const cwd = yield* fileSystem.makeTempDirectoryScoped();
76 const runtime = yield* makeGrokAcpRuntime({
77 grokSettings: { binaryPath: "grok" },
78 environment: process.env,
79 cwd,
80 runtimeMode: "approval-required",
81 clientInfo: { name: "t3-grok-probe", version: "0.0.0" },
82 });
83 yield* runtime.start();
84 const chunks: string[] = [];
85 const events = yield* Stream.runForEach(runtime.getEvents(), (event) => {
86 if (event._tag === "EventStreamBarrier") {
87 return Deferred.succeed(event.acknowledge, undefined);
88 }
89 if (event._tag === "ContentDelta") {
90 chunks.push(event.text);
91 }
92 return Effect.void;
93 }).pipe(Effect.forkChild);
94 const result = yield* runtime.prompt({
95 prompt: [{ type: "text", text: "Reply exactly GROK_T3_OK. Do not use any tools." }],
96 });
97 yield* runtime.drainEvents;
98 expect(result.stopReason).toBe("end_turn");
99 expect(chunks.join("")).toContain("GROK_T3_OK");
100 yield* Fiber.interrupt(events);
101 }).pipe(Effect.scoped, Effect.provide(NodeServices.layer)),
102 );
103});