import * as NodeServices from "@effect/platform-node/NodeServices";import { describe, it, assert, live } from "@effect/vitest";import { Effect, Exit, Layer, PubSub, Ref, Schema, Scope, Sink, Stream } from "effect";import * as CodexErrors from "effect-codex-app-server/errors";import { ClaudeSettings, CodexSettings, DEFAULT_SERVER_SETTINGS, ProviderDriverKind, ProviderInstanceId, ServerSettings, type ServerProvider, type ServerProviderSlashCommand, type ServerSettings as ContractServerSettings,} from "@t3tools/contracts";import * as PlatformError from "effect/PlatformError";import { HttpClient, HttpClientResponse } from "effect/unstable/http";import { ChildProcessSpawner } from "effect/unstable/process";import { deepMerge } from "@t3tools/shared/Struct";import { createModelCapabilities } from "@t3tools/shared/model"; import { checkCodexProviderStatus, type CodexAppServerProviderSnapshot } from "./CodexProvider.ts";import { checkClaudeProviderStatus } from "./ClaudeProvider.ts";import { OpenCodeRuntimeLive } from "../opencodeRuntime.ts";import { NoOpProviderEventLoggers, ProviderEventLoggers } from "./ProviderEventLoggers.ts";import { ProviderInstanceRegistryHydrationLive } from "./ProviderInstanceRegistryHydration.ts";import { haveProvidersChanged, mergeProviderSnapshot, mergeProviderSnapshots, ProviderRegistryLive, selectProvidersByKind,} from "./ProviderRegistry.ts";import { ServerConfig } from "../../config.ts";import { ServerSettingsService, type ServerSettingsShape } from "../../serverSettings.ts";import { readProviderStatusCache, resolveProviderStatusCachePath } from "../providerStatusCache.ts";import type { ProviderInstance } from "../ProviderDriver.ts";import { ProviderInstanceRegistry } from "../Services/ProviderInstanceRegistry.ts";import { ProviderRegistry } from "../Services/ProviderRegistry.ts";import { makeManualOnlyProviderMaintenanceCapabilities } from "../providerMaintenance.ts"; const defaultClaudeSettings: ClaudeSettings = Schema.decodeSync(ClaudeSettings)({});const defaultCodexSettings: CodexSettings = Schema.decodeSync(CodexSettings)({});const disabledCodexSettings: CodexSettings = Schema.decodeSync(CodexSettings)({ enabled: false,}); process.env.T3CODE_CURSOR_ENABLED = "1"; // ── Test helpers ──────────────────────────────────────────────────── const encoder = new TextEncoder(); const TestHttpClientLive = Layer.succeed( HttpClient.HttpClient, HttpClient.make((request) => Effect.succeed(HttpClientResponse.fromWeb(request, Response.json({ version: "0.0.0" }))), ),); function selectDescriptor( id: string, label: string, options: ReadonlyArray<{ id: string; label: string; isDefault?: boolean }>,) { return { id, label, type: "select" as const, options: [...options], ...(options.find((option) => option.isDefault)?.id ? { currentValue: options.find((option) => option.isDefault)?.id } : {}), };} function booleanDescriptor(id: string, label: string) { return { id, label, type: "boolean" as const, };} type TestClaudeCapabilities = { readonly email: string | undefined; readonly subscriptionType: string | undefined; readonly tokenSource: string | undefined; readonly slashCommands: ReadonlyArray<ServerProviderSlashCommand>;}; function claudeCapabilities(overrides: Partial<TestClaudeCapabilities> = {}) { return () => Effect.succeed({ email: undefined, subscriptionType: undefined, tokenSource: undefined, slashCommands: [], ...overrides, });} const noClaudeCapabilities = () => Effect.sync(() => undefined as TestClaudeCapabilities | undefined); function mockHandle(result: { stdout: string; stderr: string; code: number }) { return ChildProcessSpawner.makeHandle({ pid: ChildProcessSpawner.ProcessId(1), exitCode: Effect.succeed(ChildProcessSpawner.ExitCode(result.code)), isRunning: Effect.succeed(false), kill: () => Effect.void, unref: Effect.succeed(Effect.void), stdin: Sink.drain, stdout: Stream.make(encoder.encode(result.stdout)), stderr: Stream.make(encoder.encode(result.stderr)), all: Stream.empty, getInputFd: () => Sink.drain, getOutputFd: () => Stream.empty, });} function mockSpawnerLayer( handler: (args: ReadonlyArray<string>) => { stdout: string; stderr: string; code: number; },) { return Layer.succeed( ChildProcessSpawner.ChildProcessSpawner, ChildProcessSpawner.make((command) => { const cmd = command as unknown as { args: ReadonlyArray<string> }; return Effect.succeed(mockHandle(handler(cmd.args))); }), );} function recordingMockSpawnerLayer( handler: (args: ReadonlyArray<string>) => { stdout: string; stderr: string; code: number; },) { const commands: Array<{ readonly args: ReadonlyArray<string>; readonly env: NodeJS.ProcessEnv | undefined; }> = []; const layer = Layer.succeed( ChildProcessSpawner.ChildProcessSpawner, ChildProcessSpawner.make((command) => { const cmd = command as unknown as { args: ReadonlyArray<string>; options?: { readonly env?: NodeJS.ProcessEnv; }; }; commands.push({ args: cmd.args, env: cmd.options?.env }); return Effect.succeed(mockHandle(handler(cmd.args))); }), ); return { layer, commands };} function mockCommandSpawnerLayer( handler: ( command: string, args: ReadonlyArray<string>, ) => { stdout: string; stderr: string; code: number },) { return Layer.succeed( ChildProcessSpawner.ChildProcessSpawner, ChildProcessSpawner.make((command) => { const cmd = command as unknown as { command: string; args: ReadonlyArray<string>; }; return Effect.succeed(mockHandle(handler(cmd.command, cmd.args))); }), );} function failingSpawnerLayer(description: string) { return Layer.succeed( ChildProcessSpawner.ChildProcessSpawner, ChildProcessSpawner.make(() => Effect.fail( PlatformError.systemError({ _tag: "NotFound", module: "ChildProcess", method: "spawn", description, }), ), ), );} const codexModelCapabilities = createModelCapabilities({ optionDescriptors: [ selectDescriptor("reasoningEffort", "Reasoning", [ { id: "high", label: "High", isDefault: true }, { id: "low", label: "Low" }, ]), booleanDescriptor("fastMode", "Fast Mode"), ],}) satisfies NonNullable<ServerProvider["models"][number]["capabilities"]>; function makeCodexProbeSnapshot( input: Partial<CodexAppServerProviderSnapshot> = {},): CodexAppServerProviderSnapshot { return { version: "1.0.0", account: { account: { type: "chatgpt", email: "test@example.com", planType: "pro", }, requiresOpenaiAuth: false, }, models: [ { slug: "gpt-live-codex", name: "GPT Live Codex", isCustom: false, capabilities: codexModelCapabilities, }, ], skills: [], ...input, };} function makeMutableServerSettingsService( initial: ContractServerSettings = DEFAULT_SERVER_SETTINGS,) { return Effect.gen(function* () { const settingsRef = yield* Ref.make(initial); const changes = yield* PubSub.unbounded<ContractServerSettings>(); return { start: Effect.void, ready: Effect.void, getSettings: Ref.get(settingsRef), updateSettings: (patch) => Effect.gen(function* () { const current = yield* Ref.get(settingsRef); const next = Schema.decodeSync(ServerSettings)(deepMerge(current, patch)); yield* Ref.set(settingsRef, next); yield* PubSub.publish(changes, next); return next; }), get streamChanges() { return Stream.fromPubSub(changes); }, } satisfies ServerSettingsShape; });} it.layer(Layer.mergeAll(NodeServices.layer, ServerSettingsService.layerTest(), TestHttpClientLive))( "ProviderRegistry", (it) => { describe("checkCodexProviderStatus", () => { it.effect("uses the app-server account and model list for provider status", () => Effect.gen(function* () { const status = yield* checkCodexProviderStatus(defaultCodexSettings, () => Effect.succeed( makeCodexProbeSnapshot({ skills: [ { name: "github:gh-fix-ci", path: "/Users/test/.codex/skills/gh-fix-ci/SKILL.md", enabled: true, displayName: "CI Debug", shortDescription: "Debug failing GitHub Actions checks", }, ], }), ), ); assert.strictEqual(status.status, "ready"); assert.strictEqual(status.installed, true); assert.strictEqual(status.version, "1.0.0"); assert.strictEqual(status.auth.status, "authenticated"); assert.strictEqual(status.auth.type, "chatgpt"); assert.strictEqual(status.auth.label, "ChatGPT Pro 20x Subscription"); assert.strictEqual(status.auth.email, "test@example.com"); assert.deepStrictEqual(status.models, [ { slug: "gpt-live-codex", name: "GPT Live Codex", isCustom: false, capabilities: codexModelCapabilities, }, ]); assert.deepStrictEqual(status.skills, [ { name: "github:gh-fix-ci", path: "/Users/test/.codex/skills/gh-fix-ci/SKILL.md", enabled: true, displayName: "CI Debug", shortDescription: "Debug failing GitHub Actions checks", }, ]); }), ); it.effect("returns unauthenticated when app-server requires OpenAI auth", () => Effect.gen(function* () { const status = yield* checkCodexProviderStatus(defaultCodexSettings, () => Effect.succeed( makeCodexProbeSnapshot({ account: { account: null, requiresOpenaiAuth: true, }, }), ), ); assert.strictEqual(status.status, "error"); assert.strictEqual(status.auth.status, "unauthenticated"); assert.strictEqual( status.message, "Codex CLI is not authenticated. Run `codex login` and try again.", ); }), ); it.effect( "returns ready with unknown auth when app-server does not require OpenAI auth", () => Effect.gen(function* () { const status = yield* checkCodexProviderStatus(defaultCodexSettings, () => Effect.succeed( makeCodexProbeSnapshot({ account: { account: null, requiresOpenaiAuth: false, }, }), ), ); assert.strictEqual(status.status, "ready"); assert.strictEqual(status.auth.status, "unknown"); }), ); it.effect("returns an api key label for codex api key auth", () => Effect.gen(function* () { const status = yield* checkCodexProviderStatus(defaultCodexSettings, () => Effect.succeed( makeCodexProbeSnapshot({ account: { account: { type: "apiKey" }, requiresOpenaiAuth: false, }, }), ), ); assert.strictEqual(status.status, "ready"); assert.strictEqual(status.auth.status, "authenticated"); assert.strictEqual(status.auth.type, "apiKey"); assert.strictEqual(status.auth.label, "OpenAI API Key"); }), ); it.effect("returns an Amazon Bedrock label for codex Bedrock auth", () => Effect.gen(function* () { const status = yield* checkCodexProviderStatus(defaultCodexSettings, () => Effect.succeed( makeCodexProbeSnapshot({ account: { account: { type: "amazonBedrock" }, requiresOpenaiAuth: false, }, }), ), ); assert.strictEqual(status.status, "ready"); assert.strictEqual(status.auth.status, "authenticated"); assert.strictEqual(status.auth.type, "amazonBedrock"); assert.strictEqual(status.auth.label, "Amazon Bedrock"); }), ); it.effect("returns unavailable when codex is missing", () => Effect.gen(function* () { const status = yield* checkCodexProviderStatus(defaultCodexSettings, () => Effect.fail( new CodexErrors.CodexAppServerSpawnError({ command: "codex app-server", cause: new Error("spawn codex ENOENT"), }), ), ); assert.strictEqual(status.status, "error"); assert.strictEqual(status.installed, false); assert.strictEqual(status.auth.status, "unknown"); assert.strictEqual( status.message, "Codex CLI (`codex`) is not installed or not on PATH.", ); }), ); }); describe("ProviderRegistryLive", () => { it("treats equal provider snapshots as unchanged", () => { const providers = [ { instanceId: ProviderInstanceId.make("codex"), driver: ProviderDriverKind.make("codex"), status: "ready", enabled: true, installed: true, auth: { status: "authenticated" }, checkedAt: "2026-03-25T00:00:00.000Z", version: "1.0.0", models: [], slashCommands: [], skills: [], }, { instanceId: ProviderInstanceId.make("claudeAgent"), driver: ProviderDriverKind.make("claudeAgent"), status: "warning", enabled: true, installed: true, auth: { status: "unknown" }, checkedAt: "2026-03-25T00:00:00.000Z", version: "1.0.0", models: [], slashCommands: [], skills: [], }, ] as const satisfies ReadonlyArray<ServerProvider>; assert.strictEqual(haveProvidersChanged(providers, [...providers]), false); }); it("preserves previously discovered provider models when a refresh returns none", () => { const previousProvider = { instanceId: ProviderInstanceId.make("cursor"), driver: ProviderDriverKind.make("cursor"), status: "ready", enabled: true, installed: true, auth: { status: "authenticated" }, checkedAt: "2026-04-14T00:00:00.000Z", version: "2026.04.09-f2b0fcd", models: [ { slug: "claude-opus-4-6", name: "Opus 4.6", isCustom: false, capabilities: createModelCapabilities({ optionDescriptors: [ selectDescriptor("reasoning", "Reasoning", [ { id: "high", label: "High", isDefault: true }, ]), booleanDescriptor("fastMode", "Fast Mode"), booleanDescriptor("thinking", "Thinking"), ], }), }, ], slashCommands: [], skills: [], } as const satisfies ServerProvider; const refreshedProvider = { ...previousProvider, checkedAt: "2026-04-14T00:01:00.000Z", models: [], } satisfies ServerProvider; assert.deepStrictEqual(mergeProviderSnapshot(previousProvider, refreshedProvider).models, [ ...previousProvider.models, ]); }); it("fills missing capabilities from the previous provider snapshot", () => { const previousProvider = { instanceId: ProviderInstanceId.make("cursor"), driver: ProviderDriverKind.make("cursor"), status: "ready", enabled: true, installed: true, auth: { status: "authenticated" }, checkedAt: "2026-04-14T00:00:00.000Z", version: "2026.04.09-f2b0fcd", models: [ { slug: "claude-opus-4-6", name: "Opus 4.6", isCustom: false,Lines 1–500 of 1690. Load more
import * as NodeServices from "@effect/platform-node/NodeServices"; import { describe, it, assert, live } from "@effect/vitest"; import { Effect, Exit, Layer, PubSub, Ref, Schema, Scope, Sink, Stream } from "effect"; import * as CodexErrors from "effect-codex-app-server/errors"; import { ClaudeSettings, CodexSettings, DEFAULT_SERVER_SETTINGS, ProviderDriverKind, ProviderInstanceId, ServerSettings, type ServerProvider, type ServerProviderSlashCommand, type ServerSettings as ContractServerSettings, } from "@t3tools/contracts"; import * as PlatformError from "effect/PlatformError"; import { HttpClient, HttpClientResponse } from "effect/unstable/http"; import { ChildProcessSpawner } from "effect/unstable/process"; import { deepMerge } from "@t3tools/shared/Struct"; import { createModelCapabilities } from "@t3tools/shared/model"; import { checkCodexProviderStatus, type CodexAppServerProviderSnapshot } from "./CodexProvider.ts"; import { checkClaudeProviderStatus } from "./ClaudeProvider.ts"; import { OpenCodeRuntimeLive } from "../opencodeRuntime.ts"; import { NoOpProviderEventLoggers, ProviderEventLoggers } from "./ProviderEventLoggers.ts"; import { ProviderInstanceRegistryHydrationLive } from "./ProviderInstanceRegistryHydration.ts"; import { haveProvidersChanged, mergeProviderSnapshot, mergeProviderSnapshots, ProviderRegistryLive, selectProvidersByKind, } from "./ProviderRegistry.ts"; import { ServerConfig } from "../../config.ts"; import { ServerSettingsService, type ServerSettingsShape } from "../../serverSettings.ts"; import { readProviderStatusCache, resolveProviderStatusCachePath } from "../providerStatusCache.ts"; import type { ProviderInstance } from "../ProviderDriver.ts"; import { ProviderInstanceRegistry } from "../Services/ProviderInstanceRegistry.ts"; import { ProviderRegistry } from "../Services/ProviderRegistry.ts"; import { makeManualOnlyProviderMaintenanceCapabilities } from "../providerMaintenance.ts"; const defaultClaudeSettings: ClaudeSettings = Schema.decodeSync(ClaudeSettings)({}); const defaultCodexSettings: CodexSettings = Schema.decodeSync(CodexSettings)({}); const disabledCodexSettings: CodexSettings = Schema.decodeSync(CodexSettings)({ enabled: false, }); process.env.T3CODE_CURSOR_ENABLED = "1"; // ── Test helpers ──────────────────────────────────────────────────── const encoder = new TextEncoder(); const TestHttpClientLive = Layer.succeed( HttpClient.HttpClient, HttpClient.make((request) => Effect.succeed(HttpClientResponse.fromWeb(request, Response.json({ version: "0.0.0" }))), ), ); function selectDescriptor( id: string, label: string, options: ReadonlyArray<{ id: string; label: string; isDefault?: boolean }>, ) { return { id, label, type: "select" as const, options: [...options], ...(options.find((option) => option.isDefault)?.id ? { currentValue: options.find((option) => option.isDefault)?.id } : {}), }; } function booleanDescriptor(id: string, label: string) { return { id, label, type: "boolean" as const, }; } type TestClaudeCapabilities = { readonly email: string | undefined; readonly subscriptionType: string | undefined; readonly tokenSource: string | undefined; readonly slashCommands: ReadonlyArray<ServerProviderSlashCommand>; }; function claudeCapabilities(overrides: Partial<TestClaudeCapabilities> = {}) { return () => Effect.succeed({ email: undefined, subscriptionType: undefined, tokenSource: undefined, slashCommands: [], ...overrides, }); } const noClaudeCapabilities = () => Effect.sync(() => undefined as TestClaudeCapabilities | undefined); function mockHandle(result: { stdout: string; stderr: string; code: number }) { return ChildProcessSpawner.makeHandle({ pid: ChildProcessSpawner.ProcessId(1), exitCode: Effect.succeed(ChildProcessSpawner.ExitCode(result.code)), isRunning: Effect.succeed(false), kill: () => Effect.void, unref: Effect.succeed(Effect.void), stdin: Sink.drain, stdout: Stream.make(encoder.encode(result.stdout)), stderr: Stream.make(encoder.encode(result.stderr)), all: Stream.empty, getInputFd: () => Sink.drain, getOutputFd: () => Stream.empty, }); } function mockSpawnerLayer( handler: (args: ReadonlyArray<string>) => { stdout: string; stderr: string; code: number; }, ) { return Layer.succeed( ChildProcessSpawner.ChildProcessSpawner, ChildProcessSpawner.make((command) => { const cmd = command as unknown as { args: ReadonlyArray<string> }; return Effect.succeed(mockHandle(handler(cmd.args))); }), ); } function recordingMockSpawnerLayer( handler: (args: ReadonlyArray<string>) => { stdout: string; stderr: string; code: number; }, ) { const commands: Array<{ readonly args: ReadonlyArray<string>; readonly env: NodeJS.ProcessEnv | undefined; }> = []; const layer = Layer.succeed( ChildProcessSpawner.ChildProcessSpawner, ChildProcessSpawner.make((command) => { const cmd = command as unknown as { args: ReadonlyArray<string>; options?: { readonly env?: NodeJS.ProcessEnv; }; }; commands.push({ args: cmd.args, env: cmd.options?.env }); return Effect.succeed(mockHandle(handler(cmd.args))); }), ); return { layer, commands }; } function mockCommandSpawnerLayer( handler: ( command: string, args: ReadonlyArray<string>, ) => { stdout: string; stderr: string; code: number }, ) { return Layer.succeed( ChildProcessSpawner.ChildProcessSpawner, ChildProcessSpawner.make((command) => { const cmd = command as unknown as { command: string; args: ReadonlyArray<string>; }; return Effect.succeed(mockHandle(handler(cmd.command, cmd.args))); }), ); } function failingSpawnerLayer(description: string) { return Layer.succeed( ChildProcessSpawner.ChildProcessSpawner, ChildProcessSpawner.make(() => Effect.fail( PlatformError.systemError({ _tag: "NotFound", module: "ChildProcess", method: "spawn", description, }), ), ), ); } const codexModelCapabilities = createModelCapabilities({ optionDescriptors: [ selectDescriptor("reasoningEffort", "Reasoning", [ { id: "high", label: "High", isDefault: true }, { id: "low", label: "Low" }, ]), booleanDescriptor("fastMode", "Fast Mode"), ], }) satisfies NonNullable<ServerProvider["models"][number]["capabilities"]>; function makeCodexProbeSnapshot( input: Partial<CodexAppServerProviderSnapshot> = {}, ): CodexAppServerProviderSnapshot { return { version: "1.0.0", account: { account: { type: "chatgpt", email: "test@example.com", planType: "pro", }, requiresOpenaiAuth: false, }, models: [ { slug: "gpt-live-codex", name: "GPT Live Codex", isCustom: false, capabilities: codexModelCapabilities, }, ], skills: [], ...input, }; } function makeMutableServerSettingsService( initial: ContractServerSettings = DEFAULT_SERVER_SETTINGS, ) { return Effect.gen(function* () { const settingsRef = yield* Ref.make(initial); const changes = yield* PubSub.unbounded<ContractServerSettings>(); return { start: Effect.void, ready: Effect.void, getSettings: Ref.get(settingsRef), updateSettings: (patch) => Effect.gen(function* () { const current = yield* Ref.get(settingsRef); const next = Schema.decodeSync(ServerSettings)(deepMerge(current, patch)); yield* Ref.set(settingsRef, next); yield* PubSub.publish(changes, next); return next; }), get streamChanges() { return Stream.fromPubSub(changes); }, } satisfies ServerSettingsShape; }); } it.layer(Layer.mergeAll(NodeServices.layer, ServerSettingsService.layerTest(), TestHttpClientLive))( "ProviderRegistry", (it) => { describe("checkCodexProviderStatus", () => { it.effect("uses the app-server account and model list for provider status", () => Effect.gen(function* () { const status = yield* checkCodexProviderStatus(defaultCodexSettings, () => Effect.succeed( makeCodexProbeSnapshot({ skills: [ { name: "github:gh-fix-ci", path: "/Users/test/.codex/skills/gh-fix-ci/SKILL.md", enabled: true, displayName: "CI Debug", shortDescription: "Debug failing GitHub Actions checks", }, ], }), ), ); assert.strictEqual(status.status, "ready"); assert.strictEqual(status.installed, true); assert.strictEqual(status.version, "1.0.0"); assert.strictEqual(status.auth.status, "authenticated"); assert.strictEqual(status.auth.type, "chatgpt"); assert.strictEqual(status.auth.label, "ChatGPT Pro 20x Subscription"); assert.strictEqual(status.auth.email, "test@example.com"); assert.deepStrictEqual(status.models, [ { slug: "gpt-live-codex", name: "GPT Live Codex", isCustom: false, capabilities: codexModelCapabilities, }, ]); assert.deepStrictEqual(status.skills, [ { name: "github:gh-fix-ci", path: "/Users/test/.codex/skills/gh-fix-ci/SKILL.md", enabled: true, displayName: "CI Debug", shortDescription: "Debug failing GitHub Actions checks", }, ]); }), ); it.effect("returns unauthenticated when app-server requires OpenAI auth", () => Effect.gen(function* () { const status = yield* checkCodexProviderStatus(defaultCodexSettings, () => Effect.succeed( makeCodexProbeSnapshot({ account: { account: null, requiresOpenaiAuth: true, }, }), ), ); assert.strictEqual(status.status, "error"); assert.strictEqual(status.auth.status, "unauthenticated"); assert.strictEqual( status.message, "Codex CLI is not authenticated. Run `codex login` and try again.", ); }), ); it.effect( "returns ready with unknown auth when app-server does not require OpenAI auth", () => Effect.gen(function* () { const status = yield* checkCodexProviderStatus(defaultCodexSettings, () => Effect.succeed( makeCodexProbeSnapshot({ account: { account: null, requiresOpenaiAuth: false, }, }), ), ); assert.strictEqual(status.status, "ready"); assert.strictEqual(status.auth.status, "unknown"); }), ); it.effect("returns an api key label for codex api key auth", () => Effect.gen(function* () { const status = yield* checkCodexProviderStatus(defaultCodexSettings, () => Effect.succeed( makeCodexProbeSnapshot({ account: { account: { type: "apiKey" }, requiresOpenaiAuth: false, }, }), ), ); assert.strictEqual(status.status, "ready"); assert.strictEqual(status.auth.status, "authenticated"); assert.strictEqual(status.auth.type, "apiKey"); assert.strictEqual(status.auth.label, "OpenAI API Key"); }), ); it.effect("returns an Amazon Bedrock label for codex Bedrock auth", () => Effect.gen(function* () { const status = yield* checkCodexProviderStatus(defaultCodexSettings, () => Effect.succeed( makeCodexProbeSnapshot({ account: { account: { type: "amazonBedrock" }, requiresOpenaiAuth: false, }, }), ), ); assert.strictEqual(status.status, "ready"); assert.strictEqual(status.auth.status, "authenticated"); assert.strictEqual(status.auth.type, "amazonBedrock"); assert.strictEqual(status.auth.label, "Amazon Bedrock"); }), ); it.effect("returns unavailable when codex is missing", () => Effect.gen(function* () { const status = yield* checkCodexProviderStatus(defaultCodexSettings, () => Effect.fail( new CodexErrors.CodexAppServerSpawnError({ command: "codex app-server", cause: new Error("spawn codex ENOENT"), }), ), ); assert.strictEqual(status.status, "error"); assert.strictEqual(status.installed, false); assert.strictEqual(status.auth.status, "unknown"); assert.strictEqual( status.message, "Codex CLI (`codex`) is not installed or not on PATH.", ); }), ); }); describe("ProviderRegistryLive", () => { it("treats equal provider snapshots as unchanged", () => { const providers = [ { instanceId: ProviderInstanceId.make("codex"), driver: ProviderDriverKind.make("codex"), status: "ready", enabled: true, installed: true, auth: { status: "authenticated" }, checkedAt: "2026-03-25T00:00:00.000Z", version: "1.0.0", models: [], slashCommands: [], skills: [], }, { instanceId: ProviderInstanceId.make("claudeAgent"), driver: ProviderDriverKind.make("claudeAgent"), status: "warning", enabled: true, installed: true, auth: { status: "unknown" }, checkedAt: "2026-03-25T00:00:00.000Z", version: "1.0.0", models: [], slashCommands: [], skills: [], }, ] as const satisfies ReadonlyArray<ServerProvider>; assert.strictEqual(haveProvidersChanged(providers, [...providers]), false); }); it("preserves previously discovered provider models when a refresh returns none", () => { const previousProvider = { instanceId: ProviderInstanceId.make("cursor"), driver: ProviderDriverKind.make("cursor"), status: "ready", enabled: true, installed: true, auth: { status: "authenticated" }, checkedAt: "2026-04-14T00:00:00.000Z", version: "2026.04.09-f2b0fcd", models: [ { slug: "claude-opus-4-6", name: "Opus 4.6", isCustom: false, capabilities: createModelCapabilities({ optionDescriptors: [ selectDescriptor("reasoning", "Reasoning", [ { id: "high", label: "High", isDefault: true }, ]), booleanDescriptor("fastMode", "Fast Mode"), booleanDescriptor("thinking", "Thinking"), ], }), }, ], slashCommands: [], skills: [], } as const satisfies ServerProvider; const refreshedProvider = { ...previousProvider, checkedAt: "2026-04-14T00:01:00.000Z", models: [], } satisfies ServerProvider; assert.deepStrictEqual(mergeProviderSnapshot(previousProvider, refreshedProvider).models, [ ...previousProvider.models, ]); }); it("fills missing capabilities from the previous provider snapshot", () => { const previousProvider = { instanceId: ProviderInstanceId.make("cursor"), driver: ProviderDriverKind.make("cursor"), status: "ready", enabled: true, installed: true, auth: { status: "authenticated" }, checkedAt: "2026-04-14T00:00:00.000Z", version: "2026.04.09-f2b0fcd", models: [ { slug: "claude-opus-4-6", name: "Opus 4.6", isCustom: false,