Skip to content
Closed
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
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,7 @@ export interface AntigravityAdapterV2Options {
cwd: string,
) => Effect.Effect<void>;
readonly onSessionEvent?: AcpAdapterV2Flavor["onSessionEvent"];
readonly onModelSelected?: (modelId: string) => Effect.Effect<void>;
readonly nativeLogging?: Parameters<typeof makeAcpAdapterV2>[0]["nativeLogging"];
readonly continuationRequests?: Parameters<typeof makeAcpAdapterV2>[0]["continuationRequests"];
}
Expand Down Expand Up @@ -172,16 +173,22 @@ export function makeAntigravityAcpAdapterFlavor(
...(options.onSessionEvent === undefined ? {} : { onSessionEvent: options.onSessionEvent }),
applyModelSelection: Effect.fn("AntigravityAdapterV2.applyModelSelection")(function* ({
runtime,
startResult,
modelSelection,
}) {
const defaultModel = yield* options.defaultModel;
return yield* applyAntigravityAcpModelSelection({
const selected = yield* applyAntigravityAcpModelSelection({
runtime,
models: startResult.sessionSetupResult.models,
model:
modelSelection.model === ANTIGRAVITY_DEFAULT_MODEL ? undefined : modelSelection.model,
defaultModel,
mapError: (cause) => cause,
});
if (selected !== undefined && options.onModelSelected) {
yield* options.onModelSelected(selected);
}
return selected;
}),
sessionModeForPolicy: (policy) => antigravityPermissionMode(policy.runtimeMode),
clientFileSystem: {
Expand Down
76 changes: 76 additions & 0 deletions apps/server/src/provider/AntigravityProvider.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ import {
buildAntigravityModelsFromSession,
makeAntigravityProvider,
} from "./AntigravityProvider.ts";
import { antigravity143Session } from "./acp/antigravity143.fixture.ts";

const decodeSettings = Schema.decodeSync(AntigravitySettings);
const instanceId = ProviderInstanceId.make("antigravity-test");
Expand Down Expand Up @@ -358,6 +359,81 @@ it.layer(layerTest)("Antigravity provider snapshots", (it) => {
),
);

it.effect("lists ACP 1.4 concrete models through grouped thinking-level updates", () =>
Effect.scoped(
Effect.gen(function* () {
const harness = yield* makeHarness();
yield* harness.initialize;
yield* harness.provider.onSessionStarted({
...started,
sessionSetupResult: antigravity143Session,
});
const snapshot = yield* harness.provider.snapshot.getSnapshot;
expect(snapshot.models.map((model) => model.slug)).toEqual(
antigravity143Session.models.availableModels.map((model) => model.modelId),
);
expect(snapshot.models.find((model) => model.isDefault)?.slug).toBe(
"gemini-3.8-flash-high",
);

yield* harness.provider.onConfigOptionsUpdated(
antigravity143Session.configOptions.map((option) =>
option.id === "model"
? { ...option, currentValue: "claude-sonnet-5-5" }
: { ...option, currentValue: "low" },
),
);
expect((yield* harness.provider.snapshot.getSnapshot).models).toEqual(snapshot.models);
}),
),
);

it.effect("defaults to the concrete model a grouped session selected", () =>
Effect.scoped(
Effect.gen(function* () {
const harness = yield* makeHarness();
yield* harness.initialize;
yield* harness.provider.onSessionStarted({
...started,
sessionSetupResult: antigravity143Session,
});
yield* harness.provider.onModelSelected("claude-sonnet-5-5-low");
yield* harness.provider.onConfigOptionsUpdated(
antigravity143Session.configOptions.map((option) =>
option.id === "model"
? { ...option, currentValue: "claude-sonnet-5-5" }
: { ...option, currentValue: "low" },
),
);
const models = (yield* harness.provider.snapshot.getSnapshot).models;
expect(models.filter((model) => model.isDefault).map((model) => model.slug)).toEqual([
"claude-sonnet-5-5-low",
]);
}),
),
);

it.effect("lists models added by config updates to ungrouped sessions", () =>
Effect.scoped(
Effect.gen(function* () {
const harness = yield* makeHarness();
yield* harness.initialize;
yield* harness.provider.onSessionStarted(started);

const configOptions = [
{
...modelConfig,
options: [...modelOptions, { value: "gemini-4-argon-high", name: "Gemini 4 Argon" }],
},
];
yield* harness.provider.onConfigOptionsUpdated(configOptions);
expect((yield* harness.provider.snapshot.getSnapshot).models).toEqual(
buildAntigravityModelsFromSession({ configOptions }),
);
}),
),
);

it.effect("does not guess a version before a custom launcher's first handshake", () =>
Effect.scoped(
Effect.gen(function* () {
Expand Down
43 changes: 39 additions & 4 deletions apps/server/src/provider/AntigravityProvider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ import {
type ServerProviderDraft,
} from "@t3tools/provider-core/server/snapshotProbe";
import { resolveUsageLimitsAfterProbe } from "@t3tools/provider-core/server/usageLimits";
import { antigravityGroupedModels } from "./acp/AntigravityAcpSupport.ts";

const EMPTY_MODEL_CAPABILITIES = createModelCapabilities({ optionDescriptors: [] });
const MAX_WORKSPACE_SNAPSHOTS = 32;
Expand All @@ -47,9 +48,9 @@ type SessionSetupResult = Pick<
export function buildAntigravityModelsFromSession(
setup: SessionSetupResult,
): ReadonlyArray<ServerProviderModel> {
const config = setup.configOptions?.find(
(option) => option.id === "model" || option.category === "model",
);
const config = antigravityGroupedModels(setup)
? undefined
: setup.configOptions?.find((option) => option.id === "model" || option.category === "model");
const currentValue =
config?.type === "select" ? config.currentValue : setup.models?.currentModelId;
const entries =
Expand Down Expand Up @@ -115,6 +116,8 @@ function isMissingInstallation(error: EffectAcpErrors.AcpError | ProviderSetupEr
interface AntigravityProviderState {
readonly draft: ServerProviderDraft;
readonly authRevision: number;
/** Config updates omit `models`, so keep a grouped session's concrete list. */
readonly groupedModels: EffectAcpSchema.SessionModelState | null;
}

interface AntigravityProviderOptions {
Expand Down Expand Up @@ -159,6 +162,7 @@ export const makeAntigravityProvider = Effect.fn("makeAntigravityProvider")(func
const metadata = yield* SubscriptionRef.make<AntigravityProviderState>({
draft: initialDraft,
authRevision: 0,
groupedModels: null,
});
// Preserve disk-discovered skills when callbacks replace workspace commands; the registry will not rescan them.
const discoveredSkills = new Map<string, ServerProvider["skills"]>();
Expand Down Expand Up @@ -286,6 +290,7 @@ export const makeAntigravityProvider = Effect.fn("makeAntigravityProvider")(func
const workspace = cwd ? workspaces.find((entry) => entry.cwd === cwd) : undefined;
return {
authRevision: state.authRevision + 1,
groupedModels: antigravityGroupedModels(started.sessionSetupResult) ?? null,
draft: {
...draft,
installed: true,
Expand Down Expand Up @@ -324,13 +329,41 @@ export const makeAntigravityProvider = Effect.fn("makeAntigravityProvider")(func
const onConfigOptionsUpdated = Effect.fn("AntigravityProvider.onConfigOptionsUpdated")(function* (
configOptions: ReadonlyArray<EffectAcpSchema.SessionConfigOption>,
) {
const models = buildAntigravityModelsFromSession({ configOptions });
yield* SubscriptionRef.update(metadata, (state) => {
if (state.draft.auth.status !== "authenticated") return state;
const models = buildAntigravityModelsFromSession(
state.groupedModels ? { models: state.groupedModels } : { configOptions },
);
return { ...state, draft: { ...state.draft, models } };
});
});

// Grouped config updates name only the family and thinking level.
const onModelSelected = Effect.fn("AntigravityProvider.onModelSelected")(function* (
modelId: string,
) {
yield* SubscriptionRef.update(metadata, (state) => {
const grouped = state.groupedModels;
if (
state.draft.auth.status !== "authenticated" ||
!grouped ||
grouped.currentModelId === modelId ||
!grouped.availableModels.some((model) => model.modelId === modelId)
) {
return state;
}
const groupedModels = { ...grouped, currentModelId: modelId };
return {
...state,
groupedModels,
draft: {
...state.draft,
models: buildAntigravityModelsFromSession({ models: groupedModels }),
},
};
});
});

const onAvailableCommands = Effect.fn("AntigravityProvider.onAvailableCommands")(function* (
commands: ReadonlyArray<EffectAcpSchema.AvailableCommand>,
cwd?: string,
Expand Down Expand Up @@ -375,6 +408,7 @@ export const makeAntigravityProvider = Effect.fn("makeAntigravityProvider")(func
(state) =>
({
authRevision: state.authRevision + 1,
groupedModels: null,
draft: {
...state.draft,
auth: { status: "unauthenticated" },
Expand Down Expand Up @@ -425,6 +459,7 @@ export const makeAntigravityProvider = Effect.fn("makeAntigravityProvider")(func
snapshot: { ...managed, getSnapshot },
onSessionStarted,
onConfigOptionsUpdated,
onModelSelected,
onAvailableCommands,
onSignedOut: clearAccountMetadata(),
onAuthRequired: clearAccountMetadata(),
Expand Down
14 changes: 14 additions & 0 deletions apps/server/src/provider/Drivers/AntigravityDriver.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -333,6 +333,20 @@ it.layer(layerTest)("AntigravityDriver", (it) => {
}).pipe(Effect.scoped),
);

it.effect.skipIf(windowsHost)("reports the configured ACP client name to the agent", () =>
Effect.gen(function* () {
const h = yield* makeHarness({ config: { acpClientName: "custom-client" } });
yield* h.refresh();
const requests = yield* h.readRequests;
expect(
requests
.filter((request) => request.method === "initialize")
.map((request) => request.params?.clientInfo),
).toEqual([{ name: "custom-client", version: "0.0.0" }]);
yield* h.assertClosed;
}).pipe(Effect.scoped),
);

it.effect.skipIf(windowsHost)(
"closes refresh processes and clears account metadata when Google sign-in is required",
() =>
Expand Down
4 changes: 4 additions & 0 deletions apps/server/src/provider/Drivers/AntigravityDriver.ts
Original file line number Diff line number Diff line change
Expand Up @@ -241,6 +241,9 @@ export const AntigravityDriver: ProviderDriver<
);
const runtime = yield* makeAntigravityAcpRuntime({
...input,
...(settings.acpClientName
? { clientInfo: { ...input.clientInfo, name: settings.acpClientName } }
: {}),
authMethod: auth.authMethod,
childProcessSpawner: spawner,
spawn: buildAntigravityAcpSpawnInput({
Expand Down Expand Up @@ -425,6 +428,7 @@ export const AntigravityDriver: ProviderDriver<
? provider.onAvailableCommands(event.availableCommands)
: Effect.void;
},
onModelSelected: provider.onModelSelected,
continuationRequests,
nativeLogging: (threadId) =>
makeNativeLogger({
Expand Down
35 changes: 34 additions & 1 deletion apps/server/src/provider/acp/AntigravityAcpSupport.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import {
applyAntigravityAcpModelSelection,
buildAntigravityPrompt,
} from "./AntigravityAcpSupport.ts";
import { antigravity143Session } from "./antigravity143.fixture.ts";

const modelConfig = {
id: "model",
Expand All @@ -34,13 +35,20 @@ function makeModelRuntime(
failure?: EffectAcpErrors.AcpError,
) {
const selections: string[] = [];
const sessionModelSelections: string[] = [];
const setModel = Effect.fn("AntigravityAcpSupportTest.setModel")(function* (model: string) {
if (failure) return yield* failure;
selections.push(model);
});
const setSessionModel = (model: string) =>
Effect.sync(() => {
sessionModelSelections.push(model);
return {};
});
return {
runtime: { getConfigOptions: Effect.succeed(configOptions), setModel },
runtime: { getConfigOptions: Effect.succeed(configOptions), setModel, setSessionModel },
selections,
sessionModelSelections,
};
}

Expand Down Expand Up @@ -137,6 +145,31 @@ describe("applyAntigravityAcpModelSelection", () => {
expect(selections).toEqual(["gemini-saved"]);
}),
);

it.effect("selects concrete models when ACP 1.4 groups thinking levels by family", () =>
Effect.gen(function* () {
const { runtime, selections, sessionModelSelections } = makeModelRuntime(
antigravity143Session.configOptions,
);
const select = (model: string) =>
applyAntigravityAcpModelSelection({
runtime,
models: antigravity143Session.models,
model,
defaultModel: "gemini-3.8-flash-high",
mapError: (cause) => cause,
});

expect(yield* select("claude-sonnet-5-5-low")).toBe("claude-sonnet-5-5-low");
expect(yield* select(ANTIGRAVITY_DEFAULT_MODEL)).toBe("gemini-3.8-flash-high");
expect(sessionModelSelections).toEqual(["claude-sonnet-5-5-low", "gemini-3.8-flash-high"]);
expect(selections).toEqual([]);

const error = yield* select("claude-opus-5-5-low").pipe(Effect.flip);
expect(error).toMatchObject({ _tag: "AcpRequestError", code: -32602 });
expect(sessionModelSelections).toHaveLength(2);
}),
);
});

const imageAttachment = {
Expand Down
Loading
Loading