Skip to content

Conversation

@echarles
Copy link
Member

@echarles echarles commented Feb 5, 2026

No description provided.

Copilot AI review requested due to automatic review settings February 5, 2026 14:33
Copy link
Member Author

@echarles echarles left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

@echarles echarles merged commit 570c608 into main Feb 5, 2026
13 of 21 checks passed
@netlify
Copy link

netlify bot commented Feb 5, 2026

Deploy Preview for datalayer-core failed.

Name Link
🔨 Latest commit 0892bce
🔍 Latest deploy log https://app.netlify.com/projects/datalayer-core/deploys/6984aa4852b1c3000952d5da

Copy link

Copilot AI left a 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,
Copy link

Copilot AI Feb 5, 2026

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.

Suggested change
body=body,
json=body,

Copilot uses AI. Check for mistakes.
response = self._fetch( # type: ignore
"{}/api/spacer/v1/agent-spaces/{}".format(self.urls.spacer_url, uid), # type: ignore
method="PUT",
body=body,
Copy link

Copilot AI Feb 5, 2026

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.

Suggested change
body=body,
json=body,

Copilot uses AI. Check for mistakes.
Comment on lines +1886 to +1904
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'];
};
Copy link

Copilot AI Feb 5, 2026

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.

Copilot uses AI. Check for mistakes.
// Filter to only include ai-agents-env runtimes
const agentRuntimes = (resp.runtimes as AgentRuntimeData[])
.filter(
(rt: AgentRuntimeData) => rt.environment_name === 'ai-agents-env',
Copy link

Copilot AI Feb 5, 2026

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';

Copilot uses AI. Check for mistakes.
Comment on lines +1937 to +1946
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),
Copy link

Copilot AI Feb 5, 2026

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 => { ... }

Copilot uses AI. Check for mistakes.
Comment on lines +2023 to +2029
type CreateAgentRuntimeRequest = {
environmentName?: string;
givenName?: string;
creditsLimit?: number;
type?: string;
editorVariant?: string; // 'none', 'notebook', or 'document'
};
Copy link

Copilot AI Feb 5, 2026

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.

Copilot uses AI. Check for mistakes.
Comment on lines +15 to +302
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
Copy link

Copilot AI Feb 5, 2026

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.

Copilot uses AI. Check for mistakes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant