authorgravatar for rodrigobrechard@gmail.comRodrigo Brechard <rodrigobrechard@gmail.com> 2026-08-04 13:07:22+02:00
committergravatar for noreply@github.comGitHub <noreply@github.com> 2026-08-04 13:07:22+02:00
log966cc05a9007a4ad1b7f8d5d3a1fc0e5c82a5b94
treefd41311f214a8969d50900b95e028f73e4025929
parent1cbd88aba042f3209e1538a927775a7c4f7fdb06
signature Signed by PGP key B5690EEEBB952194

fix(contracts): decode ServerProviders forward-compatibly (#5327)

Co-authored-by: Rodrigo Brechard <rodrigo@clubtidy.fr>

2 files changed, 41 insertions(+), 2 deletions(-)

packages/contracts/src/server.test.ts+36-1
......@@ -1,12 +1,30 @@
11import * as Schema from "effect/Schema";
22import { describe, expect, it } from "vite-plus/test";
33
4import { ServerConfig, ServerProvider, ServerUpsertKeybindingResult } from "./server.ts";
4import {
5 ServerConfig,
6 ServerProvider,
7 ServerProviders,
8 ServerUpsertKeybindingResult,
9} from "./server.ts";
510
611const decodeServerProvider = Schema.decodeUnknownSync(ServerProvider);
12const decodeServerProviders = Schema.decodeUnknownSync(ServerProviders);
713const decodeUpsertKeybindingResult = Schema.decodeUnknownSync(ServerUpsertKeybindingResult);
814const decodeAvailableEditors = Schema.decodeUnknownSync(ServerConfig.fields.availableEditors);
915
16const baseProviderSnapshot = {
17 instanceId: "codex",
18 driver: "codex",
19 enabled: true,
20 installed: true,
21 version: "1.0.0",
22 status: "ready",
23 auth: { status: "authenticated" },
24 checkedAt: "2026-04-10T00:00:00.000Z",
25 models: [],
26};
27
1028describe("ServerProvider", () => {
1129 it("defaults capability arrays when decoding provider snapshots", () => {
1230 const parsed = decodeServerProvider({
......@@ -119,4 +137,21 @@ describe("server config forward compatibility", () => {
119137
120138 expect(parsed).toEqual(["zed", "vscode"]);
121139 });
140
141 // A provider status this build has never seen (a new ServerProviderState,
142 // ServerProviderAuthStatus, etc. member) previously failed the whole
143 // `providers` array, taking every other provider down with it and, since
144 // `providers` sits inside `ServerConfig`, failing the whole config decode —
145 // an older client would drop its connection over one provider it can't
146 // render. Dropping just that element keeps every other provider working.
147 it("drops providers this build cannot decode instead of failing the whole array", () => {
148 const decodedBase = decodeServerProvider(baseProviderSnapshot);
149
150 const parsed = decodeServerProviders([
151 baseProviderSnapshot,
152 { ...baseProviderSnapshot, instanceId: "future", status: "some-future-status" },
153 ]);
154
155 expect(parsed).toEqual([decodedBase]);
156 });
122157});
packages/contracts/src/server.ts+5-1
......@@ -197,7 +197,11 @@ export const ServerProvider = Schema.Struct({
197197});
198198export type ServerProvider = typeof ServerProvider.Type;
199199
200export const ServerProviders = Schema.Array(ServerProvider);
200// Provider status kinds grow over time (ServerProviderState,
201// ServerProviderAuthStatus, ServerProviderVersionAdvisoryStatus,
202// ServerProviderUpdateStatus); an older client must not fail the whole config
203// decode over one provider it cannot render.
204export const ServerProviders = ForwardCompatibleArray(ServerProvider);
201205export type ServerProviders = typeof ServerProviders.Type;
202206
203207/**