mirror of
https://github.com/openclaw/openclaw.git
synced 2026-08-02 06:11:37 +00:00
fix(huggingface): discover models when HTTP proxy is required (#110924)
This commit is contained in:
49
extensions/huggingface/models.proxy.test.ts
Normal file
49
extensions/huggingface/models.proxy.test.ts
Normal 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);
|
||||
});
|
||||
});
|
||||
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user