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
Original file line number Diff line number Diff line change
Expand Up @@ -196,11 +196,85 @@ describe("AntigravityAdapterV2 session open", () => {
cause: undefined,
}),
requiresSignIn: true,
failure: undefined,
},
{
name: "ACP authentication required",
cause: EffectAcpErrors.AcpRequestError.authRequired(),
requiresSignIn: true,
failure: undefined,
},
{
name: "Gemini Enterprise licensing rate limit",
cause: EffectAcpErrors.AcpRequestError.fromProtocolError(
{
code: -32603,
message: "Gemini Enterprise license resolution failed (HTTP 429)",
},
{ method: "authenticate" },
),
requiresSignIn: undefined,
failure: {
class: "usage_limit",
message:
"Antigravity's Gemini Enterprise license check is rate limited. Wait before retrying; signing in again will not reset this limit.",
code: "RATE_LIMIT_EXCEEDED",
retryable: true,
},
},
{
name: "licensing rate limit reported as authentication required",
cause: EffectAcpErrors.AcpRequestError.fromProtocolError(
{
code: -32000,
message: "Gemini Enterprise license resolution failed (HTTP 429)",
},
{ method: "authenticate" },
),
requiresSignIn: undefined,
failure: {
class: "usage_limit",
message:
"Antigravity's Gemini Enterprise license check is rate limited. Wait before retrying; signing in again will not reset this limit.",
code: "RATE_LIMIT_EXCEEDED",
retryable: true,
},
},
{
name: "unrelated authentication rate limit",
cause: EffectAcpErrors.AcpRequestError.fromProtocolError(
{
code: -32000,
message: "Authentication failed (HTTP 429)",
},
{ method: "authenticate" },
),
requiresSignIn: true,
failure: undefined,
},
{
name: "licensing service failure",
cause: EffectAcpErrors.AcpRequestError.fromProtocolError(
{
code: -32603,
message: "Gemini Enterprise license resolution failed (HTTP 500)",
},
{ method: "authenticate" },
),
requiresSignIn: undefined,
failure: undefined,
},
{
name: "quota text outside authentication",
cause: EffectAcpErrors.AcpRequestError.fromProtocolError(
{
code: -32603,
message: "Gemini Enterprise license resolution failed (HTTP 429)",
},
{ method: "session/new" },
),
requiresSignIn: undefined,
failure: undefined,
},
{
name: "transport failure",
Expand All @@ -209,8 +283,9 @@ describe("AntigravityAdapterV2 session open", () => {
cause: undefined,
}),
requiresSignIn: undefined,
failure: undefined,
},
] as const)("classifies $name during session open", ({ cause, requiresSignIn }) =>
] as const)("classifies $name during session open", ({ cause, requiresSignIn, failure }) =>
Effect.gen(function* () {
const instanceId = ProviderInstanceId.make("antigravity-sign-in-test");
const adapter = yield* makeAntigravityAdapterV2({
Expand Down Expand Up @@ -239,6 +314,7 @@ describe("AntigravityAdapterV2 session open", () => {
error._tag === "ProviderAdapterOpenSessionError" && error.requiresSignIn,
requiresSignIn,
);
assert.deepEqual("failure" in error ? error.failure : undefined, failure);
}).pipe(Effect.provide(layerSession), Effect.scoped),
);
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,10 @@ import {
makeAntigravityUserInputResponse,
normalizeAntigravityToolCall,
} from "../../provider/acp/AntigravityProtocol.ts";
import { isAntigravitySignInRequiredError } from "../../provider/antigravityAuthSupport.ts";
import {
antigravitySessionOpenFailure,
isAntigravitySignInRequiredError,
} from "../../provider/antigravityAuthSupport.ts";
import {
AcpProviderCapabilitiesV2,
makeAcpAdapterV2,
Expand Down Expand Up @@ -273,6 +276,7 @@ export function makeAntigravityAcpAdapterFlavor(
// ACP 1.3.0 treats /compact as ordinary model input.
supportsCompaction: false,
requiresSignIn: isAntigravitySignInRequiredError,
openSessionFailure: antigravitySessionOpenFailure,
// Antigravity can leave a prompt open without updates; long tools and approvals pause this.
promptInactivityTimeout: "15 minutes",
// 1.4.5 returns the root trajectory's Gemini usageMetadata delta; 1.4.1 returns none.
Expand Down
35 changes: 29 additions & 6 deletions apps/server/src/orchestration-v2/ProviderTurnStartService.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1673,14 +1673,40 @@ effectIt.effect("leaves the run starting when a session-open failure will be ret
}),
);

effectIt.effect("fails the run without a retry when the provider needs sign-in", () =>
effectIt.effect.each([
{
name: "needs sign-in",
sessionFailure: { requiresSignIn: true as const },
failure: {
class: "provider_error",
message: "Failed to open antigravity provider session new-provider-session.",
},
},
{
name: "is rate limited while opening the session",
sessionFailure: {
failure: {
class: "usage_limit" as const,
message: "Antigravity's Gemini Enterprise license check is rate limited.",
code: "RATE_LIMIT_EXCEEDED",
retryable: true,
},
},
failure: {
class: "usage_limit",
message: "Antigravity's Gemini Enterprise license check is rate limited.",
code: "RATE_LIMIT_EXCEEDED",
retryable: true,
},
},
])("fails the run without a retry when the provider $name", ({ sessionFailure, failure }) =>
Effect.gen(function* () {
const harness = makeLocalCommandHarness({
text: "Continue",
openFailure: new ProviderAdapter.ProviderAdapterOpenSessionError({
driver: ProviderDriverKind.make("antigravity"),
providerSessionId: ProviderSessionId.make("new-provider-session"),
requiresSignIn: true,
...sessionFailure,
}),
});

Expand All @@ -1692,10 +1718,7 @@ effectIt.effect("fails the run without a retry when the provider needs sign-in",
{
type: "error",
status: "failed",
failure: {
class: "provider_error",
message: "Failed to open antigravity provider session new-provider-session.",
},
failure,
},
]);
}),
Expand Down
41 changes: 24 additions & 17 deletions apps/server/src/orchestration-v2/ProviderTurnStartService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import {
type OrchestrationV2DomainEvent,
type OrchestrationV2ContextHandoff,
type OrchestrationV2ExecutionNode,
type OrchestrationV2ProviderFailure,
type OrchestrationV2ProviderThread,
type OrchestrationV2Run,
type OrchestrationV2RunAttempt,
Expand Down Expand Up @@ -521,6 +522,7 @@ export const layer: Layer.Layer<
readonly title: string;
readonly error: Error;
readonly message?: string;
readonly failure?: OrchestrationV2ProviderFailure;
}) {
const nestedCause = "cause" in failed.error ? failed.error.cause : undefined;
yield* settleRunBeforeStart({
Expand All @@ -532,18 +534,20 @@ export const layer: Layer.Layer<
item: {
type: "error",
title: failed.title,
failure: makeProviderFailure({
cause: failed.error,
message:
failed.message ??
knownProviderFailureMessage(failed.error) ??
(nestedCause instanceof Error
? nestedCause.message
: typeof nestedCause === "string"
? nestedCause
: failed.error.message),
class: "provider_error",
}),
failure:
failed.failure ??
makeProviderFailure({
cause: failed.error,
message:
failed.message ??
knownProviderFailureMessage(failed.error) ??
(nestedCause instanceof Error
? nestedCause.message
: typeof nestedCause === "string"
? nestedCause
: failed.error.message),
class: "provider_error",
}),
},
});
},
Expand Down Expand Up @@ -808,15 +812,18 @@ export const layer: Layer.Layer<
);
if (sessionResult._tag === "Failure") {
const failure = sessionResult.failure;
const requiresSignIn =
"cause" in failure &&
isProviderAdapterOpenSessionError(failure.cause) &&
failure.cause.requiresSignIn === true;
if (input.willRetry === true && !requiresSignIn) return yield* failure;
const openFailure =
"cause" in failure && isProviderAdapterOpenSessionError(failure.cause)
? failure.cause
: undefined;
const requiresSignIn = openFailure?.requiresSignIn === true;
const rateLimited = openFailure?.failure?.class === "usage_limit";
if (input.willRetry === true && !requiresSignIn && !rateLimited) return yield* failure;
yield* settleStartFailure({
signal: "provider-session-open-failure",
title: "Provider session failed to open",
error: sessionResult.failure,
...(openFailure?.failure === undefined ? {} : { failure: openFailure.failure }),
});
return;
}
Expand Down
32 changes: 30 additions & 2 deletions apps/server/src/provider/antigravityAuthSupport.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
// @effect-diagnostics-next-line nodeBuiltinImport:off - Effect's symlink has no type argument, and Windows needs a junction to link without elevation.
import * as NodeFSP from "node:fs/promises";

import type { AntigravityAuthMethod, ProviderInstanceId } from "@t3tools/contracts";
import type {
AntigravityAuthMethod,
OrchestrationV2ProviderFailure,
ProviderInstanceId,
} from "@t3tools/contracts";
import * as HostProcess from "@t3tools/shared/HostProcess";
import { resolveNodeExecutable, nodeRuntimeUnavailableMessage } from "@t3tools/shared/nodeRuntime";
import * as Digest from "effect/crypto/Digest";
Expand All @@ -17,6 +21,7 @@ import * as ChildProcessSpawner from "effect/process/ChildProcessSpawner";
import * as AcpErrors from "effect-acp/errors";

import { collectUint8StreamText } from "@t3tools/provider-core/server/collectStreamText";
import { makeProviderFailure } from "@t3tools/provider-core/server/failure";
import type * as AcpSessionRuntime from "@t3tools/provider-acp/server/AcpSessionRuntime";
import {
antigravityUserSkillDirectories,
Expand Down Expand Up @@ -161,9 +166,32 @@ function authSupportError(detail: string) {
return new AcpErrors.AcpTransportError({ detail, cause: undefined });
}

function isAntigravityLicensingRateLimit(error: unknown): boolean {
return (
isAcpRequestError(error) &&
error.method === "authenticate" &&
error.errorMessage.startsWith("Gemini Enterprise license resolution failed (HTTP 429)")
);
}

export function antigravitySessionOpenFailure(
cause: unknown,
): OrchestrationV2ProviderFailure | undefined {
if (!isAntigravityLicensingRateLimit(cause)) return undefined;
return makeProviderFailure({
class: "usage_limit",
message:
"Antigravity's Gemini Enterprise license check is rate limited. Wait before retrying; signing in again will not reset this limit.",
code: "RATE_LIMIT_EXCEEDED",
retryable: true,
});
}

export function isAntigravitySignInRequiredError(error: unknown): boolean {
return (
(isAcpRequestError(error) && error.code === -32000) ||
(isAcpRequestError(error) &&
error.code === -32000 &&
!isAntigravityLicensingRateLimit(error)) ||
(isAcpTransportError(error) && error.detail === ANTIGRAVITY_SIGN_IN_REQUIRED_MESSAGE)
);
}
Expand Down
20 changes: 11 additions & 9 deletions packages/provider-acp/src/server/adapter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -318,6 +318,7 @@ export interface AcpAdapterV2Flavor {
) => Effect.Effect<OrchestrationV2ProviderFailure | undefined>
>;
readonly requiresSignIn?: (cause: unknown) => boolean;
readonly openSessionFailure?: (cause: unknown) => OrchestrationV2ProviderFailure | undefined;
/**
* Runs before attachment paths, T3 instructions and native slash-command detection,
* so a rewrite can turn the user's text into a native command.
Expand Down Expand Up @@ -9407,15 +9408,16 @@ export const makeAcpAdapterV2 = Effect.fn("makeAcpAdapterV2")(function* (
},
(effect, input) =>
effect.pipe(
Effect.mapError(
(cause) =>
new ProviderAdapter.ProviderAdapterOpenSessionError({
driver,
providerSessionId: input.providerSessionId,
...(flavor.requiresSignIn?.(cause) === true ? { requiresSignIn: true } : {}),
cause,
}),
),
Effect.mapError((cause) => {
const failure = flavor.openSessionFailure?.(cause);
return new ProviderAdapter.ProviderAdapterOpenSessionError({
driver,
providerSessionId: input.providerSessionId,
...(flavor.requiresSignIn?.(cause) === true ? { requiresSignIn: true } : {}),
...(failure === undefined ? {} : { failure }),
cause,
});
}),
),
),
});
Expand Down
1 change: 1 addition & 0 deletions packages/provider-core/src/server/ProviderAdapter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -200,6 +200,7 @@ export class ProviderAdapterOpenSessionError extends Schema.TaggedError<Provider
driver: ProviderDriverKind,
providerSessionId: ProviderSessionId,
requiresSignIn: Schema.optional(Schema.Literal(true)),
failure: Schema.optional(OrchestrationV2ProviderFailure),
cause: Schema.optional(Schema.Defect()),
},
) {
Expand Down
Loading