mirror of
https://github.com/langbot-app/LangBot.git
synced 2026-07-30 16:16:07 +00:00
Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 8737b818b6 |
@@ -12,6 +12,7 @@ from urllib.parse import quote, unquote, urlparse
|
||||
|
||||
import httpx
|
||||
|
||||
from ....cloud.entitlements import EntitlementFeatureUnavailableError
|
||||
from ....core import app
|
||||
from ....skill.utils import parse_frontmatter
|
||||
from ....utils import httpclient
|
||||
@@ -119,7 +120,13 @@ class SkillService:
|
||||
box_service = self._box_service()
|
||||
if box_service is None:
|
||||
return []
|
||||
return [self._serialize_skill(skill) for skill in await box_service.list_skills(execution_context)]
|
||||
try:
|
||||
skills = await box_service.list_skills(execution_context)
|
||||
except EntitlementFeatureUnavailableError as error:
|
||||
if error.feature == 'managed_sandbox':
|
||||
return []
|
||||
raise
|
||||
return [self._serialize_skill(skill) for skill in skills]
|
||||
|
||||
async def get_skill(self, context: TenantContext, skill_name: str) -> Optional[dict]:
|
||||
execution_context = await self._execution_context(context)
|
||||
|
||||
@@ -17,6 +17,14 @@ class EntitlementUnavailableError(RuntimeError):
|
||||
self.entitlement_revision = entitlement_revision
|
||||
|
||||
|
||||
class EntitlementFeatureUnavailableError(EntitlementUnavailableError):
|
||||
"""Raised when an active entitlement explicitly omits a capability."""
|
||||
|
||||
def __init__(self, message: str, *, feature: str, entitlement_revision: int | None = None) -> None:
|
||||
super().__init__(message, entitlement_revision=entitlement_revision)
|
||||
self.feature = feature
|
||||
|
||||
|
||||
class EntitlementSnapshot(pydantic.BaseModel):
|
||||
"""Capability projection consumed by open-source Core.
|
||||
|
||||
@@ -82,7 +90,11 @@ class EntitlementSnapshot(pydantic.BaseModel):
|
||||
|
||||
def require_feature(self, feature: str) -> None:
|
||||
if self.features.get(feature) is not True:
|
||||
raise EntitlementUnavailableError(f'Workspace entitlement does not grant {feature}')
|
||||
raise EntitlementFeatureUnavailableError(
|
||||
f'Workspace entitlement does not grant {feature}',
|
||||
feature=feature,
|
||||
entitlement_revision=self.entitlement_revision,
|
||||
)
|
||||
|
||||
def limit(self, name: str) -> int:
|
||||
value = self.limits.get(name)
|
||||
|
||||
@@ -8,6 +8,7 @@ import pytest
|
||||
|
||||
from langbot.pkg.api.http.context import ExecutionContext
|
||||
from langbot.pkg.api.http.service.skill import SkillService
|
||||
from langbot.pkg.cloud.entitlements import EntitlementFeatureUnavailableError, EntitlementUnavailableError
|
||||
|
||||
|
||||
_CONTEXT = ExecutionContext(
|
||||
@@ -113,6 +114,42 @@ class TestRequireBoxForWrite:
|
||||
service = SkillService(self._ap_with_disabled_box())
|
||||
assert await service.list_skills(_CONTEXT) == []
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_list_skills_returns_empty_when_managed_sandbox_is_not_granted(self):
|
||||
box_service = SimpleNamespace(
|
||||
available=True,
|
||||
list_skills=AsyncMock(
|
||||
side_effect=EntitlementFeatureUnavailableError(
|
||||
'Workspace entitlement does not grant managed_sandbox',
|
||||
feature='managed_sandbox',
|
||||
)
|
||||
),
|
||||
)
|
||||
service = SkillService(
|
||||
SimpleNamespace(
|
||||
workspace_service=_workspace_service(),
|
||||
box_service=box_service,
|
||||
)
|
||||
)
|
||||
|
||||
assert await service.list_skills(_CONTEXT) == []
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_list_skills_preserves_other_entitlement_failures(self):
|
||||
box_service = SimpleNamespace(
|
||||
available=True,
|
||||
list_skills=AsyncMock(side_effect=EntitlementUnavailableError('control plane unavailable')),
|
||||
)
|
||||
service = SkillService(
|
||||
SimpleNamespace(
|
||||
workspace_service=_workspace_service(),
|
||||
box_service=box_service,
|
||||
)
|
||||
)
|
||||
|
||||
with pytest.raises(EntitlementUnavailableError, match='control plane unavailable'):
|
||||
await service.list_skills(_CONTEXT)
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_read_skill_file_refused_when_box_unavailable(self):
|
||||
service = SkillService(self._ap_with_disabled_box())
|
||||
|
||||
Reference in New Issue
Block a user