Skip to content

Commit 0c4036a

Browse files
Merge pull request #1053 from corbitsdev/corbitsdev/cl-7966-catalog-fold
Fold Go and Zen model catalogs into one parameterized module
2 parents 69aa8ab + e1883fe commit 0c4036a

16 files changed

Lines changed: 546 additions & 737 deletions

‎src/config.test.ts‎

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -43,12 +43,10 @@ import {
4343
} from "../packages/zen/src/index.js";
4444
import {
4545
prefetchGoModels,
46-
resetGoModelDiscoveryForTests,
47-
} from "./provider/opencode-go-models.js";
48-
import {
4946
prefetchZenModels,
47+
resetGoModelDiscoveryForTests,
5048
resetZenModelDiscoveryForTests,
51-
} from "./provider/zen-models.js";
49+
} from "./provider/model-catalogs.js";
5250
import {
5351
generateSessionId,
5452
initSessionDir,

‎src/config/index.ts‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -57,12 +57,12 @@ import {
5757
} from "../provider/grok-responses.js";
5858
import { BIFROST_PROVIDER } from "../provider/bifrost-adapter.js";
5959
import { isOllamaProviderId, ollamaOpenAIBaseURL } from "../provider/ollama.js";
60-
import { selectableGoModelIds } from "../provider/opencode-go-models.js";
60+
import { selectableGoModelIds } from "../provider/model-catalogs.js";
6161
import {
6262
OPENCODE_GO_MESSAGES_PROVIDER,
6363
ZEN_MESSAGES_PROVIDER,
6464
} from "../provider/anthropic-session-adapter.js";
65-
import { selectableZenModelIds } from "../provider/zen-models.js";
65+
import { selectableZenModelIds } from "../provider/model-catalogs.js";
6666
import {
6767
OPENAI_RESPONSES_PROVIDER,
6868
OPENAI_SESSION_ID_OPTION,

‎src/provider/bounded-model-catalog.ts‎

Lines changed: 70 additions & 66 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,68 @@ export type CatalogDiscoveryState =
1212
| { readonly status: "unavailable"; readonly message: string }
1313
| { readonly status: "malformed"; readonly message: string };
1414

15+
function catalogErrorMessage(error: unknown): string {
16+
return error instanceof Error ? error.message : String(error);
17+
}
18+
19+
function declaredCatalogBytes(response: Response): number | undefined {
20+
const raw = response.headers.get("content-length");
21+
if (raw === null || raw.length === 0) return undefined;
22+
const n = Number(raw);
23+
if (!Number.isFinite(n) || n < 0) return undefined;
24+
return n;
25+
}
26+
27+
async function readBoundedCatalogText(
28+
response: Response,
29+
args: { readonly maxBytes: number; readonly oversizeMessage: string },
30+
): Promise<
31+
| { readonly ok: true; readonly text: string }
32+
| { readonly ok: false; readonly message: string }
33+
> {
34+
const declared = declaredCatalogBytes(response);
35+
if (declared !== undefined && declared > args.maxBytes) {
36+
await response.body?.cancel().catch(() => undefined);
37+
return { ok: false, message: args.oversizeMessage };
38+
}
39+
40+
try {
41+
const body = response.body;
42+
if (body === null) {
43+
const text = await response.text();
44+
if (new TextEncoder().encode(text).byteLength > args.maxBytes) {
45+
return { ok: false, message: args.oversizeMessage };
46+
}
47+
return { ok: true, text };
48+
}
49+
50+
const reader = body.getReader();
51+
const chunks: Uint8Array[] = [];
52+
let total = 0;
53+
for (;;) {
54+
const { done, value } = await reader.read();
55+
if (done) break;
56+
if (value === undefined) continue;
57+
total += value.byteLength;
58+
if (total > args.maxBytes) {
59+
await reader.cancel().catch(() => undefined);
60+
return { ok: false, message: args.oversizeMessage };
61+
}
62+
chunks.push(value);
63+
}
64+
65+
const buffer = new Uint8Array(total);
66+
let offset = 0;
67+
for (const chunk of chunks) {
68+
buffer.set(chunk, offset);
69+
offset += chunk.byteLength;
70+
}
71+
return { ok: true, text: new TextDecoder().decode(buffer) };
72+
} catch (error) {
73+
return { ok: false, message: catalogErrorMessage(error) };
74+
}
75+
}
76+
1577
export function createBoundedModelCatalog(args: {
1678
baseURL: string;
1779
seedIds: readonly string[];
@@ -32,14 +94,6 @@ export function createBoundedModelCatalog(args: {
3294
let inflight: Promise<readonly string[]> | undefined;
3395
let snapshot: readonly string[] | undefined;
3496

35-
function declaredCatalogBytes(response: Response): number | undefined {
36-
const raw = response.headers.get("content-length");
37-
if (raw === null || raw.length === 0) return undefined;
38-
const n = Number(raw);
39-
if (!Number.isFinite(n) || n < 0) return undefined;
40-
return n;
41-
}
42-
4397
function oversizeMessage(kind: "bytes" | "models"): string {
4498
if (kind === "bytes") {
4599
return `${catalogLabel} catalog exceeds ${String(maxBytes)} bytes`;
@@ -53,66 +107,16 @@ export function createBoundedModelCatalog(args: {
53107
| { readonly ok: true; readonly value: unknown }
54108
| { readonly ok: false; readonly message: string }
55109
> {
56-
const declared = declaredCatalogBytes(response);
57-
if (declared !== undefined && declared > maxBytes) {
58-
await response.body?.cancel().catch(() => undefined);
59-
return { ok: false, message: oversizeMessage("bytes") };
60-
}
61-
62-
const body = response.body;
63-
if (body === null) {
64-
try {
65-
const text = await response.text();
66-
if (new TextEncoder().encode(text).byteLength > maxBytes) {
67-
return { ok: false, message: oversizeMessage("bytes") };
68-
}
69-
const value: unknown = JSON.parse(text);
70-
return { ok: true, value };
71-
} catch (error) {
72-
return {
73-
ok: false,
74-
message: error instanceof Error ? error.message : String(error),
75-
};
76-
}
77-
}
78-
79-
const reader = body.getReader();
80-
const chunks: Uint8Array[] = [];
81-
let total = 0;
110+
const text = await readBoundedCatalogText(response, {
111+
maxBytes,
112+
oversizeMessage: oversizeMessage("bytes"),
113+
});
114+
if (!text.ok) return text;
82115
try {
83-
for (;;) {
84-
const { done, value } = await reader.read();
85-
if (done) break;
86-
if (value === undefined) continue;
87-
total += value.byteLength;
88-
if (total > maxBytes) {
89-
await reader.cancel().catch(() => undefined);
90-
return { ok: false, message: oversizeMessage("bytes") };
91-
}
92-
chunks.push(value);
93-
}
94-
} catch (error) {
95-
return {
96-
ok: false,
97-
message: error instanceof Error ? error.message : String(error),
98-
};
99-
}
100-
101-
const buffer = new Uint8Array(total);
102-
let offset = 0;
103-
for (const chunk of chunks) {
104-
buffer.set(chunk, offset);
105-
offset += chunk.byteLength;
106-
}
107-
108-
try {
109-
const value: unknown = JSON.parse(new TextDecoder().decode(buffer));
116+
const value: unknown = JSON.parse(text.text);
110117
return { ok: true, value };
111118
} catch (error) {
112-
return {
113-
ok: false,
114-
message: error instanceof Error ? error.message : String(error),
115-
};
119+
return { ok: false, message: catalogErrorMessage(error) };
116120
}
117121
}
118122

@@ -130,7 +134,7 @@ export function createBoundedModelCatalog(args: {
130134
} catch (error) {
131135
return {
132136
status: "unavailable",
133-
message: error instanceof Error ? error.message : String(error),
137+
message: catalogErrorMessage(error),
134138
};
135139
}
136140

‎src/provider/identity-divergence.test.ts‎

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -33,12 +33,10 @@ import {
3333
import { OAUTH_SURFACES } from "../tui/provider/choices.js";
3434
import {
3535
resetGoModelDiscoveryForTests,
36-
selectableGoModelIds,
37-
} from "./opencode-go-models.js";
38-
import {
3936
resetZenModelDiscoveryForTests,
37+
selectableGoModelIds,
4038
selectableZenModelIds,
41-
} from "./zen-models.js";
39+
} from "./model-catalogs.js";
4240

4341
// CL-5691: provider/model identity unification. FIRST_CLASS_PROVIDERS is the
4442
// canonical static registry; the Codex/xAI live-fetch fallbacks stay separate

0 commit comments

Comments
 (0)