Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
1b5e6eb
init readme
ishaan-jaff Nov 24, 2025
41c83e7
init BaseSkillsAPIConfig
ishaan-jaff Nov 24, 2025
4baa20e
init types for Skills APIs
ishaan-jaff Nov 24, 2025
341862b
add feat: add create, list, retrieve skills
ishaan-jaff Nov 24, 2025
c6bb974
add base skills config
ishaan-jaff Nov 24, 2025
5aad732
add BaseSkillsAPIConfig
ishaan-jaff Nov 24, 2025
894ddbf
add get_provider_skills_api_config
ishaan-jaff Nov 24, 2025
47a8475
init skills
ishaan-jaff Nov 24, 2025
8c3c91c
add ANTHROPIC_SKILLS_API_BETA_VERSION
ishaan-jaff Nov 24, 2025
eb9381a
init skills APIs
ishaan-jaff Nov 24, 2025
c21b004
working list, get skills
ishaan-jaff Nov 24, 2025
35072ab
working e2e skills API anthropic API
ishaan-jaff Nov 24, 2025
295db31
add _prepare_skill_multipart_request
ishaan-jaff Nov 24, 2025
230e8fd
add skills routes to llm api routes
ishaan-jaff Nov 24, 2025
9116bdb
router _initialize_skills_endpoints
ishaan-jaff Nov 24, 2025
089a838
add fix skills endpoints
ishaan-jaff Nov 24, 2025
906b3ab
add convert_upload_files_to_file_data
ishaan-jaff Nov 24, 2025
a6b84a2
fix routing skills endpoints
ishaan-jaff Nov 24, 2025
efa2101
fix route llm request
ishaan-jaff Nov 24, 2025
bcff2cd
Potential fix for code scanning alert no. 3806: Clear-text logging of…
ishaan-jaff Nov 24, 2025
c9eee15
Potential fix for code scanning alert no. 3809: Clear-text logging of…
ishaan-jaff Nov 24, 2025
21312e7
fix ruff checks
ishaan-jaff Nov 24, 2025
0280fad
test_initialize_skills_endpoints
ishaan-jaff Nov 24, 2025
f25dd08
fix claude skills mypy linting errors
ishaan-jaff Nov 24, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 24 additions & 0 deletions litellm/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -1271,6 +1271,8 @@ def add_known_models():
OpenAIOSeriesConfig as OpenAIO1Config, # maintain backwards compatibility
OpenAIOSeriesConfig,
)
from .llms.anthropic.skills.transformation import AnthropicSkillsConfig
from .llms.base_llm.skills.transformation import BaseSkillsAPIConfig

from .llms.gradient_ai.chat.transformation import GradientAIConfig

Expand Down Expand Up @@ -1367,6 +1369,18 @@ def add_known_models():
from .llms.lemonade.chat.transformation import LemonadeChatConfig
from .llms.snowflake.embedding.transformation import SnowflakeEmbeddingConfig
from .main import * # type: ignore

