fix(huggingface): discover models when HTTP proxy is required (#110924)

This commit is contained in:
mushuiyu886
2026-07-19 04:31:24 +08:00
committed by GitHub
parent b6f905b410
commit 331eb0d439
2 changed files with 64 additions and 12 deletions

View File

@@ -0,0 +1,49 @@
// Hugging Face 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<typeof import("openclaw/plugin-sdk/ssrf-runtime")>()),
fetchWithSsrFGuard: fetchWithSsrFGuardMock,
}));
import { discoverHuggingfaceModels } from "./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("Hugging Face model discovery proxy policy", () => {
it("allows the guarded official catalog request to use an eligible HTTP proxy", async () => {
process.env.VITEST = "false";
process.env.NODE_ENV = "development";
const response = new Response("unavailable", { status: 503 });
const release = vi.fn(async () => undefined);
fetchWithSsrFGuardMock.mockResolvedValue({ response, release });
await discoverHuggingfaceModels("hf_test_token");
expect(fetchWithSsrFGuardMock).toHaveBeenCalledWith(
expect.objectContaining({
mode: "trusted_env_proxy",
url: "https://router.huggingface.co/v1/models",
}),
);
expect(release).toHaveBeenCalledTimes(1);
});
});

View File

@@ -1,4 +1,5 @@
// Huggingface plugin module implements models behavior.
import { withTrustedEnvProxyGuardedFetchMode } from "openclaw/plugin-sdk/fetch-runtime";
import { resolveTimerTimeoutMs } from "openclaw/plugin-sdk/number-runtime";
import { readProviderJsonResponse } from "openclaw/plugin-sdk/provider-http";
import type { ModelDefinitionConfig } from "openclaw/plugin-sdk/provider-model-types";
@@ -139,19 +140,21 @@ export async function discoverHuggingfaceModels(
try {
const requestTimeoutMs = resolveTimerTimeoutMs(timeoutMs, HUGGINGFACE_DISCOVERY_TIMEOUT_MS);
const { response, release } = await fetchWithSsrFGuard({
url: `${HUGGINGFACE_BASE_URL}/models`,
init: {
signal: AbortSignal.timeout(requestTimeoutMs),
headers: {
Authorization: `Bearer ${trimmedKey}`,
"Content-Type": "application/json",
const { response, release } = await fetchWithSsrFGuard(
withTrustedEnvProxyGuardedFetchMode({
url: `${HUGGINGFACE_BASE_URL}/models`,
init: {
signal: AbortSignal.timeout(requestTimeoutMs),
headers: {
Authorization: `Bearer ${trimmedKey}`,
"Content-Type": "application/json",
},
},
},
timeoutMs: requestTimeoutMs,
policy: ssrfPolicyFromHttpBaseUrlAllowedHostname(HUGGINGFACE_BASE_URL),
auditContext: "huggingface-model-discovery",
});
timeoutMs: requestTimeoutMs,
policy: ssrfPolicyFromHttpBaseUrlAllowedHostname(HUGGINGFACE_BASE_URL),
auditContext: "huggingface-model-discovery",
}),
);
try {
if (!response.ok) {
await response.body?.cancel().catch(() => undefined);