mirror of
https://github.com/langbot-app/LangBot.git
synced 2026-09-09 19:47:15 +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
|
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')
|
@group.group_class('system', '/api/v1/system')
|
||||||
class SystemRouterGroup(group.RouterGroup):
|
class SystemRouterGroup(group.RouterGroup):
|
||||||
async def initialize(self) -> None:
|
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)
|
@self.route('/info', methods=['GET'], auth_type=group.AuthType.NONE)
|
||||||
async def _() -> str:
|
async def _() -> str:
|
||||||
# Read wizard_status and wizard_progress from metadata table
|
# Read wizard_status and wizard_progress from metadata table
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import datetime
|
||||||
import json
|
import json
|
||||||
import logging
|
import logging
|
||||||
from types import SimpleNamespace
|
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.api.http.service.user import ControlPlaneDirectoryRequiredError, UserService
|
||||||
from langbot.pkg.entity.persistence.base import Base
|
from langbot.pkg.entity.persistence.base import Base
|
||||||
from langbot.pkg.entity.persistence.metadata import WorkspaceMetadata
|
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.user import User
|
||||||
from langbot.pkg.entity.persistence.workspace import (
|
from langbot.pkg.entity.persistence.workspace import (
|
||||||
Workspace,
|
Workspace,
|
||||||
@@ -462,6 +464,12 @@ async def test_api_key_context_returns_bound_identity_without_workspace_permissi
|
|||||||
)
|
)
|
||||||
assert invalid_auth.status_code == 401
|
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(
|
response = await client.get(
|
||||||
'/api/v1/system/context',
|
'/api/v1/system/context',
|
||||||
headers={
|
headers={
|
||||||
@@ -478,6 +486,34 @@ async def test_api_key_context_returns_bound_identity_without_workspace_permissi
|
|||||||
'permissions': [],
|
'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(
|
bearer_response = await client.get(
|
||||||
'/api/v1/system/context',
|
'/api/v1/system/context',
|
||||||
headers={'Authorization': f'Bearer {created["key"]}'},
|
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
|
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(
|
revoke_response = await client.delete(
|
||||||
f'/api/v1/apikeys/{created["id"]}',
|
f'/api/v1/apikeys/{created["id"]}',
|
||||||
headers=_auth(owner_token, workspace_uuid),
|
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']},
|
headers={'X-API-Key': created['key']},
|
||||||
)
|
)
|
||||||
assert revoked_response.status_code == 401
|
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(
|
async def test_cloud_projection_is_selected_explicitly_and_collaboration_runs_in_core(
|
||||||
|
|||||||
Reference in New Issue
Block a user