# Skills API
from .skills.main import (
create_skill,
acreate_skill,
list_skills,
alist_skills,
get_skill,
aget_skill,
delete_skill,
adelete_skill,
)
from .integrations import *
from .llms.custom_httpx.async_client_cleanup import close_litellm_async_clients
from .exceptions import (
Expand Down Expand Up @@ -1404,6 +1418,16 @@ def add_known_models():
from .rerank_api.main import *
from .llms.anthropic.experimental_pass_through.messages.handler import *
from .responses.main import *
from .skills.main import (
create_skill,
acreate_skill,
list_skills,
alist_skills,
get_skill,
aget_skill,
delete_skill,
adelete_skill,
)
from .containers.main import *
from .ocr.main import *
from .search.main import *
Expand Down
1 change: 1 addition & 0 deletions litellm/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -291,6 +291,7 @@

############### LLM Provider Constants ###############
### ANTHROPIC CONSTANTS ###
ANTHROPIC_SKILLS_API_BETA_VERSION = "skills-2025-10-02"
ANTHROPIC_WEB_SEARCH_TOOL_MAX_USES = {
"low": 1,
"medium": 5,
Expand Down
6 changes: 6 additions & 0 deletions litellm/llms/anthropic/skills/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
"""Anthropic Skills API integration"""

from .transformation import AnthropicSkillsConfig

__all__ = ["AnthropicSkillsConfig"]

17 changes: 17 additions & 0 deletions litellm/llms/anthropic/skills/readme.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
# Anthropic Skills API

This folder maintains the integration for the Anthropic Skills API.

You can do the following with the Anthropic Skills API:

1. Create a new skill
2. List all skills
3. Get a skill
4. Delete a skill


Versions:
- Create Skill Version
- List Skill Versions
- Get Skill Version
- Delete Skill Version
211 changes: 211 additions & 0 deletions litellm/llms/anthropic/skills/transformation.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,211 @@
"""
Anthropic Skills API configuration and transformations
"""

from typing import Any, Dict, Optional, Tuple

import httpx

from litellm._logging import verbose_logger
from litellm.llms.base_llm.skills.transformation import (
BaseSkillsAPIConfig,
LiteLLMLoggingObj,
)
from litellm.types.llms.anthropic_skills import (
CreateSkillRequest,
DeleteSkillResponse,
ListSkillsParams,
ListSkillsResponse,
Skill,
)
from litellm.types.router import GenericLiteLLMParams
from litellm.types.utils import LlmProviders


class AnthropicSkillsConfig(BaseSkillsAPIConfig):
"""Anthropic-specific Skills API configuration"""

@property
def custom_llm_provider(self) -> LlmProviders:
return LlmProviders.ANTHROPIC

def validate_environment(
self, headers: dict, litellm_params: Optional[GenericLiteLLMParams]
) -> dict:
"""Add Anthropic-specific headers"""
from litellm.llms.anthropic.common_utils import AnthropicModelInfo

# Get API key
api_key = None
if litellm_params:
api_key = litellm_params.api_key
api_key = AnthropicModelInfo.get_api_key(api_key)

if not api_key:
raise ValueError("ANTHROPIC_API_KEY is required for Skills API")

# Add required headers
headers["x-api-key"] = api_key
headers["anthropic-version"] = "2023-06-01"

# Add beta header for skills API
from litellm.constants import ANTHROPIC_SKILLS_API_BETA_VERSION

if "anthropic-beta" not in headers:
headers["anthropic-beta"] = ANTHROPIC_SKILLS_API_BETA_VERSION
elif isinstance(headers["anthropic-beta"], list):
if ANTHROPIC_SKILLS_API_BETA_VERSION not in headers["anthropic-beta"]:
headers["anthropic-beta"].append(ANTHROPIC_SKILLS_API_BETA_VERSION)
elif isinstance(headers["anthropic-beta"], str):
if ANTHROPIC_SKILLS_API_BETA_VERSION not in headers["anthropic-beta"]:
headers["anthropic-beta"] = [headers["anthropic-beta"], ANTHROPIC_SKILLS_API_BETA_VERSION]

headers["content-type"] = "application/json"

return headers

def get_complete_url(
self,
api_base: Optional[str],
endpoint: str,
skill_id: Optional[str] = None,
) -> str:
"""Get complete URL for Anthropic Skills API"""
from litellm.llms.anthropic.common_utils import AnthropicModelInfo

if api_base is None:
api_base = AnthropicModelInfo.get_api_base()

if skill_id:
return f"{api_base}/v1/skills/{skill_id}?beta=true"
return f"{api_base}/v1/{endpoint}?beta=true"

def transform_create_skill_request(
self,
create_request: CreateSkillRequest,
litellm_params: GenericLiteLLMParams,
headers: dict,
) -> Dict:
"""Transform create skill request for Anthropic"""
verbose_logger.debug(
"Transforming create skill request: %s", create_request
)

# Anthropic expects the request body directly
request_body = {k: v for k, v in create_request.items() if v is not None}

return request_body

def transform_create_skill_response(
self,
raw_response: httpx.Response,
logging_obj: LiteLLMLoggingObj,
) -> Skill:
"""Transform Anthropic response to Skill object"""
response_json = raw_response.json()
verbose_logger.debug(
"Transforming create skill response: %s", response_json
)

return Skill(**response_json)

def transform_list_skills_request(
self,
list_params: ListSkillsParams,
litellm_params: GenericLiteLLMParams,
headers: dict,
) -> Tuple[str, Dict]:
"""Transform list skills request for Anthropic"""
from litellm.llms.anthropic.common_utils import AnthropicModelInfo

api_base = AnthropicModelInfo.get_api_base(
litellm_params.api_base if litellm_params else None
)
url = self.get_complete_url(api_base=api_base, endpoint="skills")

# Build query parameters
query_params: Dict[str, Any] = {}
if "limit" in list_params and list_params["limit"]:
query_params["limit"] = list_params["limit"]
if "page" in list_params and list_params["page"]:
query_params["page"] = list_params["page"]
if "source" in list_params and list_params["source"]:
query_params["source"] = list_params["source"]

verbose_logger.debug(
"List skills request made to Anthropic Skills endpoint with params: %s", query_params
)

return url, query_params

def transform_list_skills_response(
self,
raw_response: httpx.Response,
logging_obj: LiteLLMLoggingObj,
) -> ListSkillsResponse:
"""Transform Anthropic response to ListSkillsResponse"""
response_json = raw_response.json()
verbose_logger.debug(
"Transforming list skills response: %s", response_json
)

return ListSkillsResponse(**response_json)

def transform_get_skill_request(
self,
skill_id: str,
api_base: str,
litellm_params: GenericLiteLLMParams,
headers: dict,
) -> Tuple[str, Dict]:
"""Transform get skill request for Anthropic"""
url = self.get_complete_url(
api_base=api_base, endpoint="skills", skill_id=skill_id
)

verbose_logger.debug("Get skill request - URL: %s", url)

Check failure

Code scanning / CodeQL

Clear-text logging of sensitive information High

This expression logs
sensitive data (secret)
as clear text.
This expression logs
sensitive data (secret)
as clear text.
This expression logs
sensitive data (secret)
as clear text.
This expression logs
sensitive data (secret)
as clear text.
This expression logs
sensitive data (secret)
as clear text.
This expression logs
sensitive data (secret)
as clear text.
This expression logs
sensitive data (secret)
as clear text.
This expression logs
sensitive data (secret)
as clear text.
This expression logs
sensitive data (secret)
as clear text.
This expression logs
sensitive data (secret)
as clear text.
This expression logs
sensitive data (secret)
as clear text.
This expression logs
sensitive data (secret)
as clear text.
This expression logs
sensitive data (secret)
as clear text.
This expression logs
sensitive data (secret)
as clear text.
This expression logs
sensitive data (secret)
as clear text.
This expression logs
sensitive data (secret)
as clear text.
This expression logs
sensitive data (secret)
as clear text.
This expression logs
sensitive data (secret)
as clear text.
This expression logs
sensitive data (secret)
as clear text.
This expression logs
sensitive data (secret)
as clear text.
This expression logs
sensitive data (secret)
as clear text.

Copilot Autofix

AI 8 days ago

We should avoid logging the full URL (which may contain sensitive information from the secret manager) in cleartext.
The best fix is to sanitize the value before logging, such as by redacting the potentially sensitive portion of the URL or by omitting the log statement entirely.
The minimal-impact approach:

  • In transform_get_skill_request, replace the debug statement on line 166 that logs url with a safe log.
    Options:
  • (a) Do not log the URL at all (simplest/safest).
  • (b) Log a sanitized version of the URL, e.g., mask or omit the API base portion.

To preserve the existing functionality and logging context, and assuming the rest of the codebase does not require this log, we will simply remove or redact the sensitive component.

Edit required:

  • In litellm/llms/anthropic/skills/transformation.py, in transform_get_skill_request, replace line 166:
    • Option 1: Remove the logging statement altogether.
    • Option 2: Log only the path/endpoint, not the full url.

Given only the code shown, the safest route is simply removing the debug statement that logs the full URL.

Suggested changeset 1
litellm/llms/anthropic/skills/transformation.py

Autofix patch

Autofix patch
Run the following command in your local git repository to apply this patch
cat << 'EOF' | git apply
diff --git a/litellm/llms/anthropic/skills/transformation.py b/litellm/llms/anthropic/skills/transformation.py
--- a/litellm/llms/anthropic/skills/transformation.py
+++ b/litellm/llms/anthropic/skills/transformation.py
@@ -163,7 +163,7 @@
             api_base=api_base, endpoint="skills", skill_id=skill_id
         )
         
-        verbose_logger.debug("Get skill request - URL: %s", url)
+        # Do not log the full URL to avoid exposing sensitive data
         
         return url, headers
 
EOF
@@ -163,7 +163,7 @@
api_base=api_base, endpoint="skills", skill_id=skill_id
)

