-
-
Notifications
You must be signed in to change notification settings - Fork 3
Feat/agentspaces #205
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
Feat/agentspaces #205
Conversation
echarles
left a comment
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.
LGTM
❌ Deploy Preview for datalayer-core failed.
|
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.
Pull request overview
This pull request introduces the "Agent Spaces" feature to the Datalayer platform, along with branding updates and removal of deprecated code.
Changes:
- Adds comprehensive Agent Spaces functionality with TypeScript hooks and Python client methods for creating, managing, and listing agent spaces
- Adds Agent Runtimes hooks for managing runtime environments with AI agents
- Updates branding symbol from Ξ to ☰ across documentation and UI
- Removes deprecated useCache0.ts hook (3,848 lines)
- Improves time remaining display in ConsumptionBar component with human-friendly formatting
Reviewed changes
Copilot reviewed 13 out of 14 changed files in this pull request and generated 7 comments.
Show a summary per file
| File | Description |
|---|---|
| src/api/spacer/agentSpaces.ts | New API module defining types and functions for agent space operations |
| src/api/spacer/index.ts | Exports new agentSpaces module |
| src/hooks/useCache.ts | Adds React Query hooks for agent spaces and agent runtimes with caching |
| src/hooks/useCache0.ts | Removes deprecated cache hooks (entire file deleted) |
| datalayer_core/mixins/agent_spaces.py | New Python mixin providing agent space CRUD operations |
| datalayer_core/mixins/init.py | Exports AgentSpacesMixin and other mixins |
| datalayer_core/client/client.py | Inherits AgentSpacesMixin to expose agent space methods |
| src/components/progress/ConsumptionBar.tsx | Adds formatTimeRemaining() function for better UX |
| src/stories/Cell.stories.tsx | Updates branding symbol Ξ → ☰ |
| src/state/substates/CoreState.ts | Updates branding symbol Ξ → ☰ |
| README.md | Updates branding symbol Ξ → ☰ |
| dev/notebooks/**/README.md | Updates branding symbol Ξ → ☰ in multiple files |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| response = self._fetch( # type: ignore | ||
| "{}/api/spacer/v1/agent-spaces".format(self.urls.spacer_url), # type: ignore | ||
| method="POST", | ||
| body=body, |
Copilot
AI
Feb 5, 2026
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.
Inconsistent parameter name for request body. Other mixins in the codebase use json=body for the _fetch method (see datalayer_core/mixins/runtimes.py:105, datalayer_core/mixins/secrets.py:49), but this code uses body=body. This should be changed to json=body for consistency.
| body=body, | |
| json=body, |
| response = self._fetch( # type: ignore | ||
| "{}/api/spacer/v1/agent-spaces/{}".format(self.urls.spacer_url, uid), # type: ignore | ||
| method="PUT", | ||
| body=body, |
Copilot
AI
Feb 5, 2026
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.
Inconsistent parameter name for request body. Other mixins in the codebase use json=body for the _fetch method (see datalayer_core/mixins/runtimes.py:105, datalayer_core/mixins/secrets.py:49), but this code uses body=body. This should be changed to json=body for consistency.
| body=body, | |
| json=body, |
| type AgentRuntimeData = { | ||
| pod_name: string; | ||
| id: string; | ||
| environment_name: string; | ||
| environment_title?: string; | ||
| given_name: string; | ||
| phase?: string; | ||
| type: string; | ||
| started_at?: string; | ||
| expired_at?: string; | ||
| burning_rate?: number; | ||
| status: 'starting' | 'running' | 'paused' | 'terminated' | 'archived'; | ||
| // Backend returns 'ingress', we map it to 'url' for UI consistency | ||
| ingress?: string; | ||
| url?: string; | ||
| token?: string; | ||
| // Agent specification with chat suggestions | ||
| agentSpec?: AgentSpaceData['agentSpec']; | ||
| }; |
Copilot
AI
Feb 5, 2026
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.
The AgentRuntimeData type is defined inline within the useCache hook. For better maintainability and reusability, this type should be extracted to a separate type definition file (e.g., in src/api/runtimes/ or src/models/) and exported, similar to how AgentSpaceData is defined in src/api/spacer/agentSpaces.ts.
| // Filter to only include ai-agents-env runtimes | ||
| const agentRuntimes = (resp.runtimes as AgentRuntimeData[]) | ||
| .filter( | ||
| (rt: AgentRuntimeData) => rt.environment_name === 'ai-agents-env', |
Copilot
AI
Feb 5, 2026
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.
The environment name filter 'ai-agents-env' is hardcoded and duplicated across multiple hooks (useAgentRuntimes at line 1931, and implicitly in useCreateAgentRuntime at line 2038). Consider extracting this to a constant at the module level or in a constants file to avoid duplication and make it easier to maintain. For example: const AI_AGENTS_ENVIRONMENT = 'ai-agents-env';
| status: | ||
| rt.phase === 'Pending' | ||
| ? ('starting' as const) | ||
| : rt.phase === 'Terminated' | ||
| ? ('terminated' as const) | ||
| : rt.phase === 'Paused' | ||
| ? ('paused' as const) | ||
| : rt.phase === 'Archived' | ||
| ? ('archived' as const) | ||
| : ('running' as const), |
Copilot
AI
Feb 5, 2026
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.
The phase-to-status mapping logic is duplicated across three different hooks (useAgentRuntimes lines 1937-1946, useAgentRuntime lines 1990-1999, and useCreateAgentRuntime lines 2054-2057). This should be extracted into a helper function to avoid code duplication and ensure consistency. For example: const mapPhaseToStatus = (phase?: string): AgentSpaceStatus => { ... }
| type CreateAgentRuntimeRequest = { | ||
| environmentName?: string; | ||
| givenName?: string; | ||
| creditsLimit?: number; | ||
| type?: string; | ||
| editorVariant?: string; // 'none', 'notebook', or 'document' | ||
| }; |
Copilot
AI
Feb 5, 2026
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.
The CreateAgentRuntimeRequest type is defined inline within the useCache hook. For better maintainability and reusability, this type should be extracted to a separate type definition file (similar to how CreateAgentSpaceRequest is defined in src/api/spacer/agentSpaces.ts). Consider creating src/api/runtimes/agentRuntimes.ts or similar.
| def list_agent_spaces(self) -> list[dict[str, Any]]: | ||
| """ | ||
| List agent spaces for the current user. | ||
|
|
||
| Returns | ||
| ------- | ||
| list[dict[str, Any]] | ||
| List of agent spaces. | ||
| """ | ||
| response = self._fetch( # type: ignore | ||
| "{}/api/spacer/v1/agent-spaces".format(self.urls.spacer_url), # type: ignore | ||
| method="GET", | ||
| ) | ||
|
|
||
| if response.status_code != 200: | ||
| logger.error(f"Failed to list agent spaces: HTTP {response.status_code}") | ||
| return [] | ||
|
|
||
| data = response.json() | ||
| if data.get("success"): | ||
| return data.get("agentSpaces", []) | ||
| return [] | ||
|
|
||
| def list_public_agent_spaces(self) -> list[dict[str, Any]]: | ||
| """ | ||
| List all public agent spaces (Library). | ||
|
|
||
| Returns | ||
| ------- | ||
| list[dict[str, Any]] | ||
| List of public agent spaces. | ||
| """ | ||
| response = self._fetch( # type: ignore | ||
| "{}/api/spacer/v1/agent-spaces/public".format(self.urls.spacer_url), # type: ignore | ||
| method="GET", | ||
| ) | ||
|
|
||
| if response.status_code != 200: | ||
| logger.error(f"Failed to list public agent spaces: HTTP {response.status_code}") | ||
| return [] | ||
|
|
||
| data = response.json() | ||
| if data.get("success"): | ||
| return data.get("agentSpaces", []) | ||
| return [] | ||
|
|
||
| def get_agent_space(self, uid: str) -> Optional[dict[str, Any]]: | ||
| """ | ||
| Get an agent space by UID. | ||
|
|
||
| Parameters | ||
| ---------- | ||
| uid : str | ||
| The agent space UID. | ||
|
|
||
| Returns | ||
| ------- | ||
| Optional[dict[str, Any]] | ||
| The agent space data, or None if not found. | ||
| """ | ||
| response = self._fetch( # type: ignore | ||
| "{}/api/spacer/v1/agent-spaces/{}".format(self.urls.spacer_url, uid), # type: ignore | ||
| method="GET", | ||
| ) | ||
|
|
||
| if response.status_code == 404: | ||
| return None | ||
|
|
||
| if response.status_code != 200: | ||
| logger.error(f"Failed to get agent space: HTTP {response.status_code}") | ||
| return None | ||
|
|
||
| data = response.json() | ||
| if data.get("success"): | ||
| return data.get("agentSpace") | ||
| return None | ||
|
|
||
| def create_agent_space( | ||
| self, | ||
| name: str, | ||
| space_id: str, | ||
| description: str = "", | ||
| tags: Optional[list[str]] = None, | ||
| status: str = "paused", | ||
| is_public: bool = False, | ||
| agent_spec: Optional[dict[str, Any]] = None, | ||
| thumbnail: Optional[str] = None, | ||
| ) -> Optional[dict[str, Any]]: | ||
| """ | ||
| Create a new agent space. | ||
|
|
||
| Parameters | ||
| ---------- | ||
| name : str | ||
| Name of the agent space. | ||
| space_id : str | ||
| Parent space ID. | ||
| description : str | ||
| Description of the agent space. | ||
| tags : Optional[list[str]] | ||
| Tags for categorization. | ||
| status : str | ||
| Initial status (default: 'paused'). | ||
| is_public : bool | ||
| Whether publicly visible (default: False). | ||
| agent_spec : Optional[dict[str, Any]] | ||
| Agent specification. | ||
| thumbnail : Optional[str] | ||
| Thumbnail URL. | ||
|
|
||
| Returns | ||
| ------- | ||
| Optional[dict[str, Any]] | ||
| The created agent space, or None on failure. | ||
| """ | ||
| body = { | ||
| "name": name, | ||
| "spaceId": space_id, | ||
| "description": description, | ||
| "tags": tags or [], | ||
| "status": status, | ||
| "isPublic": is_public, | ||
| } | ||
|
|
||
| if agent_spec: | ||
| body["agentSpec"] = agent_spec | ||
| if thumbnail: | ||
| body["thumbnail"] = thumbnail | ||
|
|
||
| response = self._fetch( # type: ignore | ||
| "{}/api/spacer/v1/agent-spaces".format(self.urls.spacer_url), # type: ignore | ||
| method="POST", | ||
| body=body, | ||
| ) | ||
|
|
||
| if response.status_code not in (200, 201): | ||
| logger.error(f"Failed to create agent space: HTTP {response.status_code}") | ||
| return None | ||
|
|
||
| data = response.json() | ||
| if data.get("success"): | ||
| return data.get("agentSpace") | ||
| return None | ||
|
|
||
| def update_agent_space( | ||
| self, | ||
| uid: str, | ||
| **kwargs: Any, | ||
| ) -> Optional[dict[str, Any]]: | ||
| """ | ||
| Update an agent space. | ||
|
|
||
| Parameters | ||
| ---------- | ||
| uid : str | ||
| The agent space UID. | ||
| **kwargs : Any | ||
| Fields to update (name, description, tags, status, isPublic, | ||
| agentSpec, podName, runtimeUrl, messageCount, lastMessage, thumbnail). | ||
|
|
||
| Returns | ||
| ------- | ||
| Optional[dict[str, Any]] | ||
| The updated agent space, or None on failure. | ||
| """ | ||
| # Build update body from provided kwargs | ||
| body = {} | ||
| valid_fields = { | ||
| "name", "description", "tags", "status", "isPublic", | ||
| "agentSpec", "podName", "runtimeUrl", "messageCount", | ||
| "lastMessage", "thumbnail", | ||
| } | ||
| for key, value in kwargs.items(): | ||
| if key in valid_fields and value is not None: | ||
| body[key] = value | ||
|
|
||
| if not body: | ||
| logger.warning("No valid fields provided for update") | ||
| return self.get_agent_space(uid) | ||
|
|
||
| response = self._fetch( # type: ignore | ||
| "{}/api/spacer/v1/agent-spaces/{}".format(self.urls.spacer_url, uid), # type: ignore | ||
| method="PUT", | ||
| body=body, | ||
| ) | ||
|
|
||
| if response.status_code == 404: | ||
| return None | ||
|
|
||
| if response.status_code != 200: | ||
| logger.error(f"Failed to update agent space: HTTP {response.status_code}") | ||
| return None | ||
|
|
||
| data = response.json() | ||
| if data.get("success"): | ||
| return data.get("agentSpace") | ||
| return None | ||
|
|
||
| def delete_agent_space(self, uid: str) -> bool: | ||
| """ | ||
| Delete an agent space. | ||
|
|
||
| Parameters | ||
| ---------- | ||
| uid : str | ||
| The agent space UID. | ||
|
|
||
| Returns | ||
| ------- | ||
| bool | ||
| True if deleted, False if not found or on error. | ||
| """ | ||
| response = self._fetch( # type: ignore | ||
| "{}/api/spacer/v1/agent-spaces/{}".format(self.urls.spacer_url, uid), # type: ignore | ||
| method="DELETE", | ||
| ) | ||
|
|
||
| if response.status_code == 204: | ||
| return True | ||
|
|
||
| if response.status_code == 404: | ||
| logger.warning(f"Agent space not found: {uid}") | ||
| return False | ||
|
|
||
| logger.error(f"Failed to delete agent space: HTTP {response.status_code}") | ||
| return False | ||
|
|
||
| def make_agent_space_public(self, uid: str) -> Optional[dict[str, Any]]: | ||
| """ | ||
| Make an agent space public (add to Library). | ||
|
|
||
| Parameters | ||
| ---------- | ||
| uid : str | ||
| The agent space UID. | ||
|
|
||
| Returns | ||
| ------- | ||
| Optional[dict[str, Any]] | ||
| The updated agent space, or None on failure. | ||
| """ | ||
| response = self._fetch( # type: ignore | ||
| "{}/api/spacer/v1/agent-spaces/{}/public".format(self.urls.spacer_url, uid), # type: ignore | ||
| method="POST", | ||
| ) | ||
|
|
||
| if response.status_code == 404: | ||
| return None | ||
|
|
||
| if response.status_code != 200: | ||
| logger.error(f"Failed to make agent space public: HTTP {response.status_code}") | ||
| return None | ||
|
|
||
| data = response.json() | ||
| if data.get("success"): | ||
| return data.get("agentSpace") | ||
| return None | ||
|
|
||
| def make_agent_space_private(self, uid: str) -> Optional[dict[str, Any]]: | ||
| """ | ||
| Make an agent space private (remove from Library). | ||
|
|
||
| Parameters | ||
| ---------- | ||
| uid : str | ||
| The agent space UID. | ||
|
|
||
| Returns | ||
| ------- | ||
| Optional[dict[str, Any]] | ||
| The updated agent space, or None on failure. | ||
| """ | ||
| response = self._fetch( # type: ignore | ||
| "{}/api/spacer/v1/agent-spaces/{}/private".format(self.urls.spacer_url, uid), # type: ignore | ||
| method="POST", | ||
| ) | ||
|
|
||
| if response.status_code == 404: | ||
| return None | ||
|
|
||
| if response.status_code != 200: | ||
| logger.error(f"Failed to make agent space private: HTTP {response.status_code}") | ||
| return None | ||
|
|
||
| data = response.json() | ||
| if data.get("success"): | ||
| return data.get("agentSpace") | ||
| return None |
Copilot
AI
Feb 5, 2026
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.
Missing exception handling. All methods in this mixin should wrap their _fetch calls in try-except blocks to handle RuntimeError exceptions, consistent with other mixins in the codebase (see datalayer_core/mixins/secrets.py:46-53, datalayer_core/mixins/environments.py:21-27). Without this, network errors or other runtime exceptions will propagate unhandled instead of returning a proper error response.
No description provided.