diff --git a/apps/api/src/api/tool_instances.py b/apps/api/src/api/tool_instances.py index 92a0cf1..7aa4908 100644 --- a/apps/api/src/api/tool_instances.py +++ b/apps/api/src/api/tool_instances.py @@ -427,6 +427,9 @@ class CreateInstanceRequest(BaseModel): display_name: str | None = Field( default=None, description="Optional display name for the instance" ) + workspace_id: str | None = Field( + default=None, description="UUID of workspace to mount (replaces clone_mode)" + ) clone_mode: str = Field( default="mount", description="Repository access mode: 'mount' or 'clone'" ) @@ -849,9 +852,34 @@ async def create_instance( session, data.config_profile_id, user_id, project_id, tool_type_id ) + # Resolve workspace if provided + workspace = None + workspace_id = None + if data.workspace_id: + from src.models.workspace import Workspace as WorkspaceModel + + try: + workspace_id = uuid.UUID(data.workspace_id) + except ValueError: + raise HTTPException( + status_code=status.HTTP_400_BAD_REQUEST, + detail="Invalid workspace_id format", + ) + workspace = await session.get(WorkspaceModel, workspace_id) + if workspace is None: + raise HTTPException( + status_code=status.HTTP_404_NOT_FOUND, + detail="workspace not found", + ) + if workspace.repo_id != repo_id: + raise HTTPException( + status_code=status.HTTP_400_BAD_REQUEST, + detail="workspace does not belong to this repository", + ) + try: - # Validate clone mode requirements - if data.clone_mode == "clone": + # Validate clone mode requirements (legacy path) + if data.clone_mode == "clone" and not workspace: if not repo.remote_url: raise HTTPException( status_code=status.HTTP_400_BAD_REQUEST, @@ -876,8 +904,10 @@ async def create_instance( # Find free port tool_port = find_free_port() - # Determine repo path based on clone mode - if data.clone_mode == "clone": + # Determine repo path based on workspace or clone mode + if workspace: + repo_path = workspace.path + elif data.clone_mode == "clone": # Get SSH key for cloning ssh_key = await session.get(SSHKey, repo.ssh_key_id) if ssh_key is None: @@ -1143,6 +1173,7 @@ services: status="pending", compose_path=compose_path, port=tool_port, + workspace_id=workspace_id, clone_mode=data.clone_mode, branch=data.new_branch if data.new_branch @@ -1649,11 +1680,18 @@ async def start_instance( if tool_type and tool_type.definition_type == "manifest" and tool_type.manifest_id: logger.info("Using manifest-based startup for instance %s", instance.id) - # Determine repo path - repo = await session.get(GitRepository, instance.repository_id) - repo_path = repo.path if repo else "" - if instance.clone_mode == "clone": - repo_path = os.path.join(instance_dir, "repo-clone") + # Determine repo path (workspace takes precedence) + repo_path = "" + if instance.workspace_id: + from src.models.workspace import Workspace as WorkspaceModel + workspace = await session.get(WorkspaceModel, instance.workspace_id) + if workspace: + repo_path = workspace.path + else: + repo = await session.get(GitRepository, instance.repository_id) + repo_path = repo.path if repo else "" + if instance.clone_mode == "clone": + repo_path = os.path.join(instance_dir, "repo-clone") try: ( @@ -1686,8 +1724,8 @@ async def start_instance( ) else: # ── LEGACY FLOW ────────────────────────────────────────── - # Mount SSH key for clone-mode instances - if instance.clone_mode == "clone": + # Mount SSH key for clone-mode instances (skip for workspace-based) + if instance.clone_mode == "clone" and not instance.workspace_id: repo = await session.get(GitRepository, instance.repository_id) if repo and repo.ssh_key_id: ssh_key = await session.get(SSHKey, repo.ssh_key_id)