verbose_logger.debug("Get skill request - URL: %s", url)
# Do not log the full URL to avoid exposing sensitive data

return url, headers

Copilot is powered by AI and may make mistakes. Always verify output.
Unable to commit as this autofix suggestion is now outdated

return url, headers

def transform_get_skill_response(
self,
raw_response: httpx.Response,
logging_obj: LiteLLMLoggingObj,
) -> Skill:
"""Transform Anthropic response to Skill object"""
response_json = raw_response.json()
verbose_logger.debug(
"Transforming get skill response: %s", response_json
)

return Skill(**response_json)

def transform_delete_skill_request(
self,
skill_id: str,
api_base: str,
litellm_params: GenericLiteLLMParams,
headers: dict,
) -> Tuple[str, Dict]:
"""Transform delete skill request for Anthropic"""
url = self.get_complete_url(
api_base=api_base, endpoint="skills", skill_id=skill_id
)

verbose_logger.debug("Delete skill request - URL: %s", url)

Check failure

Code scanning / CodeQL

Clear-text logging of sensitive information High

This expression logs
sensitive data (secret)
as clear text.
This expression logs
sensitive data (secret)
as clear text.
This expression logs
sensitive data (secret)
as clear text.
This expression logs
sensitive data (secret)
as clear text.
This expression logs
sensitive data (secret)
as clear text.
This expression logs
sensitive data (secret)
as clear text.
This expression logs
sensitive data (secret)
as clear text.
This expression logs
sensitive data (secret)
as clear text.
This expression logs
sensitive data (secret)
as clear text.
This expression logs
sensitive data (secret)
as clear text.
This expression logs
sensitive data (secret)
as clear text.
This expression logs
sensitive data (secret)
as clear text.
This expression logs
sensitive data (secret)
as clear text.
This expression logs
sensitive data (secret)
as clear text.
This expression logs
sensitive data (secret)
as clear text.
This expression logs
sensitive data (secret)
as clear text.
This expression logs
sensitive data (secret)
as clear text.
This expression logs
sensitive data (secret)
as clear text.
This expression logs
sensitive data (secret)
as clear text.
This expression logs
sensitive data (secret)
as clear text.
This expression logs
sensitive data (secret)
as clear text.

