feat: add support for Hugging Face API operations

This change enables full support for Hugging Face API requests, including repository creation, by:
1. Identifying HF API requests via path patterns.
2. Allowing POST, PUT, PATCH, and DELETE methods for these requests.
3. Correctly forwarding request bodies and headers.
4. Skipping caching for API operations.

Fixes: huggingface_hub.errors.HfHubHTTPError: Client error '405 Method Not Allowed'
This commit is contained in:
google-labs-jules[bot] committed 2026-01-06 14:53:29 +00:00
1 parent da89e08f41
commit 893f976ab5
3 files changed
+87 -13

No files matched your search

+18 -9
View File
@@ -11,6 +11,7 @@
import { CONFIG, createConfig } from './config/index.js';
import { SORTED_PLATFORMS, transformPath } from './config/platforms.js';
import { configureAIHeaders, isAIInferenceRequest } from './protocols/ai.js';
import { configureHuggingFaceHeaders, isHuggingFaceAPIRequest } from './protocols/huggingface.js';
import {
fetchToken,
getScopeFromUrl,
@@ -126,7 +127,10 @@ async function handleRequest(request, env, ctx) {
// Check if this is an AI inference request
const isAI = isAIInferenceRequest(request, url);
// Check cache first (skip cache for Git, Git LFS, Docker, and AI inference operations)
// Check if this is a Hugging Face API request
const isHF = isHuggingFaceAPIRequest(request, url);
// Check cache first (skip cache for Git, Git LFS, Docker, AI inference, and HF API operations)
/** @type {Cache | null} */
// @ts-ignore - Cloudflare Workers cache API
const cache =
@@ -134,7 +138,7 @@ async function handleRequest(request, env, ctx) {
? /** @type {any} */ (caches).default // eslint-disable-line jsdoc/reject-any-type
: null;
if (cache && !isGit && !isGitLFS && !isDocker && !isAI) {
if (cache && !isGit && !isGitLFS && !isDocker && !isAI && !isHF) {
try {
// For Range requests, try cache match first
const cacheKey = new Request(targetUrl, {
@@ -177,10 +181,10 @@ async function handleRequest(request, env, ctx) {
redirect: 'follow'
};
// Add body for POST/PUT/PATCH requests (Git/Docker/AI inference operations)
// Add body for POST/PUT/PATCH/DELETE requests (Git/Docker/AI/HF operations)
if (
['POST', 'PUT', 'PATCH'].includes(request.method) &&
(isGit || isGitLFS || isDocker || isAI)
['POST', 'PUT', 'PATCH', 'DELETE'].includes(request.method) &&
(isGit || isGitLFS || isDocker || isAI || isHF)
) {
fetchOptions.body = request.body;
}
@@ -188,9 +192,9 @@ async function handleRequest(request, env, ctx) {
// Cast headers to Headers for proper typing
const requestHeaders = /** @type {Headers} */ (fetchOptions.headers);
// Set appropriate headers for Git/Docker/AI vs regular requests
if (isGit || isGitLFS || isDocker || isAI) {
// For Git/Docker/AI operations, copy all headers from the original request
// Set appropriate headers for Git/Docker/AI/HF vs regular requests
if (isGit || isGitLFS || isDocker || isAI || isHF) {
// For Git/Docker/AI/HF operations, copy all headers from the original request
// This ensures protocol compliance
for (const [key, value] of request.headers.entries()) {
// Skip headers that might cause issues with proxying
@@ -209,6 +213,10 @@ async function handleRequest(request, env, ctx) {
if (isAI) {
configureAIHeaders(requestHeaders, request);
}
if (isHF) {
configureHuggingFaceHeaders(requestHeaders, request);
}
} else {
// Regular file download headers
Object.assign(fetchOptions, {
@@ -617,8 +625,9 @@ async function handleRequest(request, env, ctx) {
const isDocker = isDockerRequest(request, new URL(request.url));
const isAI = isAIInferenceRequest(request, new URL(request.url));
const isGitLFS = isGitLFSRequest(request, new URL(request.url));
const isHF = isHuggingFaceAPIRequest(request, new URL(request.url));
return isGit || isGitLFS || isDocker || isAI
return isGit || isGitLFS || isDocker || isAI || isHF
? response
: addPerformanceHeaders(response, monitor);
}
+63
View File
@@ -0,0 +1,63 @@
/**
* Xget - High-performance acceleration engine for developer resources
* Copyright (C) 2025 Xi Xu
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
/**
* Hugging Face protocol handler for Xget
*/
/**
* Detects if a request is a Hugging Face API operation.
*
* Identifies Hugging Face API requests by checking for:
* - Hugging Face platform prefix (/hf/)
* - API path segment (/api/)
*
* @param {Request} request - The incoming request object
* @param {URL} url - Parsed URL object
* @returns {boolean} True if this is a Hugging Face API operation
*/
export function isHuggingFaceAPIRequest(request, url) {
// Check for Hugging Face API endpoints
if (url.pathname.startsWith('/hf/api/')) {
return true;
}
// Also check for token endpoint which is often used
if (url.pathname.startsWith('/hf/token')) {
return true;
}
return false;
}
/**
* Configures headers for Hugging Face API requests.
*
* @param {Headers} headers - The headers object to modify
* @param {Request} request - The original request
*/
export function configureHuggingFaceHeaders(headers, request) {
// Pass through Authorization header if present
if (request.headers.has('Authorization')) {
headers.set('Authorization', request.headers.get('Authorization'));
}
if (request.method === 'POST' && !headers.has('Content-Type')) {
headers.set('Content-Type', 'application/json');
}
}
+6 -4
View File
@@ -25,6 +25,7 @@ import { CONFIG } from '../config/index.js';
// Imported protocol checks
import { isAIInferenceRequest } from '../protocols/ai.js';
import { isGitLFSRequest, isGitRequest } from '../protocols/git.js';
import { isHuggingFaceAPIRequest } from '../protocols/huggingface.js';
/**
* Detects if a request is a container registry operation (Docker/OCI).
@@ -72,7 +73,7 @@ export function isDockerRequest(request, url) {
}
// Re-export for standard usage
export { isAIInferenceRequest, isGitLFSRequest, isGitRequest };
export { isAIInferenceRequest, isGitLFSRequest, isGitRequest, isHuggingFaceAPIRequest };
/**
* Validates incoming requests against security rules.
@@ -90,15 +91,16 @@ export { isAIInferenceRequest, isGitLFSRequest, isGitRequest };
* @returns {{valid: boolean, error?: string, status?: number}} Validation result object
*/
export function validateRequest(request, url, config = CONFIG) {
// Allow POST method for Git, Git LFS, Docker, and AI inference operations
// Allow POST method for Git, Git LFS, Docker, AI inference, and HF API operations
const isGit = isGitRequest(request, url);
const isGitLFS = isGitLFSRequest(request, url);
const isDocker = isDockerRequest(request, url);
const isAI = isAIInferenceRequest(request, url);
const isHF = isHuggingFaceAPIRequest(request, url);
const allowedMethods =
isGit || isGitLFS || isDocker || isAI
? ['GET', 'HEAD', 'POST', 'PUT', 'PATCH']
isGit || isGitLFS || isDocker || isAI || isHF
? ['GET', 'HEAD', 'POST', 'PUT', 'PATCH', 'DELETE']
: config.SECURITY.ALLOWED_METHODS;
if (!allowedMethods.includes(request.method)) {