2460 lines · 92.6 KB · TypeScript
RawDownload
// @effect-diagnostics nodeBuiltinImport:offimport * as NodePath from "node:path";import * as NodeOS from "node:os";import * as NodeFSP from "node:fs/promises";import * as NodeURL from "node:url"; import * as NodeServices from "@effect/platform-node/NodeServices";import { assert, it } from "@effect/vitest";import * as Deferred from "effect/Deferred";import * as Effect from "effect/Effect";import * as Fiber from "effect/Fiber";import * as Layer from "effect/Layer";import * as Ref from "effect/Ref";import * as Schema from "effect/Schema";import * as Stream from "effect/Stream";import * as TestClock from "effect/testing/TestClock"; import {  ApprovalRequestId,  GrokSettings,  ProviderDriverKind,  ProviderInstanceId,  ThreadId,  TurnId,  type ProviderRuntimeEvent,} from "@t3tools/contracts"; import { ServerConfig } from "../../config.ts";import {  grokPromptSettlementBelongsToContext,  isGrokEnterPlanModeToolCall,  makeGrokAdapter,  nextGrokPlanModeActive,  selectGrokPermissionOptionId,} from "./GrokAdapter.ts";import { execScriptSource, writeFakeCli } from "../../testUtils/fakeCli.ts";import { HostProcessPlatform } from "@t3tools/shared/hostProcess";const decodeGrokSettings = Schema.decodeSync(GrokSettings); const __dirname = NodePath.dirname(NodeURL.fileURLToPath(import.meta.url));const mockAgentPath = NodePath.join(__dirname, "../../../scripts/acp-mock-agent.ts");// Stopping a session kills the agent with SIGTERM; Windows terminates the// process instead, so the mock never sees a signal to log.const windowsHost = HostProcessPlatform.defaultValue() === "win32"; async function makeMockGrokWrapper(extraEnv?: Record<string, string>) {  const dir = await NodeFSP.mkdtemp(NodePath.join(NodeOS.tmpdir(), "grok-acp-mock-"));  return writeFakeCli({    directory: dir,    name: "fake-grok",    env: extraEnv ?? {},    source: execScriptSource({ scriptPath: mockAgentPath }),  });} function waitForFileContent(  filePath: string,  attempts = 40,  expectedContent?: string,): Effect.Effect<string> {  const readAttempt = (remainingAttempts: number): Effect.Effect<string> =>    Effect.gen(function* () {      if (remainingAttempts <= 0) {        return yield* Effect.die(new Error(`Timed out waiting for file content at ${filePath}`));      }      const raw = yield* Effect.tryPromise(() => NodeFSP.readFile(filePath, "utf8")).pipe(        Effect.orElseSucceed(() => ""),      );      if (        raw.trim().length > 0 &&        (expectedContent === undefined || raw.includes(expectedContent))      ) {        return raw;      }      yield* Effect.sleep("25 millis");      return yield* readAttempt(remainingAttempts - 1);    });  return readAttempt(attempts);} async function readJsonLines(filePath: string) {  const raw = await NodeFSP.readFile(filePath, "utf8");  return raw    .split("\n")    .map((line) => line.trim())    .filter((line) => line.length > 0)    .map((line) => JSON.parse(line) as Record<string, unknown>);} const grokAdapterTestLayer = ServerConfig.layerTest(process.cwd(), {  prefix: "t3code-grok-adapter-test-",}).pipe(Layer.provideMerge(NodeServices.layer)); const makeTestAdapter = (binaryPath: string, options?: Parameters<typeof makeGrokAdapter>[1]) =>  makeGrokAdapter(decodeGrokSettings({ binaryPath }), options).pipe(Effect.orDie); it("detects enter_plan_mode tool calls from title and rawInput", () => {  assert.isTrue(    isGrokEnterPlanModeToolCall({      title: "enter_plan_mode",      data: { toolCallId: "1" },    }),  );  assert.isTrue(    isGrokEnterPlanModeToolCall({      title: "Plan mode entered",      data: { toolCallId: "1", rawInput: { variant: "EnterPlanMode" } },    }),  );  assert.isFalse(    isGrokEnterPlanModeToolCall({      title: "write",      data: { toolCallId: "1", rawInput: { file_path: "/tmp/x", content: "y" } },    }),  );}); it("only sets planModeActive after a successful enter_plan_mode", () => {  const enter = {    title: "enter_plan_mode",    data: { toolCallId: "1" },  };  assert.isFalse(nextGrokPlanModeActive(false, { ...enter, status: "pending" }));  assert.isTrue(nextGrokPlanModeActive(false, { ...enter, status: "inProgress" }));  assert.isTrue(nextGrokPlanModeActive(false, { ...enter, status: "completed" }));  assert.isFalse(nextGrokPlanModeActive(false, { ...enter, status: "failed" }));  assert.isFalse(nextGrokPlanModeActive(true, { ...enter, status: "failed" }));  assert.isTrue(    nextGrokPlanModeActive(true, {      title: "write",      status: "completed",      data: { toolCallId: "2" },    }),  );}); function grokPermissionRequest(  options: ReadonlyArray<{    readonly optionId: string;    readonly kind: "allow_once" | "allow_always" | "reject_once" | "reject_always";  }>,) {  return {    sessionId: "mock-session-1",    toolCall: {      toolCallId: "tool-call-1",      title: "cat package.json",      kind: "execute" as const,      status: "pending" as const,    },    options: options.map((option) => ({      optionId: option.optionId,      name: option.kind,      kind: option.kind,    })),  };} it("maps Always allow to allow_once when Grok omits allow_always", () => {  const request = grokPermissionRequest([    { optionId: "allow-once", kind: "allow_once" },    { optionId: "reject-once", kind: "reject_once" },  ]);   assert.equal(selectGrokPermissionOptionId(request, "acceptForSession"), "allow-once");  assert.equal(selectGrokPermissionOptionId(request, "accept"), "allow-once");  assert.equal(selectGrokPermissionOptionId(request, "decline"), "reject-once");}); it("prefers allow_always when Grok offers it", () => {  const request = grokPermissionRequest([    { optionId: "allow-once", kind: "allow_once" },    { optionId: "allow-always", kind: "allow_always" },    { optionId: "reject-once", kind: "reject_once" },  ]);   assert.equal(selectGrokPermissionOptionId(request, "acceptForSession"), "allow-always");  assert.equal(selectGrokPermissionOptionId(request, "accept"), "allow-once");}); it("requires a settlement to match the live Grok turn", () => {  const staleTurnId = TurnId.make("stale-turn");  const replacementTurnId = TurnId.make("replacement-turn");   assert.isFalse(    grokPromptSettlementBelongsToContext({      liveAcpSessionId: "session-1",      expectedAcpSessionId: "session-1",      liveActiveTurnId: replacementTurnId,      liveSessionActiveTurnId: replacementTurnId,      turnId: staleTurnId,    }),  );  assert.isFalse(    grokPromptSettlementBelongsToContext({      liveAcpSessionId: "replacement-session",      expectedAcpSessionId: "stale-session",      liveActiveTurnId: staleTurnId,      liveSessionActiveTurnId: staleTurnId,      turnId: staleTurnId,    }),  );  assert.isTrue(    grokPromptSettlementBelongsToContext({      liveAcpSessionId: "session-1",      expectedAcpSessionId: "session-1",      liveActiveTurnId: staleTurnId,      liveSessionActiveTurnId: staleTurnId,      turnId: staleTurnId,    }),  );}); it.layer(grokAdapterTestLayer)("GrokAdapterLive", (it) => {  it.effect("sends runtime context with the current model without changing saved prompts", () =>    Effect.gen(function* () {      const threadId = ThreadId.make("grok-runtime-context");      const tempDir = yield* Effect.promise(() =>        NodeFSP.mkdtemp(NodePath.join(NodeOS.tmpdir(), "grok-runtime-context-")),      );      const requestLogPath = NodePath.join(tempDir, "requests.ndjson");      const wrapperPath = yield* Effect.promise(() =>        makeMockGrokWrapper({ T3_ACP_REQUEST_LOG_PATH: requestLogPath }),      );      const adapter = yield* makeTestAdapter(wrapperPath);      yield* adapter.startSession({        threadId,        cwd: process.cwd(),        runtimeMode: "full-access",        modelSelection: { instanceId: ProviderInstanceId.make("grok"), model: "grok-mock-alt" },      });      yield* adapter.sendTurn({ threadId, input: "First prompt" });      yield* adapter.sendTurn({        threadId,        input: "Second prompt",        modelSelection: {          instanceId: ProviderInstanceId.make("grok"),          model: "grok-4.6",          options: [{ id: "reasoningEffort", value: "low" }],        },      });      const snapshot = yield* adapter.readThread(threadId);      assert.deepEqual(        snapshot.turns.map((turn) => turn.items),        [          [            {              prompt: [{ type: "text", text: "First prompt" }],              result: { stopReason: "end_turn" },            },          ],          [            {              prompt: [{ type: "text", text: "Second prompt" }],              result: { stopReason: "end_turn" },            },          ],        ],      );      yield* adapter.stopSession(threadId);      const requests = yield* Effect.promise(() => readJsonLines(requestLogPath));      const prompts = requests        .filter((request) => request.method === "session/prompt")        .map(          (request) => (request.params as { prompt: Array<{ type: string; text: string }> }).prompt,        );      assert.equal(prompts.length, 2);      assert.deepEqual(prompts[0]?.[0], { type: "text", text: "First prompt" });      assert.include(prompts[0]?.[1]?.text, "Grok harness, as grok-mock-alt");      assert.deepEqual(prompts[1]?.[0], { type: "text", text: "Second prompt" });      assert.include(prompts[1]?.[1]?.text, "Grok harness, as grok-4.6");      assert.include(prompts[1]?.[1]?.text, "with low reasoning effort");      assert.include(prompts[1]?.[1]?.text, "embed images and videos");    }),  );   it.effect("starts a session and maps mock ACP prompt flow to runtime events", () =>    Effect.gen(function* () {      const threadId = ThreadId.make("grok-mock-thread");      const wrapperPath = yield* Effect.promise(() => makeMockGrokWrapper());      const adapter = yield* makeTestAdapter(wrapperPath);       const runtimeEvents: ProviderRuntimeEvent[] = [];      const turnCompleted = yield* Deferred.make<void>();      const runtimeEventsFiber = yield* Stream.runForEach(adapter.streamEvents, (event) =>        Effect.sync(() => {          runtimeEvents.push(event);        }).pipe(          Effect.andThen(            event.type === "turn.completed"              ? Deferred.succeed(turnCompleted, undefined)              : Effect.void,          ),        ),      ).pipe(Effect.forkChild);       const session = yield* adapter.startSession({        threadId,        provider: ProviderDriverKind.make("grok"),        cwd: process.cwd(),        runtimeMode: "full-access",        modelSelection: { instanceId: ProviderInstanceId.make("grok"), model: "grok-mock-alt" },      });       assert.equal(session.provider, "grok");      assert.equal(session.model, "grok-mock-alt");      assert.deepStrictEqual(session.resumeCursor, {        schemaVersion: 1,        sessionId: "mock-session-1",      });       yield* adapter.sendTurn({        threadId,        input: "hello grok",        attachments: [],      });       yield* Deferred.await(turnCompleted);      yield* Fiber.interrupt(runtimeEventsFiber);      const types = runtimeEvents.map((e) => e.type);       assert.includeMembers(types, [        "session.started",        "session.state.changed",        "thread.started",        "turn.started",        "item.started",        "content.delta",        "turn.completed",      ] as const);       const delta = runtimeEvents.find((e) => e.type === "content.delta");      assert.isDefined(delta);      if (delta?.type === "content.delta") {        assert.equal(delta.payload.delta, "hello from mock");      }       yield* adapter.stopSession(threadId);    }),  );   it.effect.skipIf(windowsHost)("closes the ACP child process when a session stops", () =>    Effect.gen(function* () {      const threadId = ThreadId.make("grok-stop-session-close");      const tempDir = yield* Effect.promise(() =>        NodeFSP.mkdtemp(NodePath.join(NodeOS.tmpdir(), "grok-adapter-exit-log-")),      );      const exitLogPath = NodePath.join(tempDir, "exit.log");       const wrapperPath = yield* Effect.promise(() =>        makeMockGrokWrapper({          T3_ACP_EXIT_LOG_PATH: exitLogPath,        }),      );      const adapter = yield* makeTestAdapter(wrapperPath);       yield* adapter.startSession({        threadId,        provider: ProviderDriverKind.make("grok"),        cwd: process.cwd(),        runtimeMode: "full-access",        modelSelection: { instanceId: ProviderInstanceId.make("grok"), model: "grok-build" },      });       yield* adapter.stopSession(threadId);       const exitLog = yield* waitForFileContent(exitLogPath);      assert.include(exitLog, "SIGTERM");    }),  );   it.effect("reports a Grok session running only while the prompt is in flight", () =>    Effect.gen(function* () {      const threadId = ThreadId.make("grok-session-ready-after-prompt");      const wrapperPath = yield* Effect.promise(() =>        makeMockGrokWrapper({          T3_ACP_EMIT_TOOL_CALLS: "1",        }),      );      const adapter = yield* makeTestAdapter(wrapperPath);      const requestOpened =        yield* Deferred.make<Extract<ProviderRuntimeEvent, { type: "request.opened" }>>();      const eventsFiber = yield* Stream.runForEach(adapter.streamEvents, (event) =>        event.type === "request.opened"          ? Deferred.succeed(requestOpened, event).pipe(Effect.ignore)          : Effect.void,      ).pipe(Effect.forkChild);       yield* adapter.startSession({        threadId,        provider: ProviderDriverKind.make("grok"),        cwd: process.cwd(),        runtimeMode: "approval-required",        modelSelection: { instanceId: ProviderInstanceId.make("grok"), model: "grok-build" },      });       const sendTurnFiber = yield* adapter        .sendTurn({ threadId, input: "check lifecycle", attachments: [] })        .pipe(Effect.forkChild);      const requestOpenedEvent = yield* Deferred.await(requestOpened);       const runningSessions = yield* adapter.listSessions();      const runningSession = runningSessions.find((session) => session.threadId === threadId);      assert.equal(runningSession?.status, "running");      assert.isDefined(runningSession?.activeTurnId);       yield* adapter.respondToRequest(        threadId,        ApprovalRequestId.make(String(requestOpenedEvent.requestId)),        "accept",      );      yield* Fiber.join(sendTurnFiber);       const readySessions = yield* adapter.listSessions();      const readySession = readySessions.find((session) => session.threadId === threadId);      assert.equal(readySession?.status, "ready");      assert.isUndefined(readySession?.activeTurnId);       yield* Fiber.interrupt(eventsFiber);      yield* adapter.stopSession(threadId);    }),  );   it.effect("restores ready without completing an unstarted turn when preparation fails", () =>    Effect.gen(function* () {      const threadId = ThreadId.make("grok-preparation-failure-while-connecting");      const wrapperPath = yield* Effect.promise(() => makeMockGrokWrapper());      const adapter = yield* makeTestAdapter(wrapperPath);       const runtimeEvents: ProviderRuntimeEvent[] = [];      const runtimeEventsFiber = yield* Stream.runForEach(adapter.streamEvents, (event) =>        Effect.sync(() => {          runtimeEvents.push(event);        }),      ).pipe(Effect.forkChild);       yield* adapter.startSession({        threadId,        provider: ProviderDriverKind.make("grok"),        cwd: process.cwd(),        runtimeMode: "full-access",        modelSelection: { instanceId: ProviderInstanceId.make("grok"), model: "grok-build" },      });       const error = yield* Effect.flip(        adapter.sendTurn({          threadId,          input: "prepare invalid attachment",          attachments: [            {              type: "image",              id: "missing-image",              name: "missing.png",              mimeType: "image/png",              sizeBytes: 1,            },          ],        }),      );      for (let yieldAttempt = 0; yieldAttempt < 4; yieldAttempt += 1) {        yield* Effect.yieldNow;      }       const turnCompletedEvent = runtimeEvents.find(        (event): event is Extract<ProviderRuntimeEvent, { type: "turn.completed" }> =>          event.type === "turn.completed",      );      const readySessions = yield* adapter.listSessions();      const readySession = readySessions.find((session) => session.threadId === threadId);       assert.equal(error._tag, "ProviderAdapterRequestError");      assert.isUndefined(turnCompletedEvent);      assert.equal(readySession?.status, "ready");      assert.isUndefined(readySession?.activeTurnId);       yield* Fiber.interrupt(runtimeEventsFiber);      yield* adapter.stopSession(threadId);    }),  );   it.effect("completes a Grok turn from xAI prompt completion when the prompt RPC hangs", () =>    Effect.gen(function* () {      const threadId = ThreadId.make("grok-xai-prompt-complete-fallback");      const wrapperPath = yield* Effect.promise(() =>        makeMockGrokWrapper({          T3_ACP_EMIT_XAI_PROMPT_COMPLETE_THEN_HANG: "1",          T3_ACP_EMIT_FOREIGN_SESSION_UPDATES: "1",        }),      );      const adapter = yield* makeTestAdapter(wrapperPath);       const runtimeEvents: ProviderRuntimeEvent[] = [];      const turnCompleted = yield* Deferred.make<void>();      const runtimeEventsFiber = yield* Stream.runForEach(adapter.streamEvents, (event) =>        Effect.sync(() => {          runtimeEvents.push(event);        }).pipe(          Effect.andThen(            event.type === "turn.completed"              ? Deferred.succeed(turnCompleted, undefined)

Lines 1–500 of 2460.