Skip to content

Commit 6338a55

Browse files
committed
Derive ProviderCatalogEntry from ProviderSettings instead of duplicating its field list
src/config/index.ts:95-130 (ProviderCatalogEntry) and src/config/settings.ts:21-45 (ProviderSettings) independently declared the same provider record, so a field added to one silently never reached the catalog conversion in catalogEntryAsProviderSettings/buildProviderCatalog. ProviderCatalogEntry is now Omit<ProviderSettings, "name" | "contextWindow"> plus its required name and the OAuth-only markers (codexProfile/codexAccountId/xaiProfile) that have no settings.json counterpart. Added a round-trip test in src/config.test.ts that fails if a shared field stops surviving buildProviderCatalog -> catalogEntryAsProviderSettings.
1 parent d1d9c5a commit 6338a55

2 files changed

Lines changed: 36 additions & 27 deletions

File tree

src/config.test.ts

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import { mkdtemp, mkdir, writeFile, rm } from "node:fs/promises";
33
import { tmpdir } from "node:os";
44
import { join } from "node:path";
55

6-
import { buildBifrostSource, buildOpenAISource, buildProviderCatalog, KEYLESS_API_KEY, loadConfig, providerCatalogToSettings, runtimeSettingsWithCatalog, SOURCE_MAX_TOKENS } from "./config/index.js";
6+
import { buildBifrostSource, buildOpenAISource, buildProviderCatalog, catalogEntryAsProviderSettings, KEYLESS_API_KEY, loadConfig, providerCatalogToSettings, runtimeSettingsWithCatalog, SOURCE_MAX_TOKENS } from "./config/index.js";
77
import type { Config, UnconfiguredConfig } from "./config/index.js";
88
import { mergeProviderIntoSettings, type ResolvedProvider, type Settings } from "./config/settings.js";
99

@@ -648,6 +648,31 @@ describe("buildProviderCatalog", () => {
648648
});
649649
expect(restOut).toEqual(restExisting);
650650
});
651+
652+
test("round-trips every ProviderSettings field a catalog entry can carry through buildProviderCatalog and back", () => {
653+
// ProviderCatalogEntry is defined as Omit<ProviderSettings, "name" | "contextWindow">.
654+
// This exercises every field that relationship carries over, so a field
655+
// added to ProviderSettings and forgotten in the two conversion sites
656+
// below fails here instead of being silently dropped at runtime.
657+
const provider: Settings["providers"][string] = {
658+
baseURL: "https://fp/v1",
659+
apiKey: "fp-key",
660+
models: ["fp-large"],
661+
defaultModel: "fp-large",
662+
free: true,
663+
bifrostVirtualKey: true,
664+
};
665+
const settings: Settings = { providers: { fp: provider } };
666+
const catalog = buildProviderCatalog(settings, {
667+
providerName: "fp",
668+
baseURL: provider.baseURL,
669+
apiKey: "fp-key",
670+
model: "fp-large",
671+
} as ResolvedProvider);
672+
const entry = catalog.find((c) => c.name === "fp")!;
673+
const roundTripped = { fp: catalogEntryAsProviderSettings(entry) };
674+
expect(roundTripped).toEqual({ fp: provider });
675+
});
651676
});
652677

653678
describe("mergeProviderIntoSettings", () => {

src/config/index.ts

Lines changed: 10 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -91,42 +91,26 @@ export function buildOpenAISource(fields: {
9191

9292
// One configured provider the /agent modal can switch to. Carries credentials
9393
// because live switching builds an InferenceSource from it; the modal only ever
94-
// receives fields needed for provider management, never the key.
95-
export type ProviderCatalogEntry = {
94+
// receives fields needed for provider management, never the key. Derived from
95+
// ProviderSettings (the persisted record) so a field added there is never
96+
// silently missing here: `name` becomes required (every catalog entry is
97+
// resolved to a concrete provider id) and `contextWindow` is dropped (it is a
98+
// settings-only override, never surfaced to the /agent modal). The
99+
// OAuth-profile markers below have no ProviderSettings counterpart because
100+
// such entries are never written to settings.json (their credentials live in
101+
// the Codex/xAI auth stores).
102+
export type ProviderCatalogEntry = Omit<ProviderSettings, "name" | "contextWindow"> & {
96103
name: string;
97-
baseURL: string;
98-
// Absent for keyless providers (see `keyless`). When present, carries the
99-
// secret key the harness injects as a Bearer credential.
100-
apiKey?: string;
101-
models: string[];
102-
defaultModel?: string;
103-
// True for local providers that require no authentication (e.g. Ollama).
104-
// When set, `apiKey` is omitted and resolution skips the key check.
105-
keyless?: boolean;
106-
// Manual override suppressing the status-bar dollar cost for this provider.
107-
free?: boolean;
108104
// Set when this entry is a Codex OAuth profile rather than an API-key
109105
// provider. Holds the profile name; the send path uses it to refresh the
110-
// access token before each turn. Such entries are never written to
111-
// settings.json (their credentials live in the Codex auth store).
106+
// access token before each turn.
112107
codexProfile?: string;
113108
// ChatGPT account id for a Codex profile, sent as the chatgpt-account-id
114109
// header by the Responses adapter. Present only on Codex entries.
115110
codexAccountId?: string;
116111
// Set when this entry is an xAI/Grok OAuth profile. It still routes through
117112
// openai-compatible; the marker only controls token refresh and persistence.
118113
xaiProfile?: string;
119-
// When true this provider is backed by a Bifrost virtual key. Inference
120-
// sources for it are built with provider "bifrost" so the adapter can
121-
// inject the x-bf-vk header (in addition to Authorization). The flag is
122-
// also used to enable /models auto-discovery scoped to the key.
123-
bifrostVirtualKey?: boolean;
124-
// Anthropic Messages API (x-api-key). Used by first-class Anthropic and by
125-
// OpenCode Go models that speak the messages protocol.
126-
anthropic?: boolean;
127-
// OpenCode Go multi-protocol provider. Per-model routing picks
128-
// openai-compatible, openai-responses, or anthropic at source-build time.
129-
opencodeGo?: boolean;
130114
};
131115

132116
// Build the InferenceSource for a Codex OAuth profile. Routes to the

0 commit comments

Comments
 (0)