Add Antigravity provider support#134
Conversation
- Add Antigravity driver, adapter, and runtime integration - Expose Antigravity models and provider settings in the UI - Update contracts and tests for Antigravity sessions
📝 WalkthroughWalkthroughThis PR adds a complete Antigravity provider to the Ryco system, spanning runtime CLI execution, session management, streaming text deltas, text generation for multiple content types, driver orchestration, and web UI integration. ChangesAntigravity Provider Implementation
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes Possibly related PRs
Suggested labels
Poem
🚥 Pre-merge checks | ✅ 3 | ❌ 2❌ Failed checks (1 warning, 1 inconclusive)
✅ Passed checks (3 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 5
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@apps/server/src/provider/antigravityRuntime.test.ts`:
- Line 214: Remove the leftover debug logging by deleting the console.log({
deltas, activities, result }) statement in the antigravityRuntime.test.ts test;
if you still need to inspect values during local debugging, replace it with a
conditional debug/logger call (e.g., a dev-only logger or wrap in a flag) or add
proper assertions against deltas, activities, and result so tests remain silent
in CI.
In `@apps/server/src/provider/antigravityRuntime.ts`:
- Around line 378-433: Replace the unsafe directory-diff binding
(beforeSnapshot/newConversationId against conversationsDir inside
pollForConversationDelta) with a deterministic per-run binding: generate a
unique run-scoped identifier at prompt start (e.g., runId) and either (A)
create/use an isolated per-run conversations directory (derived from runId) and
pass it into buildAntigravityPromptArgs and ChildProcess.spawn env, or (B) pass
runId through the Antigravity CLI args/env and use that to associate deltas
(store/run observedConversationId from the CLI-provided identifier rather than
discovering via newConversationId). Update locations referencing beforeSnapshot,
newConversationId, conversationsDir, and observedConversationId to use the new
run-scoped identifier and remove the snapshot/diff logic.
In `@apps/server/src/provider/Layers/AntigravityAdapter.ts`:
- Around line 815-830: The rollbackThread implementation truncates context.turns
but doesn't update continuation state; after performing
context.turns.splice(nextLength) recompute the continuation based on the new
tail (or set to { lastStepIdx: 0 } if context.turns is now empty) and persist
that continuation into both context.conversationState (e.g.
conversationState.continuation) and context.session.resumeCursor so subsequent
sendTurn uses the truncated state; update the code in rollbackThread to derive
the new continuation from the last remaining turn (or default) and assign it to
context.conversationState and context.session.resumeCursor before returning.
- Around line 736-751: interruptTurn currently emits turn.aborted immediately
and for arbitrary turnIds, causing duplicates with sendTurn; change
interruptTurn to validate that the target turn equals context.activeTurnId
before mutating state or killing the process and do not emit the turn.aborted
event here. Specifically, in interruptTurn use targetTurnId = turnId ??
context.activeTurnId, only add to context.interruptedTurnIds and invoke yield*
activeProcess.kill when targetTurnId === context.activeTurnId (and activeProcess
exists), and remove the yield* emit({ ... buildEventBase(...), type:
"turn.aborted", ... }) block; instead ensure sendTurn reads
context.interruptedTurnIds/context.activeTurnId and is the single place that
emits the turn.aborted event once when an interrupted active turn fails.
In `@packages/contracts/src/settings.ts`:
- Around line 376-378: The provider description text under the
providerSettingsForm object incorrectly references ".gemini auth"; update the
description string (the description property near providerSettingsForm) to the
correct term for Antigravity (e.g., ".antigravity auth" or a neutral phrase like
"instance auth and conversation state") so the UI/help text accurately reflects
Antigravity settings.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: e5c5429c-f23b-4ce2-a3ce-b8c766733b82
📒 Files selected for processing (20)
apps/server/src/provider/Drivers/AntigravityDriver.tsapps/server/src/provider/Layers/AntigravityAdapter.tsapps/server/src/provider/Layers/AntigravityProvider.test.tsapps/server/src/provider/Layers/AntigravityProvider.tsapps/server/src/provider/Services/AntigravityAdapter.tsapps/server/src/provider/antigravityRuntime.test.tsapps/server/src/provider/antigravityRuntime.tsapps/server/src/provider/builtInDrivers.tsapps/server/src/provider/providerSnapshot.tsapps/server/src/textGeneration/AntigravityTextGeneration.tsapps/server/src/textGeneration/TextGeneration.tsapps/web/src/components/KeybindingsToast.browser.tsxapps/web/src/components/chat/ModelPickerSidebar.tsxapps/web/src/components/chat/providerIconUtils.tsapps/web/src/components/settings/AddProviderInstanceDialog.tsxapps/web/src/components/settings/providerDriverMeta.tsapps/web/src/session-logic.tspackages/contracts/src/model.tspackages/contracts/src/providerRuntime.tspackages/contracts/src/settings.ts
| }), | ||
| }).pipe(Effect.provide(NodeServices.layer)); | ||
|
|
||
| console.log({ deltas, activities, result }); |
There was a problem hiding this comment.
Remove leftover debug logging from test.
This console.log adds noisy CI/test output without assertion value.
Suggested cleanup
- console.log({ deltas, activities, result });📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| console.log({ deltas, activities, result }); |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@apps/server/src/provider/antigravityRuntime.test.ts` at line 214, Remove the
leftover debug logging by deleting the console.log({ deltas, activities, result
}) statement in the antigravityRuntime.test.ts test; if you still need to
inspect values during local debugging, replace it with a conditional
debug/logger call (e.g., a dev-only logger or wrap in a flag) or add proper
assertions against deltas, activities, and result so tests remain silent in CI.
| const beforeSnapshot = | ||
| input.state.conversationId === undefined ? conversationSnapshot(conversationsDir) : undefined; | ||
| const args = buildAntigravityPromptArgs({ | ||
| settings: input.settings, | ||
| cwd: input.cwd, | ||
| prompt: input.prompt, | ||
| runtimeMode: input.runtimeMode, | ||
| model: input.model, | ||
| conversationId: input.state.conversationId, | ||
| }); | ||
|
|
||
| const spawner = yield* ChildProcessSpawner.ChildProcessSpawner; | ||
| const child = yield* spawner | ||
| .spawn( | ||
| ChildProcess.make(input.settings.binaryPath || "agy", [...args], { | ||
| cwd: input.cwd, | ||
| env: environment, | ||
| shell: process.platform === "win32", | ||
| }), | ||
| ) | ||
| .pipe( | ||
| Effect.mapError( | ||
| (cause) => | ||
| new AntigravityRuntimeError({ | ||
| operation: "spawn", | ||
| detail: `Failed to spawn Antigravity CLI: ${cause.message}`, | ||
| cause, | ||
| }), | ||
| ), | ||
| ); | ||
| const processHandle: AntigravityPromptProcess = { | ||
| kill: child.kill({ killSignal: "SIGTERM", forceKillAfter: "1 second" }).pipe(Effect.ignore), | ||
| }; | ||
| if (input.onProcessStart) { | ||
| yield* input.onProcessStart(processHandle); | ||
| } | ||
| yield* Effect.addFinalizer(() => | ||
| processHandle.kill.pipe(Effect.andThen(input.onProcessExit ?? Effect.void)), | ||
| ); | ||
|
|
||
| const emitActivity = (summary: string) => | ||
| input.onActivity | ||
| ? input.onActivity({ | ||
| summary, | ||
| elapsedMs: Math.max(0, Date.now() - startedAt), | ||
| ...(observedConversationId ? { conversationId: observedConversationId } : {}), | ||
| }) | ||
| : Effect.void; | ||
|
|
||
| const pollForConversationDelta = Effect.gen(function* () { | ||
| const now = Date.now(); | ||
| if (observedConversationId === undefined && beforeSnapshot !== undefined) { | ||
| const discoveredConversationId = newConversationId(conversationsDir, beforeSnapshot); | ||
| if (discoveredConversationId !== undefined) { | ||
| observedConversationId = discoveredConversationId; | ||
| yield* emitActivity("Antigravity conversation started"); |
There was a problem hiding this comment.
Conversation auto-discovery is not safe under concurrent runs.
beforeSnapshot/newConversationId(...) can bind this run to another run’s new .db when multiple prompts share the same conversations dir. That can route deltas to the wrong thread and leak content across sessions.
Please switch to a deterministic per-run binding strategy (for example: isolated per-session HOME/conversations dir, or another unique run-scoped identifier propagated through the run lifecycle) instead of directory diffing.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@apps/server/src/provider/antigravityRuntime.ts` around lines 378 - 433,
Replace the unsafe directory-diff binding (beforeSnapshot/newConversationId
against conversationsDir inside pollForConversationDelta) with a deterministic
per-run binding: generate a unique run-scoped identifier at prompt start (e.g.,
runId) and either (A) create/use an isolated per-run conversations directory
(derived from runId) and pass it into buildAntigravityPromptArgs and
ChildProcess.spawn env, or (B) pass runId through the Antigravity CLI args/env
and use that to associate deltas (store/run observedConversationId from the
CLI-provided identifier rather than discovering via newConversationId). Update
locations referencing beforeSnapshot, newConversationId, conversationsDir, and
observedConversationId to use the new run-scoped identifier and remove the
snapshot/diff logic.
| const targetTurnId = turnId ?? context.activeTurnId; | ||
| if (targetTurnId) { | ||
| context.interruptedTurnIds.add(targetTurnId); | ||
| } | ||
| const activeProcess = context.activeProcess; | ||
| if (activeProcess) { | ||
| yield* activeProcess.kill; | ||
| } | ||
| if (targetTurnId) { | ||
| yield* emit({ | ||
| ...(yield* buildEventBase({ threadId, turnId: targetTurnId })), | ||
| type: "turn.aborted", | ||
| payload: { | ||
| reason: "Interrupted by user.", | ||
| }, | ||
| }); |
There was a problem hiding this comment.
interruptTurn can emit incorrect/duplicate abort events.
This method emits turn.aborted immediately, while sendTurn also emits turn.aborted on interrupted failure. It also allows emitting abort for an arbitrary non-active turnId.
Validate interruption against the active turn and centralize abort emission to one path.
Suggested direction
const interruptTurn: AntigravityAdapterShape["interruptTurn"] = Effect.fn("interruptTurn")(
function* (threadId, turnId) {
const context = yield* requireSession(threadId);
- const targetTurnId = turnId ?? context.activeTurnId;
- if (targetTurnId) {
- context.interruptedTurnIds.add(targetTurnId);
- }
+ const targetTurnId = context.activeTurnId;
+ if (!targetTurnId) return;
+ if (turnId !== undefined && turnId !== targetTurnId) return;
+ context.interruptedTurnIds.add(targetTurnId);
const activeProcess = context.activeProcess;
if (activeProcess) {
yield* activeProcess.kill;
}
- if (targetTurnId) {
- yield* emit({
- ...(yield* buildEventBase({ threadId, turnId: targetTurnId })),
- type: "turn.aborted",
- payload: {
- reason: "Interrupted by user.",
- },
- });
- }
},
);📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| const targetTurnId = turnId ?? context.activeTurnId; | |
| if (targetTurnId) { | |
| context.interruptedTurnIds.add(targetTurnId); | |
| } | |
| const activeProcess = context.activeProcess; | |
| if (activeProcess) { | |
| yield* activeProcess.kill; | |
| } | |
| if (targetTurnId) { | |
| yield* emit({ | |
| ...(yield* buildEventBase({ threadId, turnId: targetTurnId })), | |
| type: "turn.aborted", | |
| payload: { | |
| reason: "Interrupted by user.", | |
| }, | |
| }); | |
| const targetTurnId = context.activeTurnId; | |
| if (!targetTurnId) return; | |
| if (turnId !== undefined && turnId !== targetTurnId) return; | |
| context.interruptedTurnIds.add(targetTurnId); | |
| const activeProcess = context.activeProcess; | |
| if (activeProcess) { | |
| yield* activeProcess.kill; | |
| } |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@apps/server/src/provider/Layers/AntigravityAdapter.ts` around lines 736 -
751, interruptTurn currently emits turn.aborted immediately and for arbitrary
turnIds, causing duplicates with sendTurn; change interruptTurn to validate that
the target turn equals context.activeTurnId before mutating state or killing the
process and do not emit the turn.aborted event here. Specifically, in
interruptTurn use targetTurnId = turnId ?? context.activeTurnId, only add to
context.interruptedTurnIds and invoke yield* activeProcess.kill when
targetTurnId === context.activeTurnId (and activeProcess exists), and remove the
yield* emit({ ... buildEventBase(...), type: "turn.aborted", ... }) block;
instead ensure sendTurn reads context.interruptedTurnIds/context.activeTurnId
and is the single place that emits the turn.aborted event once when an
interrupted active turn fails.
| const rollbackThread: AntigravityAdapterShape["rollbackThread"] = Effect.fn("rollbackThread")( | ||
| function* (threadId, numTurns) { | ||
| const context = yield* requireSession(threadId); | ||
| if (!Number.isInteger(numTurns) || numTurns < 1) { | ||
| return yield* new ProviderAdapterValidationError({ | ||
| provider: PROVIDER, | ||
| operation: "rollbackThread", | ||
| issue: "numTurns must be an integer >= 1.", | ||
| }); | ||
| } | ||
| const nextLength = Math.max(0, context.turns.length - numTurns); | ||
| context.turns.splice(nextLength); | ||
| return { | ||
| threadId, | ||
| turns: context.turns, | ||
| }; |
There was a problem hiding this comment.
Rollback truncates turns but does not rollback continuation cursor/session state.
After context.turns.splice(...), context.conversationState and context.session.resumeCursor still reference the pre-rollback position. The next sendTurn will continue from removed state.
Please recompute continuation from the new tail turn (or reset to { lastStepIdx: 0 } when empty) and persist it back into both context.conversationState and ProviderSession.resumeCursor.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@apps/server/src/provider/Layers/AntigravityAdapter.ts` around lines 815 -
830, The rollbackThread implementation truncates context.turns but doesn't
update continuation state; after performing context.turns.splice(nextLength)
recompute the continuation based on the new tail (or set to { lastStepIdx: 0 }
if context.turns is now empty) and persist that continuation into both
context.conversationState (e.g. conversationState.continuation) and
context.session.resumeCursor so subsequent sendTurn uses the truncated state;
update the code in rollbackThread to derive the new continuation from the last
remaining turn (or default) and assign it to context.conversationState and
context.session.resumeCursor before returning.
| description: | ||
| "Custom HOME used when running this Antigravity instance. Keeps .gemini auth and conversation state separate.", | ||
| providerSettingsForm: { |
There was a problem hiding this comment.
Fix provider description text mismatch for Antigravity.
Line 377 mentions “.gemini auth” inside Antigravity settings, which is misleading in the UI/help text and likely a copy/paste artifact.
Suggested diff
- "Custom HOME used when running this Antigravity instance. Keeps .gemini auth and conversation state separate.",
+ "Custom HOME used when running this Antigravity instance. Keeps Antigravity auth and conversation state separate.",📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| description: | |
| "Custom HOME used when running this Antigravity instance. Keeps .gemini auth and conversation state separate.", | |
| providerSettingsForm: { | |
| description: | |
| "Custom HOME used when running this Antigravity instance. Keeps Antigravity auth and conversation state separate.", | |
| providerSettingsForm: { |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@packages/contracts/src/settings.ts` around lines 376 - 378, The provider
description text under the providerSettingsForm object incorrectly references
".gemini auth"; update the description string (the description property near
providerSettingsForm) to the correct term for Antigravity (e.g., ".antigravity
auth" or a neutral phrase like "instance auth and conversation state") so the
UI/help text accurately reflects Antigravity settings.
Summary
antigravityprovider driver and adapter that runs the Antigravity CLI, projects its output into Ryco runtime events, and supports session lifecycle, interruption, rollback, and resume cursor handling.Testing
Not run-bun fmtNot run-bun lintNot run-bun typecheckNot run-bun run testSummary by CodeRabbit
Release Notes