Skip to content
Source
Sign inSign up

providerCompatibility.test.ts

329 lines12.4 KB

1import { assert, describe, it } from "@effect/vitest";
2import { ProviderDriverKind, ProviderInstanceId, type ServerProvider } from "@t3tools/contracts";
3import * as NodeServices from "@effect/platform-node/NodeServices";
4import * as Deferred from "effect/Deferred";
5import * as Fiber from "effect/Fiber";
6import * as Effect from "effect/Effect";
7import * as Layer from "effect/Layer";
8import * as PubSub from "effect/PubSub";
9import * as Ref from "effect/Ref";
10import * as Stream from "effect/Stream";
11import * as ServerConfig from "../config.ts";
12import * as ModelManifest from "./ModelManifest.ts";
13import * as ProviderRegistry from "./ProviderRegistry.ts";
14import * as ProviderLatestVersions from "@t3tools/provider-core/server/ProviderLatestVersions";
15import * as ProviderInstanceRegistry from "./ProviderInstanceRegistry.ts";
16import * as ProviderInstanceRegistryMutator from "./ProviderInstanceRegistryMutator.ts";
17import type { ProviderInstance } from "@t3tools/provider-core/server/driver";
18import { makeManualOnlyProviderMaintenanceCapabilities } from "@t3tools/provider-core/server/maintenanceResolver";
19import * as Schema from "effect/Schema";
20import {
21 applyProviderCompatibility,
22 ProviderCompatibilityPolicy,
23 resolveProviderCompatibility,
24} from "./providerCompatibility.ts";
25import bundledManifest from "./model-manifest.json" with { type: "json" };
26​
27const decodeCompatibilityPolicies = Schema.decodeUnknownSync(
28 Schema.Array(ProviderCompatibilityPolicy),
29);
30const driver = ProviderDriverKind.make("codex");
31const policy: ProviderCompatibilityPolicy = {
32 driver,
33 t3CodeRange: ">=0.0.0",
34 recommendedVersion: "2.0.0",
35 recommendedRange: ">=2.0.0 <3.0.0",
36 ranges: [
37 { range: "<1.0.0", status: "broken" },
38 { range: ">=1.0.0 <1.5.0", status: "unsupported" },
39 { range: ">=1.5.0 <2.0.0", status: "graceful" },
40 { range: ">=2.0.0 <3.0.0", status: "supported" },
41 ],
42};
43const provider: ServerProvider = {
44 driver,
45 instanceId: ProviderInstanceId.make("codex-work"),
46 enabled: true,
47 installed: true,
48 version: "0.9.0",
49 status: "error",
50 message: "Authentication failed",
51 checkedAt: "2026-09-22T00:00:00Z",
52 auth: { status: "unauthenticated" },
53 models: [],
54 skills: [],
55 slashCommands: [],
56};
57​
58describe("provider compatibility", () => {
59 it("keeps releases before V2 on the OpenCode 1 policy they shipped with", () => {
60 // Older builds fetch main's manifest; their V1 orchestrator cannot drive OpenCode 2.
61 const opencode = ProviderDriverKind.make("opencode");
62 for (const [version, expected] of [
63 ["1.14.19", "supported"],
64 ["2.0.18", "broken"],
65 ["1.14.18", "broken"],
66 ] as const) {
67 const advisory = resolveProviderCompatibility(
68 ModelManifest.BUNDLED_MODEL_MANIFEST.compatibility,
69 opencode,
70 version,
71 "0.0.45",
72 );
73 assert.strictEqual(advisory?.status, expected, `OpenCode ${version} on 0.0.45`);
74 assert.strictEqual(advisory?.recommendedVersion, "1.14.19");
75 }
76 assert.isUndefined(
77 resolveProviderCompatibility(
78 ModelManifest.BUNDLED_MODEL_MANIFEST.compatibility,
79 ProviderDriverKind.make("pi"),
80 "1.0.0",
81 "0.0.45",
82 ),
83 );
84 });
85​
86 it("compares Cursor build dates without treating semver prereleases as stable", () => {
87 const cursor = ProviderDriverKind.make("cursor");
88 const cursorPolicy: ProviderCompatibilityPolicy = {
89 driver: cursor,
90 t3CodeRange: policy.t3CodeRange,
91 ranges: [
92 { range: "<2026.05.09", status: "unsupported" },
93 { range: ">=2026.05.09", status: "supported" },
94 ],
95 };
96 for (const [version, expected] of [
97 ["2026.05.08-a1b2c3d", "unsupported"],
98 ["2026.05.09-a1b2c3d", "supported"],
99 ["2026.09.22-f2b0fcd", "supported"],
100 ["2026.05.09", "supported"],
101 ["2026.05.09-beta.1", "unknown"],
102 ] as const) {
103 assert.strictEqual(
104 resolveProviderCompatibility([cursorPolicy], cursor, version)?.status,
105 expected,
106 );
107 }
108 assert.strictEqual(
109 resolveProviderCompatibility([policy], driver, "2.0.0-a1b2c3d")?.status,
110 "unknown",
111 );
112 });
113​
114 it("recognizes Antigravity semver release tags while keeping dated candidates unknown", () => {
115 const antigravity = ProviderDriverKind.make("antigravity");
116 const taggedPolicy = {
117 ...policy,
118 driver: antigravity,
119 ranges: [{ range: "=2.0.0", status: "supported" as const }],
120 };
121 for (const [version, expected] of [
122 ["agy_acp_server_2.0.0", "supported"],
123 ["2.0.0", "supported"],
124 ["agy_acp_server_2.0.1", "unknown"],
125 ["agy_acp_server_2.0.0-beta.1", "unknown"],
126 ["agy_acp_server_20260818_01_RC01", "unknown"],
127 ] as const) {
128 assert.strictEqual(
129 resolveProviderCompatibility([taggedPolicy], antigravity, version)?.status,
130 expected,
131 );
132 }
133 });
134​
135 it.each(["1.4.5", "1.5.7"])(
136 "supports the legacy and native Antigravity runtime %s in the bundled manifest",
137 (version) => {
138 const policies = decodeCompatibilityPolicies(bundledManifest.compatibility);
139 assert.strictEqual(
140 resolveProviderCompatibility(
141 policies,
142 ProviderDriverKind.make("antigravity"),
143 `agy_acp_server_${version}`,
144 )?.status,
145 "supported",
146 );
147 },
148 );
149​
150 it("classifies boundaries and treats unlisted versions and release tags as unknown", () => {
151 for (const [version, expected] of [
152 ["0.9.9", "broken"],
153 ["1.0.0", "unsupported"],
154 ["1.5.0", "graceful"],
155 ["2.0.0", "supported"],
156 ["v2.0.0", "supported"],
157 ["3.0.0", "unknown"],
158 ["2.0.0-beta.1", "unknown"],
159 ["agy_acp_server_20260818_01_RC01", "unknown"],
160 [null, "unknown"],
161 ] as const) {
162 assert.strictEqual(resolveProviderCompatibility([policy], driver, version)?.status, expected);
163 }
164 });
165​
166 it("applies policies only within their T3 Code version range", () => {
167 const scopedPolicy = { ...policy, t3CodeRange: ">=0.0.42 <0.1.0" };
168 for (const version of ["0.0.42", "0.0.45"]) {
169 assert.strictEqual(
170 resolveProviderCompatibility([scopedPolicy], driver, "0.9.0", version)?.status,
171 "broken",
172 );
173 }
174 for (const version of ["0.0.41", "0.1.0"]) {
175 assert.isUndefined(resolveProviderCompatibility([scopedPolicy], driver, "0.9.0", version));
176 }
177 });
178​
179 it("relaxes remote policy without losing probe errors, and falls back when a policy is omitted", () => {
180 const broken = applyProviderCompatibility(provider, [], [policy]);
181 assert.strictEqual(broken.compatibilityAdvisory?.status, "broken");
182 const relaxed = { ...policy, ranges: [{ range: ">=0.0.0", status: "supported" as const }] };
183 const supported = applyProviderCompatibility(broken, [relaxed], [policy]);
184 assert.strictEqual(supported.compatibilityAdvisory?.status, "supported");
185 assert.strictEqual(supported.status, "error");
186 assert.strictEqual(supported.message, "Authentication failed");
187 assert.strictEqual(
188 applyProviderCompatibility(supported, [{ ...policy, t3CodeRange: "<0.0.0" }], [policy])
189 .compatibilityAdvisory?.status,
190 "broken",
191 );
192 const removed = applyProviderCompatibility(supported, [], []);
193 assert.isUndefined(removed.compatibilityAdvisory);
194 assert.strictEqual(removed.status, "error");
195 assert.strictEqual(removed.message, "Authentication failed");
196 assert.isUndefined(
197 applyProviderCompatibility({ ...broken, enabled: false }, [], [policy]).compatibilityAdvisory,
198 );
199 assert.isUndefined(
200 applyProviderCompatibility({ ...broken, installed: false }, [], [policy])
201 .compatibilityAdvisory,
202 );
203 });
204​
205 it("rejects invalid ranges and recommendations outside the first supported match", () => {
206 const decode = Schema.decodeUnknownSync(ProviderCompatibilityPolicy);
207 assert.doesNotThrow(() => decode(policy));
208 const prefixed = decode({
209 ...policy,
210 t3CodeRange: ">=v0.0.42 <v0.1",
211 recommendedRange: "^v2",
212 ranges: [{ range: ">=v2.0 <v3", status: "supported" }],
213 });
214 assert.strictEqual(
215 resolveProviderCompatibility([prefixed], driver, "2.0.0", "0.0.45")?.status,
216 "supported",
217 );
218 assert.strictEqual(
219 resolveProviderCompatibility([prefixed], driver, "3.0.0", "0.0.45")?.status,
220 "unknown",
221 );
222 for (const invalid of [
223 { ...policy, t3CodeRange: "*" },
224 { ...policy, recommendedVersion: "3.0.0" },
225 { ...policy, ranges: [{ range: ">=2.0.0 garbage", status: "supported" }] },
226 { ...policy, recommendedVersion: "2.0.0; echo unsafe" },
227 { ...policy, ranges: [{ range: ">=0.0.0", status: "broken" }, ...policy.ranges] },
228 ])
229 assert.throws(() => decode(invalid));
230 });
231});
232​
233it.effect("a remote policy refresh preserves a newer health result on the registry stream", () =>
234 Effect.gen(function* () {
235 const started = yield* Deferred.make<void>();
236 const release = yield* Deferred.make<void>();
237 const health = yield* Ref.make<ServerProvider>({
238 ...provider,
239 status: "ready" as ServerProvider["status"],
240 message: "Healthy",
241 });
242 const manifest = yield* Ref.make<ModelManifest.ModelManifestData>({
243 version: 1,
244 currentModels: {},
245 compatibility: [policy],
246 });
247 const instance: ProviderInstance = {
248 instanceId: provider.instanceId,
249 driverKind: driver,
250 enabled: true,
251 displayName: undefined,
252 continuationIdentity: { driverKind: driver, continuationKey: "test-codex" },
253 snapshot: {
254 getSnapshot: Ref.get(health),
255 refresh: Ref.get(health),
256 streamChanges: Stream.empty,
257 applyUsageLimits: () => Effect.void,
258 resolveMaintenance: () =>
259 Effect.succeed(
260 makeManualOnlyProviderMaintenanceCapabilities({ provider: driver, packageName: null }),
261 ),
262 },
263 orchestrationAdapter: {} as ProviderInstance["orchestrationAdapter"],
264 textGeneration: {} as ProviderInstance["textGeneration"],
265 };
266 const refresh = Deferred.succeed(started, undefined).pipe(
267 Effect.andThen(Deferred.await(release)),
268 Effect.andThen(
269 Ref.updateAndGet(manifest, (current) => ({
270 ...current,
271 compatibility: [
272 { ...policy, ranges: [{ range: ">=0.0.0", status: "supported" as const }] },
273 ],
274 })),
275 ),
276 );
277 const layerDependencies = Layer.mergeAll(
278 ProviderLatestVersions.layer,
279 Layer.succeed(ModelManifest.ModelManifest, {
280 current: Ref.get(manifest),
281 refresh,
282 forceRefresh: refresh,
283 refreshOnDemand: refresh,
284 refreshInBackground: Effect.void,
285 }),
286 Layer.succeed(ProviderInstanceRegistry.ProviderInstanceRegistry, {
287 getInstance: (id) => Effect.succeed(id === instance.instanceId ? instance : undefined),
288 listInstances: Effect.succeed([instance]),
289 listUnavailable: Effect.succeed([]),
290 streamChanges: Stream.empty,
291 subscribeChanges: Effect.flatMap(PubSub.unbounded<void>(), PubSub.subscribe),
292 }),
293 ServerConfig.layerTest(process.cwd(), { prefix: "compatibility-registry-test" }).pipe(
294 Layer.provideMerge(NodeServices.layer),
295 ),
296 );
297 yield* Effect.gen(function* () {
298 const registry = yield* ProviderRegistry.ProviderRegistry;
299 yield* Deferred.await(started);
300 assert.strictEqual(
301 (yield* registry.getProviders)[0]?.compatibilityAdvisory?.status,
302 "broken",
303 );
304 yield* Ref.set(health, provider);
305 const latestHealth = yield* registry.refreshInstance(provider.instanceId);
306 assert.strictEqual(latestHealth[0]?.message, "Authentication failed");
307 const supported = yield* Stream.toPull(
308 registry.streamChanges.pipe(
309 Stream.filter((snapshots) => snapshots[0]?.compatibilityAdvisory?.status === "supported"),
310 ),
311 );
312 const subscribed = yield* supported.pipe(Effect.forkScoped({ startImmediately: true }));
313 yield* Deferred.succeed(release, undefined);
314 const updated = (yield* Fiber.join(subscribed))[0]?.[0];
315 assert.strictEqual(updated?.compatibilityAdvisory?.status, "supported");
316 assert.strictEqual(updated?.status, "error");
317 assert.strictEqual(updated?.message, "Authentication failed");
318 }).pipe(
319 Effect.provide(
320 ProviderRegistry.layer.pipe(
321 Layer.provide(layerDependencies),
322 Layer.provide(
323 Layer.mock(ProviderInstanceRegistryMutator.ProviderInstanceRegistryMutator)({}),
324 ),
325 ),
326 ),
327 );
328 }).pipe(Effect.scoped),
329);