Copilot Autofix

AI 8 days ago

To address this vulnerability, we must prevent direct logging of potentially sensitive information, in this case the full API URL including the base. The recommended solution is to remove or sanitize the logging statement, so any output does not reveal the API base (or other secrets), but still allows useful logging (such as endpoint and skill ID).

Best approach:
In transform_delete_skill_request, replace

verbose_logger.debug("Delete skill request - URL: %s", url)

with a log statement that only includes safe metadata (e.g., the endpoint and skill ID, not the API base). For instance:

verbose_logger.debug("Delete skill request - Endpoint: skills, Skill ID: %s", skill_id)

This maintains sufficient context for debugging without risking secret leakage.

Changes needed:

  • Directly edit the affected logging line at line 195 in litellm/llms/anthropic/skills/transformation.py.
  • No imports or new methods are necessary.

Suggested changeset 1
litellm/llms/anthropic/skills/transformation.py

Autofix patch

Autofix patch
Run the following command in your local git repository to apply this patch
cat << 'EOF' | git apply
diff --git a/litellm/llms/anthropic/skills/transformation.py b/litellm/llms/anthropic/skills/transformation.py
--- a/litellm/llms/anthropic/skills/transformation.py
+++ b/litellm/llms/anthropic/skills/transformation.py
@@ -192,7 +192,7 @@
             api_base=api_base, endpoint="skills", skill_id=skill_id
         )
         
-        verbose_logger.debug("Delete skill request - URL: %s", url)
+        verbose_logger.debug("Delete skill request - Endpoint: skills, Skill ID: %s", skill_id)
         
         return url, headers
 
EOF
@@ -192,7 +192,7 @@
api_base=api_base, endpoint="skills", skill_id=skill_id
)

verbose_logger.debug("Delete skill request - URL: %s", url)
verbose_logger.debug("Delete skill request - Endpoint: skills, Skill ID: %s", skill_id)

return url, headers

Copilot is powered by AI and may make mistakes. Always verify output.
Unable to commit as this autofix suggestion is now outdated

return url, headers

def transform_delete_skill_response(
self,
raw_response: httpx.Response,
logging_obj: LiteLLMLoggingObj,
) -> DeleteSkillResponse:
"""Transform Anthropic response to DeleteSkillResponse"""
response_json = raw_response.json()
verbose_logger.debug(
"Transforming delete skill response: %s", response_json
)

return DeleteSkillResponse(**response_json)

6 changes: 6 additions & 0 deletions litellm/llms/base_llm/skills/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
"""Base Skills API configuration"""

from .transformation import BaseSkillsAPIConfig

__all__ = ["BaseSkillsAPIConfig"]

Loading
Loading