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";...@@ -38,6 +38,7 @@ import { checkClaudeProviderStatus } from "./ClaudeProvider.ts";
38import * as BackgroundPolicy from "../../background/BackgroundPolicy.ts";38import * as BackgroundPolicy from "../../background/BackgroundPolicy.ts";
39import { AntigravityInstallation } from "../AntigravityInstallation.ts";39import { AntigravityInstallation } from "../AntigravityInstallation.ts";
40import * as ModelManifest from "../ModelManifest.ts";40import * as ModelManifest from "../ModelManifest.ts";
41import { applyProviderCompatibility } from "../providerCompatibility.ts";
41import * as CodexResetCredit from "./codexResetCredit.ts";42import * as CodexResetCredit from "./codexResetCredit.ts";
42import * as OpenCodeRuntime from "../opencodeRuntime.ts";43import * as OpenCodeRuntime from "../opencodeRuntime.ts";
43import * as ProviderEventLoggers from "./ProviderEventLoggers.ts";44import * as ProviderEventLoggers from "./ProviderEventLoggers.ts";
...@@ -76,6 +77,12 @@ process.env.T3CODE_CURSOR_ENABLED = "1";...@@ -76,6 +77,12 @@ process.env.T3CODE_CURSOR_ENABLED = "1";
7677
77const encoder = new TextEncoder();78const encoder = new TextEncoder();
78const TEST_EPOCH = DateTime.makeUnsafe("1970-01-01T00:00:00.000Z");79const 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
80// Provider metadata checks use a bundled manifest and stubbed HTTP.87// Provider metadata checks use a bundled manifest and stubbed HTTP.
81const TestHttpClientLive = Layer.succeed(88const TestHttpClientLive = Layer.succeed(
...@@ -1754,7 +1761,7 @@ it.layer(Layer.mergeAll(NodeServices.layer, ServerSettingsModule.layerTest(), Te...@@ -1754,7 +1761,7 @@ it.layer(Layer.mergeAll(NodeServices.layer, ServerSettingsModule.layerTest(), Te
1754 );1761 );
1755 assert.deepStrictEqual(1762 assert.deepStrictEqual(
1756 recoveredProviders.find((provider) => provider.instanceId === codexInstanceId),1763 recoveredProviders.find((provider) => provider.instanceId === codexInstanceId),
1757 codexProvider,1764 withBundledCompatibility(codexProvider),
1758 );1765 );
17591766
1760 yield* Ref.set(catalogSnapshot, changedCatalogProvider);1767 yield* Ref.set(catalogSnapshot, changedCatalogProvider);
...@@ -1766,7 +1773,7 @@ it.layer(Layer.mergeAll(NodeServices.layer, ServerSettingsModule.layerTest(), Te...@@ -1766,7 +1773,7 @@ it.layer(Layer.mergeAll(NodeServices.layer, ServerSettingsModule.layerTest(), Te
1766 );1773 );
1767 assert.deepStrictEqual(1774 assert.deepStrictEqual(
1768 changedProviders.find((provider) => provider.instanceId === codexInstanceId),1775 changedProviders.find((provider) => provider.instanceId === codexInstanceId),
1769 codexProvider,1776 withBundledCompatibility(codexProvider),
1770 );1777 );
1771 }).pipe(Effect.provide(runtimeServices));1778 }).pipe(Effect.provide(runtimeServices));
17721779
...@@ -1880,10 +1887,13 @@ it.layer(Layer.mergeAll(NodeServices.layer, ServerSettingsModule.layerTest(), Te...@@ -1880,10 +1887,13 @@ it.layer(Layer.mergeAll(NodeServices.layer, ServerSettingsModule.layerTest(), Te
1880 yield* Fiber.join(persisted);1887 yield* Fiber.join(persisted);
1881 const cachedProvider = yield* readProviderStatusCache(filePath);1888 const cachedProvider = yield* readProviderStatusCache(filePath);
18821889
1883 assert.deepStrictEqual(cachedProvider, {1890 assert.deepStrictEqual(
1884 ...refreshedProvider,1891 cachedProvider,
1885 models: [...initialProvider.models],1892 withBundledCompatibility({
1886 });1893 ...refreshedProvider,
1894 models: [...initialProvider.models],
1895 }),
1896 );
1887 }).pipe(Effect.provide(runtimeServices));1897 }).pipe(Effect.provide(runtimeServices));
1888 }),1898 }),
1889 );1899 );
...@@ -2095,10 +2105,14 @@ it.layer(Layer.mergeAll(NodeServices.layer, ServerSettingsModule.layerTest(), Te...@@ -2095,10 +2105,14 @@ it.layer(Layer.mergeAll(NodeServices.layer, ServerSettingsModule.layerTest(), Te
2095 yield* Effect.gen(function* () {2105 yield* Effect.gen(function* () {
2096 const registry = yield* ProviderRegistry.ProviderRegistry;2106 const registry = yield* ProviderRegistry.ProviderRegistry;
20972107
2098 assert.deepStrictEqual(yield* registry.getProviders, [cachedProvider]);2108 assert.deepStrictEqual(yield* registry.getProviders, [
2099 assert.deepStrictEqual(yield* registry.refresh(codexDriver), [cachedProvider]);2109 withBundledCompatibility(cachedProvider),
2110 ]);
2111 assert.deepStrictEqual(yield* registry.refresh(codexDriver), [
2112 withBundledCompatibility(cachedProvider),
2113 ]);
2100 assert.deepStrictEqual(yield* registry.refreshInstance(codexInstanceId), [2114 assert.deepStrictEqual(yield* registry.refreshInstance(codexInstanceId), [
2101 cachedProvider,2115 withBundledCompatibility(cachedProvider),
2102 ]);2116 ]);
2103 }).pipe(Effect.provide(runtimeServices));2117 }).pipe(Effect.provide(runtimeServices));
2104 }),2118 }),
...@@ -2206,7 +2220,9 @@ it.layer(Layer.mergeAll(NodeServices.layer, ServerSettingsModule.layerTest(), Te...@@ -2206,7 +2220,9 @@ it.layer(Layer.mergeAll(NodeServices.layer, ServerSettingsModule.layerTest(), Te
22062220
2207 yield* Effect.gen(function* () {2221 yield* Effect.gen(function* () {
2208 const registry = yield* ProviderRegistry.ProviderRegistry;2222 const registry = yield* ProviderRegistry.ProviderRegistry;
2209 assert.deepStrictEqual(yield* registry.getProviders, [codexProvider]);2223 assert.deepStrictEqual(yield* registry.getProviders, [
2224 withBundledCompatibility(codexProvider),
2225 ]);
22102226
2211 yield* Ref.set(failNextList, true);2227 yield* Ref.set(failNextList, true);
2212 yield* PubSub.publish(changes, undefined);2228 yield* PubSub.publish(changes, undefined);
apps/server/src/provider/model-manifest.json+48-2
...@@ -1,12 +1,58 @@...@@ -1,12 +1,58 @@
1{1{
2 "version": 1,2 "version": 1,
3 "updatedAt": "2026-09-22T21:40:00Z",3 "updatedAt": "2026-09-24T00:11:29Z",
4 "compatibility": [4 "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 },
5 {42 {
6 "driver": "opencode",43 "driver": "opencode",
7 "t3CodeRange": ">=0.0.42",44 "t3CodeRange": ">=0.0.42",
8 "recommendedRange": ">=1.14.19",45 "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" }]
10 }56 }
11 ],57 ],
12 "currentModels": {58 "currentModels": {
apps/server/src/provider/providerCompatibility.test.ts+63
...@@ -15,6 +15,7 @@ import { ProviderRegistry } from "./Services/ProviderRegistry.ts";...@@ -15,6 +15,7 @@ import { ProviderRegistry } from "./Services/ProviderRegistry.ts";
15import { ProviderInstanceRegistry } from "./Services/ProviderInstanceRegistry.ts";15import { ProviderInstanceRegistry } from "./Services/ProviderInstanceRegistry.ts";
16import type { ProviderInstance } from "./ProviderDriver.ts";16import type { ProviderInstance } from "./ProviderDriver.ts";
17import { makeManualOnlyProviderMaintenanceCapabilities } from "./providerMaintenance.ts";17import { makeManualOnlyProviderMaintenanceCapabilities } from "./providerMaintenance.ts";
18import { BUILT_IN_DRIVERS } from "./builtInDrivers.ts";
18import * as Schema from "effect/Schema";19import * as Schema from "effect/Schema";
19import {20import {
20 applyProviderCompatibility,21 applyProviderCompatibility,
...@@ -51,6 +52,68 @@ const provider: ServerProvider = {...@@ -51,6 +52,68 @@ const provider: ServerProvider = {
51};52};
5253
53describe("provider compatibility", () => {54describe("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
54 it("classifies boundaries and treats unlisted versions and release tags as unknown", () => {117 it("classifies boundaries and treats unlisted versions and release tags as unknown", () => {
55 for (const [version, expected] of [118 for (const [version, expected] of [
56 ["0.9.9", "broken"],119 ["0.9.9", "broken"],
apps/server/src/provider/providerCompatibility.ts+10-2
...@@ -10,7 +10,7 @@ import * as Schema from "effect/Schema";...@@ -10,7 +10,7 @@ import * as Schema from "effect/Schema";
10import packageJson from "../../package.json" with { type: "json" };10import packageJson from "../../package.json" with { type: "json" };
1111
12// Deliberately uses the shared CLI gate syntax: comparator groups joined by ||.12// 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.
14const StableVersion = TrimmedNonEmptyString.pipe(14const StableVersion = TrimmedNonEmptyString.pipe(
15 Schema.check(Schema.makeFilter((value) => /^\d+\.\d+\.\d+$/.test(value))),15 Schema.check(Schema.makeFilter((value) => /^\d+\.\d+\.\d+$/.test(value))),
16);16);
...@@ -66,7 +66,15 @@ export function resolveProviderCompatibility(...@@ -66,7 +66,15 @@ export function resolveProviderCompatibility(
66 (entry) => entry.driver === driver && satisfiesSemverRange(t3CodeVersion, entry.t3CodeRange),66 (entry) => entry.driver === driver && satisfiesSemverRange(t3CodeVersion, entry.t3CodeRange),
67 );67 );
68 if (!policy) return undefined;68 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;
70 const status =78 const status =
71 stable && /^\d+\.\d+\.\d+$/.test(stable)79 stable && /^\d+\.\d+\.\d+$/.test(stable)
72 ? (policy.ranges.find((entry) => satisfiesSemverRange(stable, entry.range))?.status ??80 ? (policy.ranges.find((entry) => satisfiesSemverRange(stable, entry.range))?.status ??
apps/server/src/provider/providerMaintenanceRunner.test.ts+7-1
...@@ -211,7 +211,13 @@ function makeRegistry(...@@ -211,7 +211,13 @@ function makeRegistry(
211211
212const makeTestRunner = (212const makeTestRunner = (
213 registry: ProviderRegistryShape,213 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 },
215) =>221) =>
216 Effect.service(ProviderMaintenanceRunner.ProviderMaintenanceRunner).pipe(222 Effect.service(ProviderMaintenanceRunner.ProviderMaintenanceRunner).pipe(
217 Effect.provide(223 Effect.provide(