feat(api): provide Workspace and execution API foundations

This commit is contained in:
huanghuoguoguo
2026-10-29 12:45:02 +08:00
parent 31bcd673e7
commit 7295639423
38 changed files with 8099 additions and 1865 deletions
@@ -0,0 +1,69 @@
"""Application errors preserve business meaning and hide internal failure data."""
import pytest
from langbot_plugin.box.errors import BoxAdmissionError
from pydantic import BaseModel, ValidationError
from langbot.pkg.agent.runner.errors import (
RunnerExecutionError,
RunnerNotAuthorizedError,
RunnerNotFoundError,
RunnerProtocolError,
)
from langbot.pkg.api.application.errors import error_from_exception
from langbot.pkg.api.http.service.bot_errors import BotApplyError
from langbot.pkg.cloud.entitlements import EntitlementUnavailableError
from langbot.pkg.core.errors import TaskCapacityError
from langbot.pkg.provider.modelmgr.codex_errors import CodexProviderError
from langbot.pkg.utils.bounded_executor import BlockingWorkCapacityError
from langbot.pkg.workspace.collaboration import LastOwnerError, MembershipPermissionError
from langbot.pkg.workspace.errors import WorkspaceGenerationMismatchError
@pytest.mark.parametrize(
('exc', 'code', 'status'),
[
(RunnerExecutionError('runner', 'failed', error_code='tool_failed'), 'tool_failed', 422),
(RunnerNotAuthorizedError('runner', []), 'runner_not_authorized', 403),
(RunnerNotFoundError('runner'), 'runner_not_found', 409),
(RunnerProtocolError('runner', 'wrong protocol'), 'runner_protocol_error', 502),
(MembershipPermissionError('denied'), 'permission_denied', 403),
(LastOwnerError('last owner'), 'last_owner_required', 400),
(BoxAdmissionError('no grant'), 'managed_sandbox_unavailable', 403),
(EntitlementUnavailableError('expired'), 'entitlement_unavailable', 403),
(TaskCapacityError('full'), 'task_capacity_exceeded', 429),
(BlockingWorkCapacityError('full'), 'blocking_work_capacity_exceeded', 429),
(CodexProviderError('known safe error', 429, 'rate_limited'), 'rate_limited', 429),
(FileNotFoundError('/private/file'), 'resource_not_found', 404),
(WorkspaceGenerationMismatchError('stale placement'), 'workspace_unavailable', 409),
],
)
def test_business_error_mapping(exc, code, status):
result = error_from_exception(exc)
assert (result.code, result.status_code) == (code, status)
def test_box_entitlement_and_saved_bot_identity_are_preserved():
result = error_from_exception(EntitlementUnavailableError('expired'), operation_id='box.status')
assert (result.code, result.status_code) == ('managed_sandbox_unavailable', 403)
result = error_from_exception(BotApplyError('Saved configuration could not be applied', bot_uuid='bot-a'))
assert (result.code, result.status_code, result.details) == ('bot_apply_failed', 400, {'uuid': 'bot-a'})
def test_unknown_and_pydantic_errors_do_not_expose_raw_configuration():
class CredentialInput(BaseModel):
token: int
with pytest.raises(ValidationError) as failure:
CredentialInput(token='sensitive-value')
assert 'sensitive-value' not in error_from_exception(failure.value).message
assert error_from_exception(RuntimeError('raw sensitive backend error')).message == 'Application operation failed'
def test_typed_assistant_and_migration_errors_keep_original_status():
class ServiceError(ValueError):
code = 'stale_turn'
status = 409
result = error_from_exception(ServiceError('stale_turn'))
assert (result.code, result.status_code) == ('stale_turn', 409)