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 ProviderInstanceRegistry from "./ProviderInstanceRegistry.ts";
15import type { ProviderInstance } from "@t3tools/provider-core/server/driver";
16import { makeManualOnlyProviderMaintenanceCapabilities } from "@t3tools/provider-core/server/maintenanceResolver";
17import { BUILT_IN_DRIVERS } from "./builtInDrivers.ts";
18import * as Schema from "effect/Schema";
19import {
20 applyProviderCompatibility,
21 ProviderCompatibilityPolicy,
22 resolveProviderCompatibility,
23} from "./providerCompatibility.ts";
24
25const driver = ProviderDriverKind.make("codex");
26const policy: ProviderCompatibilityPolicy = {
27 driver,
28 t3CodeRange: ">=0.0.42 <0.1.0",
29 recommendedVersion: "2.0.0",
30 recommendedRange: ">=2.0.0 <3.0.0",
31 ranges: [
32 { range: "<1.0.0", status: "broken" },
33 { range: ">=1.0.0 <1.5.0", status: "unsupported" },
34 { range: ">=1.5.0 <2.0.0", status: "graceful" },
35 { range: ">=2.0.0 <3.0.0", status: "supported" },
36 ],
37};
38const provider: ServerProvider = {
39 driver,
40 instanceId: ProviderInstanceId.make("codex-work"),
41 enabled: true,
42 installed: true,
43 version: "0.9.0",
44 status: "error",
45 message: "Authentication failed",
46 checkedAt: "2026-09-22T00:00:00Z",
47 auth: { status: "unauthenticated" },
48 models: [],
49 skills: [],
50 slashCommands: [],
51};
52
53// The first release that runs the V2 orchestrator.
54const V2_RELEASE = "0.0.46";
55
56describe("provider compatibility", () => {
57 it("bundles a compatibility policy for every built-in harness", () => {
58 for (const builtIn of BUILT_IN_DRIVERS) {
59 // Registry entries are arbitrary external ACP agents, not one versioned harness.
60 if (builtIn.driverKind === "acpRegistry") continue;
61 assert.isDefined(
62 resolveProviderCompatibility(
63 ModelManifest.BUNDLED_MODEL_MANIFEST.compatibility,
64 builtIn.driverKind,
65 null,
66 V2_RELEASE,
67 ),
68 `Missing bundled compatibility policy for ${builtIn.driverKind}`,
69 );
70 }
71 });
72
73 it("keeps releases before V2 on the OpenCode 1 policy they shipped with", () => {
74 // Every installed build fetches main's manifest, and 0.0.45 and older run the
75 // V1 orchestrator, which cannot drive OpenCode 2.
76 const opencode = ProviderDriverKind.make("opencode");
77 for (const [version, expected] of [
78 ["1.14.19", "supported"],
79 ["2.0.18", "broken"],
80 ["1.14.18", "broken"],
81 ] as const) {
82 const advisory = resolveProviderCompatibility(
83 ModelManifest.BUNDLED_MODEL_MANIFEST.compatibility,
84 opencode,
85 version,
86 "0.0.45",
87 );
88 assert.strictEqual(advisory?.status, expected, `OpenCode ${version} on 0.0.45`);
89 assert.strictEqual(advisory?.recommendedVersion, "1.14.19");
90 }
91 // Pi has no driver before V2.
92 assert.isUndefined(
93 resolveProviderCompatibility(
94 ModelManifest.BUNDLED_MODEL_MANIFEST.compatibility,
95 ProviderDriverKind.make("pi"),
96 "1.0.0",
97 "0.0.45",
98 ),
99 );
100 });
101
102 it("supports OpenCode 2 and gives OpenCode 1.x limited support", () => {
103 const opencode = ProviderDriverKind.make("opencode");
104 for (const [version, expected] of [
105 ["2.0.18", "supported"],
106 ["2.1.0", "supported"],
107 // Early OpenCode 2 releases predate the API the adapter was built against.
108 ["2.0.17", "unsupported"],
109 ["2.0.0", "unsupported"],
110 ["1.99.0", "graceful"],
111 ["1.14.19", "graceful"],
112 ["1.14.18", "broken"],
113 ] as const) {
114 for (const t3CodeVersion of [V2_RELEASE, "0.0.46-preview.20261002.2598"]) {
115 const advisory = resolveProviderCompatibility(
116 ModelManifest.BUNDLED_MODEL_MANIFEST.compatibility,
117 opencode,
118 version,
119 t3CodeVersion,
120 );
121 assert.strictEqual(advisory?.status, expected, `OpenCode ${version} on ${t3CodeVersion}`);
122 assert.strictEqual(advisory?.recommendedRange, ">=2.0.18");
123 }
124 }
125 // The advisory rides beside the probe: a ready 1.x instance stays ready and selectable.
126 const ready = applyProviderCompatibility(
127 { ...provider, driver: opencode, version: "1.18.33", status: "ready", message: undefined },
128 undefined,
129 ModelManifest.BUNDLED_MODEL_MANIFEST.compatibility,
130 );
131 assert.strictEqual(ready.status, "ready");
132 assert.strictEqual(
133 ready.compatibilityAdvisory?.status,
134 resolveProviderCompatibility(
135 ModelManifest.BUNDLED_MODEL_MANIFEST.compatibility,
136 opencode,
137 "1.18.33",
138 )?.status,
139 );
140 });
141
142 it("compares Cursor build dates without treating semver prereleases as stable", () => {
143 const cursor = ProviderDriverKind.make("cursor");
144 const cursorPolicy: ProviderCompatibilityPolicy = {
145 driver: cursor,
146 t3CodeRange: policy.t3CodeRange,
147 ranges: [
148 { range: "<2026.05.09", status: "unsupported" },
149 { range: ">=2026.05.09", status: "supported" },
150 ],
151 };
152 for (const [version, expected] of [
153 ["2026.05.08-a1b2c3d", "unsupported"],
154 ["2026.05.09-a1b2c3d", "supported"],
155 ["2026.09.22-f2b0fcd", "supported"],
156 ["2026.05.09", "supported"],
157 ["2026.05.09-beta.1", "unknown"],
158 ] as const) {
159 assert.strictEqual(
160 resolveProviderCompatibility([cursorPolicy], cursor, version)?.status,
161 expected,
162 );
163 }
164 assert.strictEqual(
165 resolveProviderCompatibility([policy], driver, "2.0.0-a1b2c3d")?.status,
166 "unknown",
167 );
168 });
169
170 it("compares Muse versions by their release, ignoring the revision suffix", () => {
171 const muse = ProviderDriverKind.make("muse");
172 for (const [version, expected] of [
173 ["1.4.3-R5018.1", "supported"],
174 ["1.4.2-R4684", "supported"],
175 ["1.4.1-R4100.2", "unknown"],
176 ["1.4.3-beta.1", "unknown"],
177 ] as const) {
178 assert.strictEqual(
179 resolveProviderCompatibility(
180 ModelManifest.BUNDLED_MODEL_MANIFEST.compatibility,
181 muse,
182 version,
183 V2_RELEASE,
184 )?.status,
185 expected,
186 );
187 }
188 });
189
190 it("recognizes Antigravity semver release tags while keeping dated candidates unknown", () => {
191 const antigravity = ProviderDriverKind.make("antigravity");
192 const taggedPolicy = {
193 ...policy,
194 driver: antigravity,
195 ranges: [{ range: "=2.0.0", status: "supported" as const }],
196 };
197 for (const [version, expected] of [
198 ["agy_acp_server_2.0.0", "supported"],
199 ["2.0.0", "supported"],
200 ["agy_acp_server_2.0.1", "unknown"],
201 ["agy_acp_server_2.0.0-beta.1", "unknown"],
202 ["agy_acp_server_20260818_01_RC01", "unknown"],
203 ] as const) {
204 assert.strictEqual(
205 resolveProviderCompatibility([taggedPolicy], antigravity, version)?.status,
206 expected,
207 );
208 }
209 });
210
211 it("classifies boundaries and treats unlisted versions and release tags as unknown", () => {
212 for (const [version, expected] of [
213 ["0.9.9", "broken"],
214 ["1.0.0", "unsupported"],
215 ["1.5.0", "graceful"],
216 ["2.0.0", "supported"],
217 ["v2.0.0", "supported"],
218 ["3.0.0", "unknown"],
219 ["2.0.0-beta.1", "unknown"],
220 ["agy_acp_server_20260818_01_RC01", "unknown"],
221 [null, "unknown"],
222 ] as const) {
223 assert.strictEqual(resolveProviderCompatibility([policy], driver, version)?.status, expected);
224 }
225 assert.isUndefined(resolveProviderCompatibility([policy], driver, "0.9.0", "0.1.0"));
226 });
227
228 it("supports every driver without inventing policies for uncovered adapters", () => {
229 for (const kind of [
230 "codex",
231 "claudeAgent",
232 "cursor",
233 "grok",
234 "opencode",
235 "antigravity",
236 "customDriver",
237 ]) {
238 const adapter = ProviderDriverKind.make(kind);
239 assert.strictEqual(
240 resolveProviderCompatibility([{ ...policy, driver: adapter }], adapter, "2.0.0")?.status,
241 "supported",
242 );
243 assert.isUndefined(resolveProviderCompatibility([], adapter, "2.0.0"));
244 }
245 });
246
247 it("relaxes remote policy without losing probe errors, and falls back when a policy is omitted", () => {
248 const broken = applyProviderCompatibility(provider, [], [policy]);
249 assert.strictEqual(broken.compatibilityAdvisory?.status, "broken");
250 const relaxed = { ...policy, ranges: [{ range: ">=0.0.0", status: "supported" as const }] };
251 const supported = applyProviderCompatibility(broken, [relaxed], [policy]);
252 assert.strictEqual(supported.compatibilityAdvisory?.status, "supported");
253 assert.strictEqual(supported.status, "error");
254 assert.strictEqual(supported.message, "Authentication failed");
255 assert.strictEqual(
256 applyProviderCompatibility(supported, [{ ...policy, t3CodeRange: ">=9.0.0" }], [policy])
257 .compatibilityAdvisory?.status,
258 "broken",
259 );
260 const removed = applyProviderCompatibility(supported, [], []);
261 assert.isUndefined(removed.compatibilityAdvisory);
262 assert.strictEqual(removed.status, "error");
263 assert.strictEqual(removed.message, "Authentication failed");
264 assert.isUndefined(
265 applyProviderCompatibility({ ...broken, enabled: false }, [], [policy]).compatibilityAdvisory,
266 );
267 assert.isUndefined(
268 applyProviderCompatibility({ ...broken, installed: false }, [], [policy])
269 .compatibilityAdvisory,
270 );
271 });
272
273 it("rejects invalid ranges and recommendations outside the first supported match", () => {
274 const decode = Schema.decodeUnknownSync(ProviderCompatibilityPolicy);
275 assert.doesNotThrow(() => decode(policy));
276 const prefixed = decode({
277 ...policy,
278 t3CodeRange: ">=v0.0.42 <v0.1",
279 recommendedRange: "^v2",
280 ranges: [{ range: ">=v2.0 <v3", status: "supported" }],
281 });
282 assert.strictEqual(
283 resolveProviderCompatibility([prefixed], driver, "2.0.0")?.status,
284 "supported",
285 );
286 assert.strictEqual(
287 resolveProviderCompatibility([prefixed], driver, "3.0.0")?.status,
288 "unknown",
289 );
290 for (const invalid of [
291 { ...policy, t3CodeRange: "*" },
292 { ...policy, recommendedVersion: "3.0.0" },
293 { ...policy, ranges: [{ range: ">=2.0.0 garbage", status: "supported" }] },
294 { ...policy, recommendedVersion: "2.0.0; echo unsafe" },
295 { ...policy, ranges: [{ range: ">=0.0.0", status: "broken" }, ...policy.ranges] },
296 ])
297 assert.throws(() => decode(invalid));
298 });
299});
300
301it.effect("a remote policy refresh preserves a newer health result on the registry stream", () =>
302 Effect.gen(function* () {
303 const started = yield* Deferred.make<void>();
304 const release = yield* Deferred.make<void>();
305 const health = yield* Ref.make<ServerProvider>({
306 ...provider,
307 status: "ready" as ServerProvider["status"],
308 message: "Healthy",
309 });
310 const manifest = yield* Ref.make<ModelManifest.ModelManifestData>({
311 version: 1,
312 currentModels: {},
313 compatibility: [policy],
314 });
315 const instance: ProviderInstance = {
316 instanceId: provider.instanceId,
317 driverKind: driver,
318 enabled: true,
319 displayName: undefined,
320 continuationIdentity: { driverKind: driver, continuationKey: "test-codex" },
321 snapshot: {
322 getSnapshot: Ref.get(health),
323 refresh: Ref.get(health),
324 streamChanges: Stream.empty,
325 applyUsageLimits: () => Effect.void,
326 resolveMaintenance: () =>
327 Effect.succeed(
328 makeManualOnlyProviderMaintenanceCapabilities({ provider: driver, packageName: null }),
329 ),
330 },
331 orchestrationAdapter: {} as ProviderInstance["orchestrationAdapter"],
332 textGeneration: {} as ProviderInstance["textGeneration"],
333 };
334 const refresh = Deferred.succeed(started, undefined).pipe(
335 Effect.andThen(Deferred.await(release)),
336 Effect.andThen(
337 Ref.updateAndGet(manifest, (current) => ({
338 ...current,
339 compatibility: [
340 { ...policy, ranges: [{ range: ">=0.0.0", status: "supported" as const }] },
341 ],
342 })),
343 ),
344 );
345 const layerDependencies = Layer.mergeAll(
346 Layer.succeed(ModelManifest.ModelManifest, {
347 current: Ref.get(manifest),
348 refresh,
349 forceRefresh: refresh,
350 refreshInBackground: Effect.void,
351 }),
352 Layer.succeed(ProviderInstanceRegistry.ProviderInstanceRegistry, {
353 getInstance: (id) => Effect.succeed(id === instance.instanceId ? instance : undefined),
354 listInstances: Effect.succeed([instance]),
355 listUnavailable: Effect.succeed([]),
356 streamChanges: Stream.empty,
357 subscribeChanges: Effect.flatMap(PubSub.unbounded<void>(), PubSub.subscribe),
358 }),
359 ServerConfig.layerTest(process.cwd(), { prefix: "compatibility-registry-test" }).pipe(
360 Layer.provideMerge(NodeServices.layer),
361 ),
362 );
363 yield* Effect.gen(function* () {
364 const registry = yield* ProviderRegistry.ProviderRegistry;
365 yield* Deferred.await(started);
366 assert.strictEqual(
367 (yield* registry.getProviders)[0]?.compatibilityAdvisory?.status,
368 "broken",
369 );
370 yield* Ref.set(health, provider);
371 const latestHealth = yield* registry.refreshInstance(provider.instanceId);
372 assert.strictEqual(latestHealth[0]?.message, "Authentication failed");
373 const supported = yield* Stream.toPull(
374 registry.streamChanges.pipe(
375 Stream.filter((snapshots) => snapshots[0]?.compatibilityAdvisory?.status === "supported"),
376 ),
377 );
378 const subscribed = yield* supported.pipe(Effect.forkScoped({ startImmediately: true }));
379 yield* Deferred.succeed(release, undefined);
380 const updated = (yield* Fiber.join(subscribed))[0]?.[0];
381 assert.strictEqual(updated?.compatibilityAdvisory?.status, "supported");
382 assert.strictEqual(updated?.status, "error");
383 assert.strictEqual(updated?.message, "Authentication failed");
384 }).pipe(Effect.provide(ProviderRegistry.layer.pipe(Layer.provide(layerDependencies))));
385 }).pipe(Effect.scoped),
386);
387
388it("recomputes latest-version compatibility without losing the underlying update advisory", () => {
389 const snapshot: ServerProvider = {
390 ...provider,
391 version: "2.0.0",
392 versionAdvisory: {
393 status: "behind_latest",
394 currentVersion: "2.0.0",
395 latestVersion: "4.0.0",
396 canUpdate: true,
397 updateCommand: "npm install -g fixture@latest",
398 checkedAt: provider.checkedAt,
399 message: null,
400 },
401 };
402 const blockedPolicy: ProviderCompatibilityPolicy = {
403 ...policy,
404 ranges: [...policy.ranges, { range: ">=3.0.0", status: "broken" }],
405 };
406 const blocked = applyProviderCompatibility(snapshot, [blockedPolicy], []);
407 assert.strictEqual(blocked.compatibilityAdvisory?.latestVersionStatus, "broken");
408 const relaxed = applyProviderCompatibility(
409 blocked,
410 [{ ...policy, ranges: [{ range: ">=2.0.0", status: "supported" }] }],
411 [],
412 );
413 assert.strictEqual(relaxed.compatibilityAdvisory?.latestVersionStatus, "supported");
414 assert.deepStrictEqual(relaxed.versionAdvisory, snapshot.versionAdvisory);
415});