-
-
Notifications
You must be signed in to change notification settings - Fork 2.4k
feat: add Shipyard Neo file persistence option #8827
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -6,6 +6,7 @@ | |
| from typing import Any, cast | ||
|
|
||
| from astrbot.api import logger | ||
| from astrbot.core.db import BaseDatabase | ||
|
|
||
| from ..olayer import ( | ||
| BrowserComponent, | ||
|
|
@@ -20,6 +21,7 @@ | |
| try: | ||
| from shipyard_neo import BayClient | ||
| from shipyard_neo.sandbox import Sandbox | ||
| from shipyard_neo.errors import NotFoundError, BayError | ||
| except ImportError: | ||
| logger.warning( | ||
| "shipyard_neo_sdk is not installed. ShipyardNeoBooter will not work without it." | ||
|
|
@@ -354,12 +356,16 @@ def __init__( | |
| endpoint_url: str, | ||
| access_token: str, | ||
| profile: str = "", | ||
| persist_id: str | None = None, | ||
| ttl: int = 3600, | ||
| db_helper: BaseDatabase | None = None, | ||
| ) -> None: | ||
| self._endpoint_url = endpoint_url | ||
| self._access_token = access_token | ||
| self._profile = profile.strip() if profile else "" | ||
| self._persist_id = persist_id | ||
| self._ttl = ttl | ||
| self._db_helper = db_helper | ||
| self._client: BayClient | None = None | ||
| self._sandbox: Sandbox | None = None | ||
| self._bay_manager: Any = None # BayContainerManager when auto-started | ||
|
|
@@ -430,6 +436,8 @@ async def boot(self, session_id: str) -> None: | |
| access_token=self._access_token, | ||
| ) | ||
| await self._client.__aenter__() | ||
|
|
||
| cargo_id = await self._resolve_cargo_id() | ||
|
|
||
| # Resolve profile: user-specified > smart selection > default. | ||
| # An empty profile means auto-select; any non-empty profile must be | ||
|
|
@@ -439,6 +447,7 @@ async def boot(self, session_id: str) -> None: | |
| self._sandbox = await self._client.create_sandbox( | ||
| profile=resolved_profile, | ||
| ttl=self._ttl, | ||
| cargo_id=cargo_id, | ||
| ) | ||
|
|
||
| # --- Readiness gate: wait until sandbox session is READY --- | ||
|
|
@@ -587,6 +596,64 @@ def _score(p: Any) -> tuple[int, int]: | |
| ) | ||
|
|
||
| return chosen | ||
|
|
||
| async def _resolve_cargo_id(self) -> str | None: | ||
| if self._persist_id is None: | ||
| return None | ||
|
|
||
| if self._db_helper is None: | ||
| logger.warning( | ||
| "[Computer] persist_id is set but no db_helper provided; " | ||
| "file persistence will not work." | ||
| ) | ||
| return None | ||
|
|
||
| # Check if cargo with the persist_id already exists | ||
| ret = await self._db_helper.get_shipyard_neo_persist(self._persist_id) | ||
| cargo_id: str | None = None | ||
| if ret is not None: | ||
| cargo_id = ret.cargo_id | ||
|
|
||
| if cargo_id is not None: | ||
| # Detect if the cargo exists on Bay | ||
| try: | ||
| await self._client.cargos.get(cargo_id) | ||
| except NotFoundError: | ||
| logger.info( | ||
| "[Computer] No existing cargo found on Bay for persist_id=%s; " | ||
| "a new cargo will be created.", | ||
| self._persist_id, | ||
| ) | ||
| cargo_id = None | ||
|
Comment on lines
+621
to
+627
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. suggestion (bug_risk): Consider cleaning up stale DB mappings when the cargo no longer exists on Bay. In the Suggested implementation: # Detect if the cargo exists on Bay
try:
await self._client.cargos.get(cargo_id)
except NotFoundError:
logger.info(
"[Computer] No existing cargo found on Bay for persist_id=%s; "
"the stale DB mapping will be removed and a new cargo will be created.",
self._persist_id,
)
if self._db_helper is not None:
await self._db_helper.delete_shipyard_neo_persist(self._persist_id)
cargo_id = None
except Exception as e:
logger.warning(If
|
||
|
|
||
| if cargo_id is None: | ||
| # Create a new cargo and save the mapping | ||
| try: | ||
| try: | ||
| cargo = await self._client.cargos.create() | ||
| cargo_id = cargo.id | ||
| except BayError as e: | ||
| # 旧版 SDK 需要传入 size_limit_mb,暂时固定为 10GB | ||
| cargo = await self._client.cargos.create(size_limit_mb=10240) | ||
| cargo_id = cargo.id | ||
|
|
||
| if cargo_id is not None: | ||
| await self._db_helper.upsert_shipyard_neo_persist(self._persist_id, cargo_id) | ||
| logger.info( | ||
| "[Computer] Created new cargo for persist_id=%s: cargo_id=%s", | ||
| self._persist_id, | ||
| cargo_id, | ||
| ) | ||
| except Exception as e: | ||
| logger.error( | ||
| "[Computer] Failed to create cargo for persist_id=%s: %s; " | ||
| "file persistence will not work.", | ||
| self._persist_id, | ||
| e, | ||
| ) | ||
| cargo_id = None | ||
|
|
||
| return cargo_id | ||
|
|
||
| async def shutdown(self, *, delete_sandbox: bool = False) -> None: | ||
| if self._client is not None: | ||
|
|
||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -25,6 +25,7 @@ | |||||||||||||||||||||||||||||||||||||||||
| Preference, | ||||||||||||||||||||||||||||||||||||||||||
| ProviderStat, | ||||||||||||||||||||||||||||||||||||||||||
| SessionProjectRelation, | ||||||||||||||||||||||||||||||||||||||||||
| ShipyardNeoPersist, | ||||||||||||||||||||||||||||||||||||||||||
| SQLModel, | ||||||||||||||||||||||||||||||||||||||||||
| UmoAlias, | ||||||||||||||||||||||||||||||||||||||||||
| WebChatThread, | ||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -1867,6 +1868,56 @@ async def get_umo_aliases(self, umos: list[str] | None = None) -> list[UmoAlias] | |||||||||||||||||||||||||||||||||||||||||
| result = await session.execute(query) | ||||||||||||||||||||||||||||||||||||||||||
| return list(result.scalars().all()) | ||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||
| # ==== | ||||||||||||||||||||||||||||||||||||||||||
| # Shipyard Neo Persist Management | ||||||||||||||||||||||||||||||||||||||||||
| # ==== | ||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||
| async def upsert_shipyard_neo_persist(self, persist_id: str, cargo_id: str) -> ShipyardNeoPersist: | ||||||||||||||||||||||||||||||||||||||||||
| """Create or update the persistent mapping for a Shipyard Neo cargo.""" | ||||||||||||||||||||||||||||||||||||||||||
| async with self.get_db() as session: | ||||||||||||||||||||||||||||||||||||||||||
| session: AsyncSession | ||||||||||||||||||||||||||||||||||||||||||
| async with session.begin(): | ||||||||||||||||||||||||||||||||||||||||||
| result = await session.execute( | ||||||||||||||||||||||||||||||||||||||||||
| select(ShipyardNeoPersist).where( | ||||||||||||||||||||||||||||||||||||||||||
| col(ShipyardNeoPersist.persist_id) == persist_id, | ||||||||||||||||||||||||||||||||||||||||||
| ), | ||||||||||||||||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||||||||||||||||
| persist = result.scalar_one_or_none() | ||||||||||||||||||||||||||||||||||||||||||
| if persist: | ||||||||||||||||||||||||||||||||||||||||||
| persist.cargo_id = cargo_id | ||||||||||||||||||||||||||||||||||||||||||
| persist.updated_at = datetime.now(timezone.utc) | ||||||||||||||||||||||||||||||||||||||||||
| else: | ||||||||||||||||||||||||||||||||||||||||||
| persist = ShipyardNeoPersist( | ||||||||||||||||||||||||||||||||||||||||||
| persist_id=persist_id, | ||||||||||||||||||||||||||||||||||||||||||
| cargo_id=cargo_id, | ||||||||||||||||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||||||||||||||||
| session.add(persist) | ||||||||||||||||||||||||||||||||||||||||||
| await session.flush() | ||||||||||||||||||||||||||||||||||||||||||
| await session.refresh(persist) | ||||||||||||||||||||||||||||||||||||||||||
| return persist | ||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||
| async def get_shipyard_neo_persist(self, persist_id: str) -> ShipyardNeoPersist | None: | ||||||||||||||||||||||||||||||||||||||||||
| """Get the persistent mapping for a Shipyard Neo cargo.""" | ||||||||||||||||||||||||||||||||||||||||||
| async with self.get_db() as session: | ||||||||||||||||||||||||||||||||||||||||||
| session: AsyncSession | ||||||||||||||||||||||||||||||||||||||||||
| result = await session.execute( | ||||||||||||||||||||||||||||||||||||||||||
| select(ShipyardNeoPersist).where( | ||||||||||||||||||||||||||||||||||||||||||
| col(ShipyardNeoPersist.persist_id) == persist_id, | ||||||||||||||||||||||||||||||||||||||||||
| ), | ||||||||||||||||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||||||||||||||||
| return result.scalar_one_or_none() | ||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||
| async def delete_shipyard_neo_persist(self, persist_id: str) -> None: | ||||||||||||||||||||||||||||||||||||||||||
| """Delete the persistent mapping for a Shipyard Neo cargo.""" | ||||||||||||||||||||||||||||||||||||||||||
| async with self.get_db() as session: | ||||||||||||||||||||||||||||||||||||||||||
| session: AsyncSession | ||||||||||||||||||||||||||||||||||||||||||
| await session.execute( | ||||||||||||||||||||||||||||||||||||||||||
| delete(ShipyardNeoPersist).where( | ||||||||||||||||||||||||||||||||||||||||||
| col(ShipyardNeoPersist.persist_id) == persist_id, | ||||||||||||||||||||||||||||||||||||||||||
| ), | ||||||||||||||||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||||||||||||||||
| await session.commit() | ||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+1910
to
+1919
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||
| # ==== | ||||||||||||||||||||||||||||||||||||||||||
| # ChatUI Project Management | ||||||||||||||||||||||||||||||||||||||||||
| # ==== | ||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
issue (complexity): Consider refactoring
_resolve_cargo_idinto a short orchestration method that delegates DB lookups, Bay existence checks, cargo creation, and mapping persistence to focused helpers to reduce branching and nesting.You can keep all the new behavior but simplify
_resolve_cargo_idby:_resolve_cargo_ida short orchestration function with minimal branching.For example:
Then keep each responsibility in a small helper:
This keeps all existing behavior (persistence, compatibility fallback, logging) but:
_resolve_cargo_ideasy to read and test.