Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions apps/server/src/provider/Drivers/AntigravityDriver.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import * as Layer from "effect/Layer";
import * as Path from "effect/Path";
import * as Schema from "effect/Schema";
import * as TestClock from "effect/testing/TestClock";
import { HttpClient } from "effect/unstable/http";
import * as ChildProcessSpawner from "effect/unstable/process/ChildProcessSpawner";

import * as BackgroundPolicy from "../../background/BackgroundPolicy.ts";
Expand Down Expand Up @@ -239,6 +240,12 @@ const testLayer = ServerConfig.layerTest(process.cwd(), {
),
Layer.provideMerge(Layer.succeed(ProviderEventLoggers, NoOpProviderEventLoggers)),
Layer.provideMerge(ModelManifest.layerTest),
Layer.provideMerge(
Layer.succeed(
HttpClient.HttpClient,
HttpClient.make(() => Effect.die("Disabled Antigravity must not make an HTTP request")),
),
),
);

it.layer(testLayer)("AntigravityDriver", (it) => {
Expand Down
19 changes: 19 additions & 0 deletions apps/server/src/provider/Drivers/AntigravityDriver.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { AntigravitySettings, ProviderDriverKind, ProviderSetupError } from "@t3tools/contracts";
import { HostProcessPlatform } from "@t3tools/shared/hostProcess";
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";
Expand All @@ -9,6 +10,7 @@ import * as Path from "effect/Path";
import * as Schema from "effect/Schema";
import * as Scope from "effect/Scope";
import * as Stream from "effect/Stream";
import { HttpClient } from "effect/unstable/http";
import * as ChildProcessSpawner from "effect/unstable/process/ChildProcessSpawner";
import type { AcpError } from "effect-acp/errors";

Expand Down Expand Up @@ -41,6 +43,7 @@ import { removeAntigravitySessionFiles } from "../acp/AntigravitySessionFiles.ts
import { ProviderDriverError } from "../Errors.ts";
import { makeAntigravityAdapter } from "../Layers/AntigravityAdapter.ts";
import { makeAntigravityProvider } from "../Layers/AntigravityProvider.ts";
import { probeAntigravityUsageLimits } from "../Layers/antigravityUsageLimits.ts";
import { ProviderEventLoggers } from "../Layers/ProviderEventLoggers.ts";
import * as ModelManifest from "../ModelManifest.ts";
import {
Expand All @@ -61,6 +64,7 @@ export type AntigravityDriverEnv =
| ChildProcessSpawner.ChildProcessSpawner
| Crypto.Crypto
| FileSystem.FileSystem
| HttpClient.HttpClient
| ModelManifest.ModelManifest
| Path.Path
| ProviderEventLoggers
Expand All @@ -78,6 +82,7 @@ export const AntigravityDriver: ProviderDriver<AntigravitySettings, AntigravityD
const crypto = yield* Crypto.Crypto;
const fileSystem = yield* FileSystem.FileSystem;
const path = yield* Path.Path;
const httpClient = yield* HttpClient.HttpClient;
const spawner = yield* ChildProcessSpawner.ChildProcessSpawner;
const serverConfig = yield* ServerConfig;
const installation = yield* AntigravityInstallation;
Expand Down Expand Up @@ -276,6 +281,19 @@ export const AntigravityDriver: ProviderDriver<AntigravitySettings, AntigravityD
.pipe(Effect.provideService(Scope.Scope, processScope));
}).pipe(Effect.scoped);

const usageLimits = Effect.gen(function* () {
const checkedAt = DateTime.formatIso(yield* DateTime.now);
return yield* probeAntigravityUsageLimits({
profileDirectory,
checkedAt,
});
}).pipe(
Effect.provideService(FileSystem.FileSystem, fileSystem),
Effect.provideService(Path.Path, path),
Effect.provideService(HttpClient.HttpClient, httpClient),
Effect.catchCause(() => Effect.succeed(undefined)),
);

const provider = yield* makeAntigravityProvider(settings, {
stampIdentity: classifyModels,
probe,
Expand All @@ -285,6 +303,7 @@ export const AntigravityDriver: ProviderDriver<AntigravitySettings, AntigravityD
Effect.provideService(Path.Path, path),
Effect.orElseSucceed(() => false),
),
usageLimits,
}).pipe(
Effect.mapError(
(cause) =>
Expand Down
146 changes: 145 additions & 1 deletion apps/server/src/provider/Layers/AntigravityProvider.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import {
ProviderDriverKind,
ProviderInstanceId,
ProviderSetupError,
type ServerProviderUsageLimits,
} from "@t3tools/contracts";
import * as Deferred from "effect/Deferred";
import * as Effect from "effect/Effect";
Expand Down Expand Up @@ -111,7 +112,11 @@ const testLayer = Layer.merge(
type ProbeError = EffectAcpErrors.AcpError | ProviderSetupError;

const makeHarness = Effect.fn("makeAntigravityProviderHarness")(function* (
options: { readonly enabled?: boolean; readonly safe?: boolean } = {},
options: {
readonly enabled?: boolean;
readonly safe?: boolean;
readonly usageLimits?: Effect.Effect<ServerProviderUsageLimits | undefined>;
} = {},
) {
const initialProbe = yield* Deferred.make<EffectAcpSchema.InitializeResponse, ProbeError>();
const probeCalls = yield* Ref.make(0);
Expand All @@ -132,6 +137,7 @@ const makeHarness = Effect.fn("makeAntigravityProviderHarness")(function* (
Effect.andThen(Ref.get(safety)),
Effect.flatten,
),
...(options.usageLimits ? { usageLimits: options.usageLimits } : {}),
},
);
const initialUpdate = yield* Stream.toPull(
Expand Down Expand Up @@ -641,4 +647,142 @@ it.layer(testLayer)("Antigravity provider snapshots", (it) => {
}),
),
);

it.effect("includes usageLimits in snapshot when available and clears on sign-out", () =>
Effect.scoped(
Effect.gen(function* () {
const mockLimits: ServerProviderUsageLimits = {
checkedAt: "2026-09-09T00:00:00.000Z",
windows: [
{
id: "plan_allowance",
kind: "session",
label: "Gemini Code Assist",
usedPercent: 0,
},
],
};
const harness = yield* makeHarness({
usageLimits: Effect.succeed(mockLimits),
});
yield* harness.initialize;
const snapshot = yield* harness.provider.snapshot.getSnapshot;
expect(snapshot.usageLimits).toEqual(mockLimits);

// onSessionStarted keeps/updates usage limits
yield* harness.provider.onSessionStarted(started);
const sessionSnapshot = yield* harness.provider.snapshot.getSnapshot;
expect(sessionSnapshot.usageLimits).toEqual(mockLimits);

// onSignedOut clears usage limits
yield* harness.provider.onSignedOut;
const signedOutSnapshot = yield* harness.provider.snapshot.getSnapshot;
expect(signedOutSnapshot.usageLimits).toBeUndefined();
}),
),
);

it.effect("ignores stale onSessionStarted completion when newer session started", () =>
Effect.scoped(
Effect.gen(function* () {
const session1Gate = yield* Deferred.make<void>();
const session1Continue = yield* Deferred.make<void>();
const session1Done = yield* Deferred.make<void>();

let gateArmed = false;
let sessionCalls = 0;
const harness = yield* makeHarness({
usageLimits: Effect.gen(function* () {
if (!gateArmed) {
return { checkedAt: "2026-09-09T00:00:00.000Z", windows: [] };
}
sessionCalls++;
if (sessionCalls === 1) {
yield* Deferred.succeed(session1Gate, undefined);
yield* Deferred.await(session1Continue);
yield* Deferred.succeed(session1Done, undefined);
return { checkedAt: "2026-09-09T01:00:00.000Z", windows: [] };
}
return {
checkedAt: "2026-09-09T02:00:00.000Z",
windows: [{ id: "new_session", kind: "session", label: "New", usedPercent: 10 }],
};
}),
});
yield* harness.initialize;
Comment thread
coderabbitai[bot] marked this conversation as resolved.
gateArmed = true;

const session2LimitsApplied = yield* Stream.toPull(
harness.provider.snapshot.streamChanges.pipe(
Stream.filter((s) => s.usageLimits?.checkedAt === "2026-09-09T02:00:00.000Z"),
),
);

const fiber1 = yield* harness.provider.onSessionStarted(started).pipe(Effect.forkChild);
yield* Deferred.await(session1Gate);

const session2Started: AcpSessionRuntimeStartResult = {
...started,
sessionSetupResult: {
sessionId: "session-2",
configOptions: [
{ ...modelConfig, currentValue: "gemini-pro-agent", options: [modelOptions[9]!] },
],
},
};
yield* harness.provider.onSessionStarted(session2Started);
yield* session2LimitsApplied;

yield* Deferred.succeed(session1Continue, undefined);
yield* Deferred.await(session1Done);
yield* Fiber.join(fiber1);

const finalSnapshot = yield* harness.provider.snapshot.getSnapshot;
expect(finalSnapshot.models.map((m) => m.slug)).toEqual(["gemini-pro-agent"]);
expect(finalSnapshot.usageLimits?.checkedAt).toBe("2026-09-09T02:00:00.000Z");
}),
),
);

it.effect("does not restore usageLimits if signed out before quota probe completes", () =>
Effect.scoped(
Effect.gen(function* () {
const quotaGate = yield* Deferred.make<void>();
const quotaContinue = yield* Deferred.make<void>();
const quotaDone = yield* Deferred.make<void>();

let gateArmed = false;
const harness = yield* makeHarness({
usageLimits: Effect.gen(function* () {
if (!gateArmed) {
return undefined;
}
yield* Deferred.succeed(quotaGate, undefined);
yield* Deferred.await(quotaContinue);
yield* Deferred.succeed(quotaDone, undefined);
return {
checkedAt: "2026-09-09T03:00:00.000Z",
windows: [{ id: "stale_quota", kind: "session", label: "Stale", usedPercent: 50 }],
};
}),
});
yield* harness.initialize;
gateArmed = true;

yield* harness.provider.onSessionStarted(started);
yield* Deferred.await(quotaGate);

// Sign out while quota probe is still running in background
yield* harness.provider.onSignedOut;

// Release the deferred quota effect
yield* Deferred.succeed(quotaContinue, undefined);
yield* Deferred.await(quotaDone);

const finalSnapshot = yield* harness.provider.snapshot.getSnapshot;
expect(finalSnapshot.auth.status).toBe("unauthenticated");
expect(finalSnapshot.usageLimits).toBeUndefined();
}),
),
);
});
Loading
Loading