1import { SERVER_VERSION } from "../version.ts";
2import {
3 TrimmedNonEmptyString,
4 ServerProviderCompatibilityStatus,
5 type ProviderDriverKind,
6 type ServerProvider,
7 type ServerProviderCompatibilityAdvisory,
8} from "@t3tools/contracts";
9import { satisfiesSemverRange } from "@t3tools/shared/semver";
10import * as Schema from "effect/Schema";
11
12// Deliberately uses the shared CLI gate syntax: comparator groups joined by ||.
13// Prereleases and unrecognized release tags remain unknown.
14const StableVersion = TrimmedNonEmptyString.pipe(
15 Schema.check(Schema.makeFilter((value) => /^\d+\.\d+\.\d+$/.test(value))),
16);
17const VersionRange = TrimmedNonEmptyString.pipe(
18 Schema.check(
19 Schema.makeFilter((value) =>
20 value.split("||").every((group) => {
21 const tokens = group.trim().split(/\s+/);
22 return tokens.every((token) => /^(?:\^|>=|>|<=|<|=)?v?\d+(?:\.\d+){0,2}$/.test(token));
23 }),
24 ),
25 ),
26);
27const Policy = Schema.Struct({
28 driver: TrimmedNonEmptyString,
29 t3CodeRange: VersionRange,
30 recommendedRange: Schema.optionalKey(VersionRange),
31 recommendedVersion: Schema.optionalKey(StableVersion),
32 ranges: Schema.Array(
33 Schema.Struct({
34 range: VersionRange,
35 status: ServerProviderCompatibilityStatus,
36 }),
37 ),
38});
39
40export const ProviderCompatibilityPolicy = Policy.pipe(
41 Schema.check(
42 Schema.makeFilter(
43 (policy) => {
44 const version = policy.recommendedVersion;
45 if (version === undefined) return true;
46 return (
47 (policy.recommendedRange === undefined ||
48 satisfiesSemverRange(version, policy.recommendedRange)) &&
49 policy.ranges.find((entry) => satisfiesSemverRange(version, entry.range))?.status ===
50 "supported"
51 );
52 },
53 { expected: "a recommended version in a supported range" },
54 ),
55 ),
56);
57export type ProviderCompatibilityPolicy = typeof ProviderCompatibilityPolicy.Type;
58
59export function resolveProviderCompatibility(
60 policies: ReadonlyArray<ProviderCompatibilityPolicy> | undefined,
61 driver: ProviderDriverKind,
62 version: string | null,
63 t3CodeVersion = SERVER_VERSION,
64): ServerProviderCompatibilityAdvisory | undefined {
65 const policy = policies?.find(
66 (entry) => entry.driver === driver && satisfiesSemverRange(t3CodeVersion, entry.t3CodeRange),
67 );
68 if (!policy) return undefined;
69 const unprefixed = version?.replace(/^v/, "");
70 // Cursor appends a build hash to its date; Google's ACP runtime uses a release prefix;
71 // Muse appends a release revision. Strip only these driver-specific forms, keeping
72 // semver prereleases unknown.
73 const stable =
74 driver === "cursor"
75 ? unprefixed?.replace(/^(\d{4}\.\d{2}\.\d{2})-[a-f0-9]+$/, "$1")
76 : driver === "antigravity"
77 ? unprefixed?.replace(/^agy_acp_server_(\d+\.\d+\.\d+)$/, "$1")
78 : driver === "muse"
79 ? unprefixed?.replace(/^(\d+\.\d+\.\d+)-R\d+(?:\.\d+)?$/, "$1")
80 : unprefixed;
81 const status =
82 stable && /^\d+\.\d+\.\d+$/.test(stable)
83 ? (policy.ranges.find((entry) => satisfiesSemverRange(stable, entry.range))?.status ??
84 "unknown")
85 : "unknown";
86 const message =
87 status === "broken"
88 ? "This provider version is known to be incompatible with this T3 Code release."
89 : status === "unsupported"
90 ? "This provider version is outside the supported range for this T3 Code release."
91 : status === "graceful"
92 ? "This provider version has limited compatibility with this T3 Code release."
93 : null;
94 const recommendedVersion = policy.recommendedVersion ?? null;
95 const recommendedRange = policy.recommendedRange ?? null;
96 const recommendation = recommendedVersion ?? recommendedRange;
97 return {
98 status,
99 message: message && recommendation ? `${message} Use ${recommendation}.` : message,
100 recommendedVersion,
101 recommendedRange,
102 };
103}
104
105/** A remote policy replaces its matching bundled policy; omission keeps the bundle. */
106export function applyProviderCompatibility(
107 snapshot: ServerProvider,
108 policies: ReadonlyArray<ProviderCompatibilityPolicy> | undefined,
109 fallback: ReadonlyArray<ProviderCompatibilityPolicy> | undefined,
110): ServerProvider {
111 const { compatibilityAdvisory: _previous, ...base } = snapshot;
112 if (!snapshot.enabled || !snapshot.installed) return base;
113 const advisory =
114 resolveProviderCompatibility(policies, snapshot.driver, snapshot.version) ??
115 resolveProviderCompatibility(fallback, snapshot.driver, snapshot.version);
116 const latestVersion = snapshot.versionAdvisory?.latestVersion;
117 const latestAdvisory = latestVersion
118 ? (resolveProviderCompatibility(policies, snapshot.driver, latestVersion) ??
119 resolveProviderCompatibility(fallback, snapshot.driver, latestVersion))
120 : undefined;
121 return advisory
122 ? {
123 ...base,
124 compatibilityAdvisory: {
125 ...advisory,
126 ...(latestAdvisory ? { latestVersionStatus: latestAdvisory.status } : {}),
127 },
128 }
129 : base;
130}