-
-
Notifications
You must be signed in to change notification settings - Fork 11.2k
[April 6th] - Ishaan #25238
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[April 6th] - Ishaan #25238
Changes from all commits
c2f486a
8b0fadf
414d396
0afffe4
a65a942
cf9fdff
9e0fb6b
1c238b6
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,111 @@ | ||
| # Skills Gateway | ||
|
|
||
| <iframe width="840" height="500" src="https://www.loom.com/embed/cb74eb79df3e4c2b83a6efae54a589f9" frameborder="0" webkitallowfullscreen mozallowfullscreen allowfullscreen></iframe> | ||
|
|
||
| LiteLLM acts as a **Skills Registry** — a central place to register, manage, and discover Claude Code skills across your organization. Teams can publish skills once and have agents and developers find them through a single hub. | ||
|
|
||
| ## How it works | ||
|
|
||
| ```mermaid | ||
| graph TD | ||
| Dev["👨💻 Developer<br/>registers a skill<br/>(GitHub URL or subdir)"] -->|POST /claude-code/plugins| Proxy["LiteLLM Proxy<br/>(Skills Registry)"] | ||
|
|
||
| Admin["🔑 Admin<br/>publishes skill<br/>(marks as public)"] -->|enable via UI or API| Proxy | ||
|
|
||
| Proxy -->|GET /public/skill_hub| SkillHub["🗂️ Skill Hub<br/>(AI Hub → Skill Hub tab)"] | ||
| Proxy -->|GET /claude-code/marketplace.json| Marketplace["📦 Claude Code<br/>Marketplace endpoint"] | ||
|
|
||
| SkillHub --> Human["🧑 Human<br/>browses & discovers skills<br/>in AI Hub UI"] | ||
| Marketplace --> Agent["🤖 Agent / Claude Code<br/>installs skill with<br/>/plugin marketplace add <name>"] | ||
|
|
||
| style Proxy fill:#1a73e8,color:#fff | ||
| style SkillHub fill:#e8f0fe,color:#1a73e8 | ||
| style Marketplace fill:#e8f0fe,color:#1a73e8 | ||
| ``` | ||
|
|
||
| ## Quick start | ||
|
|
||
| ### 1. Register a skill | ||
|
|
||
| Paste any GitHub URL into the Skills UI — LiteLLM auto-detects the source type and skill name. | ||
|
|
||
| ```bash | ||
| curl -X POST https://your-proxy/claude-code/plugins \ | ||
| -H "Authorization: Bearer $LITELLM_KEY" \ | ||
| -H "Content-Type: application/json" \ | ||
| -d '{ | ||
| "name": "grill-me", | ||
| "source": { | ||
| "source": "git-subdir", | ||
| "url": "https://github.com/mattpocock/skills", | ||
| "path": "grill-me" | ||
| }, | ||
| "description": "Interview skill for relentless questioning", | ||
| "domain": "Productivity", | ||
| "namespace": "interviews" | ||
| }' | ||
| ``` | ||
|
|
||
| Skills nested in subdirectories (e.g. `github.com/org/repo/tree/main/skill-name`) are supported — LiteLLM parses the URL automatically in the UI. | ||
|
|
||
| ### 2. Publish to hub | ||
|
|
||
| In the Admin UI: **AI Hub → Skill Hub → Select Skills to Make Public**. | ||
|
|
||
| Or via API: | ||
|
|
||
| ```bash | ||
| curl -X POST https://your-proxy/claude-code/plugins/grill-me/enable \ | ||
| -H "Authorization: Bearer $LITELLM_KEY" | ||
| ``` | ||
|
|
||
| ### 3. Browse the hub | ||
|
|
||
| Public skills appear at: | ||
| - **Admin UI**: AI Hub → Skill Hub tab | ||
| - **Public page**: `/ui/model_hub` → Skill Hub tab (no login required) | ||
| - **API**: `GET /public/skill_hub` | ||
|
|
||
| ### 4. Install in Claude Code | ||
|
|
||
| Point Claude Code at your proxy marketplace once: | ||
|
|
||
| ```json title="~/.claude/settings.json" | ||
| { | ||
| "extraKnownMarketplaces": { | ||
| "my-org": { | ||
| "source": "url", | ||
| "url": "https://your-proxy/claude-code/marketplace.json" | ||
| } | ||
| } | ||
| } | ||
| ``` | ||
|
|
||
| Then install any skill: | ||
|
|
||
| ``` | ||
| /plugin marketplace add grill-me | ||
| ``` | ||
|
|
||
| ## Skill fields | ||
|
|
||
| | Field | Description | | ||
| |-------|-------------| | ||
| | `name` | Unique skill identifier (used in `/plugin marketplace add`) | | ||
| | `source` | Git source — `github`, `url`, or `git-subdir` | | ||
| | `description` | Short description shown in the hub | | ||
| | `domain` | Category for grouping (e.g. `Engineering`, `Productivity`) | | ||
| | `namespace` | Subcategory within a domain (e.g. `quality`, `meetings`) | | ||
| | `keywords` | Tags for search and filtering | | ||
| | `version` | Semver string | | ||
|
|
||
| ## API reference | ||
|
|
||
| | Endpoint | Auth | Description | | ||
| |----------|------|-------------| | ||
| | `POST /claude-code/plugins` | Required | Register a skill | | ||
| | `GET /claude-code/plugins` | Required | List all skills (admin) | | ||
| | `POST /claude-code/plugins/{name}/enable` | Required | Publish a skill | | ||
| | `POST /claude-code/plugins/{name}/disable` | Required | Unpublish a skill | | ||
| | `GET /public/skill_hub` | None | List public skills | | ||
| | `GET /claude-code/marketplace.json` | None | Claude Code marketplace manifest | |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -11,6 +11,7 @@ | |
| from litellm.constants import ( | ||
| MANAGED_OBJECT_STALENESS_CUTOFF_DAYS, | ||
| MAX_OBJECTS_PER_POLL_CYCLE, | ||
| STALE_OBJECT_CLEANUP_BATCH_SIZE, | ||
| ) | ||
|
|
||
| if TYPE_CHECKING: | ||
|
|
@@ -32,21 +33,49 @@ def __init__( | |
| self.prisma_client: PrismaClient = prisma_client | ||
| self.llm_router: Router = llm_router | ||
|
|
||
| async def _expire_stale_rows( | ||
| self, cutoff: datetime, batch_size: int | ||
| ) -> int: | ||
| """Execute the bounded UPDATE that marks stale rows as 'stale_expired'. | ||
|
|
||
| Isolated so it can be swapped / mocked in tests without touching the | ||
| orchestration logic in ``_cleanup_stale_managed_objects``. | ||
|
|
||
| Uses PostgreSQL syntax (``$1::timestamptz``, ``LIMIT``, double-quoted | ||
| identifiers) which is the only dialect the proxy supports — every | ||
| ``schema.prisma`` in the repo sets ``provider = "postgresql"``. | ||
| Same pattern as ``spend_log_cleanup.py``. | ||
| """ | ||
| return await self.prisma_client.db.execute_raw( | ||
| """ | ||
| UPDATE "LiteLLM_ManagedObjectTable" | ||
| SET "status" = 'stale_expired' | ||
| WHERE "id" IN ( | ||
| SELECT "id" FROM "LiteLLM_ManagedObjectTable" | ||
| WHERE "file_purpose" = 'response' | ||
| AND "status" NOT IN ('completed', 'complete', 'failed', 'expired', 'cancelled', 'stale_expired') | ||
| AND "created_at" < $1::timestamptz | ||
| ORDER BY "created_at" ASC | ||
| LIMIT $2 | ||
| ) | ||
| """, | ||
| cutoff, | ||
| batch_size, | ||
| ) | ||
|
Comment on lines
+36
to
+64
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
The PR's pre-submission checklist shows the test checkbox unchecked, and CLAUDE.md states: "Adding at least 1 test is a hard requirement". The At minimum, a unit test in
Without tests the batch-size guard and the refactored flow cannot be automatically regressed against. |
||
|
|
||
| async def _cleanup_stale_managed_objects(self) -> None: | ||
| """ | ||
| Mark managed objects older than MANAGED_OBJECT_STALENESS_CUTOFF_DAYS days | ||
| in non-terminal states as 'stale_expired'. These will never complete and | ||
| should not be polled. | ||
|
|
||
| Runs as a single DB query with a subquery LIMIT so no rows are loaded | ||
| into Python memory. Processes at most STALE_OBJECT_CLEANUP_BATCH_SIZE | ||
| rows per invocation to avoid overwhelming the DB when there is a large | ||
| backlog. | ||
| """ | ||
| cutoff = datetime.now(timezone.utc) - timedelta(days=MANAGED_OBJECT_STALENESS_CUTOFF_DAYS) | ||
| result = await self.prisma_client.db.litellm_managedobjecttable.update_many( | ||
| where={ | ||
| "file_purpose": "response", | ||
| "status": {"not_in": ["completed", "complete", "failed", "expired", "cancelled", "stale_expired"]}, | ||
| "created_at": {"lt": cutoff}, | ||
| }, | ||
| data={"status": "stale_expired"}, | ||
| ) | ||
| result = await self._expire_stale_rows(cutoff, STALE_OBJECT_CLEANUP_BATCH_SIZE) | ||
| if result > 0: | ||
| verbose_proxy_logger.warning( | ||
| f"CheckResponsesCost: marked {result} stale managed objects " | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,5 @@ | ||
| -- AlterTable: add budget_limits column to LiteLLM_VerificationToken | ||
| ALTER TABLE "LiteLLM_VerificationToken" ADD COLUMN IF NOT EXISTS "budget_limits" JSONB; | ||
|
|
||
| -- AlterTable: add budget_limits column to LiteLLM_TeamTable | ||
| ALTER TABLE "LiteLLM_TeamTable" ADD COLUMN IF NOT EXISTS "budget_limits" JSONB; |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,9 @@ | ||
| -- Add per-member model scope to LiteLLM_BudgetTable | ||
| -- allowed_models: empty array = inherit team models; non-empty = enforce member-level restriction | ||
| ALTER TABLE "LiteLLM_BudgetTable" | ||
| ADD COLUMN IF NOT EXISTS "allowed_models" TEXT[] DEFAULT ARRAY[]::TEXT[]; | ||
|
|
||
| -- Add default_team_member_models to LiteLLM_TeamTable | ||
| -- Seeds allowed_models for newly added team members; empty = no per-member restriction | ||
| ALTER TABLE "LiteLLM_TeamTable" | ||
| ADD COLUMN IF NOT EXISTS "default_team_member_models" TEXT[] DEFAULT ARRAY[]::TEXT[]; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The CLAUDE.md rule says: "Do not write raw SQL for proxy DB operations. Use Prisma model methods instead of
execute_raw/query_raw". A Prisma-native implementation avoids hand-written SQL, keeps the code testable with simple mocks, and removes schema-drift risk — while still bounding the batch:This is two DB round-trips instead of one, but it stays in the ORM layer and matches how the rest of the proxy interacts with the DB. Note:
spend_log_cleanup.pyalso usesexecute_rawas a precedent forDELETE … WHERE … IN (SELECT … LIMIT n)— this is a style nudge rather than a hard blocker, but worth aligning with the stated convention.Context Used: CLAUDE.md (source)
Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!