Skip to content

Commit 5d8faf9

Browse files
Merge pull request #1113 from corbitsdev/refactor/auth-provider-dedup
refactor(auth-provider): merge duplicated helpers
2 parents 1fc8f75 + 2c34bb7 commit 5d8faf9

8 files changed

Lines changed: 94 additions & 87 deletions

File tree

‎src/permission/store.ts‎

Lines changed: 4 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -80,26 +80,15 @@ function sameApproval(a: Approval, b: Approval): boolean {
8080
}
8181

8282
// Equality on every confirmed dimension except cwd: a planted file entry and
83-
// the gate's minted confirmation of it differ only in cwd.
84-
function sameGrantModuloCwd(a: Approval, b: Approval): boolean {
85-
return (
86-
a.tool === b.tool &&
87-
a.pattern === b.pattern &&
88-
a.providerModel === b.providerModel
89-
);
90-
}
83+
// the gate's minted confirmation of it differ only in cwd. One implementation;
84+
// sameGrantModuloCwd keeps its call-site name.
85+
const sameGrantModuloCwd = sameApproval;
9186

9287
async function readApprovalsField(
9388
path: string,
9489
field: string,
9590
): Promise<Approval[]> {
96-
try {
97-
const raw = await readFile(path, "utf-8");
98-
const parsed = JSON.parse(raw) as Record<string, unknown>;
99-
return parseApprovalList(parsed?.[field]);
100-
} catch {
101-
return [];
102-
}
91+
return parseApprovalList((await readObjectFile(path))[field]);
10392
}
10493

10594
async function readObjectFile(path: string): Promise<Record<string, unknown>> {

‎src/provider/anthropic-session-adapter.ts‎

Lines changed: 5 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -32,16 +32,9 @@ export function createSessionHeaderAnthropicAdapter(
3232
return { ...base, buildRequest };
3333
}
3434

35-
export function createZenAnthropicAdapter(
36-
source: AdapterSource,
37-
quirks?: unknown,
38-
): ProviderAdapter {
39-
return createSessionHeaderAnthropicAdapter(source, quirks);
40-
}
35+
// Both providers share the session-header wrapper above; keep both export
36+
// names for the adapter registration table.
37+
export const createZenAnthropicAdapter = createSessionHeaderAnthropicAdapter;
4138

42-
export function createOpenCodeGoAnthropicAdapter(
43-
source: AdapterSource,
44-
quirks?: unknown,
45-
): ProviderAdapter {
46-
return createSessionHeaderAnthropicAdapter(source, quirks);
47-
}
39+
export const createOpenCodeGoAnthropicAdapter =
40+
createSessionHeaderAnthropicAdapter;

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

Lines changed: 11 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,15 @@
11
import { type } from "arktype";
22

3-
import { requestModelsEndpoint } from "./models-endpoint.js";
3+
import {
4+
endpointErrorMessage,
5+
type ModelsEndpointDiscoveryState,
6+
ModelsEndpointResponse,
7+
requestModelsEndpoint,
8+
} from "./models-endpoint.js";
49

5-
const CatalogModelsResponse = type({
6-
data: type({ id: "string" }).array(),
7-
});
10+
const CatalogModelsResponse = ModelsEndpointResponse;
811

9-
export type CatalogDiscoveryState =
10-
| { readonly status: "models"; readonly models: readonly string[] }
11-
| { readonly status: "empty" }
12-
| { readonly status: "unavailable"; readonly message: string }
13-
| { readonly status: "malformed"; readonly message: string };
14-
15-
function catalogErrorMessage(error: unknown): string {
16-
return error instanceof Error ? error.message : String(error);
17-
}
12+
export type CatalogDiscoveryState = ModelsEndpointDiscoveryState;
1813

1914
function declaredCatalogBytes(response: Response): number | undefined {
2015
const raw = response.headers.get("content-length");
@@ -70,7 +65,7 @@ async function readBoundedCatalogText(
7065
}
7166
return { ok: true, text: new TextDecoder().decode(buffer) };
7267
} catch (error) {
73-
return { ok: false, message: catalogErrorMessage(error) };
68+
return { ok: false, message: endpointErrorMessage(error) };
7469
}
7570
}
7671

@@ -116,7 +111,7 @@ export function createBoundedModelCatalog(args: {
116111
const value: unknown = JSON.parse(text.text);
117112
return { ok: true, value };
118113
} catch (error) {
119-
return { ok: false, message: catalogErrorMessage(error) };
114+
return { ok: false, message: endpointErrorMessage(error) };
120115
}
121116
}
122117

