Skip to content

Commit cc1e634

Browse files
fix(server): released builds keep the OpenCode policy for their own version (pingdotgg#14863)
Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
1 parent 1e7c8e0 commit cc1e634

4 files changed

Lines changed: 68 additions & 95 deletions

File tree

‎apps/server/src/provider/ModelManifest.test.ts‎

Lines changed: 0 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,6 @@ import { HttpClient, HttpClientResponse } from "effect/unstable/http";
1111
import * as ServerConfig from "../config.ts";
1212
import * as ServerSettings from "../serverSettings.ts";
1313
import * as ModelManifest from "./ModelManifest.ts";
14-
import { resolveProviderCompatibility } from "./providerCompatibility.ts";
1514

1615
/**
1716
* Test policy: this file covers manifest machinery, not manifest contents.
@@ -584,62 +583,6 @@ describe("ModelManifest service", () => {
584583
);
585584
});
586585

587-
// TEMPORARY V2 preview stopgap; remove with `withPreviewCompatibility` (#2829).
588-
describe("compatibility policy by release channel", () => {
589-
const OPENCODE = ProviderDriverKind.make("opencode");
590-
// Main's OpenCode policy (#14198): stable and nightly have no OpenCode 2 runtime.
591-
const remote: ModelManifest.ModelManifestData = {
592-
...REMOTE_MANIFEST,
593-
compatibility: [
594-
{
595-
driver: OPENCODE,
596-
t3CodeRange: ">=0.0.42",
597-
recommendedRange: ">=1.14.19 <2.0.0",
598-
recommendedVersion: "1.14.19",
599-
ranges: [
600-
{ range: ">=2.0.0", status: "broken" },
601-
{ range: ">=1.14.19 <2.0.0", status: "supported" },
602-
{ range: "<1.14.19", status: "broken" },
603-
],
604-
},
605-
],
606-
};
607-
608-
for (const [channel, t3CodeVersion, expected, openCode2Status] of [
609-
[
610-
"preview builds keep the bundled policy",
611-
"0.0.44-preview.20260929.1",
612-
{ ...remote, compatibility: ModelManifest.BUNDLED_MODEL_MANIFEST.compatibility },
613-
"supported",
614-
],
615-
["stable builds adopt the fetched policy", "0.0.44", remote, "broken"],
616-
["nightly builds adopt the fetched policy", "0.0.44-nightly.20260929.1", remote, "broken"],
617-
] as const) {
618-
it.live(channel, () =>
619-
Effect.gen(function* () {
620-
const openCode2 = (manifest: ModelManifest.ModelManifestData) =>
621-
resolveProviderCompatibility(manifest.compatibility, OPENCODE, "2.0.18", t3CodeVersion)
622-
?.status;
623-
const refreshed = yield* (yield* ModelManifest.makeForVersion(t3CodeVersion)).refresh;
624-
assert.deepStrictEqual(refreshed, expected);
625-
assert.strictEqual(openCode2(refreshed), openCode2Status);
626-
627-
// A restart reads the fetched manifest back from the disk cache.
628-
const rebooted = yield* (yield* ModelManifest.makeForVersion(t3CodeVersion)).current;
629-
assert.deepStrictEqual(rebooted, expected);
630-
}).pipe(
631-
Effect.scoped,
632-
Effect.provide(
633-
serviceLayers({
634-
prefix: "model-manifest-channel-compatibility-test",
635-
response: () => Response.json(remote),
636-
}),
637-
),
638-
),
639-
);
640-
}
641-
});
642-
643586
it.effect("caches valid compatibility policies and keeps them after a malformed refresh", () => {
644587
const remote: ModelManifest.ModelManifestData = {
645588
...REMOTE_MANIFEST,

‎apps/server/src/provider/ModelManifest.ts‎

Lines changed: 3 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,6 @@ import {
1919
type ProviderDriverKind,
2020
type ServerProviderModel,
2121
} from "@t3tools/contracts";
22-
import { cliReleaseChannelOf } from "@t3tools/shared/cliRelease";
2322
import { codexModelFamily } from "@t3tools/shared/model";
2423
import * as Clock from "effect/Clock";
2524
import * as Context from "effect/Context";
@@ -31,7 +30,6 @@ import * as Schema from "effect/Schema";
3130
import * as Semaphore from "effect/Semaphore";
3231
import { HttpClient, HttpClientResponse } from "effect/unstable/http";
3332

34-
import packageJson from "../../package.json" with { type: "json" };
3533
import { ServerConfig } from "../config.ts";
3634
import * as ServerSettings from "../serverSettings.ts";
3735
import { hasValidClaudeManifestAdapters } from "./ClaudeModelManifest.ts";
@@ -142,25 +140,6 @@ const decodeManifest = Schema.decodeUnknownEffect(ModelManifestSchema);
142140
export const BUNDLED_MODEL_MANIFEST: ModelManifestData =
143141
Schema.decodeUnknownSync(ModelManifestSchema)(bundledManifestJson);
144142

145-
/**
146-
* TEMPORARY V2 preview stopgap. Revert before V2 merges into main; it is on
147-
* #2829's "Revert before merging into main" checklist.
148-
*
149-
* The fetched manifest is `main`'s, and its compatibility policy is written for
150-
* stable and nightly builds, which do not run OpenCode 2. A policy's
151-
* `t3CodeRange` cannot single out preview builds because range matching drops
152-
* prerelease tags. Preview builds therefore keep the compatibility policy they
153-
* shipped with and take everything else from the fetched manifest.
154-
*/
155-
function withPreviewCompatibility(
156-
manifest: ModelManifestData,
157-
t3CodeVersion: string,
158-
): ModelManifestData {
159-
return cliReleaseChannelOf(t3CodeVersion) === "preview"
160-
? { ...manifest, compatibility: BUNDLED_MODEL_MANIFEST.compatibility }
161-
: manifest;
162-
}
163-
164143
/** Epoch millis of the manifest's `updatedAt`, or 0 when absent or unparsable. */
165144
function manifestUpdatedAtMs(manifest: ModelManifestData): number {
166145
if (manifest.updatedAt === undefined) return 0;
@@ -357,8 +336,7 @@ const BundledOnlyModelManifest: ModelManifest["Service"] = {
357336

358337
export const layerTest = Layer.succeed(ModelManifest, BundledOnlyModelManifest);
359338

360-
/** `make` for a given T3 Code version, so tests can exercise each release channel. */
361-
export const makeForVersion = Effect.fnUntraced(function* (t3CodeVersion: string) {
339+
export const make = Effect.gen(function* () {
362340
const fileSystem = yield* FileSystem.FileSystem;
363341
const path = yield* Path.Path;
364342
const config = yield* ServerConfig;
@@ -391,7 +369,7 @@ export const makeForVersion = Effect.fnUntraced(function* (t3CodeVersion: string
391369
if (manifestUpdatedAtMs(BUNDLED_MODEL_MANIFEST) > manifestUpdatedAtMs(fromDisk.manifest)) {
392370
return;
393371
}
394-
manifest = withPreviewCompatibility(fromDisk.manifest, t3CodeVersion);
372+
manifest = fromDisk.manifest;
395373
fetchedAtMs = fromDisk.fetchedAtMs;
396374
}),
397375
);
@@ -430,7 +408,7 @@ export const makeForVersion = Effect.fnUntraced(function* (t3CodeVersion: string
430408
return manifest;
431409
}
432410

433-
manifest = withPreviewCompatibility(fetched, t3CodeVersion);
411+
manifest = fetched;
434412
fetchedAtMs = now;
435413
yield* encodeManifestCache({ fetchedAtMs: now, manifest: fetched }).pipe(
436414
Effect.flatMap((serialized) => fileSystem.writeFileString(cachePath, serialized)),
@@ -449,6 +427,4 @@ export const makeForVersion = Effect.fnUntraced(function* (t3CodeVersion: string
449427
});
450428
});
451429

452-
export const make = makeForVersion(packageJson.version);
453-
454430
export const layer = Layer.effect(ModelManifest, make);

‎apps/server/src/provider/model-manifest.json‎

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"version": 1,
3-
"updatedAt": "2026-09-30T22:00:00Z",
3+
"updatedAt": "2026-10-02T19:45:00Z",
44
"compatibility": [
55
{
66
"driver": "codex",
@@ -52,7 +52,18 @@
5252
},
5353
{
5454
"driver": "opencode",
55-
"t3CodeRange": ">=0.0.42",
55+
"t3CodeRange": ">=0.0.42 <0.0.46",
56+
"recommendedRange": ">=1.14.19 <2.0.0",
57+
"recommendedVersion": "1.14.19",
58+
"ranges": [
59+
{ "range": ">=2.0.0", "status": "broken" },
60+
{ "range": ">=1.14.19 <2.0.0", "status": "supported" },
61+
{ "range": "<1.14.19", "status": "broken" }
62+
]
63+
},
64+
{
65+
"driver": "opencode",
66+
"t3CodeRange": ">=0.0.46",
5667
"recommendedRange": ">=2.0.18",
5768
"ranges": [
5869
{ "range": ">=2.0.18", "status": "supported" },
@@ -69,7 +80,7 @@
6980
},
7081
{
7182
"driver": "pi",
72-
"t3CodeRange": ">=0.0.42",
83+
"t3CodeRange": ">=0.0.46",
7384
"recommendedRange": ">=1.0.0",
7485
"ranges": [
7586
{ "range": ">=0.80.5", "status": "supported" },

‎apps/server/src/provider/providerCompatibility.test.ts‎

Lines changed: 51 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,9 @@ const provider: ServerProvider = {
5151
slashCommands: [],
5252
};
5353

54+
// The first release that runs the V2 orchestrator.
55+
const V2_RELEASE = "0.0.46";
56+
5457
describe("provider compatibility", () => {
5558
it("bundles a compatibility policy for every built-in harness", () => {
5659
for (const builtIn of BUILT_IN_DRIVERS) {
@@ -61,12 +64,42 @@ describe("provider compatibility", () => {
6164
ModelManifest.BUNDLED_MODEL_MANIFEST.compatibility,
6265
builtIn.driverKind,
6366
null,
67+
V2_RELEASE,
6468
),
6569
`Missing bundled compatibility policy for ${builtIn.driverKind}`,
6670
);
6771
}
6872
});
6973

74+
it("keeps releases before V2 on the OpenCode 1 policy they shipped with", () => {
75+
// Every installed build fetches main's manifest, and 0.0.45 and older run the
76+
// V1 orchestrator, which cannot drive OpenCode 2.
77+
const opencode = ProviderDriverKind.make("opencode");
78+
for (const [version, expected] of [
79+
["1.14.19", "supported"],
80+
["2.0.18", "broken"],
81+
["1.14.18", "broken"],
82+
] as const) {
83+
const advisory = resolveProviderCompatibility(
84+
ModelManifest.BUNDLED_MODEL_MANIFEST.compatibility,
85+
opencode,
86+
version,
87+
"0.0.45",
88+
);
89+
assert.strictEqual(advisory?.status, expected, `OpenCode ${version} on 0.0.45`);
90+
assert.strictEqual(advisory?.recommendedVersion, "1.14.19");
91+
}
92+
// Pi has no driver before V2.
93+
assert.isUndefined(
94+
resolveProviderCompatibility(
95+
ModelManifest.BUNDLED_MODEL_MANIFEST.compatibility,
96+
ProviderDriverKind.make("pi"),
97+
"1.0.0",
98+
"0.0.45",
99+
),
100+
);
101+
});
102+
70103
it("supports OpenCode 2 and gives OpenCode 1.x limited support", () => {
71104
const opencode = ProviderDriverKind.make("opencode");
72105
for (const [version, expected] of [
@@ -79,13 +112,16 @@ describe("provider compatibility", () => {
79112
["1.14.19", "graceful"],
80113
["1.14.18", "broken"],
81114
] as const) {
82-
const advisory = resolveProviderCompatibility(
83-
ModelManifest.BUNDLED_MODEL_MANIFEST.compatibility,
84-
opencode,
85-
version,
86-
);
87-
assert.strictEqual(advisory?.status, expected, `OpenCode ${version}`);
88-
assert.strictEqual(advisory?.recommendedRange, ">=2.0.18");
115+
for (const t3CodeVersion of [V2_RELEASE, "0.0.46-preview.20261002.2598"]) {
116+
const advisory = resolveProviderCompatibility(
117+
ModelManifest.BUNDLED_MODEL_MANIFEST.compatibility,
118+
opencode,
119+
version,
120+
t3CodeVersion,
121+
);
122+
assert.strictEqual(advisory?.status, expected, `OpenCode ${version} on ${t3CodeVersion}`);
123+
assert.strictEqual(advisory?.recommendedRange, ">=2.0.18");
124+
}
89125
}
90126
// The advisory rides beside the probe: a ready 1.x instance stays ready and selectable.
91127
const ready = applyProviderCompatibility(
@@ -94,7 +130,14 @@ describe("provider compatibility", () => {
94130
ModelManifest.BUNDLED_MODEL_MANIFEST.compatibility,
95131
);
96132
assert.strictEqual(ready.status, "ready");
97-
assert.strictEqual(ready.compatibilityAdvisory?.status, "graceful");
133+
assert.strictEqual(
134+
ready.compatibilityAdvisory?.status,
135+
resolveProviderCompatibility(
136+
ModelManifest.BUNDLED_MODEL_MANIFEST.compatibility,
137+
opencode,
138+
"1.18.33",
139+
)?.status,
140+
);
98141
});
99142

100143
it("compares Cursor build dates without treating semver prereleases as stable", () => {

0 commit comments

Comments
 (0)