mirror of
https://github.com/langbot-app/LangBot.git
synced 2026-10-23 08:55:44 +08:00
fix(migration): improve runner migration and plugin installation feedback
This commit is contained in:
@@ -23,6 +23,11 @@ from langbot_plugin.api.entities.builtin.pipeline.query import provider_session
|
||||
|
||||
from ..core import app
|
||||
from . import handler
|
||||
from .errors import (
|
||||
PluginRuntimeNotConnectedError,
|
||||
PluginInstallationFailedError,
|
||||
MarketplacePluginVersionNotFoundError,
|
||||
)
|
||||
from .archive import inspect_plugin_archive_metadata
|
||||
from .github import (
|
||||
validate_github_plugin_install_info,
|
||||
@@ -89,8 +94,10 @@ async def _read_httpx_response_limited(
|
||||
response: httpx.Response,
|
||||
*,
|
||||
max_bytes: int,
|
||||
task_context: taskmgr.TaskContext | None = None,
|
||||
) -> bytes:
|
||||
content_length = response.headers.get('content-length')
|
||||
declared_size = None
|
||||
if content_length is not None:
|
||||
try:
|
||||
declared_size = int(content_length)
|
||||
@@ -99,9 +106,16 @@ async def _read_httpx_response_limited(
|
||||
if declared_size is not None and declared_size > max_bytes:
|
||||
raise ValueError(f'Remote response exceeds the {max_bytes}-byte limit')
|
||||
|
||||
started = time.monotonic()
|
||||
if task_context is not None:
|
||||
task_context.metadata.update(download_current=0, download_total=max(declared_size or 0, 0))
|
||||
body = bytearray()
|
||||
async for chunk in response.aiter_bytes(chunk_size=64 * 1024):
|
||||
body.extend(chunk)
|
||||
if task_context is not None:
|
||||
task_context.metadata.update(
|
||||
download_current=len(body), download_speed=len(body) / max(time.monotonic() - started, 0.001)
|
||||
)
|
||||
if len(body) > max_bytes:
|
||||
raise ValueError(f'Remote response exceeds the {max_bytes}-byte limit')
|
||||
return bytes(body)
|
||||
@@ -113,14 +127,25 @@ async def _marketplace_get(
|
||||
*,
|
||||
max_bytes: int,
|
||||
allow_not_found: bool = False,
|
||||
task_context: taskmgr.TaskContext | None = None,
|
||||
) -> tuple[int, bytes]:
|
||||
async with client.stream('GET', url) as response:
|
||||
if allow_not_found and response.status_code == 404:
|
||||
return response.status_code, b''
|
||||
if response.is_error:
|
||||
body = await _read_httpx_response_limited(response, max_bytes=min(max_bytes, 64 * 1024))
|
||||
try:
|
||||
payload = json.loads(body)
|
||||
except (ValueError, UnicodeDecodeError):
|
||||
payload = {}
|
||||
# Space currently returns HTTP 500 for a missing plugin release.
|
||||
if isinstance(payload, dict) and str(payload.get('msg', '')).startswith('plugin version not found:'):
|
||||
raise MarketplacePluginVersionNotFoundError('The requested plugin version is not available')
|
||||
response.raise_for_status()
|
||||
return response.status_code, await _read_httpx_response_limited(
|
||||
response,
|
||||
max_bytes=max_bytes,
|
||||
task_context=task_context,
|
||||
)
|
||||
|
||||
|
||||
@@ -134,25 +159,6 @@ def _decode_json_object(body: bytes, *, subject: str) -> dict[str, Any]:
|
||||
return payload
|
||||
|
||||
|
||||
class PluginRuntimeNotConnectedError(RuntimeError):
|
||||
"""Raised when plugin runtime operations are requested before connection."""
|
||||
|
||||
|
||||
class PluginInstallationFailedError(RuntimeError):
|
||||
"""Stable Runtime desired-state failure for one plugin installation."""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
installation_uuid: str,
|
||||
error_code: str,
|
||||
message: str,
|
||||
) -> None:
|
||||
self.installation_uuid = installation_uuid
|
||||
self.error_code = error_code
|
||||
self.runtime_message = message
|
||||
super().__init__(f'Plugin installation {installation_uuid} failed [{error_code}]: {message}')
|
||||
|
||||
|
||||
class PluginRuntimeConnector(ManagedRuntimeConnector):
|
||||
"""Plugin runtime connector"""
|
||||
|
||||
@@ -1654,6 +1660,7 @@ class PluginRuntimeConnector(ManagedRuntimeConnector):
|
||||
client,
|
||||
f'{space_url}/api/v1/marketplace/plugins/download/{plugin_author}/{plugin_name}/{version}',
|
||||
max_bytes=_MARKETPLACE_PLUGIN_DOWNLOAD_MAX_BYTES,
|
||||
task_context=task_context,
|
||||
)
|
||||
return package, version
|
||||
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
"""Shared plugin operation errors without runtime or application imports."""
|
||||
|
||||
|
||||
class PluginRuntimeNotConnectedError(RuntimeError):
|
||||
"""Raised when plugin runtime operations are requested before connection."""
|
||||
|
||||
|
||||
class PluginInstallationFailedError(RuntimeError):
|
||||
"""Stable Runtime desired-state failure for one plugin installation."""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
installation_uuid: str,
|
||||
error_code: str,
|
||||
message: str,
|
||||
) -> None:
|
||||
self.installation_uuid = installation_uuid
|
||||
self.error_code = error_code
|
||||
self.runtime_message = message
|
||||
super().__init__(f'Plugin installation {installation_uuid} failed [{error_code}]: {message}')
|
||||
|
||||
|
||||
class MarketplacePluginVersionNotFoundError(ValueError):
|
||||
"""The requested release is not available from the marketplace."""
|
||||
Reference in New Issue
Block a user