1279 lines · 48.3 KB · TypeScript
RawDownload
import {  ApprovalRequestId,  EventId,  ProviderDriverKind,  ProviderInstanceId,  RuntimeRequestId,  RuntimeTaskId,  TurnId,  type AntigravitySettings,  type ProviderApprovalDecision,  type ProviderRuntimeEvent,  type ProviderSession,  type ProviderSetupError,  type ProviderUserInputAnswers,  type RuntimeTaskStatus,  type ThreadId,  type TurnCompletedPayload,} from "@t3tools/contracts";import * as Cause from "effect/Cause";import * as Crypto from "effect/Crypto";import * as DateTime from "effect/DateTime";import * as Deferred from "effect/Deferred";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 Option from "effect/Option";import * as Path from "effect/Path";import * as PubSub from "effect/PubSub";import * as Schema from "effect/Schema";import * as Scope from "effect/Scope";import * as Semaphore from "effect/Semaphore";import * as Stream from "effect/Stream";import * as SynchronizedRef from "effect/SynchronizedRef";import * as EffectAcpErrors from "effect-acp/errors";import type * as EffectAcpSchema from "effect-acp/schema"; import { ServerConfig } from "../../config.ts";import { buildRuntimeInstructions } from "../RuntimeInstructions.ts";import * as McpProviderSession from "../../mcp/McpProviderSession.ts";import type { AntigravityAuth } from "../AntigravityAuth.ts";import {  ProviderAdapterRequestError,  ProviderAdapterSessionClosedError,  ProviderAdapterSessionNotFoundError,  ProviderAdapterValidationError,  type ProviderAdapterError,} from "../Errors.ts";import {  ANTIGRAVITY_SIGN_IN_REQUIRED_MESSAGE,  isAntigravitySignInRequiredError,} from "../antigravityAuthSupport.ts";import { mapAcpToAdapterError } from "../acp/AcpAdapterSupport.ts";import {  makeAcpAssistantItemEvent,  makeAcpContentDeltaEvent,  makeAcpPlanUpdatedEvent,  makeAcpRequestOpenedEvent,  makeAcpRequestResolvedEvent,  makeAcpToolCallEvent,} from "../acp/AcpCoreRuntimeEvents.ts";import { makeAcpNativeLoggerFactory } from "../acp/AcpNativeLogging.ts";import { parsePermissionRequest, type AcpToolCallState } from "../acp/AcpRuntimeModel.ts";import type * as AcpSessionRuntime from "../acp/AcpSessionRuntime.ts";import {  antigravityPermissionMode,  antigravityModelOptions,  applyAntigravityAcpModelSelection,  buildAntigravityPrompt,  type AntigravityAcpRuntimeInput,  resolveAntigravityModel,} from "../acp/AntigravityAcpSupport.ts";import {  antigravityApprovalOptions,  antigravitySubagentOutput,  classifyAntigravitySubagentToolCall,  extractAntigravityUserInputQuestion,  isAntigravityOpenCommand,  isAntigravitySubagentReplayStart,  isAntigravityUserInputRequest,  makeAntigravityUserInputResponse,  normalizeAntigravityToolCall,  sanitizeAntigravityToolPayload,  selectAntigravityPermissionOptionId,} from "../acp/AntigravityProtocol.ts";import type { ProviderAdapterShape } from "../Services/ProviderAdapter.ts";import type { EventNdjsonLogger } from "./EventNdjsonLogger.ts"; const PROVIDER = ProviderDriverKind.make("antigravity");const ResumeCursor = Schema.Struct({  schemaVersion: Schema.Literal(1),  sessionId: Schema.NonEmptyString,});const decodeResumeCursor = Schema.decodeUnknownOption(ResumeCursor);const isAcpError = Schema.is(EffectAcpErrors.AcpError); type Adapter = ProviderAdapterShape<ProviderAdapterError>;type Runtime = Pick<  AcpSessionRuntime.AcpSessionRuntime["Service"],  | "handleRequestPermission"  | "handleReadTextFile"  | "handleWriteTextFile"  | "start"  | "setMode"  | "setModel"  | "getConfigOptions"  | "getEvents"  | "drainEvents"  | "prompt"  | "cancel">;type NativePermission = EffectAcpSchema.RequestPermissionRequest;type NativePermissionResponse = EffectAcpSchema.RequestPermissionResponse; function mapAntigravityError(threadId: ThreadId, method: string, cause: EffectAcpErrors.AcpError) {  return isAntigravitySignInRequiredError(cause)    ? new ProviderAdapterRequestError({        provider: PROVIDER,        method,        detail: ANTIGRAVITY_SIGN_IN_REQUIRED_MESSAGE,        cause,      })    : mapAcpToAdapterError(PROVIDER, threadId, method, cause);} export interface AntigravityAdapterOptions {  readonly instanceId: ProviderInstanceId;  readonly makeRuntime: (    input: Omit<AntigravityAcpRuntimeInput, "spawn" | "childProcessSpawner" | "onAuthorizationUrl">,  ) => Effect.Effect<Runtime, EffectAcpErrors.AcpError | ProviderSetupError, Scope.Scope>;  readonly withProcess: AntigravityAuth["withProcess"];  readonly onSessionStarted?: (    started: AcpSessionRuntime.AcpSessionRuntimeStartResult,    cwd: string,  ) => Effect.Effect<void>;  readonly onAvailableCommands?: (    commands: ReadonlyArray<EffectAcpSchema.AvailableCommand>,    cwd: string,  ) => Effect.Effect<void>;  readonly onConfigOptionsUpdated?: (    configOptions: ReadonlyArray<EffectAcpSchema.SessionConfigOption>,  ) => Effect.Effect<void>;  readonly onAuthRequired?: Effect.Effect<void>;  /** Model the provider default alias selects, when the account offers it. */  readonly defaultModel?: Effect.Effect<string | undefined>;  readonly nativeEventLogger?: EventNdjsonLogger;} interface PendingApproval {  readonly request: NativePermission;  readonly response: Deferred.Deferred<{    readonly decision: ProviderApprovalDecision;    readonly result: NativePermissionResponse;  }>;} interface PendingQuestion {  readonly request: NativePermission;  readonly response: Deferred.Deferred<{    readonly answers: ProviderUserInputAnswers;    readonly result: NativePermissionResponse;  }>;} interface OpenCommand {  readonly toolCall: AcpToolCallState;  readonly turnId: TurnId | undefined;  readonly promoted: boolean;} interface OpenSubagent {  readonly turnId: TurnId | undefined;  readonly status: "pending" | "running" | undefined;  readonly description?: string;} function subagentLinkage(toolCallId: string) {  return {    taskId: RuntimeTaskId.make(toolCallId),    taskType: "subagent_batch",    toolUseId: toolCallId,    title: "Antigravity subagent batch",  };} interface TurnIntent {  readonly turnId: TurnId;  readonly generation: number;  settled: boolean;} interface SessionContext {  readonly threadId: ThreadId;  readonly cwd: string;  readonly nativeSessionId: string;  readonly scope: Scope.Closeable;  readonly runtime: Runtime;  readonly promptLock: Semaphore.Semaphore;  readonly stopLock: Semaphore.Semaphore;  readonly commandLock: Semaphore.Semaphore;  readonly approvals: Map<ApprovalRequestId, PendingApproval>;  readonly questions: Map<ApprovalRequestId, PendingQuestion>;  readonly commands: Map<string, OpenCommand>;  /** Keep only IDs after settlement or MCP exclusion so merged late updates cannot change identity. */  readonly subagents: Map<string, OpenSubagent | "finished" | "mcp">;  readonly turns: Array<{ id: TurnId; items: Array<unknown> }>;  session: ProviderSession;  activeTurnId: TurnId | undefined;  promptFiber: Fiber.Fiber<EffectAcpSchema.PromptResponse, EffectAcpErrors.AcpError> | undefined;  generation: number;  stopped: boolean;  closed: boolean;  disconnected: boolean;} const CLIENT_FILE_MAX_BYTES = 8 * 1024 * 1024; function isInsideRoot(path: Path.Path, root: string, candidate: string): boolean {  const relative = path.relative(root, candidate);  return relative === "" || (!relative.startsWith("..") && !path.isAbsolute(relative));} /** Resolves an agent-supplied path and rejects anything outside the session roots. */const resolveClientFilePath = Effect.fn("AntigravityAdapter.resolveClientFilePath")(  function* (input: {    readonly fileSystem: FileSystem.FileSystem;    readonly path: Path.Path;    readonly allowedRoots: ReadonlyArray<string>;    readonly requestPath: string;  }) {    const { path } = input;    const resolved = path.resolve(input.requestPath);    // Follow symlinks on the parent so a link out of the workspace cannot escape it.    const parent = yield* input.fileSystem      .realPath(path.dirname(resolved))      .pipe(Effect.orElseSucceed(() => path.dirname(resolved)));    const real = path.join(parent, path.basename(resolved));    const roots = yield* Effect.forEach(input.allowedRoots, (root) =>      input.fileSystem.realPath(root).pipe(Effect.orElseSucceed(() => root)),    );    if (!roots.some((root) => isInsideRoot(path, root, real))) {      return yield* EffectAcpErrors.AcpRequestError.invalidParams(        `Path '${input.requestPath}' is outside the session workspace.`,      );    }    return real;  },); const readClientTextFile = Effect.fn("AntigravityAdapter.readClientTextFile")(function* (input: {  readonly fileSystem: FileSystem.FileSystem;  readonly path: Path.Path;  readonly allowedRoots: ReadonlyArray<string>;  readonly request: EffectAcpSchema.ReadTextFileRequest;}): Effect.fn.Return<EffectAcpSchema.ReadTextFileResponse, EffectAcpErrors.AcpError> {  const filePath = yield* resolveClientFilePath({ ...input, requestPath: input.request.path });  const info = yield* input.fileSystem    .stat(filePath)    .pipe(      Effect.mapError(() =>        EffectAcpErrors.AcpRequestError.resourceNotFound(`File '${input.request.path}' not found.`),      ),    );  if (info.type !== "File" || Number(info.size) > CLIENT_FILE_MAX_BYTES) {    return yield* EffectAcpErrors.AcpRequestError.invalidParams(      `File '${input.request.path}' is not a readable text file under ${CLIENT_FILE_MAX_BYTES} bytes.`,    );  }  const text = yield* input.fileSystem    .readFileString(filePath)    .pipe(      Effect.mapError(() =>        EffectAcpErrors.AcpRequestError.internalError(`Could not read '${input.request.path}'.`),      ),    );  const line = input.request.line ?? undefined;  const limit = input.request.limit ?? undefined;  if (line === undefined && limit === undefined) {    return { content: text };  }  // ACP lines are 1-indexed. `limit` is a line count.  const lines = text.split("\n");  const start = Math.max(0, (line ?? 1) - 1);  const end = limit === undefined ? lines.length : Math.min(lines.length, start + limit);  return { content: lines.slice(start, end).join("\n") };}); const writeClientTextFile = Effect.fn("AntigravityAdapter.writeClientTextFile")(function* (input: {  readonly fileSystem: FileSystem.FileSystem;  readonly path: Path.Path;  readonly allowedRoots: ReadonlyArray<string>;  readonly request: EffectAcpSchema.WriteTextFileRequest;}): Effect.fn.Return<EffectAcpSchema.WriteTextFileResponse, EffectAcpErrors.AcpError> {  const filePath = yield* resolveClientFilePath({ ...input, requestPath: input.request.path });  yield* input.fileSystem.makeDirectory(input.path.dirname(filePath), { recursive: true }).pipe(    Effect.andThen(input.fileSystem.writeFileString(filePath, input.request.content)),    Effect.mapError(() =>      EffectAcpErrors.AcpRequestError.internalError(`Could not write '${input.request.path}'.`),    ),  );  return {};}); /** Keeps one official ACP process per thread and drains a cancelled prompt before steering. */export const makeAntigravityAdapter = Effect.fn("makeAntigravityAdapter")(function* (  settings: AntigravitySettings,  options: AntigravityAdapterOptions,) {  const crypto = yield* Crypto.Crypto;  const fileSystem = yield* FileSystem.FileSystem;  const path = yield* Path.Path;  const serverConfig = yield* ServerConfig;  const ownerScope = yield* Effect.scope;  const makeNativeLoggers = yield* makeAcpNativeLoggerFactory();  const sessions = new Map<ThreadId, SessionContext>();  const locks = yield* SynchronizedRef.make(new Map<ThreadId, Semaphore.Semaphore>());  const events = yield* PubSub.unbounded<ProviderRuntimeEvent>();  const nowIso = Effect.map(DateTime.now, DateTime.formatIso);  const randomId = crypto.randomUUIDv4.pipe(    Effect.mapError(      (cause) =>        new ProviderAdapterRequestError({          provider: PROVIDER,          method: "crypto/randomUUIDv4",          detail: "Could not create an Antigravity event ID.",          cause,        }),    ),  );  const stamp = Effect.all({    eventId: Effect.map(randomId, EventId.make),    createdAt: nowIso,  });  const emit = (event: ProviderRuntimeEvent) => PubSub.publish(events, event).pipe(Effect.asVoid);   const withThreadLock = <A, E, R>(threadId: ThreadId, task: Effect.Effect<A, E, R>) =>    SynchronizedRef.modifyEffect(locks, (current) => {      const existing = current.get(threadId);      if (existing) return Effect.succeed([existing, current] as const);      return Semaphore.make(1).pipe(        Effect.map((lock) => [lock, new Map(current).set(threadId, lock)] as const),      );    }).pipe(Effect.flatMap((lock) => lock.withPermit(task)));   const requireSession = (threadId: ThreadId) => {    const context = sessions.get(threadId);    return context && !context.stopped      ? Effect.succeed(context)      : Effect.fail(new ProviderAdapterSessionNotFoundError({ provider: PROVIDER, threadId }));  };   const cancelRequests = Effect.fn("AntigravityAdapter.cancelRequests")(function* (    context: SessionContext,  ) {    for (const pending of context.approvals.values()) {      yield* Deferred.succeed(pending.response, {        decision: "cancel",        result: { outcome: { outcome: "cancelled" } },      });    }    for (const pending of context.questions.values()) {      yield* Deferred.succeed(pending.response, {        answers: {},        result: { outcome: { outcome: "cancelled" } },      });    }  });   const finishBackgroundCommands = (context: SessionContext) =>    context.commandLock.withPermit(      Effect.gen(function* () {        for (const [id, command] of context.commands) {          if (!command.promoted) continue;          yield* emit({            type: "task.completed",            ...(yield* stamp),            provider: PROVIDER,            threadId: context.threadId,            turnId: command.turnId,            payload: {              taskId: RuntimeTaskId.make(id),              taskType: "local_bash",              toolUseId: id,              status: "stopped",            },          });        }        context.commands.clear();      }),    );   const finishSubagents = (    context: SessionContext,    status: Extract<RuntimeTaskStatus, "cancelled" | "failed" | "idle">,    error?: string,  ) =>    context.commandLock.withPermit(      Effect.gen(function* () {        for (const [id, subagent] of context.subagents) {          if (subagent === "finished" || subagent === "mcp") continue;          yield* emit({            type: "task.updated",            ...(yield* stamp),            provider: PROVIDER,            threadId: context.threadId,            turnId: subagent.turnId,            payload: {              ...subagentLinkage(id),              status,              ...(status === "idle"                ? {                    description: "Turn ended. Individual agent status is unavailable.",                    timelineBypass: true,                  }                : {}),              ...(error ? { error } : {}),            },          });          context.subagents.set(id, "finished");        }      }),    );   const stopContext = (context: SessionContext) =>    context.stopLock      .withPermit(        Effect.gen(function* () {          if (context.closed) return;          context.stopped = true;          yield* Effect.gen(function* () {            yield* cancelRequests(context);            if (context.promptFiber && !context.disconnected) {              yield* Effect.ignore(context.runtime.cancel);            }          }).pipe(Effect.ensuring(Scope.close(context.scope, Exit.void)));          context.closed = true;          if (sessions.get(context.threadId) === context) sessions.delete(context.threadId);          yield* finishBackgroundCommands(context);          yield* finishSubagents(            context,            context.disconnected ? "failed" : "cancelled",            context.disconnected ? "Antigravity process stopped." : undefined,          );          context.subagents.clear();          yield* emit({            type: "session.exited",            ...(yield* stamp),            provider: PROVIDER,            threadId: context.threadId,            payload: {              exitKind: context.disconnected ? "error" : "graceful",              ...(context.disconnected ? { reason: "Antigravity process stopped." } : {}),            },          });        }),      )      .pipe(Effect.uninterruptible);   const handlePermission = Effect.fn("AntigravityAdapter.handlePermission")(function* (    context: SessionContext,    request: NativePermission,  ): Effect.fn.Return<NativePermissionResponse, ProviderAdapterError> {    if (context.stopped || request.sessionId !== context.nativeSessionId) {      return { outcome: { outcome: "cancelled" } };    }    const requestId = ApprovalRequestId.make(yield* randomId);    const runtimeRequestId = RuntimeRequestId.make(requestId);    const turnId = context.activeTurnId;    const rawPayload = sanitizeAntigravityToolPayload(request);     if (isAntigravityUserInputRequest(request)) {      const question = extractAntigravityUserInputQuestion(request);      if (!question) return { outcome: { outcome: "cancelled" } };      const response = yield* Deferred.make<{        answers: ProviderUserInputAnswers;        result: NativePermissionResponse;      }>();      context.questions.set(requestId, { request, response });      return yield* Effect.gen(function* () {        yield* emit({          type: "user-input.requested",          ...(yield* stamp),          provider: PROVIDER,          threadId: context.threadId,          turnId,          requestId: runtimeRequestId,          payload: { questions: [question] },          raw: { source: "acp.jsonrpc", method: "session/request_permission", payload: rawPayload },        });        const answer = yield* Deferred.await(response);        yield* emit({          type: "user-input.resolved",          ...(yield* stamp),          provider: PROVIDER,          threadId: context.threadId,          turnId,          requestId: runtimeRequestId,          payload: { answers: answer.answers },        });        return answer.result;

Lines 1–500 of 1279.