diff --git a/extensions/deepinfra/provider-models.proxy.test.ts b/extensions/deepinfra/provider-models.proxy.test.ts new file mode 100644 index 000000000000..9208df38e6a1 --- /dev/null +++ b/extensions/deepinfra/provider-models.proxy.test.ts @@ -0,0 +1,51 @@ +// DeepInfra proxy tests cover the live model discovery transport policy. +import { afterEach, describe, expect, it, vi } from "vitest"; + +const fetchWithSsrFGuardMock = vi.hoisted(() => vi.fn()); + +vi.mock("openclaw/plugin-sdk/ssrf-runtime", async (importOriginal) => ({ + ...(await importOriginal()), + fetchWithSsrFGuard: fetchWithSsrFGuardMock, +})); + +import { discoverDeepInfraModels } from "./provider-models.js"; + +const ORIGINAL_VITEST = process.env.VITEST; +const ORIGINAL_NODE_ENV = process.env.NODE_ENV; + +function restoreEnv(key: "VITEST" | "NODE_ENV", value: string | undefined) { + if (value === undefined) { + delete process.env[key]; + } else { + process.env[key] = value; + } +} + +afterEach(() => { + restoreEnv("VITEST", ORIGINAL_VITEST); + restoreEnv("NODE_ENV", ORIGINAL_NODE_ENV); + fetchWithSsrFGuardMock.mockReset(); + vi.restoreAllMocks(); +}); + +describe("DeepInfra model discovery proxy policy", () => { + it("allows the guarded official catalog request to use an eligible HTTP proxy", async () => { + delete process.env.VITEST; + process.env.NODE_ENV = "development"; + const release = vi.fn(async () => undefined); + fetchWithSsrFGuardMock.mockResolvedValue({ + response: new Response("unavailable", { status: 503 }), + release, + }); + + await discoverDeepInfraModels({ hasApiKey: true }); + + expect(fetchWithSsrFGuardMock).toHaveBeenCalledWith( + expect.objectContaining({ + mode: "trusted_env_proxy", + url: "https://api.deepinfra.com/v1/openai/models?sort_by=openclaw&filter=with_meta", + }), + ); + expect(release).toHaveBeenCalledOnce(); + }); +}); diff --git a/extensions/deepinfra/provider-models.ts b/extensions/deepinfra/provider-models.ts index dec4f1b7d075..0177514d8b6c 100644 --- a/extensions/deepinfra/provider-models.ts +++ b/extensions/deepinfra/provider-models.ts @@ -1,4 +1,6 @@ // Deepinfra provider module implements model/runtime integration. + +import { withTrustedEnvProxyGuardedFetchMode } from "openclaw/plugin-sdk/fetch-runtime"; import { isProviderApiKeyConfigured } from "openclaw/plugin-sdk/provider-auth"; import { getCachedLiveProviderModelRows, @@ -8,6 +10,7 @@ import { buildManifestModelProviderConfig } from "openclaw/plugin-sdk/provider-c import type { ModelDefinitionConfig } from "openclaw/plugin-sdk/provider-model-shared"; import { createSubsystemLogger } from "openclaw/plugin-sdk/runtime-env"; import { hasConfiguredSecretInput } from "openclaw/plugin-sdk/secret-input"; +import { fetchWithSsrFGuard } from "openclaw/plugin-sdk/ssrf-runtime"; import { asPositiveSafeInteger } from "openclaw/plugin-sdk/string-coerce-runtime"; import manifest from "./openclaw.plugin.json" with { type: "json" }; @@ -413,6 +416,7 @@ export async function discoverDeepInfraSurfaces(options?: { buildRequestHeaders: () => ({ Accept: "application/json" }), auditContext: "deepinfra-model-discovery", shouldCacheRows: hasDeepInfraSurfaceModelRows, + fetchGuard: (params) => fetchWithSsrFGuard(withTrustedEnvProxyGuardedFetchMode(params)), }); if (data.length === 0) { log.warn("No models found from DeepInfra agent-projection endpoint, using static catalog");