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
1 change: 1 addition & 0 deletions apps/server/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@
"@ff-labs/fff-node": "0.11.0",
"@muse-code/sdk": "1.4.6",
"@napi-rs/keyring": "^1.3.0",
"@t3tools/client-runtime": "workspace:*",
"@t3tools/provider-acp": "workspace:*",
"@t3tools/provider-acp-registry": "workspace:*",
"@t3tools/provider-core": "workspace:*",
Expand Down
10 changes: 10 additions & 0 deletions apps/server/src/policy/managedProviders.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,12 @@ import { kiroClient } from "@t3tools/provider-kiro/client";
import { museClient } from "@t3tools/provider-muse/client";
import { openCodeClient } from "@t3tools/provider-opencode/client";
import { piClient } from "@t3tools/provider-pi/client";
import {
bedrockClient,
cloudflareClient,
ollamaClient,
zedClient,
} from "@t3tools/client-runtime/provider-clients";
import type { ProviderClientDefinition } from "@t3tools/provider-core/client";

interface KnownDriver {
Expand Down Expand Up @@ -50,6 +56,10 @@ const PACKAGED_CLIENTS: ReadonlyArray<ProviderClientDefinition> = [
museClient,
openCodeClient,
piClient,
ollamaClient,
zedClient,
cloudflareClient,
bedrockClient,
acpRegistryClient,
];

Expand Down
10 changes: 5 additions & 5 deletions apps/server/src/processRunner.ts
Original file line number Diff line number Diff line change
Expand Up @@ -272,11 +272,11 @@ const runProcessCore = Effect.fn("processRunner.runProcessCore")(function* (
const outputMode = input.outputMode ?? "error";
const truncatedMarker = input.truncatedMarker ?? "";
const extendEnv = input.env !== undefined;
const spawnCommand = yield* resolveSpawnCommand(
input.command,
input.args,
input.env === undefined ? {} : { env: input.env, extendEnv },
);
const spawnCwd = input.spawnCwd ?? input.cwd;
const spawnCommand = yield* resolveSpawnCommand(input.command, input.args, {
...(input.env === undefined ? {} : { env: input.env, extendEnv }),
...(spawnCwd ? { cwd: spawnCwd } : {}),
});

const toSpawnError = (cause: unknown) =>
new ProcessSpawnError({
Expand Down
73 changes: 72 additions & 1 deletion apps/server/src/provider/AntigravityProvider.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,12 @@ const makeHarness = Effect.fn("makeAntigravityProviderHarness")(function* (
readonly enabled?: boolean;
readonly safe?: boolean;
readonly probeUsage?: Effect.Effect<ServerProviderUsageLimits>;
readonly probeAuth?: Effect.Effect<{
readonly authenticated: boolean;
readonly type?: string;
readonly label?: string;
readonly email?: string;
}>;
} = {},
) {
const initialProbe = yield* Deferred.make<EffectAcpSchema.InitializeResponse, ProbeError>();
Expand All @@ -150,6 +156,7 @@ const makeHarness = Effect.fn("makeAntigravityProviderHarness")(function* (
stampIdentity: (snapshot) => Effect.succeed({ ...snapshot, instanceId, driver }),
...(options.auth ? { auth: options.auth } : {}),
...(options.probeUsage ? { probeUsage: options.probeUsage } : {}),
...(options.probeAuth ? { probeAuth: options.probeAuth } : {}),
Comment thread
maria-rcks marked this conversation as resolved.
Resolved
probe: Ref.update(probeCalls, (count) => count + 1).pipe(
Effect.andThen(Ref.get(probe)),
Effect.flatten,
Expand Down Expand Up @@ -394,11 +401,75 @@ it.layer(layerTest)("Antigravity provider snapshots", (it) => {
),
);

it.effect(
"updates status, email, and text generation when probeAuth transitions from authenticated to unauthenticated",
() =>
Effect.scoped(
Effect.gen(function* () {
const probeAuthRef = yield* Ref.make<
Effect.Effect<{
readonly authenticated: boolean;
readonly type?: string;
readonly label?: string;
readonly email?: string;
}>
>(
Effect.succeed({
authenticated: true,
type: "oauth-personal",
label: "Google account",
email: "user@example.com",
}),
);
const harness = yield* makeHarness({
probeAuth: Effect.flatten(Ref.get(probeAuthRef)),
});
yield* harness.initialize;

const authenticatedSnapshot = yield* harness.provider.snapshot.getSnapshot;
expect(authenticatedSnapshot).toMatchObject({
installed: true,
status: "ready",
auth: {
status: "authenticated",
type: "oauth-personal",
label: "Google account",
email: "user@example.com",
},
supportsTextGeneration: true,
});

// Transition to unauthenticated
yield* Ref.set(
probeAuthRef,
Effect.succeed({
authenticated: false,
type: "oauth-personal",
label: "Google account",
}),
);
const unauthenticatedSnapshot = yield* harness.provider.snapshot.refresh;
expect(unauthenticatedSnapshot).toMatchObject({
installed: true,
status: "warning",
auth: {
status: "unauthenticated",
},
supportsTextGeneration: false,
});
expect(unauthenticatedSnapshot.auth).not.toHaveProperty("email");
}),
),
);

it.effect("clears all account metadata on sign-out and authentication failure", () =>
Effect.scoped(
Effect.gen(function* () {
const auth = { type: "gemini-api-key", label: "Gemini API key" };
const harness = yield* makeHarness({ auth });
const harness = yield* makeHarness({
auth,
probeAuth: Effect.succeed({ authenticated: false }),
});
yield* harness.initialize;
for (const clear of [harness.provider.onSignedOut, harness.provider.onAuthRequired]) {
yield* harness.provider.onSessionStarted(started, "/workspace");
Expand Down
50 changes: 46 additions & 4 deletions apps/server/src/provider/AntigravityProvider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -165,6 +165,15 @@ interface AntigravityProviderOptions {
readonly clearUsage?: Effect.Effect<void>;
readonly maintenanceCapabilities?: ProviderMaintenanceCapabilities;
readonly auth?: { readonly type: string; readonly label: string };
readonly probeAuth?: Effect.Effect<
| {
readonly authenticated: boolean;
readonly type?: string;
readonly label?: string;
readonly email?: string;
}
| undefined
>;
}

export const makeAntigravityProvider = Effect.fn("makeAntigravityProvider")(function* (
Expand Down Expand Up @@ -292,16 +301,44 @@ export const makeAntigravityProvider = Effect.fn("makeAntigravityProvider")(func
: `Antigravity did not respond to its local health check within ${HEALTH_CHECK_TIMEOUT}.`;
const supportsTextGeneration =
initialized !== undefined ? yield* options.supportsTextGeneration : false;
const probedAuth =
initialized !== undefined && options.probeAuth
? yield* options.probeAuth.pipe(Effect.orElseSucceed(() => undefined))
: undefined;
const updatedAt = DateTime.formatIso(yield* DateTime.now);
const next = yield* SubscriptionRef.updateAndGet(metadata, (state) => {
if (state.authRevision !== before.authRevision) return state;
const { message: _previousMessage, ...draft } = state.draft;
const authenticated = draft.auth.status === "authenticated";
const authStatus =
probedAuth !== undefined
? probedAuth.authenticated
? "authenticated"
: "unauthenticated"
: draft.auth.status;
const authenticated = authStatus === "authenticated";
const authType =
probedAuth !== undefined
? probedAuth.authenticated || usesCredentials
? (probedAuth.type ?? options.auth?.type)
: undefined
: (draft.auth.type ?? options.auth?.type);
const authLabel =
probedAuth !== undefined
? probedAuth.authenticated || usesCredentials
? (probedAuth.label ?? options.auth?.label)
: undefined
: (draft.auth.label ?? options.auth?.label);
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Resolved
const authEmail =
probedAuth !== undefined
? probedAuth.authenticated
? probedAuth.email
: undefined
: draft.auth.email;
const message =
errorMessage ??
(authenticated
? undefined
: draft.auth.status === "unauthenticated"
: authStatus === "unauthenticated"
? signInMessage
: authUncheckedMessage);
return {
Expand All @@ -312,6 +349,12 @@ export const makeAntigravityProvider = Effect.fn("makeAntigravityProvider")(func
// Lightweight custom-launcher probes lack versions; preserve the real handshake's version.
version: version === "unknown" ? draft.version : version || draft.version,
status: errorMessage ? "error" : authenticated ? "ready" : "warning",
auth: {
status: authStatus,
...(authType ? { type: authType } : {}),
...(authLabel ? { label: authLabel } : {}),
...(authEmail ? { email: authEmail } : {}),
},
checkedAt: updatedAt,
usageLimits: missingInstallation ? undefined : draft.usageLimits,
...(missingInstallation
Expand All @@ -325,8 +368,7 @@ export const makeAntigravityProvider = Effect.fn("makeAntigravityProvider")(func
: {}),
...(initialized !== undefined
? {
supportsTextGeneration:
supportsTextGeneration && draft.auth.status !== "unauthenticated",
supportsTextGeneration: supportsTextGeneration && authStatus !== "unauthenticated",
}
: {}),
...(message ? { message } : {}),
Expand Down
Loading
Loading