Skip to content
Closed
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
4 changes: 4 additions & 0 deletions src/providers/model-discovery.ts
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,10 @@ export function providerModelDiscoverySpecError(spec: ProviderModelDiscoverySpec
if (/^[a-z][a-z\d+.-]*:/i.test(path) || path.startsWith("//") || path.includes("?") || path.includes("#")) {
return "discovery path must be a query-free relative/origin path";
}
if (path.includes("\\")) return "discovery path must use forward slashes";
if (path.split("/").some(segment => segment.replace(/%2e/gi, ".") === "..")) {
return "discovery path must not contain parent-directory segments";
}
}
const queryEntries = Object.entries(spec.query ?? {});
if (queryEntries.length > 32) return "discovery query may contain at most 32 entries";
Expand Down
33 changes: 33 additions & 0 deletions tests/helpers/provider-registry-discovery.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import { clearModelCache } from "../../src/codex/model-cache";
import { PROVIDER_REGISTRY, type ProviderModelDiscoverySpec } from "../../src/providers/registry";

interface RegistryDiscoveryOverrides {
preserveCustomDestination?: boolean;
}

/** Temporarily override registry-owned discovery policy with a clean cache before and after. */
export async function withRegistryDiscovery<T>(
providerId: string,
spec: ProviderModelDiscoverySpec,
run: () => Promise<T> | T,
overrides: RegistryDiscoveryOverrides = {},
): Promise<T> {
const entry = PROVIDER_REGISTRY.find(row => row.id === providerId);
if (!entry) throw new Error(`missing ${providerId} registry entry`);
const originalDiscovery = entry.modelDiscovery;
const originalPreserveCustomDestination = entry.preserveCustomDestination;
clearModelCache(providerId);
entry.modelDiscovery = spec;
if (overrides.preserveCustomDestination !== undefined) {
entry.preserveCustomDestination = overrides.preserveCustomDestination;
}
try {
return await run();
} finally {
if (originalDiscovery === undefined) delete entry.modelDiscovery;
else entry.modelDiscovery = originalDiscovery;
if (originalPreserveCustomDestination === undefined) delete entry.preserveCustomDestination;
else entry.preserveCustomDestination = originalPreserveCustomDestination;
clearModelCache(providerId);
Comment thread
coderabbitai[bot] marked this conversation as resolved.
}
}
15 changes: 4 additions & 11 deletions tests/provider-connection-test.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@ import { join } from "node:path";
import { tmpdir } from "node:os";
import { handleManagementAPI } from "../src/server/management-api";
import { saveConfig } from "../src/config";
import { PROVIDER_REGISTRY } from "../src/providers/registry";
import type { OcxConfig } from "../src/types";
import { withRegistryDiscovery } from "./helpers/provider-registry-discovery";

const TEST_DIR = join(tmpdir(), "ocx-conn-test");
const previousHome = process.env.OPENCODEX_HOME;
Expand Down Expand Up @@ -178,13 +178,9 @@ describe("POST /api/providers/test (WP040 connectivity probe)", () => {
});

