Repository navigation
fix(antigravity): preserve full account model catalog #28433
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -6,6 +6,7 @@ import { | |
| type AntigravitySettings, | ||
| } from "@t3tools/contracts"; | ||
| import * as HostProcess from "@t3tools/shared/HostProcess"; | ||
| import * as Deferred from "effect/Deferred"; | ||
| import * as Effect from "effect/Effect"; | ||
| import * as FileSystem from "effect/FileSystem"; | ||
| import * as Layer from "effect/Layer"; | ||
|
|
@@ -46,13 +47,18 @@ const blockedCredentialKeys = new Set([ | |
| "GOOGLE_APPLICATION_CREDENTIALS", | ||
| "GOOGLE_GENAI_USE_VERTEXAI", | ||
| ]); | ||
| const modelCatalogMigrationMarker = ".t3-model-catalog-v1"; | ||
|
|
||
| function shellQuote(value: string): string { | ||
| return `'${value.replaceAll("'", `'"'"'`)}'`; | ||
| } | ||
|
|
||
| const makeHarness = Effect.fn("makeAntigravityDriverHarness")(function* ( | ||
| options: { readonly config?: Partial<AntigravitySettings>; readonly enabled?: boolean } = {}, | ||
| options: { | ||
| readonly config?: Partial<AntigravitySettings>; | ||
| readonly enabled?: boolean; | ||
| readonly catalogMigrated?: boolean; | ||
| } = {}, | ||
| ) { | ||
| const fs = yield* FileSystem.FileSystem; | ||
| const path = yield* Path.Path; | ||
|
|
@@ -68,6 +74,10 @@ const makeHarness = Effect.fn("makeAntigravityDriverHarness")(function* ( | |
| const requestLog = path.join(root, "requests.jsonl"); | ||
| const directories = yield* resolveAntigravityInstanceDirectories(config.stateDir, instanceId); | ||
| const profileDirectory = directories.profile; | ||
| if (options.catalogMigrated ?? true) { | ||
| yield* fs.makeDirectory(profileDirectory, { recursive: true }); | ||
| yield* fs.writeFileString(path.join(profileDirectory, modelCatalogMigrationMarker), "1\n"); | ||
| } | ||
| const instancePath = `${path.join(root, "instance-bin")}:${baseEnv.PATH ?? ""}`; | ||
|
|
||
| const makeExecutable = Effect.fn("AntigravityDriverTest.makeExecutable")(function* ( | ||
|
|
@@ -109,6 +119,7 @@ const makeHarness = Effect.fn("makeAntigravityDriverHarness")(function* ( | |
| const controls = { selected: first, failResolution: false, beforeAcquire: Effect.void }; | ||
| const acquisitions: Array<{ binaryPath: string | undefined; path: string | undefined }> = []; | ||
| const releases: Array<string | null> = []; | ||
| const releaseObserved = yield* Deferred.make<void>(); | ||
| const launches: Array<{ | ||
| command: string; | ||
| args: ReadonlyArray<string>; | ||
|
|
@@ -149,7 +160,7 @@ const makeHarness = Effect.fn("makeAntigravityDriverHarness")(function* ( | |
| yield* Effect.addFinalizer(() => | ||
| Effect.sync(() => { | ||
| releases.push(selected.version); | ||
| }), | ||
| }).pipe(Effect.andThen(Deferred.succeed(releaseObserved, undefined))), | ||
| ); | ||
| return selected; | ||
| }), | ||
|
|
@@ -231,6 +242,7 @@ const makeHarness = Effect.fn("makeAntigravityDriverHarness")(function* ( | |
| controls, | ||
| acquisitions, | ||
| releases, | ||
| releaseObserved, | ||
| launches, | ||
| readRequests, | ||
| assertClosed, | ||
|
|
@@ -269,6 +281,29 @@ const layerTest = ProviderHostLive.layer.pipe( | |
| ); | ||
|
|
||
| it.layer(layerTest)("AntigravityDriver", (it) => { | ||
| it.effect.skipIf(windowsHost)("refreshes a legacy model catalog once on create", () => | ||
| Effect.gen(function* () { | ||
| const h = yield* makeHarness({ catalogMigrated: false }); | ||
| yield* Deferred.await(h.releaseObserved); | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
|
|
||
| const snapshot = yield* h.instance.snapshot.getSnapshot; | ||
| expect(snapshot.status).toBe("ready"); | ||
| expect(snapshot.models.map((model) => model.slug)).toEqual([ | ||
| "gemini-test-low", | ||
| "gemini-test-high", | ||
| ]); | ||
| expect(h.acquisitions).toEqual([{ binaryPath: "", path: h.instancePath }]); | ||
| expect(h.releases).toEqual([h.first.version]); | ||
| const requests = yield* h.readRequests; | ||
| expect(requests.map((request) => request.method)).toEqual([ | ||
| "initialize", | ||
| "auth/login", | ||
| "session/new", | ||
| ]); | ||
| yield* h.assertClosed; | ||
| }).pipe(Effect.scoped), | ||
| ); | ||
|
|
||
| it.effect.skipIf(windowsHost)( | ||
| "refreshes through the selected executable and personal Google ACP", | ||
| () => | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -14,6 +14,7 @@ import * as FileSystem from "effect/FileSystem"; | |
| import * as Path from "effect/Path"; | ||
| 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 ChildProcessSpawner from "effect/process/ChildProcessSpawner"; | ||
| import { HttpClient } from "effect/http"; | ||
|
|
@@ -68,6 +69,8 @@ import { withInstanceIdentity } from "@t3tools/provider-core/server/instanceIden | |
| import { discoverAntigravitySkills, resolveAntigravityUserHome } from "./AntigravitySkills.ts"; | ||
|
|
||
| const DRIVER = ProviderDriverKind.make("antigravity"); | ||
| const MODEL_CATALOG_MIGRATION_MARKER = ".t3-model-catalog-v1"; | ||
| const modelCatalogMigrationGate = Semaphore.makeUnsafe(1); | ||
| const decodeSettings = Schema.decodeSync(AntigravitySettings); | ||
| const isNodeRuntimeUnavailableError = Schema.is(NodeRuntimeUnavailableError); | ||
|
|
||
|
|
@@ -502,6 +505,34 @@ export const AntigravityDriver: ProviderDriver< | |
| ), | ||
| ); | ||
|
|
||
| // Existing Antigravity accounts can have a provider-status cache created before | ||
| // account-scoped Claude/GPT discovery was supported. Refresh that catalog once | ||
| // after upgrading, then leave periodic health checks lightweight (the ACP bundle | ||
| // is a large PyInstaller executable and must not be spawned every minute). | ||
| if (settings.enabled) { | ||
| const migrationMarkerPath = path.join(profileDirectory, MODEL_CATALOG_MIGRATION_MARKER); | ||
| yield* modelCatalogMigrationGate | ||
| .withPermits(1)( | ||
| Effect.gen(function* () { | ||
| const alreadyMigrated = yield* fileSystem | ||
| .exists(migrationMarkerPath) | ||
| .pipe(Effect.orElseSucceed(() => false)); | ||
| if (alreadyMigrated) return; | ||
|
|
||
| yield* refreshModels(); | ||
|
Comment on lines
+520
to
+522
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Limit the automatic refresh to legacy cached catalogs. A new enabled profile has no marker, even when no account has signed in and no catalog needs migration. This branch starts the large ACP bundle anyway. If sign-in is required, the refresh fails without writing a marker, so the launch repeats on every server start. Check for a legacy cached catalog before starting the migration; normal sign-in already publishes a catalog. (raw.githubusercontent.com) As per coding guidelines, “Make sure all changes are considerate of performance impact.”
|
||
| yield* fileSystem.writeFileString(migrationMarkerPath, "1\n"); | ||
| }).pipe( | ||
| Effect.catchCause((cause) => | ||
| Effect.logWarning( | ||
| "Could not complete the one-time Antigravity model catalog refresh.", | ||
| { instanceId, cause }, | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Keep the refresh failure cause out of log annotations.
As per coding guidelines, “Attributes and log annotations stay bounded: no raw payloads, command arguments or output, signed URLs, credentials, query strings, or arbitrary defect text.”
|
||
| ), | ||
| ), | ||
| ), | ||
| ) | ||
| .pipe(Effect.forkScoped); | ||
| } | ||
|
|
||
| return { | ||
| instanceId, | ||
| driverKind: DRIVER, | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Preserve the catalog when session model state is absent.
If a session starts with a full
configOptionscatalog but nomodels, Line 336 savesundefined. A later partialConfigOptionsUpdatedevent then rebuilds the catalog without the original choices. Those models disappear again. Retain the account’s initial config-derived choices for this case, and clear them on sign-out. Add a focused test with a config-only session setup. The existing account-switch test demonstrates that a setup withoutmodelsis supported. (raw.githubusercontent.com)As per coding guidelines, “Backend behavior changes ship with focused tests for that behavior.”
Source: Coding guidelines