Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
81 changes: 19 additions & 62 deletions src/domain/models/models-dev.ts
Original file line number Diff line number Diff line change
Expand Up @@ -226,13 +226,14 @@ const resolveAllowedMappings = (input: {
});
};

const patchCanonicalProviders = (input: {
registry: ModelsDevRegistry;
const mergeKleisProviderModels = (input: {
upstreamRegistry: ModelsDevRegistry;
baseOrigin: string;
mappings: readonly ProxyMapping[];
modelScopes: readonly string[] | null;
}): void => {
}): JsonObject => {
const models: JsonObject = {};

for (const mapping of input.mappings) {
const sourceProvider = getObjectProperty(
input.upstreamRegistry,
Expand All @@ -249,49 +250,6 @@ const patchCanonicalProviders = (input: {
continue;
}

const apiUrl = `${input.baseOrigin}${mapping.routeBasePath}`;
const providerModels = cloneProviderModels({
sourceModels: getObjectProperty(sourceProvider, "models") ?? {},
apiUrl,
npm: mapping.npm,
shouldIncludeModel: (modelId) =>
isModelSupportedByProxyProvider(mapping.internalProvider, modelId) &&
isModelInScope({
model: modelId,
route,
modelScopes: input.modelScopes,
}),
});
if (!Object.keys(providerModels).length) {
continue;
}

const provider = cloneJsonValue(sourceProvider);
provider.id = mapping.canonicalProvider;
provider.env = [PROXY_API_KEY_ENV];
provider.api = apiUrl;
provider.npm = mapping.npm;
provider.models = providerModels;
input.registry[mapping.canonicalProvider] = provider;
}
};

const mergeKleisProviderModels = (input: {
registry: ModelsDevRegistry;
baseOrigin: string;
mappings: readonly ProxyMapping[];
}): JsonObject => {
const models: JsonObject = {};

for (const mapping of input.mappings) {
const sourceProvider = getObjectProperty(
input.registry,
mapping.canonicalProvider
);
if (!sourceProvider) {
continue;
}

Object.assign(
models,
cloneProviderModels({
Expand All @@ -301,7 +259,12 @@ const mergeKleisProviderModels = (input: {
modelPrefix: mapping.canonicalProvider,
sourceLabel: mapping.canonicalProvider,
shouldIncludeModel: (modelId) =>
isModelSupportedByProxyProvider(mapping.internalProvider, modelId),
isModelSupportedByProxyProvider(mapping.internalProvider, modelId) &&
isModelInScope({
model: modelId,
route,
modelScopes: input.modelScopes,
}),
})
);
}
Expand All @@ -310,9 +273,10 @@ const mergeKleisProviderModels = (input: {
};

const toKleisProviderEntry = (input: {
registry: ModelsDevRegistry;
upstreamRegistry: ModelsDevRegistry;
baseOrigin: string;
mappings: readonly ProxyMapping[];
modelScopes: readonly string[] | null;
}): JsonObject => {
return {
id: KLEIS_PROVIDER_ID,
Expand All @@ -324,13 +288,16 @@ const toKleisProviderEntry = (input: {

const appendKleisProviderEntry = (input: {
registry: ModelsDevRegistry;
upstreamRegistry: ModelsDevRegistry;
baseOrigin: string;
mappings: readonly ProxyMapping[];
modelScopes: readonly string[] | null;
}): void => {
const generatedProvider = toKleisProviderEntry({
registry: input.registry,
upstreamRegistry: input.upstreamRegistry,
baseOrigin: input.baseOrigin,
mappings: input.mappings,
modelScopes: input.modelScopes,
});
const existingProvider = getObjectProperty(input.registry, KLEIS_PROVIDER_ID);

Expand Down Expand Up @@ -375,25 +342,15 @@ export const buildProxyModelsRegistry = (
providerScopes,
accountProviderScopes,
});
const registry: ModelsDevRegistry = input.apiKeyScopes
? {}
: cloneJsonValue(input.upstreamRegistry);
const registry = cloneJsonValue(input.upstreamRegistry);
const baseOrigin = normalizeOrigin(input.baseOrigin);

if (input.apiKeyScopes) {
patchCanonicalProviders({
registry,
upstreamRegistry: input.upstreamRegistry,
baseOrigin,
mappings,
modelScopes,
});
}

appendKleisProviderEntry({
registry,
upstreamRegistry: input.upstreamRegistry,
baseOrigin,
mappings,
modelScopes,
});

return registry;
Expand Down
60 changes: 46 additions & 14 deletions tests/domain/models-dev-contract.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -247,18 +247,20 @@ describe("models registry contract", () => {
expect(Object.keys(kleis.models ?? {})).toHaveLength(0);
});

test("applies api key provider and model scopes", () => {
test("applies api key scopes only to the kleis aggregate provider", () => {
const registry = buildProxyModelsRegistry({
upstreamRegistry: upstreamRegistry as unknown as Record<string, unknown>,
baseOrigin: "https://kleis.example/api/kmd_abc123",
configuredProviders: ["codex", "claude", "copilot"],
apiKeyScopes: {
providerScopes: ["codex", "copilot"],
modelScopes: ["openai/gpt-5.3-codex", "gpt-5-mini"],
accountProviderScopes: null,
},
});

expect(Object.keys(registry).sort()).toEqual([
"anthropic",
"github-copilot",
"kleis",
"openai",
Expand All @@ -268,16 +270,20 @@ describe("models registry contract", () => {
env?: string[];
models?: Record<string, { id?: string; provider?: { api?: string } }>;
};
expect(openai.env).toEqual(["KLEIS_API_KEY"]);
expect(Object.keys(openai.models ?? {})).toEqual(["gpt-5.3-codex"]);
expect(openai.env).toEqual(["OPENAI_API_KEY"]);
expect(Object.keys(openai.models ?? {})).toEqual([
"gpt-5.3-codex",
"gpt-5",
"text-embedding-3-large",
]);
expect(openai.models?.["gpt-5.3-codex"]?.provider?.api).toBe(
"https://kleis.example/api/kmd_abc123/openai/v1"
"https://api.openai.com/v1"
);

const copilot = registry["github-copilot"] as {
models?: Record<string, { id?: string }>;
};
expect(Object.keys(copilot.models ?? {})).toEqual(["gpt-5-mini"]);
expect(Object.keys(copilot.models ?? {})).toEqual(["gpt-5", "gpt-5-mini"]);

const kleis = registry.kleis as {
models?: Record<string, { id?: string }>;
Expand All @@ -288,27 +294,41 @@ describe("models registry contract", () => {
]);
});

test("scoped mode omits non-proxy upstream providers", () => {
test("scoped mode preserves upstream providers unchanged", () => {
const registry = buildProxyModelsRegistry({
upstreamRegistry: upstreamRegistry as unknown as Record<string, unknown>,
baseOrigin: "https://kleis.example/api/kmd_xyz789",
configuredProviders: ["codex"],
apiKeyScopes: {
providerScopes: ["codex"],
modelScopes: null,
accountProviderScopes: null,
},
});

expect(registry.anthropic).toBeUndefined();
expect(registry["github-copilot"]).toBeUndefined();
const anthropic = registry.anthropic as {
env?: string[];
models?: Record<string, { provider?: { api?: string } }>;
};
expect(anthropic.env).toEqual(["ANTHROPIC_API_KEY"]);
expect(anthropic.models?.["claude-sonnet-4"]?.provider?.api).toBe(
"https://api.anthropic.com/v1"
);

const copilot = registry["github-copilot"] as {
env?: string[];
models?: Record<string, unknown>;
};
expect(copilot.env).toEqual(["GITHUB_TOKEN"]);
expect(Object.keys(copilot.models ?? {})).toEqual(["gpt-5", "gpt-5-mini"]);

const kleis = registry.kleis as {
models?: Record<string, unknown>;
};
expect(Object.keys(kleis.models ?? {})).toEqual(["openai/gpt-5.3-codex"]);
});

test("account-scoped mode only exposes providers backed by scoped accounts", () => {
test("account-scoped mode only narrows the kleis aggregate provider", () => {
const registry = buildProxyModelsRegistry({
upstreamRegistry: upstreamRegistry as unknown as Record<string, unknown>,
baseOrigin: "https://kleis.example/api/kmd_acc123",
Expand All @@ -320,20 +340,32 @@ describe("models registry contract", () => {
},
});

expect(Object.keys(registry).sort()).toEqual(["anthropic", "kleis"]);
expect(Object.keys(registry).sort()).toEqual([
"anthropic",
"github-copilot",
"kleis",
"openai",
]);

const anthropic = registry.anthropic as {
env?: string[];
models?: Record<string, { id?: string; provider?: { api?: string } }>;
};
expect(anthropic.env).toEqual(["KLEIS_API_KEY"]);
expect(anthropic.env).toEqual(["ANTHROPIC_API_KEY"]);
expect(Object.keys(anthropic.models ?? {})).toEqual(["claude-sonnet-4"]);
expect(anthropic.models?.["claude-sonnet-4"]?.provider?.api).toBe(
"https://kleis.example/api/kmd_acc123/anthropic/v1"
"https://api.anthropic.com/v1"
);

const kleis = registry.kleis as {
models?: Record<string, unknown>;
};
expect(Object.keys(kleis.models ?? {})).toEqual([
"anthropic/claude-sonnet-4",
]);
});

test("account scopes further narrow provider scopes", () => {
test("account scopes further narrow the kleis aggregate provider", () => {
const registry = buildProxyModelsRegistry({
upstreamRegistry: upstreamRegistry as unknown as Record<string, unknown>,
baseOrigin: "https://kleis.example/api/kmd_acc456",
Expand All @@ -345,7 +377,7 @@ describe("models registry contract", () => {
},
});

expect(registry.openai).toBeUndefined();
expect(registry.openai).toBeDefined();
expect(registry.anthropic).toBeDefined();

const kleis = registry.kleis as {
Expand Down