authorgravatar for julius0216@outlook.comJulius Marminge <julius0216@outlook.com> 2026-09-23 17:33:48-07:00
committergravatar for noreply@github.comGitHub <noreply@github.com> 2026-09-23 17:33:48-07:00
logd4cd7d5c33122473da22ae118c93477ef7e38310
treed14bcaf64d28b39993ed07be4c180e34b818c054
parenteffaab94e36992ea9712c2a56d5ff602a3cead04
signature Signed by PGP key B5690EEEBB952194

fix(providers): restore compatibility ranges for every harness (#13328)


5 files changed, 154 insertions(+), 15 deletions(-)

apps/server/src/provider/Layers/ProviderRegistry.test.ts+26-10
......@@ -38,6 +38,7 @@ import { checkClaudeProviderStatus } from "./ClaudeProvider.ts";
3838import * as BackgroundPolicy from "../../background/BackgroundPolicy.ts";
3939import { AntigravityInstallation } from "../AntigravityInstallation.ts";
4040import * as ModelManifest from "../ModelManifest.ts";
41import { applyProviderCompatibility } from "../providerCompatibility.ts";
4142import * as CodexResetCredit from "./codexResetCredit.ts";
4243import * as OpenCodeRuntime from "../opencodeRuntime.ts";
4344import * as ProviderEventLoggers from "./ProviderEventLoggers.ts";
......@@ -76,6 +77,12 @@ process.env.T3CODE_CURSOR_ENABLED = "1";
7677
7778const encoder = new TextEncoder();
7879const TEST_EPOCH = DateTime.makeUnsafe("1970-01-01T00:00:00.000Z");
80const withBundledCompatibility = (snapshot: ServerProvider) =>
81 applyProviderCompatibility(
82 snapshot,
83 undefined,
84 ModelManifest.BUNDLED_MODEL_MANIFEST.compatibility,
85 );
7986
8087// Provider metadata checks use a bundled manifest and stubbed HTTP.
8188const TestHttpClientLive = Layer.succeed(
......@@ -1754,7 +1761,7 @@ it.layer(Layer.mergeAll(NodeServices.layer, ServerSettingsModule.layerTest(), Te
17541761 );
17551762 assert.deepStrictEqual(
17561763 recoveredProviders.find((provider) => provider.instanceId === codexInstanceId),
1757 codexProvider,
1764 withBundledCompatibility(codexProvider),
17581765 );
17591766
17601767 yield* Ref.set(catalogSnapshot, changedCatalogProvider);
......@@ -1766,7 +1773,7 @@ it.layer(Layer.mergeAll(NodeServices.layer, ServerSettingsModule.layerTest(), Te
17661773 );
17671774 assert.deepStrictEqual(
17681775 changedProviders.find((provider) => provider.instanceId === codexInstanceId),
1769 codexProvider,
1776 withBundledCompatibility(codexProvider),
17701777 );
17711778 }).pipe(Effect.provide(runtimeServices));
17721779
......@@ -1880,10 +1887,13 @@ it.layer(Layer.mergeAll(NodeServices.layer, ServerSettingsModule.layerTest(), Te
18801887 yield* Fiber.join(persisted);
18811888 const cachedProvider = yield* readProviderStatusCache(filePath);
18821889
1883 assert.deepStrictEqual(cachedProvider, {
1884 ...refreshedProvider,
1885 models: [...initialProvider.models],
1886 });
1890 assert.deepStrictEqual(
1891 cachedProvider,
1892 withBundledCompatibility({
1893 ...refreshedProvider,
1894 models: [...initialProvider.models],
1895 }),
1896 );
18871897 }).pipe(Effect.provide(runtimeServices));
18881898 }),
18891899 );
......@@ -2095,10 +2105,14 @@ it.layer(Layer.mergeAll(NodeServices.layer, ServerSettingsModule.layerTest(), Te
20952105 yield* Effect.gen(function* () {
20962106 const registry = yield* ProviderRegistry.ProviderRegistry;
20972107
2098 assert.deepStrictEqual(yield* registry.getProviders, [cachedProvider]);
2099 assert.deepStrictEqual(yield* registry.refresh(codexDriver), [cachedProvider]);
2108 assert.deepStrictEqual(yield* registry.getProviders, [
2109 withBundledCompatibility(cachedProvider),
2110 ]);
2111 assert.deepStrictEqual(yield* registry.refresh(codexDriver), [
2112 withBundledCompatibility(cachedProvider),
2113 ]);
21002114 assert.deepStrictEqual(yield* registry.refreshInstance(codexInstanceId), [
2101 cachedProvider,
2115 withBundledCompatibility(cachedProvider),
21022116 ]);
21032117 }).pipe(Effect.provide(runtimeServices));
21042118 }),
......@@ -2206,7 +2220,9 @@ it.layer(Layer.mergeAll(NodeServices.layer, ServerSettingsModule.layerTest(), Te
22062220
22072221 yield* Effect.gen(function* () {
22082222 const registry = yield* ProviderRegistry.ProviderRegistry;
2209 assert.deepStrictEqual(yield* registry.getProviders, [codexProvider]);
2223 assert.deepStrictEqual(yield* registry.getProviders, [
2224 withBundledCompatibility(codexProvider),
2225 ]);
22102226
22112227 yield* Ref.set(failNextList, true);
22122228 yield* PubSub.publish(changes, undefined);
apps/server/src/provider/model-manifest.json+48-2
......@@ -1,12 +1,58 @@
11{
22 "version": 1,
3 "updatedAt": "2026-09-22T21:40:00Z",
3 "updatedAt": "2026-09-24T00:11:29Z",
44 "compatibility": [
5 {
6 "driver": "codex",
7 "t3CodeRange": ">=0.0.42",
8 "recommendedRange": ">=0.129.0",
9 "ranges": [
10 { "range": ">=0.129.0", "status": "supported" },
11 { "range": "<0.129.0", "status": "broken" }
12 ]
13 },
14 {
15 "driver": "claudeAgent",
16 "t3CodeRange": ">=0.0.42",
17 "recommendedRange": ">=2.1.280",
18 "ranges": [
19 { "range": ">=2.1.280", "status": "supported" },
20 { "range": ">=2.1.111 <2.1.280", "status": "graceful" },
21 { "range": "<2.1.111", "status": "unsupported" }
22 ]
23 },
24 {
25 "driver": "cursor",
26 "t3CodeRange": ">=0.0.42",
27 "recommendedRange": ">=2026.05.09",
28 "ranges": [
29 { "range": ">=2026.05.09", "status": "supported" },
30 { "range": "<2026.05.09", "status": "unknown" }
31 ]
32 },
33 {
34 "driver": "grok",
35 "t3CodeRange": ">=0.0.42",
36 "recommendedRange": ">=1.0.13",
37 "ranges": [
38 { "range": ">=1.0.13", "status": "supported" },
39 { "range": "<1.0.13", "status": "unknown" }
40 ]
41 },
542 {
643 "driver": "opencode",
744 "t3CodeRange": ">=0.0.42",
845 "recommendedRange": ">=1.14.19",
9 "ranges": [{ "range": "<1.14.19", "status": "broken" }]
46 "ranges": [
47 { "range": ">=1.14.19", "status": "supported" },
48 { "range": "<1.14.19", "status": "broken" }
49 ]
50 },
51 {
52 "driver": "antigravity",
53 "t3CodeRange": ">=0.0.42",
54 "recommendedRange": "=1.1.1",
55 "ranges": [{ "range": "=1.1.1", "status": "supported" }]
1056 }
1157 ],
1258 "currentModels": {
apps/server/src/provider/providerCompatibility.test.ts+63
......@@ -15,6 +15,7 @@ import { ProviderRegistry } from "./Services/ProviderRegistry.ts";
1515import { ProviderInstanceRegistry } from "./Services/ProviderInstanceRegistry.ts";
1616import type { ProviderInstance } from "./ProviderDriver.ts";
1717import { makeManualOnlyProviderMaintenanceCapabilities } from "./providerMaintenance.ts";
18import { BUILT_IN_DRIVERS } from "./builtInDrivers.ts";
1819import * as Schema from "effect/Schema";
1920import {
2021 applyProviderCompatibility,
......@@ -51,6 +52,68 @@ const provider: ServerProvider = {
5152};
5253
5354describe("provider compatibility", () => {
55 it("bundles a compatibility policy for every built-in harness", () => {
56 for (const builtIn of BUILT_IN_DRIVERS) {
57 assert.isDefined(
58 resolveProviderCompatibility(
59 ModelManifest.BUNDLED_MODEL_MANIFEST.compatibility,
60 builtIn.driverKind,
61 null,
62 ),
63 `Missing bundled compatibility policy for ${builtIn.driverKind}`,
64 );
65 }
66 });
67
68 it("compares Cursor build dates without treating semver prereleases as stable", () => {
69 const cursor = ProviderDriverKind.make("cursor");
70 const cursorPolicy: ProviderCompatibilityPolicy = {
71 driver: cursor,
72 t3CodeRange: policy.t3CodeRange,
73 ranges: [
74 { range: "<2026.05.09", status: "unsupported" },
75 { range: ">=2026.05.09", status: "supported" },
76 ],
77 };
78 for (const [version, expected] of [
79 ["2026.05.08-a1b2c3d", "unsupported"],
80 ["2026.05.09-a1b2c3d", "supported"],
81 ["2026.09.22-f2b0fcd", "supported"],
82 ["2026.05.09", "supported"],
83 ["2026.05.09-beta.1", "unknown"],
84 ] as const) {
85 assert.strictEqual(
86 resolveProviderCompatibility([cursorPolicy], cursor, version)?.status,
87 expected,
88 );
89 }
90 assert.strictEqual(
91 resolveProviderCompatibility([policy], driver, "2.0.0-a1b2c3d")?.status,
92 "unknown",
93 );
94 });
95
96 it("recognizes Antigravity semver release tags while keeping dated candidates unknown", () => {
97 const antigravity = ProviderDriverKind.make("antigravity");
98 const taggedPolicy = {
99 ...policy,
100 driver: antigravity,
101 ranges: [{ range: "=2.0.0", status: "supported" as const }],
102 };
103 for (const [version, expected] of [
104 ["agy_acp_server_2.0.0", "supported"],
105 ["2.0.0", "supported"],
106 ["agy_acp_server_2.0.1", "unknown"],
107 ["agy_acp_server_2.0.0-beta.1", "unknown"],
108 ["agy_acp_server_20260818_01_RC01", "unknown"],
109 ] as const) {
110 assert.strictEqual(
111 resolveProviderCompatibility([taggedPolicy], antigravity, version)?.status,
112 expected,
113 );
114 }
115 });
116
54117 it("classifies boundaries and treats unlisted versions and release tags as unknown", () => {
55118 for (const [version, expected] of [
56119 ["0.9.9", "broken"],
apps/server/src/provider/providerCompatibility.ts+10-2
......@@ -10,7 +10,7 @@ import * as Schema from "effect/Schema";
1010import packageJson from "../../package.json" with { type: "json" };
1111
1212// Deliberately uses the shared CLI gate syntax: comparator groups joined by ||.
13// Release tags and prereleases remain unknown instead of matching stable ranges.
13// Prereleases and unrecognized release tags remain unknown.
1414const StableVersion = TrimmedNonEmptyString.pipe(
1515 Schema.check(Schema.makeFilter((value) => /^\d+\.\d+\.\d+$/.test(value))),
1616);
......@@ -66,7 +66,15 @@ export function resolveProviderCompatibility(
6666 (entry) => entry.driver === driver && satisfiesSemverRange(t3CodeVersion, entry.t3CodeRange),
6767 );
6868 if (!policy) return undefined;
69 const stable = version?.replace(/^v/, "");
69 const unprefixed = version?.replace(/^v/, "");
70 // Cursor appends a build hash to its date; Google's ACP runtime uses a release prefix.
71 // Strip only these driver-specific forms, keeping semver prereleases unknown.
72 const stable =
73 driver === "cursor"
74 ? unprefixed?.replace(/^(\d{4}\.\d{2}\.\d{2})-[a-f0-9]+$/, "$1")
75 : driver === "antigravity"
76 ? unprefixed?.replace(/^agy_acp_server_(\d+\.\d+\.\d+)$/, "$1")
77 : unprefixed;
7078 const status =
7179 stable && /^\d+\.\d+\.\d+$/.test(stable)
7280 ? (policy.ranges.find((entry) => satisfiesSemverRange(stable, entry.range))?.status ??
apps/server/src/provider/providerMaintenanceRunner.test.ts+7-1
......@@ -211,7 +211,13 @@ function makeRegistry(
211211
212212const makeTestRunner = (
213213 registry: ProviderRegistryShape,
214 manifest = ModelManifest.BUNDLED_MODEL_MANIFEST,
214 // Generic updater fixtures use synthetic versions. Keep their compatibility
215 // unknown so real harness minimums do not bypass the command under test.
216 manifest: ModelManifest.ModelManifestData = {
217 version: 1,
218 currentModels: {},
219 compatibility: [{ driver: CODEX_DRIVER, t3CodeRange: ">=0.0.42", ranges: [] }],
220 },
215221) =>
216222 Effect.service(ProviderMaintenanceRunner.ProviderMaintenanceRunner).pipe(
217223 Effect.provide(