fix(coding-agent): isolated gateway catalog from local model config
The pi-native-only filter still let any non-pi-native models.yml provider override (baseUrl/apiKey/headers) reach the broker-backed gateway: config keys shadowed broker credentials (bypassing account pooling, refresh, and accounting) and a custom baseUrl redirected the broker bearer to a configured endpoint. Generalized the flag to ignoreLocalModelConfig, which short-circuits loadCustomModels so the gateway serves bundled plus broker-discovered catalog metadata only, applying no local overrides, config API keys, custom models, or custom discovery. Fixes #6615
This commit is contained in:
@@ -5,11 +5,11 @@ import { TempDir } from "@oh-my-pi/pi-utils";
|
||||
import { ModelRegistry } from "../../config/model-registry";
|
||||
import { indexModelsByRequestId } from "../auth-gateway-cli";
|
||||
|
||||
function stubAuthStorage(): AuthStorage {
|
||||
function stubAuthStorage(configKeys?: string[]): AuthStorage {
|
||||
const stub = {
|
||||
setFallbackResolver: () => {},
|
||||
clearConfigApiKeys: () => {},
|
||||
setConfigApiKey: () => {},
|
||||
setConfigApiKey: (provider: string) => configKeys?.push(provider),
|
||||
removeConfigApiKey: () => {},
|
||||
hasAuth: () => true,
|
||||
getAll: () => ({ anthropic: {} }),
|
||||
@@ -49,9 +49,11 @@ describe("indexModelsByRequestId (auth-gateway catalog)", () => {
|
||||
expect(index.get("claude-opus-5-repro")?.id).toBe("claude-opus-5-repro");
|
||||
});
|
||||
|
||||
test("ignores client-side pi-native routing in the gateway registry", async () => {
|
||||
test("gateway registry ignores local models.yml credential and routing overrides", async () => {
|
||||
using tempDir = TempDir.createSync("@omp-auth-gateway-catalog-");
|
||||
const modelsPath = tempDir.join("models.yml");
|
||||
// anthropic: a plain credential/baseUrl override (no transport) — the
|
||||
// reviewer's leak. openai: a pi-native gateway route — the self-routing loop.
|
||||
await Bun.write(
|
||||
modelsPath,
|
||||
[
|
||||
@@ -59,25 +61,36 @@ describe("indexModelsByRequestId (auth-gateway catalog)", () => {
|
||||
" anthropic:",
|
||||
" baseUrl: http://127.0.0.1:18899",
|
||||
" apiKey: gateway-token",
|
||||
" openai:",
|
||||
" baseUrl: http://127.0.0.1:18899",
|
||||
" apiKey: gateway-token",
|
||||
" transport: pi-native",
|
||||
"",
|
||||
].join("\n"),
|
||||
);
|
||||
|
||||
const clientRegistry = new ModelRegistry(stubAuthStorage(), modelsPath);
|
||||
const routedModel = clientRegistry.find("anthropic", "claude-sonnet-4-5");
|
||||
expect(routedModel?.transport).toBe("pi-native");
|
||||
expect(routedModel?.baseUrl).toBe("http://127.0.0.1:18899");
|
||||
// A normal client registry applies the local overrides and installs the
|
||||
// config API keys into AuthStorage.
|
||||
const clientKeys: string[] = [];
|
||||
const clientRegistry = new ModelRegistry(stubAuthStorage(clientKeys), modelsPath);
|
||||
expect(clientRegistry.find("anthropic", "claude-sonnet-4-5")?.baseUrl).toBe("http://127.0.0.1:18899");
|
||||
expect(clientRegistry.getAll().find(model => model.provider === "openai")?.transport).toBe("pi-native");
|
||||
expect(clientKeys).toContain("anthropic");
|
||||
|
||||
const gatewayRegistry = new ModelRegistry(stubAuthStorage(), modelsPath, {
|
||||
ignorePiNativeProviderConfig: true,
|
||||
// The gateway registry ignores models.yml entirely: bundled routing wins,
|
||||
// no config key reaches AuthStorage, and no pi-native self-route survives.
|
||||
const gatewayKeys: string[] = [];
|
||||
const gatewayRegistry = new ModelRegistry(stubAuthStorage(gatewayKeys), modelsPath, {
|
||||
ignoreLocalModelConfig: true,
|
||||
});
|
||||
const gatewayModel = gatewayRegistry.find("anthropic", "claude-sonnet-4-5");
|
||||
const bundledModel = getBundledModels("anthropic").find(model => model.id === "claude-sonnet-4-5");
|
||||
if (!gatewayModel || !bundledModel) throw new Error("expected bundled Anthropic model");
|
||||
|
||||
expect(gatewayModel.transport).toBeUndefined();
|
||||
expect(gatewayModel.baseUrl).toBe(bundledModel.baseUrl);
|
||||
expect(gatewayModel.transport).toBeUndefined();
|
||||
expect(gatewayKeys).toHaveLength(0);
|
||||
expect(gatewayRegistry.getAll().find(model => model.provider === "openai")?.transport).toBeUndefined();
|
||||
expect(indexModelsByRequestId(gatewayRegistry.getAll(), new Set(["anthropic"])).get(gatewayModel.id)).toBe(
|
||||
gatewayModel,
|
||||
);
|
||||
|
||||
@@ -200,15 +200,18 @@ async function runServe(flags: AuthGatewayCommandArgs["flags"]): Promise<void> {
|
||||
// Build the model resolver + catalog from the ModelRegistry — the same
|
||||
// component the TUI/CLI use — scoped to providers we hold credentials for.
|
||||
// `getAll()` is a superset of the bundled catalog (bundled first, then
|
||||
// cached + discovered), so the discovery-only models omp itself reaches
|
||||
// become routable through the gateway instead of freezing on the compiled
|
||||
// snapshot. Format handlers ask `resolveModel` to translate a
|
||||
// client-requested `model` field into a pi-ai `Model<Api>` before dispatch;
|
||||
// cached + broker-discovered), so the discovery-only models omp itself
|
||||
// reaches become routable through the gateway instead of freezing on the
|
||||
// compiled snapshot. `ignoreLocalModelConfig` keeps the host's `models.yml`
|
||||
// out of the picture: client-side provider overrides (baseUrl/apiKey/headers/
|
||||
// transport) and custom models must never route a broker-backed gateway or
|
||||
// shadow broker credentials. Format handlers ask `resolveModel` to translate
|
||||
// a client-requested `model` field into a pi-ai `Model<Api>` before dispatch;
|
||||
// `listModels` powers `/v1/models`.
|
||||
const snapshot = storage.exportSnapshot();
|
||||
const providersWithCreds = new Set<string>();
|
||||
for (const entry of snapshot.credentials) providersWithCreds.add(entry.provider);
|
||||
const registry = new ModelRegistry(storage, undefined, { ignorePiNativeProviderConfig: true });
|
||||
const registry = new ModelRegistry(storage, undefined, { ignoreLocalModelConfig: true });
|
||||
await registry.refresh();
|
||||
let modelById = indexModelsByRequestId(registry.getAll(), providersWithCreds);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user