import * as NodeServices from "@effect/platform-node/NodeServices";import { expect, it } from "@effect/vitest";import { AntigravitySettings, ApprovalRequestId, ProviderInstanceId, ThreadId, type ProviderRuntimeEvent,} from "@t3tools/contracts";import * as Deferred from "effect/Deferred";import * as Crypto from "effect/Crypto";import * as Effect from "effect/Effect";import * as Exit from "effect/Exit";import * as Fiber from "effect/Fiber";import * as FileSystem from "effect/FileSystem";import * as Layer from "effect/Layer";import * as Path from "effect/Path";import * as Queue from "effect/Queue";import * as Schema from "effect/Schema";import * as Stream from "effect/Stream";import * as ChildProcessSpawner from "effect/unstable/process/ChildProcessSpawner";import * as AcpErrors from "effect-acp/errors";import type * as AcpSchema from "effect-acp/schema"; import { ServerConfig } from "../../config.ts";import { ANTIGRAVITY_SIGN_IN_REQUIRED_MESSAGE } from "../antigravityAuthSupport.ts";import type { AcpSessionRuntimeEvent } from "../acp/AcpSessionRuntime.ts";import { makeAntigravityAcpRuntime } from "../acp/AntigravityAcpSupport.ts";import { mergeToolCallState, parseSessionUpdateEvent, type AcpToolCallState,} from "../acp/AcpRuntimeModel.ts";import { makeAntigravityAdapter, type AntigravityAdapterOptions } from "./AntigravityAdapter.ts"; const instanceId = ProviderInstanceId.make("antigravity-test");const threadId = ThreadId.make("antigravity-thread");const nativeSessionId = "b75db7e9-cd99-40e5-aa63-ac2b4674a6a9";const nativeDefault = "gemini-test-low";const nativeAlternative = "gemini-test-high";const decodeSettings = Schema.decodeSync(AntigravitySettings);const decodeRequestLog = Schema.decodeEffect( Schema.Array( Schema.fromJsonString( Schema.Struct({ method: Schema.String, params: Schema.optional(Schema.Unknown) }), ), ),); interface NativePrompt { readonly index: number; readonly content: ReadonlyArray<AcpSchema.ContentBlock>; readonly result: Deferred.Deferred<AcpSchema.PromptResponse, AcpErrors.AcpError>;} type Runtime = Effect.Success<ReturnType<AntigravityAdapterOptions["makeRuntime"]>>; function nativeToolUpdate( update: Extract< AcpSchema.SessionNotification["update"], { sessionUpdate: "tool_call" | "tool_call_update" } >, previous?: AcpToolCallState,) { const event = parseSessionUpdateEvent({ sessionId: nativeSessionId, update }).events.find( (event) => event._tag === "ToolCallUpdated", ); if (!event) throw new Error("Expected a native tool update"); return { ...event, toolCall: mergeToolCallState(previous, event.toolCall) };} const makeHarness = Effect.fn("makeAntigravityAdapterHarness")(function* (options?: { readonly enabled?: boolean; readonly holdCancel?: boolean; readonly holdClose?: boolean; readonly holdDispatch?: boolean;}) { const runtimeEvents = yield* Queue.unbounded<AcpSessionRuntimeEvent>(); const canonicalEvents = yield* Queue.unbounded<ProviderRuntimeEvent>(); const prompts = yield* Queue.unbounded<NativePrompt>(); const cancellations = yield* Queue.unbounded<number>(); const cancelRelease = yield* Deferred.make<void>(); const closeStarted = yield* Deferred.make<void>(); const closeRelease = yield* Deferred.make<void>(); const dispatchStarted = yield* Deferred.make<void>(); const dispatchRelease = yield* Deferred.make<void>(); const seen: ProviderRuntimeEvent[] = []; const calls: string[] = []; const launches: Array<Parameters<AntigravityAdapterOptions["makeRuntime"]>[0]> = []; const stops: Array<Effect.Effect<void>> = []; const controls = { failModel: false, failAuth: false, authInvalidations: 0, closed: 0 }; let currentModel = nativeDefault; let promptIndex = 0; let active: NativePrompt | undefined; const fileHandlers: { read?: Parameters<Runtime["handleReadTextFile"]>[0]; write?: Parameters<Runtime["handleWriteTextFile"]>[0]; } = {}; let permissionHandler: | (( request: AcpSchema.RequestPermissionRequest, ) => Effect.Effect<AcpSchema.RequestPermissionResponse, AcpErrors.AcpError>) | undefined; const configOptions = (): ReadonlyArray<AcpSchema.SessionConfigOption> => [ { id: "model", name: "Model", type: "select", category: "model", currentValue: currentModel, options: [ { value: nativeDefault, name: "Gemini test low" }, { value: nativeAlternative, name: "Gemini test high" }, ], }, ]; const drainEvents = Effect.gen(function* () { const acknowledge = yield* Deferred.make<void>(); yield* Queue.offer(runtimeEvents, { _tag: "EventStreamBarrier", acknowledge }); yield* Deferred.await(acknowledge); }); const emitNative = (event: AcpSessionRuntimeEvent) => Queue.offer(runtimeEvents, event).pipe(Effect.asVoid); const runtime: Effect.Success<ReturnType<AntigravityAdapterOptions["makeRuntime"]>> = { handleRequestPermission: (handler) => Effect.sync(() => { permissionHandler = handler; }), handleReadTextFile: (handler) => Effect.sync(() => { fileHandlers.read = handler; }), handleWriteTextFile: (handler) => Effect.sync(() => { fileHandlers.write = handler; }), start: () => Effect.gen(function* () { if (controls.failAuth) { return yield* new AcpErrors.AcpTransportError({ detail: ANTIGRAVITY_SIGN_IN_REQUIRED_MESSAGE, cause: undefined, }); } currentModel = nativeDefault; calls.push("start"); yield* emitNative({ _tag: "AvailableCommandsUpdated", availableCommands: [ { name: "plan", description: "Create a plan" }, { name: "logout", description: "Sign out" }, ], rawPayload: {}, }); return { sessionId: nativeSessionId, initializeResult: { protocolVersion: 1, agentCapabilities: { sessionCapabilities: { resume: {} } }, }, sessionSetupResult: { sessionId: nativeSessionId, configOptions: configOptions() }, modelConfigId: "model", }; }), getConfigOptions: Effect.sync(configOptions), setModel: (model) => Effect.gen(function* () { calls.push(`model:${model}`); if (controls.failModel) { controls.failModel = false; return yield* AcpErrors.AcpRequestError.invalidParams("Native model selection failed."); } currentModel = model; }), setMode: (mode) => Effect.sync(() => { calls.push(`mode:${mode}`); return {}; }), getEvents: () => Stream.fromQueue(runtimeEvents), drainEvents, prompt: (payload, promptOptions) => Effect.gen(function* () { yield* Deferred.succeed(dispatchStarted, undefined); if (options?.holdDispatch) yield* Deferred.await(dispatchRelease); const prompt: NativePrompt = { index: ++promptIndex, content: payload.prompt, result: yield* Deferred.make<AcpSchema.PromptResponse, AcpErrors.AcpError>(), }; active = prompt; calls.push(`prompt:${prompt.index}`); if (promptOptions?.dispatched) yield* Deferred.succeed(promptOptions.dispatched, undefined); yield* Queue.offer(prompts, prompt); return yield* Deferred.await(prompt.result).pipe( Effect.ensuring( Effect.sync(() => { if (active === prompt) active = undefined; }), ), ); }), cancel: Effect.gen(function* () { const prompt = active; if (!prompt) return; calls.push(`cancel:${prompt.index}`); yield* Queue.offer(cancellations, prompt.index); if (options?.holdCancel) yield* Deferred.await(cancelRelease); yield* Deferred.succeed(prompt.result, { stopReason: "cancelled" }); yield* Deferred.await(prompt.result); yield* drainEvents; calls.push(`drained:${prompt.index}`); }), }; const commandUpdates: Array<ReadonlyArray<AcpSchema.AvailableCommand>> = []; const adapter = yield* makeAntigravityAdapter( decodeSettings({ enabled: options?.enabled ?? true }), { instanceId, makeRuntime: (input) => Effect.gen(function* () { launches.push(input); yield* Effect.addFinalizer(() => Effect.gen(function* () { yield* Deferred.succeed(closeStarted, undefined); if (options?.holdClose) yield* Deferred.await(closeRelease); controls.closed += 1; }), ); return runtime; }), withProcess: (stop, task) => Effect.suspend(() => { stops.push(stop); return task; }), onAvailableCommands: (commands) => Effect.sync(() => { commandUpdates.push(commands); }), onAuthRequired: Effect.sync(() => { controls.authInvalidations += 1; }), }, ); yield* adapter.streamEvents.pipe( Stream.runForEach((event) => Effect.sync(() => { seen.push(event); }).pipe(Effect.andThen(Queue.offer(canonicalEvents, event))), ), Effect.forkScoped({ startImmediately: true }), ); yield* Effect.addFinalizer(() => Effect.all([ Deferred.succeed(cancelRelease, undefined), Deferred.succeed(closeRelease, undefined), Deferred.succeed(dispatchRelease, undefined), ]).pipe(Effect.asVoid), ); const waitForEvent = Effect.fn("AntigravityAdapterTest.waitForEvent")(function* < T extends ProviderRuntimeEvent, >(predicate: (event: ProviderRuntimeEvent) => event is T) { while (true) { const event = yield* Queue.take(canonicalEvents); if (predicate(event)) return event; } }); const invokePermission = (request: AcpSchema.RequestPermissionRequest) => Effect.suspend(() => permissionHandler ? permissionHandler(request) : Effect.die("Missing native permission handler"), ); return { fileHandlers, adapter, calls, launches, commandUpdates, controls, seen, stops, waitForEvent, emitNative, invokePermission, closeStarted, closeRelease, cancelRelease, dispatchStarted, dispatchRelease, nextPrompt: Queue.take(prompts), nextCancellation: Queue.take(cancellations), drainEvents, hasActivePrompt: () => active !== undefined, };}); const layer = ServerConfig.layerTest(process.cwd(), { prefix: "t3-antigravity-adapter-test-",}).pipe(Layer.provideMerge(NodeServices.layer)); it.layer(layer)("AntigravityAdapter", (it) => { it.effect( "runs native auth, resume, models, commands, and streaming through the ACP transport", () => Effect.gen(function* () { const fileSystem = yield* FileSystem.FileSystem; const path = yield* Path.Path; const crypto = yield* Crypto.Crypto; const childProcessSpawner = yield* ChildProcessSpawner.ChildProcessSpawner; const cwd = yield* fileSystem.makeTempDirectoryScoped({ prefix: "t3-antigravity-transport-", }); const mockAgentPath = yield* path.fromFileUrl( new URL("../../../scripts/acp-mock-agent.ts", import.meta.url), ); const requestLog = path.join(cwd, "requests.ndjson"); const commands: string[] = []; const modelSelections: string[] = []; const observed: ProviderRuntimeEvent[] = []; const completed = yield* Deferred.make<void>(); const adapter = yield* makeAntigravityAdapter(decodeSettings({ enabled: true }), { instanceId, withProcess: (_stop, task) => task, makeRuntime: (input) => makeAntigravityAcpRuntime({ ...input, childProcessSpawner, spawn: { command: process.execPath, args: [mockAgentPath], cwd: input.cwd, env: { ...process.env, T3_ACP_ANTIGRAVITY: "1", T3_ACP_REQUEST_LOG_PATH: requestLog, }, extendEnv: false, }, }).pipe(Effect.provideService(Crypto.Crypto, crypto)), onAvailableCommands: (available) => Effect.sync(() => { commands.push(...available.map((command) => command.name)); }), onConfigOptionsUpdated: (configOptions) => Effect.sync(() => { const model = configOptions.find((option) => option.category === "model"); if (model?.type === "select") modelSelections.push(model.currentValue); }), }); yield* adapter.streamEvents.pipe( Stream.runForEach((event) => Effect.gen(function* () { observed.push(event); if (event.type === "turn.completed") yield* Deferred.succeed(completed, undefined); }), ), Effect.forkScoped({ startImmediately: true }), ); const original = yield* adapter.startSession({ threadId, cwd, runtimeMode: "auto-accept-edits", modelSelection: { instanceId, model: nativeAlternative }, }); yield* adapter.stopSession(threadId); const resumed = yield* adapter.startSession({ threadId, cwd, runtimeMode: "auto-accept-edits", modelSelection: { instanceId, model: nativeAlternative }, resumeCursor: original.resumeCursor, }); expect(resumed.model).toBe(nativeAlternative); yield* adapter.sendTurn({ threadId, input: "Reply with one short line." }); yield* Deferred.await(completed); expect(commands).toEqual(["plan", "logout", "plan", "logout"]); expect(modelSelections.length).toBeGreaterThan(0); expect(modelSelections.every((model) => model === nativeAlternative)).toBe(true); expect( observed .filter((event) => event.type === "content.delta") .map((event) => event.payload.delta) .join(""), ).toBe("hello from mock"); const lines = (yield* fileSystem.readFileString(requestLog)).trim().split("\n"); const requests = yield* decodeRequestLog(lines); expect( requests .filter((request) => request.method === "authenticate") .map((request) => request.params), ).toEqual([{ methodId: "oauth-personal" }, { methodId: "oauth-personal" }]); expect(requests.some((request) => request.method === "session/resume")).toBe(true); expect(requests.some((request) => request.method === "session/load")).toBe(false); expect( requests .filter((request) => request.method === "session/set_config_option") .map((request) => request.params), ).toContainEqual({ sessionId: "mock-session-1", configId: "mode", value: "auto_edit" }); }), ); it.effect("reapplies the exact saved model and mode after a native resume", () => Effect.gen(function* () { const h = yield* makeHarness(); const first = yield* h.adapter.startSession({ threadId, cwd: process.cwd(), runtimeMode: "auto-accept-edits", modelSelection: { instanceId, model: nativeAlternative }, }); expect(first.model).toBe(nativeAlternative); yield* h.adapter.stopSession(threadId); const second = yield* h.adapter.startSession({ threadId, cwd: "/tmp", runtimeMode: "auto-accept-edits", resumeCursor: first.resumeCursor, modelSelection: { instanceId, model: nativeAlternative }, }); expect(second.model).toBe(nativeAlternative); // The adapter resolves the cwd it was given through the host Path. expect(second.cwd).toBe((yield* Path.Path).resolve("/tmp")); expect(h.launches[1]?.resumeSessionId).toBe(nativeSessionId); expect(h.calls).toEqual([ "start", `model:${nativeAlternative}`, "mode:auto_edit", "start", `model:${nativeAlternative}`, "mode:auto_edit", ]); expect(h.commandUpdates.at(-1)?.map((command) => command.name)).toEqual(["plan", "logout"]); expect(h.adapter.capabilities.supportsConversationRollback).toBe(false); const rollback = yield* h.adapter.rollbackThread(threadId, 1).pipe(Effect.exit); expect(Exit.isFailure(rollback)).toBe(true); }), ); it.effect("keeps thoughts, native command results, and replies on the active turn", () => Effect.gen(function* () { const h = yield* makeHarness(); yield* h.adapter.startSession({ threadId, cwd: process.cwd(), runtimeMode: "approval-required", }); const sending = yield* h.adapter .sendTurn({ threadId, input: "Read the file" }) .pipe(Effect.forkChild); const prompt = yield* h.nextPrompt; yield* h.emitNative({ _tag: "ThoughtDelta", text: "I will read it.", rawPayload: {} }); yield* h.emitNative({ _tag: "ToolCallUpdated", toolCall: { toolCallId: "command-1", kind: "execute", status: "completed", data: { rawInput: { CommandLine: "cat probe.txt", Cwd: "/tmp" }, rawOutput: { combinedOutput: "after\n", exitCode: 0 }, }, }, rawPayload: {}, }); yield* h.emitNative({ _tag: "ContentDelta", text: "The file says after.", rawPayload: {} }); yield* Deferred.succeed(prompt.result, { stopReason: "end_turn" }); const result = yield* Fiber.join(sending); yield* h.waitForEvent((event) => event.type === "turn.completed"); const deltas = h.seen.filter((event) => event.type === "content.delta"); expect(deltas.map((event) => event.payload.streamKind)).toEqual([ "reasoning_text", "assistant_text", ]); expect(deltas.every((event) => event.turnId === result.turnId)).toBe(true); const tool = h.seen.find( (event) => event.type === "item.completed" && event.payload.itemType === "command_execution", ); expect(tool?.type === "item.completed" ? tool.payload.data : undefined).toMatchObject({ command: "cat probe.txt", cwd: "/tmp", item: { aggregatedOutput: "after\n", exitCode: 0 }, }); }), ); it.effect("does not auto-approve a remaining native request in full access", () => Effect.gen(function* () { const h = yield* makeHarness(); yield* h.adapter.startSession({ threadId, cwd: process.cwd(), runtimeMode: "full-access" }); const permission = yield* h .invokePermission({ sessionId: nativeSessionId, toolCall: { toolCallId: "write-1", kind: "edit", title: "Write probe.txt" }, options: [ { optionId: "native:allow", name: "Allow", kind: "allow_once" }, { optionId: "native:deny", name: "Deny", kind: "reject_once" },Lines 1–500 of 1295. Load more
import * as NodeServices from "@effect/platform-node/NodeServices"; import { expect, it } from "@effect/vitest"; import { AntigravitySettings, ApprovalRequestId, ProviderInstanceId, ThreadId, type ProviderRuntimeEvent, } from "@t3tools/contracts"; import * as Deferred from "effect/Deferred"; import * as Crypto from "effect/Crypto"; import * as Effect from "effect/Effect"; import * as Exit from "effect/Exit"; import * as Fiber from "effect/Fiber"; import * as FileSystem from "effect/FileSystem"; import * as Layer from "effect/Layer"; import * as Path from "effect/Path"; import * as Queue from "effect/Queue"; import * as Schema from "effect/Schema"; import * as Stream from "effect/Stream"; import * as ChildProcessSpawner from "effect/unstable/process/ChildProcessSpawner"; import * as AcpErrors from "effect-acp/errors"; import type * as AcpSchema from "effect-acp/schema"; import { ServerConfig } from "../../config.ts"; import { ANTIGRAVITY_SIGN_IN_REQUIRED_MESSAGE } from "../antigravityAuthSupport.ts"; import type { AcpSessionRuntimeEvent } from "../acp/AcpSessionRuntime.ts"; import { makeAntigravityAcpRuntime } from "../acp/AntigravityAcpSupport.ts"; import { mergeToolCallState, parseSessionUpdateEvent, type AcpToolCallState, } from "../acp/AcpRuntimeModel.ts"; import { makeAntigravityAdapter, type AntigravityAdapterOptions } from "./AntigravityAdapter.ts"; const instanceId = ProviderInstanceId.make("antigravity-test"); const threadId = ThreadId.make("antigravity-thread"); const nativeSessionId = "b75db7e9-cd99-40e5-aa63-ac2b4674a6a9"; const nativeDefault = "gemini-test-low"; const nativeAlternative = "gemini-test-high"; const decodeSettings = Schema.decodeSync(AntigravitySettings); const decodeRequestLog = Schema.decodeEffect( Schema.Array( Schema.fromJsonString( Schema.Struct({ method: Schema.String, params: Schema.optional(Schema.Unknown) }), ), ), ); interface NativePrompt { readonly index: number; readonly content: ReadonlyArray<AcpSchema.ContentBlock>; readonly result: Deferred.Deferred<AcpSchema.PromptResponse, AcpErrors.AcpError>; } type Runtime = Effect.Success<ReturnType<AntigravityAdapterOptions["makeRuntime"]>>; function nativeToolUpdate( update: Extract< AcpSchema.SessionNotification["update"], { sessionUpdate: "tool_call" | "tool_call_update" } >, previous?: AcpToolCallState, ) { const event = parseSessionUpdateEvent({ sessionId: nativeSessionId, update }).events.find( (event) => event._tag === "ToolCallUpdated", ); if (!event) throw new Error("Expected a native tool update"); return { ...event, toolCall: mergeToolCallState(previous, event.toolCall) }; } const makeHarness = Effect.fn("makeAntigravityAdapterHarness")(function* (options?: { readonly enabled?: boolean; readonly holdCancel?: boolean; readonly holdClose?: boolean; readonly holdDispatch?: boolean; }) { const runtimeEvents = yield* Queue.unbounded<AcpSessionRuntimeEvent>(); const canonicalEvents = yield* Queue.unbounded<ProviderRuntimeEvent>(); const prompts = yield* Queue.unbounded<NativePrompt>(); const cancellations = yield* Queue.unbounded<number>(); const cancelRelease = yield* Deferred.make<void>(); const closeStarted = yield* Deferred.make<void>(); const closeRelease = yield* Deferred.make<void>(); const dispatchStarted = yield* Deferred.make<void>(); const dispatchRelease = yield* Deferred.make<void>(); const seen: ProviderRuntimeEvent[] = []; const calls: string[] = []; const launches: Array<Parameters<AntigravityAdapterOptions["makeRuntime"]>[0]> = []; const stops: Array<Effect.Effect<void>> = []; const controls = { failModel: false, failAuth: false, authInvalidations: 0, closed: 0 }; let currentModel = nativeDefault; let promptIndex = 0; let active: NativePrompt | undefined; const fileHandlers: { read?: Parameters<Runtime["handleReadTextFile"]>[0]; write?: Parameters<Runtime["handleWriteTextFile"]>[0]; } = {}; let permissionHandler: | (( request: AcpSchema.RequestPermissionRequest, ) => Effect.Effect<AcpSchema.RequestPermissionResponse, AcpErrors.AcpError>) | undefined; const configOptions = (): ReadonlyArray<AcpSchema.SessionConfigOption> => [ { id: "model", name: "Model", type: "select", category: "model", currentValue: currentModel, options: [ { value: nativeDefault, name: "Gemini test low" }, { value: nativeAlternative, name: "Gemini test high" }, ], }, ]; const drainEvents = Effect.gen(function* () { const acknowledge = yield* Deferred.make<void>(); yield* Queue.offer(runtimeEvents, { _tag: "EventStreamBarrier", acknowledge }); yield* Deferred.await(acknowledge); }); const emitNative = (event: AcpSessionRuntimeEvent) => Queue.offer(runtimeEvents, event).pipe(Effect.asVoid); const runtime: Effect.Success<ReturnType<AntigravityAdapterOptions["makeRuntime"]>> = { handleRequestPermission: (handler) => Effect.sync(() => { permissionHandler = handler; }), handleReadTextFile: (handler) => Effect.sync(() => { fileHandlers.read = handler; }), handleWriteTextFile: (handler) => Effect.sync(() => { fileHandlers.write = handler; }), start: () => Effect.gen(function* () { if (controls.failAuth) { return yield* new AcpErrors.AcpTransportError({ detail: ANTIGRAVITY_SIGN_IN_REQUIRED_MESSAGE, cause: undefined, }); } currentModel = nativeDefault; calls.push("start"); yield* emitNative({ _tag: "AvailableCommandsUpdated", availableCommands: [ { name: "plan", description: "Create a plan" }, { name: "logout", description: "Sign out" }, ], rawPayload: {}, }); return { sessionId: nativeSessionId, initializeResult: { protocolVersion: 1, agentCapabilities: { sessionCapabilities: { resume: {} } }, }, sessionSetupResult: { sessionId: nativeSessionId, configOptions: configOptions() }, modelConfigId: "model", }; }), getConfigOptions: Effect.sync(configOptions), setModel: (model) => Effect.gen(function* () { calls.push(`model:${model}`); if (controls.failModel) { controls.failModel = false; return yield* AcpErrors.AcpRequestError.invalidParams("Native model selection failed."); } currentModel = model; }), setMode: (mode) => Effect.sync(() => { calls.push(`mode:${mode}`); return {}; }), getEvents: () => Stream.fromQueue(runtimeEvents), drainEvents, prompt: (payload, promptOptions) => Effect.gen(function* () { yield* Deferred.succeed(dispatchStarted, undefined); if (options?.holdDispatch) yield* Deferred.await(dispatchRelease); const prompt: NativePrompt = { index: ++promptIndex, content: payload.prompt, result: yield* Deferred.make<AcpSchema.PromptResponse, AcpErrors.AcpError>(), }; active = prompt; calls.push(`prompt:${prompt.index}`); if (promptOptions?.dispatched) yield* Deferred.succeed(promptOptions.dispatched, undefined); yield* Queue.offer(prompts, prompt); return yield* Deferred.await(prompt.result).pipe( Effect.ensuring( Effect.sync(() => { if (active === prompt) active = undefined; }), ), ); }), cancel: Effect.gen(function* () { const prompt = active; if (!prompt) return; calls.push(`cancel:${prompt.index}`); yield* Queue.offer(cancellations, prompt.index); if (options?.holdCancel) yield* Deferred.await(cancelRelease); yield* Deferred.succeed(prompt.result, { stopReason: "cancelled" }); yield* Deferred.await(prompt.result); yield* drainEvents; calls.push(`drained:${prompt.index}`); }), }; const commandUpdates: Array<ReadonlyArray<AcpSchema.AvailableCommand>> = []; const adapter = yield* makeAntigravityAdapter( decodeSettings({ enabled: options?.enabled ?? true }), { instanceId, makeRuntime: (input) => Effect.gen(function* () { launches.push(input); yield* Effect.addFinalizer(() => Effect.gen(function* () { yield* Deferred.succeed(closeStarted, undefined); if (options?.holdClose) yield* Deferred.await(closeRelease); controls.closed += 1; }), ); return runtime; }), withProcess: (stop, task) => Effect.suspend(() => { stops.push(stop); return task; }), onAvailableCommands: (commands) => Effect.sync(() => { commandUpdates.push(commands); }), onAuthRequired: Effect.sync(() => { controls.authInvalidations += 1; }), }, ); yield* adapter.streamEvents.pipe( Stream.runForEach((event) => Effect.sync(() => { seen.push(event); }).pipe(Effect.andThen(Queue.offer(canonicalEvents, event))), ), Effect.forkScoped({ startImmediately: true }), ); yield* Effect.addFinalizer(() => Effect.all([ Deferred.succeed(cancelRelease, undefined), Deferred.succeed(closeRelease, undefined), Deferred.succeed(dispatchRelease, undefined), ]).pipe(Effect.asVoid), ); const waitForEvent = Effect.fn("AntigravityAdapterTest.waitForEvent")(function* < T extends ProviderRuntimeEvent, >(predicate: (event: ProviderRuntimeEvent) => event is T) { while (true) { const event = yield* Queue.take(canonicalEvents); if (predicate(event)) return event; } }); const invokePermission = (request: AcpSchema.RequestPermissionRequest) => Effect.suspend(() => permissionHandler ? permissionHandler(request) : Effect.die("Missing native permission handler"), ); return { fileHandlers, adapter, calls, launches, commandUpdates, controls, seen, stops, waitForEvent, emitNative, invokePermission, closeStarted, closeRelease, cancelRelease, dispatchStarted, dispatchRelease, nextPrompt: Queue.take(prompts), nextCancellation: Queue.take(cancellations), drainEvents, hasActivePrompt: () => active !== undefined, }; }); const layer = ServerConfig.layerTest(process.cwd(), { prefix: "t3-antigravity-adapter-test-", }).pipe(Layer.provideMerge(NodeServices.layer)); it.layer(layer)("AntigravityAdapter", (it) => { it.effect( "runs native auth, resume, models, commands, and streaming through the ACP transport", () => Effect.gen(function* () { const fileSystem = yield* FileSystem.FileSystem; const path = yield* Path.Path; const crypto = yield* Crypto.Crypto; const childProcessSpawner = yield* ChildProcessSpawner.ChildProcessSpawner; const cwd = yield* fileSystem.makeTempDirectoryScoped({ prefix: "t3-antigravity-transport-", }); const mockAgentPath = yield* path.fromFileUrl( new URL("../../../scripts/acp-mock-agent.ts", import.meta.url), ); const requestLog = path.join(cwd, "requests.ndjson"); const commands: string[] = []; const modelSelections: string[] = []; const observed: ProviderRuntimeEvent[] = []; const completed = yield* Deferred.make<void>(); const adapter = yield* makeAntigravityAdapter(decodeSettings({ enabled: true }), { instanceId, withProcess: (_stop, task) => task, makeRuntime: (input) => makeAntigravityAcpRuntime({ ...input, childProcessSpawner, spawn: { command: process.execPath, args: [mockAgentPath], cwd: input.cwd, env: { ...process.env, T3_ACP_ANTIGRAVITY: "1", T3_ACP_REQUEST_LOG_PATH: requestLog, }, extendEnv: false, }, }).pipe(Effect.provideService(Crypto.Crypto, crypto)), onAvailableCommands: (available) => Effect.sync(() => { commands.push(...available.map((command) => command.name)); }), onConfigOptionsUpdated: (configOptions) => Effect.sync(() => { const model = configOptions.find((option) => option.category === "model"); if (model?.type === "select") modelSelections.push(model.currentValue); }), }); yield* adapter.streamEvents.pipe( Stream.runForEach((event) => Effect.gen(function* () { observed.push(event); if (event.type === "turn.completed") yield* Deferred.succeed(completed, undefined); }), ), Effect.forkScoped({ startImmediately: true }), ); const original = yield* adapter.startSession({ threadId, cwd, runtimeMode: "auto-accept-edits", modelSelection: { instanceId, model: nativeAlternative }, }); yield* adapter.stopSession(threadId); const resumed = yield* adapter.startSession({ threadId, cwd, runtimeMode: "auto-accept-edits", modelSelection: { instanceId, model: nativeAlternative }, resumeCursor: original.resumeCursor, }); expect(resumed.model).toBe(nativeAlternative); yield* adapter.sendTurn({ threadId, input: "Reply with one short line." }); yield* Deferred.await(completed); expect(commands).toEqual(["plan", "logout", "plan", "logout"]); expect(modelSelections.length).toBeGreaterThan(0); expect(modelSelections.every((model) => model === nativeAlternative)).toBe(true); expect( observed .filter((event) => event.type === "content.delta") .map((event) => event.payload.delta) .join(""), ).toBe("hello from mock"); const lines = (yield* fileSystem.readFileString(requestLog)).trim().split("\n"); const requests = yield* decodeRequestLog(lines); expect( requests .filter((request) => request.method === "authenticate") .map((request) => request.params), ).toEqual([{ methodId: "oauth-personal" }, { methodId: "oauth-personal" }]); expect(requests.some((request) => request.method === "session/resume")).toBe(true); expect(requests.some((request) => request.method === "session/load")).toBe(false); expect( requests .filter((request) => request.method === "session/set_config_option") .map((request) => request.params), ).toContainEqual({ sessionId: "mock-session-1", configId: "mode", value: "auto_edit" }); }), ); it.effect("reapplies the exact saved model and mode after a native resume", () => Effect.gen(function* () { const h = yield* makeHarness(); const first = yield* h.adapter.startSession({ threadId, cwd: process.cwd(), runtimeMode: "auto-accept-edits", modelSelection: { instanceId, model: nativeAlternative }, }); expect(first.model).toBe(nativeAlternative); yield* h.adapter.stopSession(threadId); const second = yield* h.adapter.startSession({ threadId, cwd: "/tmp", runtimeMode: "auto-accept-edits", resumeCursor: first.resumeCursor, modelSelection: { instanceId, model: nativeAlternative }, }); expect(second.model).toBe(nativeAlternative); // The adapter resolves the cwd it was given through the host Path. expect(second.cwd).toBe((yield* Path.Path).resolve("/tmp")); expect(h.launches[1]?.resumeSessionId).toBe(nativeSessionId); expect(h.calls).toEqual([ "start", `model:${nativeAlternative}`, "mode:auto_edit", "start", `model:${nativeAlternative}`, "mode:auto_edit", ]); expect(h.commandUpdates.at(-1)?.map((command) => command.name)).toEqual(["plan", "logout"]); expect(h.adapter.capabilities.supportsConversationRollback).toBe(false); const rollback = yield* h.adapter.rollbackThread(threadId, 1).pipe(Effect.exit); expect(Exit.isFailure(rollback)).toBe(true); }), ); it.effect("keeps thoughts, native command results, and replies on the active turn", () => Effect.gen(function* () { const h = yield* makeHarness(); yield* h.adapter.startSession({ threadId, cwd: process.cwd(), runtimeMode: "approval-required", }); const sending = yield* h.adapter .sendTurn({ threadId, input: "Read the file" }) .pipe(Effect.forkChild); const prompt = yield* h.nextPrompt; yield* h.emitNative({ _tag: "ThoughtDelta", text: "I will read it.", rawPayload: {} }); yield* h.emitNative({ _tag: "ToolCallUpdated", toolCall: { toolCallId: "command-1", kind: "execute", status: "completed", data: { rawInput: { CommandLine: "cat probe.txt", Cwd: "/tmp" }, rawOutput: { combinedOutput: "after\n", exitCode: 0 }, }, }, rawPayload: {}, }); yield* h.emitNative({ _tag: "ContentDelta", text: "The file says after.", rawPayload: {} }); yield* Deferred.succeed(prompt.result, { stopReason: "end_turn" }); const result = yield* Fiber.join(sending); yield* h.waitForEvent((event) => event.type === "turn.completed"); const deltas = h.seen.filter((event) => event.type === "content.delta"); expect(deltas.map((event) => event.payload.streamKind)).toEqual([ "reasoning_text", "assistant_text", ]); expect(deltas.every((event) => event.turnId === result.turnId)).toBe(true); const tool = h.seen.find( (event) => event.type === "item.completed" && event.payload.itemType === "command_execution", ); expect(tool?.type === "item.completed" ? tool.payload.data : undefined).toMatchObject({ command: "cat probe.txt", cwd: "/tmp", item: { aggregatedOutput: "after\n", exitCode: 0 }, }); }), ); it.effect("does not auto-approve a remaining native request in full access", () => Effect.gen(function* () { const h = yield* makeHarness(); yield* h.adapter.startSession({ threadId, cwd: process.cwd(), runtimeMode: "full-access" }); const permission = yield* h .invokePermission({ sessionId: nativeSessionId, toolCall: { toolCallId: "write-1", kind: "edit", title: "Write probe.txt" }, options: [ { optionId: "native:allow", name: "Allow", kind: "allow_once" }, { optionId: "native:deny", name: "Deny", kind: "reject_once" },