| ... | ... | @@ -49,7 +49,11 @@ import { |
| 49 | 49 | } from "./ProviderRegistry.ts"; |
| 50 | 50 | import * as ServerConfig from "../../config.ts"; |
| 51 | 51 | import * as ServerSettingsModule from "../../serverSettings.ts"; |
| 52 | | import { readProviderStatusCache, resolveProviderStatusCachePath } from "../providerStatusCache.ts"; |
| 52 | import { |
| 53 | readProviderStatusCache, |
| 54 | resolveProviderStatusCachePath, |
| 55 | writeProviderStatusCache, |
| 56 | } from "../providerStatusCache.ts"; |
| 53 | 57 | import { COMPACT_SLASH_COMMAND } from "../providerSnapshot.ts"; |
| 54 | 58 | import type { ProviderInstance } from "../ProviderDriver.ts"; |
| 55 | 59 | import * as ProviderInstanceRegistry from "../Services/ProviderInstanceRegistry.ts"; |
| ... | ... | @@ -900,6 +904,183 @@ it.layer(Layer.mergeAll(NodeServices.layer, ServerSettingsModule.layerTest(), Te |
| 900 | 904 | assert.deepStrictEqual(afterFailure.models, [authoritativeProvider.models[0]!]); |
| 901 | 905 | }); |
| 902 | 906 | |
| 907 | describe("Codex model inventories", () => { |
| 908 | const cachedProvider = { |
| 909 | instanceId: ProviderInstanceId.make("codex-personal"), |
| 910 | driver: ProviderDriverKind.make("codex"), |
| 911 | status: "ready", |
| 912 | enabled: true, |
| 913 | installed: true, |
| 914 | auth: { status: "authenticated" }, |
| 915 | checkedAt: "2026-09-04T19:00:00.000Z", |
| 916 | version: "0.153.3", |
| 917 | models: [ |
| 918 | "vega-alpha", |
| 919 | "joule-alpha", |
| 920 | "kindle-alpha", |
| 921 | "ultima-alpha", |
| 922 | "solstice-alpha", |
| 923 | ].map((slug) => ({ slug, name: slug, isCustom: false, capabilities: null })), |
| 924 | slashCommands: [], |
| 925 | skills: [], |
| 926 | } satisfies ServerProvider; |
| 927 | const customModel = { |
| 928 | slug: "custom-model", |
| 929 | name: "Custom model", |
| 930 | isCustom: true, |
| 931 | capabilities: null, |
| 932 | } as const; |
| 933 | const refreshedProvider = { |
| 934 | ...cachedProvider, |
| 935 | checkedAt: "2026-09-04T19:01:00.000Z", |
| 936 | models: [ |
| 937 | { slug: "gpt-6-astra", name: "GPT 6 Astra", isCustom: false, capabilities: null }, |
| 938 | cachedProvider.models[0]!, |
| 939 | customModel, |
| 940 | ], |
| 941 | } satisfies ServerProvider; |
| 942 | const pendingProvider = { |
| 943 | ...cachedProvider, |
| 944 | status: "warning", |
| 945 | installed: false, |
| 946 | auth: { status: "unknown" }, |
| 947 | models: [customModel], |
| 948 | } satisfies ServerProvider; |
| 949 | const failedProvider = { |
| 950 | ...pendingProvider, |
| 951 | checkedAt: "2026-09-04T19:02:00.000Z", |
| 952 | status: "error", |
| 953 | installed: true, |
| 954 | } satisfies ServerProvider; |
| 955 | |
| 956 | it("drops retired alpha models after discovery, including without OpenAI authentication", () => { |
| 957 | for (const authStatus of ["authenticated", "unknown"] as const) { |
| 958 | assert.deepStrictEqual( |
| 959 | mergeProviderSnapshot(cachedProvider, { |
| 960 | ...refreshedProvider, |
| 961 | auth: { status: authStatus }, |
| 962 | }).models, |
| 963 | refreshedProvider.models, |
| 964 | ); |
| 965 | } |
| 966 | }); |
| 967 | |
| 968 | it("keeps discovered models during startup and failed probes without restoring removed custom models", () => { |
| 969 | for (const provider of [pendingProvider, failedProvider]) { |
| 970 | assert.deepStrictEqual( |
| 971 | mergeProviderSnapshot( |
| 972 | { |
| 973 | ...cachedProvider, |
| 974 | models: [...cachedProvider.models, { ...customModel, slug: "removed-custom" }], |
| 975 | }, |
| 976 | provider, |
| 977 | ).models, |
| 978 | [customModel, ...cachedProvider.models], |
| 979 | ); |
| 980 | } |
| 981 | }); |
| 982 | |
| 983 | it("clears discovered models after sign-out, disable, uninstall, or empty discovery", () => { |
| 984 | const emptyProvider = { ...refreshedProvider, models: [customModel] }; |
| 985 | const clearedProviders = [ |
| 986 | { ...emptyProvider, status: "error", auth: { status: "unauthenticated" } }, |
| 987 | { ...emptyProvider, status: "disabled", enabled: false }, |
| 988 | { ...emptyProvider, status: "error", installed: false, auth: { status: "unknown" } }, |
| 989 | emptyProvider, |
| 990 | { ...emptyProvider, models: [] }, |
| 991 | ] satisfies ReadonlyArray<ServerProvider>; |
| 992 | |
| 993 | for (const provider of clearedProviders) { |
| 994 | assert.deepStrictEqual( |
| 995 | mergeProviderSnapshot(cachedProvider, provider).models, |
| 996 | provider.models, |
| 997 | ); |
| 998 | } |
| 999 | }); |
| 1000 | |
| 1001 | it.effect("persists removals across failed refreshes and registry restarts", () => |
| 1002 | Effect.gen(function* () { |
| 1003 | const config = yield* ServerConfig.ServerConfig; |
| 1004 | const filePath = yield* resolveProviderStatusCachePath({ |
| 1005 | cacheDir: config.providerStatusCacheDir, |
| 1006 | instanceId: cachedProvider.instanceId, |
| 1007 | }); |
| 1008 | yield* writeProviderStatusCache({ filePath, provider: cachedProvider }); |
| 1009 | const nextProvider = yield* Ref.make<ServerProvider>(refreshedProvider); |
| 1010 | const instance = { |
| 1011 | instanceId: cachedProvider.instanceId, |
| 1012 | driverKind: cachedProvider.driver, |
| 1013 | continuationIdentity: { |
| 1014 | driverKind: cachedProvider.driver, |
| 1015 | continuationKey: "codex:instance:codex-personal", |
| 1016 | }, |
| 1017 | displayName: undefined, |
| 1018 | enabled: true, |
| 1019 | snapshot: { |
| 1020 | maintenanceCapabilities: makeManualOnlyProviderMaintenanceCapabilities({ |
| 1021 | provider: cachedProvider.driver, |
| 1022 | packageName: null, |
| 1023 | }), |
| 1024 | getSnapshot: Effect.succeed(pendingProvider), |
| 1025 | refresh: Ref.get(nextProvider), |
| 1026 | streamChanges: Stream.empty, |
| 1027 | applyUsageLimits: () => Effect.void, |
| 1028 | }, |
| 1029 | adapter: {} as ProviderInstance["adapter"], |
| 1030 | textGeneration: {} as ProviderInstance["textGeneration"], |
| 1031 | } satisfies ProviderInstance; |
| 1032 | const instanceRegistryLayer = Layer.succeed( |
| 1033 | ProviderInstanceRegistry.ProviderInstanceRegistry, |
| 1034 | { |
| 1035 | getInstance: (id) => |
| 1036 | Effect.succeed(id === instance.instanceId ? instance : undefined), |
| 1037 | listInstances: Effect.succeed([instance]), |
| 1038 | listUnavailable: Effect.succeed([]), |
| 1039 | streamChanges: Stream.empty, |
| 1040 | subscribeChanges: Effect.flatMap(PubSub.unbounded<void>(), PubSub.subscribe), |
| 1041 | }, |
| 1042 | ); |
| 1043 | const retainedModels = [ |
| 1044 | customModel, |
| 1045 | ...refreshedProvider.models.filter((model) => !model.isCustom), |
| 1046 | ]; |
| 1047 | |
| 1048 | for (const restarted of [false, true]) { |
| 1049 | yield* Effect.gen(function* () { |
| 1050 | const registry = yield* ProviderRegistry.ProviderRegistry; |
| 1051 | const expectedModels = restarted |
| 1052 | ? retainedModels |
| 1053 | : [customModel, ...cachedProvider.models]; |
| 1054 | assert.deepStrictEqual((yield* registry.getProviders)[0]?.models, expectedModels); |
| 1055 | |
| 1056 | yield* registry.refreshInstance(instance.instanceId); |
| 1057 | assert.deepStrictEqual( |
| 1058 | (yield* readProviderStatusCache(filePath))?.models, |
| 1059 | restarted ? retainedModels : refreshedProvider.models, |
| 1060 | ); |
| 1061 | |
| 1062 | yield* Ref.set(nextProvider, failedProvider); |
| 1063 | const afterFailure = yield* registry.refreshInstance(instance.instanceId); |
| 1064 | assert.deepStrictEqual(afterFailure[0]?.models, retainedModels); |
| 1065 | assert.deepStrictEqual( |
| 1066 | (yield* readProviderStatusCache(filePath))?.models, |
| 1067 | retainedModels, |
| 1068 | ); |
| 1069 | }).pipe( |
| 1070 | Effect.provide(ProviderRegistryLive.pipe(Layer.provide(instanceRegistryLayer))), |
| 1071 | Effect.scoped, |
| 1072 | ); |
| 1073 | } |
| 1074 | }).pipe( |
| 1075 | Effect.provide( |
| 1076 | ServerConfig.layerTest(process.cwd(), { |
| 1077 | prefix: "t3-codex-retired-model-cache-", |
| 1078 | }).pipe(Layer.provideMerge(NodeServices.layer)), |
| 1079 | ), |
| 1080 | ), |
| 1081 | ); |
| 1082 | }); |
| 1083 | |
| 903 | 1084 | describe("Antigravity model inventories", () => { |
| 904 | 1085 | const previousProvider = { |
| 905 | 1086 | instanceId: ProviderInstanceId.make("antigravity-personal"), |