@@ -134,7 +129,7 @@ export function createBoundedModelCatalog(args: {
134129
} catch (error) {
135130
return {
136131
status: "unavailable",
137-
message: catalogErrorMessage(error),
132+
message: endpointErrorMessage(error),
138133
};
139134
}
140135

‎src/provider/context-window.ts‎

Lines changed: 15 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -28,17 +28,23 @@ let contextWindowRegistry: Record<string, number> = {};
2828
// later models.dev refresh because it lives beside the registry, not in it.
2929
let contextWindowOverrides: Record<string, number> = {};
3030

31-
export function setModelContextWindows(
32-
windows: Record<string, number> | undefined,
33-
): void {
34-
contextWindowRegistry = windows ?? {};
31+
// Both tables are wholesale-replaced on refresh; one factory keeps the two
32+
// trivial setters from drifting. `undefined` (no cache yet) means empty.
33+
function createRegistrySetter(
34+
replace: (windows: Record<string, number>) => void,
35+
): (windows: Record<string, number> | undefined) => void {
36+
return (windows) => replace(windows ?? {});
3537
}
3638

37-
export function setProviderContextWindowOverrides(
38-
windows: Record<string, number> | undefined,
39-
): void {
40-
contextWindowOverrides = windows ?? {};
41-
}
39+
export const setModelContextWindows = createRegistrySetter((windows) => {
40+
contextWindowRegistry = windows;
41+
});
42+
43+
export const setProviderContextWindowOverrides = createRegistrySetter(
44+
(windows) => {
45+
contextWindowOverrides = windows;
46+
},
47+
);
4248

4349
export type ProviderContextWindowSource = {
4450
models: readonly string[];

‎src/provider/model-catalogs.ts‎

Lines changed: 24 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -9,23 +9,37 @@ import {
99
import { createBoundedModelCatalog } from "./bounded-model-catalog.js";
1010

1111
// Bound live /models so a huge or hostile catalog cannot blow process memory.
12-
export const MAX_GO_CATALOG_BYTES = 256 * 1024;
13-
export const MAX_GO_CATALOG_MODELS = 1024;
14-
export const MAX_ZEN_CATALOG_BYTES = 256 * 1024;
15-
export const MAX_ZEN_CATALOG_MODELS = 1024;
12+
// One shared bound for both catalogs; the per-catalog names stay as aliases.
13+
const MAX_CATALOG_BYTES = 256 * 1024;
14+
const MAX_CATALOG_MODELS = 1024;
15+
16+
export const MAX_GO_CATALOG_BYTES = MAX_CATALOG_BYTES;
17+
export const MAX_GO_CATALOG_MODELS = MAX_CATALOG_MODELS;
18+
export const MAX_ZEN_CATALOG_BYTES = MAX_CATALOG_BYTES;
19+
export const MAX_ZEN_CATALOG_MODELS = MAX_CATALOG_MODELS;
20+
21+
// The two live catalogs differ only in source and label — one row each.
22+
const MODEL_CATALOG_CONFIGS = {
23+
go: {
24+
baseURL: OPENCODE_GO_BASE_URL,
25+
seedIds: OPENCODE_GO_MODEL_IDS,
26+
catalogLabel: "OpenCode Go",
27+
},
28+
zen: {
29+
baseURL: ZEN_DEFAULT_BASE_URL,
30+
seedIds: ZEN_MODEL_IDS,
31+
catalogLabel: "OpenCode Zen",
32+
},
33+
} as const;
1634

1735
const goCatalog = createBoundedModelCatalog({
18-
baseURL: OPENCODE_GO_BASE_URL,
19-
seedIds: OPENCODE_GO_MODEL_IDS,
20-
catalogLabel: "OpenCode Go",
36+
...MODEL_CATALOG_CONFIGS.go,
2137
maxBytes: MAX_GO_CATALOG_BYTES,
2238
maxModels: MAX_GO_CATALOG_MODELS,
2339
});
2440

2541
const zenCatalog = createBoundedModelCatalog({
26-
baseURL: ZEN_DEFAULT_BASE_URL,
27-
seedIds: ZEN_MODEL_IDS,
28-
catalogLabel: "OpenCode Zen",
42+
...MODEL_CATALOG_CONFIGS.zen,
2943
maxBytes: MAX_ZEN_CATALOG_BYTES,
3044
maxModels: MAX_ZEN_CATALOG_MODELS,
3145
});

‎src/provider/models-endpoint.ts‎

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import { type } from "arktype";
2+
13
import { normalizeOpenAICompatibleBaseURL } from "../config/settings.js";
24

35
export const DEFAULT_MODELS_REQUEST_TIMEOUT_MS = 10_000;
@@ -8,6 +10,23 @@ export function modelsEndpointURL(baseURL: string): string {
810
);
911
}
1012

13+
// One /models `{ data: [{ id }] }` shape shared by every discovery importer
14+
// (bounded-model-catalog, ollama) so the parsers cannot drift.
15+
export const ModelsEndpointResponse = type({
16+
data: type({ id: "string" }).array(),
17+
});
18+
19+
export type ModelsEndpointDiscoveryState =
20+
| { readonly status: "models"; readonly models: readonly string[] }
21+
| { readonly status: "empty" }
22+
| { readonly status: "unavailable"; readonly message: string }
23+
| { readonly status: "malformed"; readonly message: string };
24+
25+
// Single unknown→message coercion for discovery failure paths.
26+
export function endpointErrorMessage(error: unknown): string {
27+
return error instanceof Error ? error.message : String(error);
28+
}
29+
1130
// Single GET against an OpenAI-compatible /models endpoint. Every caller that
1231
// probes a provider's model list goes through here so URL normalization and
1332
// the request timeout stay consistent.

‎src/provider/ollama.ts‎

Lines changed: 10 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,11 @@
11
import { type } from "arktype";
22

3-
import { requestModelsEndpoint } from "./models-endpoint.js";
3+
import {
4+
endpointErrorMessage,
5+
type ModelsEndpointDiscoveryState,
6+
ModelsEndpointResponse,
7+
requestModelsEndpoint,
8+
} from "./models-endpoint.js";
49

510
export const OLLAMA_PROVIDER_ID = "ollama";
611

@@ -36,15 +41,9 @@ export function ollamaOpenAIBaseURL(rootURL: string): string {
3641
return `${normalizeOllamaRootURL(rootURL)}/v1`;
3742
}
3843

39-
const OllamaModelsResponse = type({
40-
data: type({ id: "string" }).array(),
41-
});
44+
const OllamaModelsResponse = ModelsEndpointResponse;
4245

43-
export type OllamaDiscoveryState =
44-
| { readonly status: "models"; readonly models: readonly string[] }
45-
| { readonly status: "empty" }
46-
| { readonly status: "unavailable"; readonly message: string }
47-
| { readonly status: "malformed"; readonly message: string };
46+
export type OllamaDiscoveryState = ModelsEndpointDiscoveryState;
4847

4948
/** Discover installed Ollama models without leaking transport or parsing failures. */
5049
export async function discoverOllamaModels(args: {
@@ -58,7 +57,7 @@ export async function discoverOllamaModels(args: {
5857
} catch (error) {
5958
return {
6059
status: "malformed",
61-
message: error instanceof Error ? error.message : String(error),
60+
message: endpointErrorMessage(error),
6261
};
6362
}
6463

@@ -72,7 +71,7 @@ export async function discoverOllamaModels(args: {
7271
} catch (error) {
7372
return {
7473
status: "unavailable",
75-
message: error instanceof Error ? error.message : String(error),
74+
message: endpointErrorMessage(error),
7675
};
7776
}
7877

‎src/provider/reasoning-effort.ts‎

Lines changed: 6 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -61,17 +61,13 @@ const UNKNOWN_MODEL_EFFORTS: readonly ReasoningEffort[] = [
6161
"high",
6262
];
6363

64-
// Muse Spark (Responses protocol) accepts minimal through high. Not `none` —
64+
// Muse Spark (Responses protocol) accepts minimal through high — the same
65+
// ladder as DEFAULT_EFFORTS above. Not `none` —
6566
// the gateway rejects it with HTTP 400 on `reasoning.effort`. Measured on
6667
// muse-spark-1.3-contributor and muse-spark-1.2-contributor via the Go
6768
// endpoint and muse-spark-1.3-contributor-free via Zen: `minimal` returns 200
6869
// and `none` returns 400 on all three. See CL-7867.
69-
const MUSE_SPARK_EFFORTS: readonly ReasoningEffort[] = [
70-
"minimal",
71-
"low",
72-
"medium",
73-
"high",
74-
];
70+
const MUSE_SPARK_EFFORTS: readonly ReasoningEffort[] = DEFAULT_EFFORTS;
7571

7672
// Matched by prefix, not by an id list. The family ships under five ids across
7773
// two catalogs — `muse-spark-1.3-contributor` / `-1.2-contributor` in
@@ -85,13 +81,9 @@ function isMuseSparkModel(model: string): boolean {
8581
return /^muse-spark/i.test(model.trim());
8682
}
8783

88-
// grok-4.6 accepts xhigh; grok-4.5 and composer stay on the unknown-model subset.
89-
const GROK_46_EFFORTS: readonly ReasoningEffort[] = [
90-
"low",
91-
"medium",
92-
"high",
93-
"xhigh",
94-
];
84+
// grok-4.6 accepts xhigh — the same ladder as CODEX_EFFORTS above;
85+
// grok-4.5 and composer stay on the unknown-model subset.
86+
const GROK_46_EFFORTS: readonly ReasoningEffort[] = CODEX_EFFORTS;
9587
const GROK_46_MODELS: readonly string[] = ["grok-4.6"];
9688

9789
// GPT-6 Astra accepts low through max on both the OpenAI API and Codex surfaces.

0 commit comments

Comments
 (0)