fix(claude-code): echo installation_preference on the public skill hub
Some checks failed
LiteLLM Rust / rust-lint (push) Has been cancelled
LiteLLM Rust / rust-test (push) Has been cancelled
LiteLLM Rust / rust-wheel (push) Has been cancelled
Terraform Modules / fmt, validate, test (aws) (push) Has been cancelled
Terraform Modules / fmt, validate, test (gcp) (push) Has been cancelled
Terraform Provider / gofmt, vet, build, test (push) Has been cancelled
Terraform Provider / Provider endpoints vs proxy OpenAPI schema (push) Has been cancelled

This commit is contained in:
mateo-berri 2026-09-29 09:35:11 -07:00
parent d51f84cc9c
commit 4caf39425d
2 changed files with 44 additions and 0 deletions

View file

@ -322,6 +322,7 @@ async def get_mcp_servers():
async def public_skill_hub():
"""Return enabled (public) Claude Code skills — no auth required."""
from litellm.proxy.anthropic_endpoints.claude_code_endpoints.claude_code_marketplace import (
_get_installation_preference,
_get_prisma_client,
)
from litellm.types.proxy.claude_code_endpoints import (
@ -352,6 +353,7 @@ async def public_skill_hub():
homepage=manifest.get("homepage"),
domain=manifest.get("domain"),
namespace=manifest.get("namespace"),
installation_preference=_get_installation_preference(manifest),
)
)
return ListPluginsResponse(plugins=items, count=len(items))

View file

@ -1,6 +1,8 @@
import asyncio
import json
import re
from datetime import datetime, timezone
from types import SimpleNamespace
from typing import Final
from unittest.mock import AsyncMock, MagicMock, patch
@ -1469,3 +1471,43 @@ async def test_fetch_remote_autorouter_presets_parses_and_rejects_empty(monkeypa
response.json = MagicMock(return_value={})
with pytest.raises(ValueError, match="empty"):
await _fetch_remote_autorouter_presets("https://example.test/presets.json")
@pytest.mark.parametrize("preference", ["auto_install", None])
def test_public_skill_hub_echoes_registered_installation_preference(preference):
from litellm.proxy.anthropic_endpoints.claude_code_endpoints.claude_code_marketplace import register_plugin
from litellm.proxy._types import LitellmUserRoles, UserAPIKeyAuth
from litellm.types.proxy.claude_code_endpoints import RegisterPluginRequest
table: Final = MagicMock()
table.find_unique = AsyncMock(return_value=None)
table.create = AsyncMock(side_effect=lambda data: SimpleNamespace(id="plugin-id", **data))
table.find_many = AsyncMock(
side_effect=lambda where: [SimpleNamespace(id="plugin-id", **table.create.call_args.kwargs["data"])]
)
prisma: Final = MagicMock()
prisma.db.litellm_claudecodeplugintable = table
app: Final = FastAPI()
app.include_router(router)
with patch("litellm.proxy.proxy_server.prisma_client", prisma):
asyncio.run(
register_plugin(
request=RegisterPluginRequest(
name="security-review",
source={
"source": "archive",
"url": "https://plugins.example.com/security-review-1.0.0.zip",
"sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
},
installation_preference=preference,
),
user_api_key_dict=UserAPIKeyAuth(user_role=LitellmUserRoles.PROXY_ADMIN, user_id="admin"),
)
)
response: Final = TestClient(app).get("/public/skill_hub")
assert response.status_code == 200
assert [(p["name"], p["installation_preference"]) for p in response.json()["plugins"]] == [
("security-review", preference)
]