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 @@ -343,9 +343,11 @@ function EnvironmentDetail({ environmentId }: { readonly environmentId: Environm
</Text>
</View>
<Text className="text-sm text-foreground-muted">
{provider.installed
? (provider.version ?? "Version unknown")
: "Not installed"}
{provider.status === "checking"
? "Checking provider status"
: provider.installed
? (provider.version ?? "Version unknown")
: "Not installed"}
{provider.versionAdvisory?.latestVersion
? ` · Latest ${provider.versionAdvisory.latestVersion}`
: ""}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -248,17 +248,19 @@ function ProviderAccount({
? state.message
: !provider.enabled
? "Turned off"
: signedIn
? "Signed in"
: isDiscovering
? "Discovering sign-in methods…"
: needsExternalSetup
? "No in-app sign-in advertised. Follow the provider's docs to finish setup."
: !provider.installed
? "Not installed"
: usesCredentials
? "Connect to load models"
: "Not signed in";
: provider.status === "checking"
? "Checking provider status"
: signedIn
? "Signed in"
: isDiscovering
? "Discovering sign-in methods…"
: needsExternalSetup
? "No in-app sign-in advertised. Follow the provider's docs to finish setup."
: !provider.installed
? "Not installed"
: usesCredentials
? "Connect to load models"
: "Not signed in";
// A provider with several sign-in methods picks one from a menu on the same control.
const methodActions: MenuAction[] = methods.map((method) => ({
id: `method:${method.id}`,
Expand Down
2 changes: 1 addition & 1 deletion apps/server/src/provider/ClaudeProvider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -790,7 +790,7 @@ export const makePendingClaudeProvider = Effect.fn("ClaudeProvider.makePendingCl
probe: {
installed: false,
version: null,
status: "warning",
status: "checking",
auth: { status: "unknown" },
message: "Claude provider status has not been checked in this session yet.",
},
Expand Down
2 changes: 1 addition & 1 deletion apps/server/src/provider/CodexProvider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -694,7 +694,7 @@ const makePendingCodexProvider = Effect.fn("CodexProvider.makePendingCodexProvid
probe: {
installed: false,
version: null,
status: "warning",
status: "checking",
auth: { status: "unknown" },
message: "Codex provider status has not been checked in this session yet.",
},
Expand Down
1 change: 1 addition & 0 deletions apps/server/src/provider/Drivers/CodexDriver.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -292,6 +292,7 @@ it.layer(layerTest)("CodexDriver", (it) => {
yield* instance.auth!.logout(Effect.void);
const after = Option.getOrThrow(yield* Fiber.join(disconnected));
expect(after.auth.status).toBe("unauthenticated");
expect(after.status).toBe("warning");
expect(after.auth.email).toBeUndefined();
expect(after.installed).toBe(true);
expect(after.models).toEqual([]);
Expand Down
9 changes: 5 additions & 4 deletions apps/server/src/provider/Drivers/CodexManagedProvider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -73,10 +73,11 @@ export const makeManagedCodexProvider = Effect.fn("makeManagedCodexProvider")(fu
const check = Effect.gen(function* () {
const base = yield* pending;
if (!enabled) return base;
const checkedBase = { ...base, status: "warning" as const };
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Resolved
const executable = yield* runtime.installation.resolve().pipe(Effect.option);
if (Option.isNone(executable))
return {
...base,
...checkedBase,
installed: false,
models: [],
message: "Set up Codex to get started.",
Expand All @@ -85,7 +86,7 @@ export const makeManagedCodexProvider = Effect.fn("makeManagedCodexProvider")(fu
const saved = yield* runtime.auth.read.pipe(Effect.orElseSucceed(() => Option.none()));
if (Option.isSome(saved) && !saved.value.scopes.includes("chatgpt.tokens.use.direct"))
return {
...base,
...checkedBase,
installed: true,
version: executable.value.version,
models: [],
Expand All @@ -99,7 +100,7 @@ export const makeManagedCodexProvider = Effect.fn("makeManagedCodexProvider")(fu
};
if (Option.isNone(saved))
return {
...base,
...checkedBase,
installed: true,
version: executable.value.version,
models: [],
Expand Down Expand Up @@ -183,7 +184,7 @@ export const makeManagedCodexProvider = Effect.fn("makeManagedCodexProvider")(fu
runtime.auth.read.pipe(
Effect.orElseSucceed(() => Option.none()),
Effect.map((current) => ({
...base,
...checkedBase,
installed: true,
version: executable.value.version,
models: [],
Expand Down
4 changes: 2 additions & 2 deletions apps/server/src/provider/ProviderRegistry.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1189,7 +1189,7 @@ it.layer(
} satisfies ServerProvider;
const pendingProvider = {
...cachedProvider,
status: "warning",
status: "checking",
installed: false,
auth: { status: "unknown" },
models: [customModel],
Expand Down Expand Up @@ -2908,7 +2908,7 @@ it.layer(

const pendingRebuild = yield* Stream.toPull(
codexSnapshots.pipe(
Stream.filter((provider) => provider.status === "warning" && !provider.installed),
Stream.filter((provider) => provider.status === "checking" && !provider.installed),
),
);
yield* serverSettings.updateSettings({
Expand Down
4 changes: 3 additions & 1 deletion apps/server/src/provider/ProviderRegistry.ts
Original file line number Diff line number Diff line change
Expand Up @@ -218,7 +218,9 @@ const shouldRetainMissingProviderModels = (provider: ServerProvider): boolean =>
const isPendingAntigravityAuthentication =
isAntigravity && provider.status === "warning" && provider.auth.status === "unknown";
const isPendingInitialProbe =
provider.enabled && !provider.installed && provider.status === "warning";
provider.enabled &&
!provider.installed &&
(provider.status === "checking" || provider.status === "warning");
const isIncompleteInstalledProbe =
provider.installed &&
(provider.status === "error" || (isCodex && provider.status === "warning"));
Expand Down
26 changes: 25 additions & 1 deletion apps/web/src/components/chat/ProviderStatusBanner.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,31 @@ const provider: ServerProvider = {
},
};

describe("compatibility banners", () => {
describe("provider status banners", () => {
it("keeps initial checks quiet without hiding completed probe failures", () => {
const { compatibilityAdvisory: _compatibilityAdvisory, ...base } = provider;
const checking: ServerProvider = {
...base,
installed: false,
version: null,
status: "checking",
auth: { status: "unknown" },
message: "Codex provider status has not been checked in this session yet.",
};
expect(shouldShowProviderStatusBanner(checking, null)).toBe(false);
expect(shouldShowProviderStatusBanner({ ...checking, status: "warning" }, null)).toBe(true);
expect(shouldShowProviderStatusBanner({ ...checking, status: "error" }, null)).toBe(true);
expect(
shouldShowProviderStatusBanner(
{
...checking,
compatibilityAdvisory: { ...provider.compatibilityAdvisory!, status: "broken" },
},
null,
),
).toBe(true);
});

it("shows and dismisses a warning on a healthy provider, then clears it after policy relaxation", () => {
expect(shouldShowProviderStatusBanner(provider, null)).toBe(true);
expect(shouldShowProviderStatusBanner(provider, getProviderStatusBannerKey(provider))).toBe(
Expand Down
2 changes: 1 addition & 1 deletion apps/web/src/components/chat/ProviderStatusBanner.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ export function getProviderStatusBannerKey(status: ServerProvider | null): strin
incompatible.message ?? "",
].join("\u0000");
}
if (status.status === "ready") return null;
if (status.status === "ready" || status.status === "checking") return null;
// Antigravity validates credentials at session start; unknown restart auth is not failure.
if (
status.driver === "antigravity" &&
Expand Down
13 changes: 13 additions & 0 deletions apps/web/src/components/settings/providerStatus.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,19 @@ const provider: ServerProvider = {
};

describe("getProviderSummary", () => {
it("presents an initial check neutrally before installation is known", () => {
const checking: ServerProvider = {
...provider,
installed: false,
status: "checking",
auth: { status: "unknown" },
};
expect(getProviderStatusKey(checking)).toBe("neutral");
expect(getProviderSummary(checking).headline).toBe("Checking provider status");
expect(getProviderSummary({ ...checking, status: "error" }).headline).toBe("Not found");
expect(getProviderSummary({ ...checking, enabled: false }).headline).toBe("Disabled");
});

it("does not hide a provider error behind a previous authenticated state", () => {
expect(
getProviderSummary({
Expand Down
3 changes: 2 additions & 1 deletion apps/web/src/components/settings/providerStatus.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,12 +40,13 @@ function isAntigravitySignInUnchecked(provider: ServerProvider): boolean {

export function getProviderStatusKey(provider: ServerProvider | undefined): ProviderStatusKey {
if (provider && !provider.enabled) return "disabled";
if (provider?.status === "checking") return "neutral";
if (provider && isAntigravitySignInUnchecked(provider)) return "neutral";
return provider?.status ?? "warning";
}

export function getProviderSummary(provider: ServerProvider | undefined) {
if (!provider) {
if (!provider || (provider.enabled && provider.status === "checking")) {
return {
headline: "Checking provider status",
detail: "Waiting for the server to report installation and authentication details.",
Expand Down
1 change: 1 addition & 0 deletions apps/web/src/onboarding/providerReadiness.logic.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ function quoteProviderBinary(
export function getOnboardingProviderState(provider: ServerProvider | undefined) {
if (provider === undefined) return "checking";
if (!provider.enabled || provider.status === "disabled") return "disabled";
if (provider.status === "checking") return "checking";
if (!provider.installed && provider.status === "warning" && provider.auth.status === "unknown")
return "checking";
if (!provider.installed) return "install";
Expand Down
8 changes: 7 additions & 1 deletion packages/client-runtime/src/promptLinks.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -129,7 +129,7 @@ describe("resolvePromptLinkModelSelection", () => {
const provider = (
instanceId: string,
driver: string,
status: "ready" | "warning" | "error" | "disabled" = "ready",
status: "checking" | "ready" | "warning" | "error" | "disabled" = "ready",
) => ({
instanceId: ProviderInstanceId.make(instanceId),
driver: ProviderDriverKind.make(driver),
Expand Down Expand Up @@ -168,5 +168,11 @@ describe("resolvePromptLinkModelSelection", () => {
expect(
resolvePromptLinkModelSelection(providers, { provider: "cursor", model: null }),
).toBeNull();
expect(
resolvePromptLinkModelSelection([provider("codex", "codex", "checking")], {
provider: "codex",
model: "small",
}),
).toEqual({ selection: { instanceId: "codex", model: "small" }, exact: true });
});
});
2 changes: 1 addition & 1 deletion packages/client-runtime/src/promptLinks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,7 @@ export function resolvePromptLinkModelSelection(
);
const instance =
usable.find((provider) => provider.status === "ready") ??
usable.find((provider) => provider.status === "warning");
usable.find((provider) => provider.status === "warning" || provider.status === "checking");
if (instance === undefined) return null;
const requested =
request.model === null
Expand Down
8 changes: 7 additions & 1 deletion packages/contracts/src/server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,13 @@ export type ServerConfigIssue = typeof ServerConfigIssue.Type;

const ServerConfigIssues = ForwardCompatibleArray(ServerConfigIssue);

export const ServerProviderState = Schema.Literals(["ready", "warning", "error", "disabled"]);
export const ServerProviderState = Schema.Literals([
"checking",
"ready",
"warning",
"error",
"disabled",
]);
export type ServerProviderState = typeof ServerProviderState.Type;

export const ServerProviderAuthStatus = Schema.Literals([
Expand Down
Loading