test("reports only eligible deduplicated models from a registry discovery contract", async () => {
const entry = PROVIDER_REGISTRY.find(row => row.id === "together");
if (!entry) throw new Error("missing together registry entry");
const original = entry.modelDiscovery;
entry.modelDiscovery = {
await withRegistryDiscovery("together", {
filter: { anyOf: [{ path: ["type"], equalsAny: ["chat"] }] },
};
try {
}, async () => {
globalThis.fetch = (async () => Response.json({
data: [
{ id: "chat-model", type: "chat" },
Expand All @@ -202,10 +198,7 @@ describe("POST /api/providers/test (WP040 connectivity probe)", () => {
const { body } = await probe(config, "together");
expect(body.ok).toBe(true);
expect(body.models).toBe(1);
} finally {
if (original === undefined) delete entry.modelDiscovery;
else entry.modelDiscovery = original;
}
});
});

test("Google's models-array response shape is accepted (x-goog-api-key path)", async () => {
Expand Down
56 changes: 24 additions & 32 deletions tests/provider-model-discovery-contract.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { readFileSync } from "node:fs";
import { join } from "node:path";
import { gatherRoutedModels } from "../src/codex/catalog";
import { catalogHintsFromModelsApiItem } from "../src/codex/catalog/provider-fetch";
import { clearModelCache } from "../src/codex/model-cache";
import { clearModelCache, getFreshCached, setCached } from "../src/codex/model-cache";
import { buildModelsRequest } from "../src/oauth";
import { KEY_LOGIN_PROVIDERS, validateApiKey } from "../src/oauth/key-providers";
import { deriveKeyLoginMap, providerConfigSeed } from "../src/providers/derive";
Expand All @@ -17,6 +17,7 @@ import { PROVIDER_REGISTRY, type ProviderModelDiscoverySpec } from "../src/provi
import { routeModel } from "../src/router";
import type { OcxConfig, OcxProviderConfig } from "../src/types";
import { withStubbedProviderFetch } from "./helpers/catalog-provider-fetch";
import { withRegistryDiscovery } from "./helpers/provider-registry-discovery";

const FIXTURE = readFileSync(join(import.meta.dir, "fixtures/provider-model-discovery.json"), "utf8");
const originalFetch = globalThis.fetch;
Expand All @@ -36,37 +37,7 @@ async function withTogetherDiscovery<T>(
spec: ProviderModelDiscoverySpec,
run: () => Promise<T> | T,
): Promise<T> {
const entry = togetherEntry();
const original = entry.modelDiscovery;
const originalPreserveCustomDestination = entry.preserveCustomDestination;
entry.modelDiscovery = spec;
entry.preserveCustomDestination = true;
try {
return await run();
} finally {
if (original === undefined) delete entry.modelDiscovery;
else entry.modelDiscovery = original;
if (originalPreserveCustomDestination === undefined) delete entry.preserveCustomDestination;
else entry.preserveCustomDestination = originalPreserveCustomDestination;
clearModelCache("together");
}
}

async function withRegistryDiscovery<T>(
providerId: string,
spec: ProviderModelDiscoverySpec,
run: () => Promise<T> | T,
): Promise<T> {
const entry = PROVIDER_REGISTRY.find(row => row.id === providerId);
if (!entry) throw new Error(`missing ${providerId} registry entry`);
const original = entry.modelDiscovery;
entry.modelDiscovery = spec;
try {
return await run();
} finally {
if (original === undefined) delete entry.modelDiscovery;
else entry.modelDiscovery = original;
}
return withRegistryDiscovery("together", spec, run, { preserveCustomDestination: true });
}

function togetherConfig(overrides: Partial<OcxProviderConfig> = {}): OcxConfig {
Expand Down Expand Up @@ -96,13 +67,34 @@ describe("registry-owned provider model discovery", () => {
.toContain("https");
expect(providerModelDiscoverySpecError({ path: "models?unbounded=true" }))
.toContain("query-free");
for (const path of [
"../../internal/models",
"models/../internal",
"models/%2e%2e/internal",
"models/.%2E/internal",
"models/%2e./internal",
]) {
expect(providerModelDiscoverySpecError({ path })).toContain("parent-directory");
}
expect(providerModelDiscoverySpecError({ path: String.raw`models\..\internal` }))
.toContain("forward slashes");
expect(providerModelDiscoverySpecError({ path: "models/model..variant" })).toBeNull();
expect(providerModelDiscoverySpecError({
url: "https://api.example.test/models",
path: "models",
} as unknown as ProviderModelDiscoverySpec)).toContain("mutually exclusive");
expect(providerModelDiscoverySpecError({ maxModels: 25 })).toBeNull();
});

test("clears cached rows before applying a temporary registry discovery policy", async () => {
setCached("together", []);
expect(getFreshCached("together", 60_000)).toEqual([]);

await withTogetherDiscovery({ maxModels: 25 }, () => {
expect(getFreshCached("together", 60_000)).toBeNull();
});
});

test("limits collision preservation to fixed API-key destinations", () => {
for (const entry of PROVIDER_REGISTRY) {
if (entry.preserveCustomDestination !== true) continue;
Expand Down
Loading