mirror of
https://github.com/langbot-app/LangBot.git
synced 2026-09-08 18:47:14 +00:00
feat(api): add system capabilities endpoint
This commit is contained in:
@@ -12,6 +12,21 @@ from .....provider.tools.loaders.mcp_policy import stdio_mcp_enabled
|
||||
from .....workspace.invitation_delivery import InvitationDeliveryService
|
||||
|
||||
|
||||
SYSTEM_CAPABILITY_OPERATIONS = (
|
||||
'bot.list',
|
||||
'bot.get',
|
||||
'bot.create',
|
||||
'bot.update',
|
||||
'bot.delete',
|
||||
'pipeline.list',
|
||||
'pipeline.get',
|
||||
'pipeline.create',
|
||||
'pipeline.update',
|
||||
'pipeline.delete',
|
||||
'pipeline.copy',
|
||||
)
|
||||
|
||||
|
||||
@group.group_class('system', '/api/v1/system')
|
||||
class SystemRouterGroup(group.RouterGroup):
|
||||
async def initialize(self) -> None:
|
||||
@@ -26,6 +41,15 @@ class SystemRouterGroup(group.RouterGroup):
|
||||
}
|
||||
)
|
||||
|
||||
@self.route('/capabilities', methods=['GET'], auth_type=group.AuthType.API_KEY)
|
||||
async def _() -> str:
|
||||
return self.success(
|
||||
data={
|
||||
'schema_version': 1,
|
||||
'operations': {operation: {'supported': True} for operation in SYSTEM_CAPABILITY_OPERATIONS},
|
||||
}
|
||||
)
|
||||
|
||||
@self.route('/info', methods=['GET'], auth_type=group.AuthType.NONE)
|
||||
async def _() -> str:
|
||||
# Read wizard_status and wizard_progress from metadata table
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import datetime
|
||||
import json
|
||||
import logging
|
||||
from types import SimpleNamespace
|
||||
@@ -22,6 +23,7 @@ from langbot.pkg.api.http.service.apikey import ApiKeyService
|
||||
from langbot.pkg.api.http.service.user import ControlPlaneDirectoryRequiredError, UserService
|
||||
from langbot.pkg.entity.persistence.base import Base
|
||||
from langbot.pkg.entity.persistence.metadata import WorkspaceMetadata
|
||||
from langbot.pkg.entity.persistence import apikey
|
||||
from langbot.pkg.entity.persistence.user import User
|
||||
from langbot.pkg.entity.persistence.workspace import (
|
||||
Workspace,
|
||||
@@ -462,6 +464,12 @@ async def test_api_key_context_returns_bound_identity_without_workspace_permissi
|
||||
)
|
||||
assert invalid_auth.status_code == 401
|
||||
|
||||
invalid_capabilities = await client.get(
|
||||
'/api/v1/system/capabilities',
|
||||
headers={'X-API-Key': 'lbk_invalid'},
|
||||
)
|
||||
assert invalid_capabilities.status_code == 401
|
||||
|
||||
response = await client.get(
|
||||
'/api/v1/system/context',
|
||||
headers={
|
||||
@@ -478,6 +486,34 @@ async def test_api_key_context_returns_bound_identity_without_workspace_permissi
|
||||
'permissions': [],
|
||||
}
|
||||
|
||||
capabilities_response = await client.get(
|
||||
'/api/v1/system/capabilities',
|
||||
headers={
|
||||
'X-API-Key': created['key'],
|
||||
'X-Workspace-Id': 'caller-selected-workspace-must-be-ignored',
|
||||
},
|
||||
)
|
||||
assert capabilities_response.status_code == 200
|
||||
capabilities = (await capabilities_response.get_json())['data']
|
||||
assert capabilities['schema_version'] == 1
|
||||
assert sorted(capabilities['operations']) == sorted(
|
||||
[
|
||||
'bot.list',
|
||||
'bot.get',
|
||||
'bot.create',
|
||||
'bot.update',
|
||||
'bot.delete',
|
||||
'pipeline.list',
|
||||
'pipeline.get',
|
||||
'pipeline.create',
|
||||
'pipeline.update',
|
||||
'pipeline.delete',
|
||||
'pipeline.copy',
|
||||
]
|
||||
)
|
||||
assert all(item == {'supported': True} for item in capabilities['operations'].values())
|
||||
assert created['key'] not in await capabilities_response.get_data(as_text=True)
|
||||
|
||||
bearer_response = await client.get(
|
||||
'/api/v1/system/context',
|
||||
headers={'Authorization': f'Bearer {created["key"]}'},
|
||||
@@ -491,6 +527,17 @@ async def test_api_key_context_returns_bound_identity_without_workspace_permissi
|
||||
)
|
||||
assert jwt_response.status_code == 401
|
||||
|
||||
await application.persistence_mgr.execute_async(
|
||||
sqlalchemy.update(apikey.ApiKey)
|
||||
.where(apikey.ApiKey.uuid == created['uuid'])
|
||||
.values(expires_at=datetime.datetime.now(datetime.UTC).replace(tzinfo=None) - datetime.timedelta(seconds=1))
|
||||
)
|
||||
expired_capabilities = await client.get(
|
||||
'/api/v1/system/capabilities',
|
||||
headers={'X-API-Key': created['key']},
|
||||
)
|
||||
assert expired_capabilities.status_code == 401
|
||||
|
||||
revoke_response = await client.delete(
|
||||
f'/api/v1/apikeys/{created["id"]}',
|
||||
headers=_auth(owner_token, workspace_uuid),
|
||||
@@ -502,6 +549,11 @@ async def test_api_key_context_returns_bound_identity_without_workspace_permissi
|
||||
headers={'X-API-Key': created['key']},
|
||||
)
|
||||
assert revoked_response.status_code == 401
|
||||
revoked_capabilities = await client.get(
|
||||
'/api/v1/system/capabilities',
|
||||
headers={'X-API-Key': created['key']},
|
||||
)
|
||||
assert revoked_capabilities.status_code == 401
|
||||
|
||||
|
||||
async def test_cloud_projection_is_selected_explicitly_and_collaboration_runs_in_core(
|
||||
|
||||
Reference in New Issue
Block a user