feat(moonshot): add Files API support to complete end-to-end batch flow

Batch requires uploading a JSONL file first via /v1/files before calling
create_batch. Without Files API support, the complete batch workflow was
broken for Moonshot.

Adds "moonshot" to all five file operation type literals (create, retrieve,
delete, list, content) and a dedicated _get_moonshot_file_credentials()
helper in files/main.py that applies the same api_base security guard as
the batch branches — caller-supplied api_base is only honoured when the
caller also supplies their own api_key, preventing the server-side
MOONSHOT_API_KEY from being forwarded to a caller-controlled endpoint.

All five operations delegate to openai_files_instance since Moonshot's
files API is OpenAI-compatible.
This commit is contained in:
Pratik Khulge 2026-06-29 18:00:47 +05:30
parent e26d70b09a
commit 40f3248f6b
2 changed files with 107 additions and 4 deletions

View file

@ -25,10 +25,13 @@ FileCreateProvider = Literal[
"hosted_vllm",
"manus",
"anthropic",
"moonshot",
]
FileRetrieveProvider = Literal["openai", "azure", "gemini", "vertex_ai", "hosted_vllm", "manus", "anthropic"]
FileDeleteProvider = Literal["openai", "azure", "gemini", "manus", "anthropic"]
FileListProvider = Literal["openai", "azure", "manus", "anthropic"]
FileRetrieveProvider = Literal[
"openai", "azure", "gemini", "vertex_ai", "hosted_vllm", "manus", "anthropic", "moonshot"
]
FileDeleteProvider = Literal["openai", "azure", "gemini", "manus", "anthropic", "moonshot"]
FileListProvider = Literal["openai", "azure", "manus", "anthropic", "moonshot"]
import litellm
from litellm import get_secret_str
from litellm.files.streaming import FileContentStreamingResponse
@ -84,6 +87,34 @@ bedrock_files_instance = BedrockFilesHandler()
#################################################
def _get_moonshot_file_credentials(
caller_api_key: str | None,
caller_api_base: str | None,
) -> tuple[str, str]:
"""Resolve Moonshot api_key and api_base for file operations.
Only trust the caller's api_base when they supply their own api_key.
When falling back to the server-side MOONSHOT_API_KEY the base URL is
resolved from server config so the key is never forwarded to a
caller-controlled endpoint.
"""
default_base = get_secret_str("MOONSHOT_API_BASE") or "https://api.moonshot.ai/v1"
if caller_api_key:
api_key = caller_api_key
api_base = caller_api_base or default_base
else:
server_key = get_secret_str("MOONSHOT_API_KEY")
if not server_key:
raise litellm.exceptions.AuthenticationError(
message="No Moonshot API key found. Pass api_key or set MOONSHOT_API_KEY in the environment.",
model="n/a",
llm_provider="moonshot",
)
api_key = server_key
api_base = default_base
return api_key, api_base
def _add_trusted_model_credentials_to_litellm_params(
litellm_params_dict: Dict[str, Any], kwargs: Dict[str, Any]
) -> None:
@ -227,6 +258,20 @@ def create_file(
organization=openai_creds.organization,
create_file_data=_create_file_request,
)
elif custom_llm_provider == "moonshot":
api_key, api_base = _get_moonshot_file_credentials(
caller_api_key=optional_params.api_key,
caller_api_base=optional_params.api_base,
)
response = openai_files_instance.create_file(
_is_async=_is_async,
api_base=api_base,
api_key=api_key,
timeout=timeout,
max_retries=optional_params.max_retries,
organization=None,
create_file_data=_create_file_request,
)
elif custom_llm_provider == "azure":
azure_creds = get_azure_credentials(
api_base=optional_params.api_base,
@ -350,6 +395,20 @@ def file_retrieve(
max_retries=optional_params.max_retries,
organization=openai_creds.organization,
)
elif custom_llm_provider == "moonshot":
api_key, api_base = _get_moonshot_file_credentials(
caller_api_key=optional_params.api_key,
caller_api_base=optional_params.api_base,
)
response = openai_files_instance.retrieve_file(
file_id=file_id,
_is_async=_is_async,
api_base=api_base,
api_key=api_key,
timeout=timeout,
max_retries=optional_params.max_retries,
organization=None,
)
elif custom_llm_provider == "azure":
azure_creds = get_azure_credentials(
api_base=optional_params.api_base,
@ -533,6 +592,20 @@ def file_delete(
max_retries=optional_params.max_retries,
organization=openai_creds.organization,
)
elif custom_llm_provider == "moonshot":
api_key, api_base = _get_moonshot_file_credentials(
caller_api_key=optional_params.api_key,
caller_api_base=optional_params.api_base,
)
response = openai_files_instance.delete_file(
file_id=file_id,
_is_async=_is_async,
api_base=api_base,
api_key=api_key,
timeout=timeout,
max_retries=optional_params.max_retries,
organization=None,
)
elif custom_llm_provider == "azure":
azure_creds = get_azure_credentials(
api_base=optional_params.api_base,
@ -737,6 +810,20 @@ def file_list(
max_retries=optional_params.max_retries,
organization=openai_creds.organization,
)
elif custom_llm_provider == "moonshot":
api_key, api_base = _get_moonshot_file_credentials(
caller_api_key=optional_params.api_key,
caller_api_base=optional_params.api_base,
)
response = openai_files_instance.list_files(
purpose=purpose,
_is_async=_is_async,
api_base=api_base,
api_key=api_key,
timeout=timeout,
max_retries=optional_params.max_retries,
organization=None,
)
elif custom_llm_provider == "azure":
azure_creds = get_azure_credentials(
api_base=optional_params.api_base,
@ -939,6 +1026,20 @@ def file_content(
max_retries=optional_params.max_retries,
organization=openai_creds.organization,
)
elif custom_llm_provider == "moonshot":
api_key, api_base = _get_moonshot_file_credentials(
caller_api_key=optional_params.api_key,
caller_api_base=optional_params.api_base,
)
response = openai_files_instance.file_content(
_is_async=_is_async,
file_content_request=_file_content_request,
api_base=api_base,
api_key=api_key,
timeout=timeout,
max_retries=optional_params.max_retries,
organization=None,
)
elif custom_llm_provider == "azure":
azure_creds = get_azure_credentials(
api_base=optional_params.api_base,

View file

@ -1,6 +1,8 @@
from typing import AsyncIterator, Dict, Iterator, Literal, NamedTuple, Union
FileContentProvider = Literal["openai", "azure", "vertex_ai", "bedrock", "hosted_vllm", "anthropic", "manus"]
FileContentProvider = Literal[
"openai", "azure", "vertex_ai", "bedrock", "hosted_vllm", "anthropic", "manus", "moonshot"
]
class FileContentStreamingResult(NamedTuple):