| 1 | import * as ScriptProgress from "@t3tools/provider-core/server/ScriptProgress"; |
| 2 | import { withAgentDeviceEnvironment } from "@t3tools/provider-core/server/mcpSession"; |
| 3 | import * as McpProviderSessions from "@t3tools/provider-core/server/McpProviderSessions"; |
| 4 | import { AntigravitySettings, ProviderDriverKind, ProviderSetupError } from "@t3tools/contracts"; |
| 5 | import * as HostProcess from "@t3tools/shared/HostProcess"; |
| 6 | import { resolveSelfInvocation } from "@t3tools/shared/nodeRuntime"; |
| 7 | import { |
| 8 | NodeRuntimeUnavailableError, |
| 9 | nodeRuntimeUnavailableMessage, |
| 10 | } from "@t3tools/shared/nodeRuntime"; |
| 11 | import * as Crypto from "effect/Crypto"; |
| 12 | import * as Deferred from "effect/Deferred"; |
| 13 | import * as Effect from "effect/Effect"; |
| 14 | import * as Exit from "effect/Exit"; |
| 15 | import * as FileSystem from "effect/FileSystem"; |
| 16 | import * as Path from "effect/Path"; |
| 17 | import * as Schema from "effect/Schema"; |
| 18 | import * as Scope from "effect/Scope"; |
| 19 | import * as Stream from "effect/Stream"; |
| 20 | import * as ChildProcessSpawner from "effect/process/ChildProcessSpawner"; |
| 21 | import { HttpClient } from "effect/http"; |
| 22 | import type { AcpError } from "effect-acp/errors"; |
| 23 | |
| 24 | import * as ProviderHost from "@t3tools/provider-core/server/ProviderHost"; |
| 25 | import { |
| 26 | isAntigravityTextGenerationAvailable, |
| 27 | makeAntigravityTextGeneration, |
| 28 | } from "../../textGeneration/AntigravityTextGeneration.ts"; |
| 29 | import { makeAntigravityAuth, type AntigravityAuth } from "../AntigravityAuth.ts"; |
| 30 | import * as AntigravityInstallation from "../AntigravityInstallation.ts"; |
| 31 | import * as AntigravityUsage from "./AntigravityUsage.ts"; |
| 32 | import { |
| 33 | antigravityAuthConfigIssue, |
| 34 | antigravityAuthLabel, |
| 35 | antigravityAuthUsesBrowser, |
| 36 | buildAntigravityAcpSpawnInput, |
| 37 | isAntigravitySignInRequiredError, |
| 38 | prepareAntigravityProfile, |
| 39 | resolveAntigravityInstanceDirectories, |
| 40 | type AntigravityAuthConfig, |
| 41 | } from "../antigravityAuthSupport.ts"; |
| 42 | import { |
| 43 | makeAntigravityAcpRuntime, |
| 44 | type AntigravityAcpRuntimeInput, |
| 45 | } from "../acp/AntigravityAcpSupport.ts"; |
| 46 | import type * as AcpSessionRuntime from "@t3tools/provider-acp/server/AcpSessionRuntime"; |
| 47 | import type { ServerProviderDraft } from "@t3tools/provider-core/server/snapshotProbe"; |
| 48 | import { |
| 49 | removeAntigravityRuntimeTempDirs, |
| 50 | removeAntigravitySessionFiles, |
| 51 | } from "../acp/AntigravitySessionFiles.ts"; |
| 52 | import * as IdAllocator from "@t3tools/provider-core/server/IdAllocator"; |
| 53 | import * as ProviderContinuationRequests from "@t3tools/provider-core/server/ProviderContinuationRequests"; |
| 54 | import { makeAntigravityAdapterV2 } from "../../orchestration-v2/Adapters/AntigravityAdapterV2.ts"; |
| 55 | import { makeAcpNativeLoggerFactory } from "@t3tools/provider-acp/server/nativeLogging"; |
| 56 | import { ProviderDriverError } from "../Errors.ts"; |
| 57 | import { makeAntigravityProvider } from "../AntigravityProvider.ts"; |
| 58 | import { makeAntigravityUsageProbe } from "../antigravityUsageLimits.ts"; |
| 59 | import * as ProviderEventLoggers from "@t3tools/provider-core/server/ProviderEventLoggers"; |
| 60 | import * as ModelCatalog from "@t3tools/provider-core/server/ModelCatalog"; |
| 61 | import { |
| 62 | defaultProviderContinuationIdentity, |
| 63 | type ProviderDriver, |
| 64 | type ProviderInstance, |
| 65 | } from "@t3tools/provider-core/server/driver"; |
| 66 | import { mergeProviderInstanceEnvironment } from "@t3tools/provider-core/server/instanceEnvironment"; |
| 67 | import { withInstanceIdentity } from "@t3tools/provider-core/server/instanceIdentity"; |
| 68 | import { discoverAntigravitySkills, resolveAntigravityUserHome } from "./AntigravitySkills.ts"; |
| 69 | |
| 70 | const DRIVER = ProviderDriverKind.make("antigravity"); |
| 71 | const decodeSettings = Schema.decodeSync(AntigravitySettings); |
| 72 | const isNodeRuntimeUnavailableError = Schema.is(NodeRuntimeUnavailableError); |
| 73 | |
| 74 | export type AntigravityDriverEnv = |
| 75 | | AntigravityInstallation.AntigravityInstallation |
| 76 | | ProviderHost.ProviderHost |
| 77 | | ChildProcessSpawner.ChildProcessSpawner |
| 78 | | Crypto.Crypto |
| 79 | | FileSystem.FileSystem |
| 80 | | HttpClient.HttpClient |
| 81 | | IdAllocator.IdAllocatorV2 |
| 82 | | ScriptProgress.ScriptProgress |
| 83 | | McpProviderSessions.McpProviderSessions |
| 84 | | ModelCatalog.ModelCatalog |
| 85 | | Path.Path |
| 86 | | ProviderEventLoggers.ProviderEventLoggers; |
| 87 | |
| 88 | /** Each instance owns its Google profile. Executable releases are shared by the environment. */ |
| 89 | export const AntigravityDriver: ProviderDriver< |
| 90 | AntigravitySettings, |
| 91 | AntigravityDriverEnv, |
| 92 | AntigravityUsage.AntigravityUsage |
| 93 | > = { |
| 94 | driverKind: DRIVER, |
| 95 | metadata: { displayName: "Antigravity", supportsMultipleInstances: true }, |
| 96 | configSchema: AntigravitySettings, |
| 97 | defaultConfig: () => decodeSettings({}), |
| 98 | usage: AntigravityUsage.antigravityUsageReader, |
| 99 | create: ({ instanceId, displayName, accentColor, environment, enabled, config }) => |
| 100 | Effect.gen(function* () { |
| 101 | const crypto = yield* Crypto.Crypto; |
| 102 | const fileSystem = yield* FileSystem.FileSystem; |
| 103 | const httpClient = yield* HttpClient.HttpClient; |
| 104 | const path = yield* Path.Path; |
| 105 | const spawner = yield* ChildProcessSpawner.ChildProcessSpawner; |
| 106 | const host = yield* ProviderHost.ProviderHost; |
| 107 | const selfInvocation = yield* resolveSelfInvocation(); |
| 108 | const installation = yield* AntigravityInstallation.AntigravityInstallation; |
| 109 | const loggers = yield* ProviderEventLoggers.ProviderEventLoggers; |
| 110 | const modelCatalog = yield* ModelCatalog.ModelCatalog; |
| 111 | const currentCatalog = modelCatalog.current(DRIVER); |
| 112 | const continuationRequests = yield* ProviderContinuationRequests.ProviderContinuationRequests; |
| 113 | const makeNativeLogger = yield* makeAcpNativeLoggerFactory(); |
| 114 | const settings = { ...config, enabled } satisfies AntigravitySettings; |
| 115 | const auth: AntigravityAuthConfig = { |
| 116 | authMethod: settings.authMethod, |
| 117 | apiKey: settings.apiKey, |
| 118 | gcpProject: settings.gcpProject, |
| 119 | gcpLocation: settings.gcpLocation, |
| 120 | }; |
| 121 | const authConfigIssue = antigravityAuthConfigIssue(auth); |
| 122 | const processEnvironment = yield* mergeProviderInstanceEnvironment(environment); |
| 123 | const userHome = resolveAntigravityUserHome( |
| 124 | yield* HostProcess.Platform, |
| 125 | processEnvironment, |
| 126 | yield* HostProcess.HomeDirectory, |
| 127 | ); |
| 128 | const directories = yield* resolveAntigravityInstanceDirectories( |
| 129 | host.paths.stateDir, |
| 130 | instanceId, |
| 131 | ).pipe( |
| 132 | Effect.provideService(Crypto.Crypto, crypto), |
| 133 | Effect.provideService(Path.Path, path), |
| 134 | Effect.mapError( |
| 135 | (cause) => |
| 136 | new ProviderDriverError({ |
| 137 | driver: DRIVER, |
| 138 | instanceId, |
| 139 | detail: "Could not resolve the Antigravity profile directory.", |
| 140 | cause, |
| 141 | }), |
| 142 | ), |
| 143 | ); |
| 144 | const profileDirectory = directories.profile; |
| 145 | // No process of this instance exists yet, so every runtime temp |
| 146 | // directory it owns is an orphan from a killed server. Older builds |
| 147 | // unpacked inside the profile. |
| 148 | for (const directory of [ |
| 149 | directories.runtimeTemp, |
| 150 | path.join(profileDirectory, "antigravity-acp", "tmp"), |
| 151 | ]) { |
| 152 | yield* removeAntigravityRuntimeTempDirs(directory).pipe( |
| 153 | Effect.provideService(FileSystem.FileSystem, fileSystem), |
| 154 | ); |
| 155 | } |
| 156 | const continuationIdentity = defaultProviderContinuationIdentity({ |
| 157 | driverKind: DRIVER, |
| 158 | instanceId, |
| 159 | }); |
| 160 | const stampIdentity = withInstanceIdentity({ |
| 161 | instanceId, |
| 162 | driverKind: DRIVER, |
| 163 | displayName, |
| 164 | accentColor, |
| 165 | continuationGroupKey: continuationIdentity.continuationKey, |
| 166 | }); |
| 167 | const classifyModels = (draft: ServerProviderDraft) => |
| 168 | currentCatalog.pipe( |
| 169 | Effect.map((catalog) => stampIdentity(ModelCatalog.applyModelCatalog(draft, catalog))), |
| 170 | ); |
| 171 | |
| 172 | const makeRuntime = Effect.fn("AntigravityDriver.makeRuntime")(function* ( |
| 173 | input: Omit<AntigravityAcpRuntimeInput, "spawn" | "childProcessSpawner">, |
| 174 | ): Effect.fn.Return< |
| 175 | AcpSessionRuntime.AcpSessionRuntime["Service"], |
| 176 | AcpError | ProviderSetupError, |
| 177 | Scope.Scope |
| 178 | > { |
| 179 | if (authConfigIssue !== null) { |
| 180 | return yield* new ProviderSetupError({ |
| 181 | instanceId, |
| 182 | operation: "configure", |
| 183 | detail: authConfigIssue, |
| 184 | }); |
| 185 | } |
| 186 | const executable = yield* installation |
| 187 | .acquire(settings.binaryPath, processEnvironment) |
| 188 | .pipe( |
| 189 | Effect.mapError( |
| 190 | (cause) => |
| 191 | new ProviderSetupError({ |
| 192 | instanceId, |
| 193 | operation: "resolve", |
| 194 | detail: cause.detail, |
| 195 | cause, |
| 196 | }), |
| 197 | ), |
| 198 | ); |
| 199 | const profile = yield* prepareAntigravityProfile({ |
| 200 | profileDirectory, |
| 201 | baseEnv: processEnvironment, |
| 202 | auth, |
| 203 | userHome, |
| 204 | tempDirectory: directories.runtimeTemp, |
| 205 | }).pipe( |
| 206 | Effect.provideService(FileSystem.FileSystem, fileSystem), |
| 207 | Effect.provideService(Path.Path, path), |
| 208 | Effect.provideService(ChildProcessSpawner.ChildProcessSpawner, spawner), |
| 209 | Effect.mapError((cause) => |
| 210 | isNodeRuntimeUnavailableError(cause.cause) |
| 211 | ? new ProviderSetupError({ |
| 212 | instanceId, |
| 213 | operation: "start", |
| 214 | detail: nodeRuntimeUnavailableMessage("Antigravity sign-in"), |
| 215 | cause, |
| 216 | }) |
| 217 | : cause, |
| 218 | ), |
| 219 | ); |
| 220 | // Each process unpacks into its own directory that dies with the |
| 221 | // runtime scope, after the child is killed. A shared directory would |
| 222 | // let one session's teardown delete files a sibling still reads. |
| 223 | // Removal is best effort: a handle can outlive the kill on Windows, |
| 224 | // and the sweep on the next driver start reclaims what is left. |
| 225 | const runtimeTempDirectory = yield* Effect.acquireRelease( |
| 226 | fileSystem.makeTempDirectory({ directory: profile.tempDirectory, prefix: "run-" }).pipe( |
| 227 | Effect.mapError( |
| 228 | (cause) => |
| 229 | new ProviderSetupError({ |
| 230 | instanceId, |
| 231 | operation: "start", |
| 232 | detail: "Could not create an Antigravity runtime temp directory.", |
| 233 | cause, |
| 234 | }), |
| 235 | ), |
| 236 | ), |
| 237 | (directory) => |
| 238 | fileSystem |
| 239 | .remove(directory, { recursive: true, force: true }) |
| 240 | .pipe( |
| 241 | Effect.catch(() => |
| 242 | Effect.logWarning("Could not remove an Antigravity runtime temp directory."), |
| 243 | ), |
| 244 | ), |
| 245 | ); |
| 246 | const runtime = yield* makeAntigravityAcpRuntime({ |
| 247 | ...input, |
| 248 | authMethod: auth.authMethod, |
| 249 | childProcessSpawner: spawner, |
| 250 | spawn: buildAntigravityAcpSpawnInput({ |
| 251 | installation: executable, |
| 252 | profile, |
| 253 | cwd: input.cwd, |
| 254 | baseEnv: ScriptProgress.withScriptProgressEnvironment( |
| 255 | withAgentDeviceEnvironment(processEnvironment, input), |
| 256 | input.processEnvironment ?? {}, |
| 257 | ), |
| 258 | auth, |
| 259 | runtimeTempDirectory, |
| 260 | }), |
| 261 | }).pipe(Effect.provideService(Crypto.Crypto, crypto)); |
| 262 | return { |
| 263 | ...runtime, |
| 264 | start: () => |
| 265 | runtime |
| 266 | .start() |
| 267 | .pipe( |
| 268 | Effect.tapError((cause): Effect.Effect<void> => |
| 269 | input.onAuthorizationUrl === undefined && isAntigravitySignInRequiredError(cause) |
| 270 | ? provider.onAuthRequired |
| 271 | : Effect.void, |
| 272 | ), |
| 273 | ), |
| 274 | }; |
| 275 | }); |
| 276 | |
| 277 | const makeDisposableRuntime = Effect.fn("AntigravityDriver.makeDisposableRuntime")(function* ( |
| 278 | input: Pick<AntigravityAcpRuntimeInput, "onAuthorizationUrl">, |
| 279 | ) { |
| 280 | const cwd = yield* fileSystem |
| 281 | .makeTempDirectoryScoped({ prefix: "t3-antigravity-setup-" }) |
| 282 | .pipe( |
| 283 | Effect.mapError( |
| 284 | () => |
| 285 | new ProviderSetupError({ |
| 286 | instanceId, |
| 287 | operation: "start", |
| 288 | detail: "Could not create an Antigravity setup workspace.", |
| 289 | }), |
| 290 | ), |
| 291 | ); |
| 292 | let sessionId: string | undefined; |
| 293 | yield* Effect.addFinalizer(() => |
| 294 | removeAntigravitySessionFiles({ |
| 295 | profileDirectory, |
| 296 | sessionId, |
| 297 | cwd, |
| 298 | }).pipe( |
| 299 | Effect.provideService(FileSystem.FileSystem, fileSystem), |
| 300 | Effect.provideService(Path.Path, path), |
| 301 | ), |
| 302 | ); |
| 303 | const runtime = yield* makeRuntime({ |
| 304 | cwd, |
| 305 | clientInfo: { name: "t3-code-provider-setup", version: "0.0.0" }, |
| 306 | mcpServers: [], |
| 307 | ...(input.onAuthorizationUrl ? { onAuthorizationUrl: input.onAuthorizationUrl } : {}), |
| 308 | }); |
| 309 | return { |
| 310 | ...runtime, |
| 311 | start: () => |
| 312 | runtime.start().pipe( |
| 313 | Effect.tap((started) => |
| 314 | Effect.sync(() => { |
| 315 | sessionId = started.sessionId; |
| 316 | }), |
| 317 | ), |
| 318 | ), |
| 319 | }; |
| 320 | }); |
| 321 | |
| 322 | const publishCatalog = ( |
| 323 | started: AcpSessionRuntime.AcpSessionRuntimeStartResult, |
| 324 | runtime: Pick<AcpSessionRuntime.AcpSessionRuntime["Service"], "getEvents" | "drainEvents">, |
| 325 | ): Effect.Effect<void> => |
| 326 | Effect.gen(function* () { |
| 327 | yield* provider.onSessionStarted(started); |
| 328 | yield* Stream.runForEach(runtime.getEvents(), (event) => { |
| 329 | if (event._tag === "EventStreamBarrier") { |
| 330 | return Deferred.succeed(event.acknowledge, undefined).pipe(Effect.asVoid); |
| 331 | } |
| 332 | if (event._tag === "ConfigOptionsUpdated") { |
| 333 | return provider.onConfigOptionsUpdated(event.configOptions); |
| 334 | } |
| 335 | return event._tag === "AvailableCommandsUpdated" |
| 336 | ? provider.onAvailableCommands(event.availableCommands) |
| 337 | : Effect.void; |
| 338 | }).pipe(Effect.forkScoped); |
| 339 | yield* runtime.drainEvents; |
| 340 | }).pipe(Effect.scoped); |
| 341 | |
| 342 | const authFlow: AntigravityAuth = yield* makeAntigravityAuth({ |
| 343 | instanceId, |
| 344 | makeRuntime: makeDisposableRuntime, |
| 345 | onAuthenticated: publishCatalog, |
| 346 | onSignedOut: Effect.suspend(() => provider.onSignedOut), |
| 347 | usesBrowser: antigravityAuthUsesBrowser(auth.authMethod), |
| 348 | }); |
| 349 | |
| 350 | // Kick the TTL-gated manifest refresh alongside the health check, as |
| 351 | // Codex and Claude do. Without it an environment that only runs |
| 352 | // Antigravity would keep classifying against a stale disk cache. |
| 353 | // The probe must not spawn. The agent is a PyInstaller one-file bundle |
| 354 | // that unpacks about 1 GB per launch, and the health check runs every |
| 355 | // minute. Resolving the install on disk is enough to report installed |
| 356 | // and version. The response below is synthetic: only agentInfo.version |
| 357 | // is read from it. Sessions and manual refreshes still spawn. |
| 358 | const probe = Effect.gen(function* () { |
| 359 | yield* modelCatalog.refreshInBackground; |
| 360 | if (authConfigIssue !== null) { |
| 361 | return yield* new ProviderSetupError({ |
| 362 | instanceId, |
| 363 | operation: "configure", |
| 364 | detail: authConfigIssue, |
| 365 | }); |
| 366 | } |
| 367 | const executable = yield* installation |
| 368 | .resolve(settings.binaryPath, processEnvironment) |
| 369 | .pipe( |
| 370 | Effect.mapError( |
| 371 | (cause) => |
| 372 | new ProviderSetupError({ |
| 373 | instanceId, |
| 374 | operation: "resolve", |
| 375 | detail: cause.detail, |
| 376 | cause, |
| 377 | }), |
| 378 | ), |
| 379 | ); |
| 380 | return { |
| 381 | protocolVersion: 1, |
| 382 | agentCapabilities: { |
| 383 | loadSession: true, |
| 384 | promptCapabilities: { image: true, audio: true, embeddedContext: true }, |
| 385 | sessionCapabilities: { list: {}, resume: {} }, |
| 386 | }, |
| 387 | authMethods: [{ id: "oauth-personal", name: "Log in with Google" }], |
| 388 | agentInfo: { |
| 389 | name: "antigravity-acp", |
| 390 | title: "Google Antigravity", |
| 391 | version: executable.version ?? "unknown", |
| 392 | }, |
| 393 | }; |
| 394 | }); |
| 395 | |
| 396 | const usage = yield* makeAntigravityUsageProbe({ |
| 397 | profileDirectory, |
| 398 | authMethod: auth.authMethod, |
| 399 | }).pipe( |
| 400 | Effect.provideService(FileSystem.FileSystem, fileSystem), |
| 401 | Effect.provideService(Path.Path, path), |
| 402 | Effect.provideService(Crypto.Crypto, crypto), |
| 403 | Effect.provideService(HttpClient.HttpClient, httpClient), |
| 404 | ); |
| 405 | const provider = yield* makeAntigravityProvider(settings, { |
| 406 | stampIdentity: classifyModels, |
| 407 | probe, |
| 408 | probeUsage: usage.probe, |
| 409 | clearUsage: usage.clear, |
| 410 | auth: { type: auth.authMethod, label: antigravityAuthLabel(auth.authMethod) }, |
| 411 | supportsTextGeneration: isAntigravityTextGenerationAvailable(profileDirectory).pipe( |
| 412 | Effect.provideService(FileSystem.FileSystem, fileSystem), |
| 413 | Effect.provideService(Path.Path, path), |
| 414 | Effect.orElseSucceed(() => false), |
| 415 | ), |
| 416 | }).pipe( |
| 417 | Effect.mapError( |
| 418 | (cause) => |
| 419 | new ProviderDriverError({ |
| 420 | driver: DRIVER, |
| 421 | instanceId, |
| 422 | detail: "Could not prepare the Antigravity provider status.", |
| 423 | cause, |
| 424 | }), |
| 425 | ), |
| 426 | ); |
| 427 | const defaultModel = currentCatalog.pipe(Effect.map((catalog) => catalog?.defaultChatModel)); |
| 428 | const orchestrationAdapter = yield* makeAntigravityAdapterV2({ |
| 429 | instanceId, |
| 430 | selfInvocation, |
| 431 | makeRuntime, |
| 432 | withProcess: authFlow.withProcess, |
| 433 | defaultModel, |
| 434 | onSessionStarted: provider.onSessionStarted, |
| 435 | onSessionEvent: (event) => { |
| 436 | if (event._tag === "ConfigOptionsUpdated") { |
| 437 | return provider.onConfigOptionsUpdated(event.configOptions); |
| 438 | } |
| 439 | return event._tag === "AvailableCommandsUpdated" |
| 440 | ? provider.onAvailableCommands(event.availableCommands) |
| 441 | : Effect.void; |
| 442 | }, |
| 443 | continuationRequests, |
| 444 | nativeLogging: (threadId) => |
| 445 | makeNativeLogger({ |
| 446 | nativeEventLogger: loggers.native, |
| 447 | provider: DRIVER, |
| 448 | threadId, |
| 449 | }), |
| 450 | }); |
| 451 | const textGeneration = yield* makeAntigravityTextGeneration({ |
| 452 | profileDirectory, |
| 453 | defaultModel, |
| 454 | withProcess: authFlow.withProcess, |
| 455 | makeRuntime: (cwd) => |
| 456 | makeRuntime({ |
| 457 | cwd, |
| 458 | clientInfo: { name: "t3-code-text", version: "0.0.0" }, |
| 459 | mcpServers: [], |
| 460 | }), |
| 461 | }); |
| 462 | |
| 463 | const refreshModels = Effect.fn("AntigravityDriver.refreshModels")( |
| 464 | function* () { |
| 465 | const processScope = yield* Scope.make(); |
| 466 | yield* Effect.addFinalizer((exit) => Scope.close(processScope, exit)); |
| 467 | yield* authFlow |
| 468 | .withProcess( |
| 469 | Scope.close(processScope, Exit.void), |
| 470 | Effect.gen(function* () { |
| 471 | const runtime = yield* makeDisposableRuntime({}); |
| 472 | const started = yield* runtime.start(); |
| 473 | yield* publishCatalog(started, runtime); |
| 474 | }), |
| 475 | ) |
| 476 | .pipe(Effect.provideService(Scope.Scope, processScope)); |
| 477 | }, |
| 478 | Effect.scoped, |
| 479 | Effect.timeoutOrElse({ |
| 480 | duration: "90 seconds", |
| 481 | orElse: () => |
| 482 | Effect.fail( |
| 483 | new ProviderDriverError({ |
| 484 | driver: DRIVER, |
| 485 | instanceId, |
| 486 | detail: "Antigravity model refresh timed out. Try again or check Google sign-in.", |
| 487 | }), |
| 488 | ), |
| 489 | }), |
| 490 | Effect.tapError((cause) => |
| 491 | isAntigravitySignInRequiredError(cause) ? provider.onAuthRequired : Effect.void, |
| 492 | ), |
| 493 | Effect.mapError((cause) => |
| 494 | cause._tag === "ProviderDriverError" |
| 495 | ? cause |
| 496 | : new ProviderDriverError({ |
| 497 | driver: DRIVER, |
| 498 | instanceId, |
| 499 | detail: isAntigravitySignInRequiredError(cause) |
| 500 | ? "Sign in to Antigravity in provider settings before refreshing models." |
| 501 | : cause._tag === "ProviderSetupError" && |
| 502 | (cause.operation === "configure" || cause.operation === "start") |
| 503 | ? cause.detail |
| 504 | : "Could not refresh Antigravity models. The previous model list is unchanged.", |
| 505 | cause, |
| 506 | }), |
| 507 | ), |
| 508 | ); |
| 509 | |
| 510 | return { |
| 511 | instanceId, |
| 512 | driverKind: DRIVER, |
| 513 | continuationIdentity, |
| 514 | displayName, |
| 515 | accentColor, |
| 516 | enabled, |
| 517 | snapshot: provider.snapshot, |
| 518 | snapshotForCwd: (cwd) => |
| 519 | !enabled |
| 520 | ? provider.snapshot.getSnapshot |
| 521 | : discoverAntigravitySkills({ cwd, userHome }).pipe( |
| 522 | Effect.provideService(FileSystem.FileSystem, fileSystem), |
| 523 | Effect.provideService(Path.Path, path), |
| 524 | Effect.flatMap((skills) => provider.snapshotForCwd(cwd, skills)), |
| 525 | Effect.mapError( |
| 526 | (cause) => |
| 527 | new ProviderDriverError({ |
| 528 | driver: DRIVER, |
| 529 | instanceId, |
| 530 | detail: "Could not read Antigravity workspace skills.", |
| 531 | cause, |
| 532 | }), |
| 533 | ), |
| 534 | ), |
| 535 | orchestrationAdapter, |
| 536 | textGeneration, |
| 537 | auth: authFlow.controller, |
| 538 | refreshModels, |
| 539 | } satisfies ProviderInstance; |
| 540 | }), |
| 541 | }; |