forked from cardosofelipe/fast-next-template
- Added `mcp-git-ops` service to `docker-compose.dev.yml` with health checks and configurations. - Integrated SSRF protection in repository URL validation for enhanced security. - Expanded `pyproject.toml` mypy settings and adjusted code to meet stricter type checking. - Improved workspace management and GitWrapper operations with error handling refinements. - Updated input validation, branching, and repository operations to align with new error structure. - Shut down thread pool executor gracefully during server cleanup.
691 lines
24 KiB
Python
691 lines
24 KiB
Python
"""
|
|
Data models for Git Operations MCP Server.
|
|
|
|
Defines data structures for git operations, workspace management,
|
|
and provider interactions.
|
|
"""
|
|
|
|
from dataclasses import dataclass, field
|
|
from datetime import UTC, datetime
|
|
from enum import Enum
|
|
from typing import Any
|
|
|
|
from pydantic import BaseModel, Field
|
|
|
|
|
|
class FileChangeType(str, Enum):
|
|
"""Types of file changes in git."""
|
|
|
|
ADDED = "added"
|
|
MODIFIED = "modified"
|
|
DELETED = "deleted"
|
|
RENAMED = "renamed"
|
|
COPIED = "copied"
|
|
UNTRACKED = "untracked"
|
|
IGNORED = "ignored"
|
|
|
|
|
|
class MergeStrategy(str, Enum):
|
|
"""Merge strategies for pull requests."""
|
|
|
|
MERGE = "merge" # Create a merge commit
|
|
SQUASH = "squash" # Squash and merge
|
|
REBASE = "rebase" # Rebase and merge
|
|
|
|
|
|
class PRState(str, Enum):
|
|
"""Pull request states."""
|
|
|
|
OPEN = "open"
|
|
CLOSED = "closed"
|
|
MERGED = "merged"
|
|
|
|
|
|
class ProviderType(str, Enum):
|
|
"""Supported git providers."""
|
|
|
|
GITEA = "gitea"
|
|
GITHUB = "github"
|
|
GITLAB = "gitlab"
|
|
|
|
|
|
class WorkspaceState(str, Enum):
|
|
"""Workspace lifecycle states."""
|
|
|
|
INITIALIZING = "initializing"
|
|
READY = "ready"
|
|
LOCKED = "locked"
|
|
STALE = "stale"
|
|
DELETED = "deleted"
|
|
|
|
|
|
# Dataclasses for internal data structures
|
|
|
|
|
|
@dataclass
|
|
class FileChange:
|
|
"""A file change in git status."""
|
|
|
|
path: str
|
|
change_type: FileChangeType
|
|
old_path: str | None = None # For renames
|
|
additions: int = 0
|
|
deletions: int = 0
|
|
|
|
def to_dict(self) -> dict[str, Any]:
|
|
"""Convert to dictionary."""
|
|
return {
|
|
"path": self.path,
|
|
"change_type": self.change_type.value,
|
|
"old_path": self.old_path,
|
|
"additions": self.additions,
|
|
"deletions": self.deletions,
|
|
}
|
|
|
|
|
|
@dataclass
|
|
class BranchInfo:
|
|
"""Information about a git branch."""
|
|
|
|
name: str
|
|
is_current: bool = False
|
|
is_remote: bool = False
|
|
tracking_branch: str | None = None
|
|
commit_sha: str | None = None
|
|
commit_message: str | None = None
|
|
ahead: int = 0
|
|
behind: int = 0
|
|
|
|
def to_dict(self) -> dict[str, Any]:
|
|
"""Convert to dictionary."""
|
|
return {
|
|
"name": self.name,
|
|
"is_current": self.is_current,
|
|
"is_remote": self.is_remote,
|
|
"tracking_branch": self.tracking_branch,
|
|
"commit_sha": self.commit_sha,
|
|
"commit_message": self.commit_message,
|
|
"ahead": self.ahead,
|
|
"behind": self.behind,
|
|
}
|
|
|
|
|
|
@dataclass
|
|
class CommitInfo:
|
|
"""Information about a git commit."""
|
|
|
|
sha: str
|
|
short_sha: str
|
|
message: str
|
|
author_name: str
|
|
author_email: str
|
|
authored_date: datetime
|
|
committer_name: str
|
|
committer_email: str
|
|
committed_date: datetime
|
|
parents: list[str] = field(default_factory=list)
|
|
|
|
def to_dict(self) -> dict[str, Any]:
|
|
"""Convert to dictionary."""
|
|
return {
|
|
"sha": self.sha,
|
|
"short_sha": self.short_sha,
|
|
"message": self.message,
|
|
"author_name": self.author_name,
|
|
"author_email": self.author_email,
|
|
"authored_date": self.authored_date.isoformat(),
|
|
"committer_name": self.committer_name,
|
|
"committer_email": self.committer_email,
|
|
"committed_date": self.committed_date.isoformat(),
|
|
"parents": self.parents,
|
|
}
|
|
|
|
|
|
@dataclass
|
|
class DiffHunk:
|
|
"""A hunk of diff content."""
|
|
|
|
old_start: int
|
|
old_lines: int
|
|
new_start: int
|
|
new_lines: int
|
|
content: str
|
|
|
|
def to_dict(self) -> dict[str, Any]:
|
|
"""Convert to dictionary."""
|
|
return {
|
|
"old_start": self.old_start,
|
|
"old_lines": self.old_lines,
|
|
"new_start": self.new_start,
|
|
"new_lines": self.new_lines,
|
|
"content": self.content,
|
|
}
|
|
|
|
|
|
@dataclass
|
|
class FileDiff:
|
|
"""Diff for a single file."""
|
|
|
|
path: str
|
|
change_type: FileChangeType
|
|
old_path: str | None = None
|
|
hunks: list[DiffHunk] = field(default_factory=list)
|
|
additions: int = 0
|
|
deletions: int = 0
|
|
is_binary: bool = False
|
|
|
|
def to_dict(self) -> dict[str, Any]:
|
|
"""Convert to dictionary."""
|
|
return {
|
|
"path": self.path,
|
|
"change_type": self.change_type.value,
|
|
"old_path": self.old_path,
|
|
"hunks": [h.to_dict() for h in self.hunks],
|
|
"additions": self.additions,
|
|
"deletions": self.deletions,
|
|
"is_binary": self.is_binary,
|
|
}
|
|
|
|
|
|
@dataclass
|
|
class PRInfo:
|
|
"""Information about a pull request."""
|
|
|
|
number: int
|
|
title: str
|
|
body: str
|
|
state: PRState
|
|
source_branch: str
|
|
target_branch: str
|
|
author: str
|
|
created_at: datetime
|
|
updated_at: datetime
|
|
merged_at: datetime | None = None
|
|
closed_at: datetime | None = None
|
|
url: str | None = None
|
|
labels: list[str] = field(default_factory=list)
|
|
assignees: list[str] = field(default_factory=list)
|
|
reviewers: list[str] = field(default_factory=list)
|
|
mergeable: bool | None = None
|
|
draft: bool = False
|
|
|
|
def to_dict(self) -> dict[str, Any]:
|
|
"""Convert to dictionary."""
|
|
return {
|
|
"number": self.number,
|
|
"title": self.title,
|
|
"body": self.body,
|
|
"state": self.state.value,
|
|
"source_branch": self.source_branch,
|
|
"target_branch": self.target_branch,
|
|
"author": self.author,
|
|
"created_at": self.created_at.isoformat(),
|
|
"updated_at": self.updated_at.isoformat(),
|
|
"merged_at": self.merged_at.isoformat() if self.merged_at else None,
|
|
"closed_at": self.closed_at.isoformat() if self.closed_at else None,
|
|
"url": self.url,
|
|
"labels": self.labels,
|
|
"assignees": self.assignees,
|
|
"reviewers": self.reviewers,
|
|
"mergeable": self.mergeable,
|
|
"draft": self.draft,
|
|
}
|
|
|
|
|
|
@dataclass
|
|
class WorkspaceInfo:
|
|
"""Information about a project workspace."""
|
|
|
|
project_id: str
|
|
path: str
|
|
state: WorkspaceState
|
|
repo_url: str | None = None
|
|
current_branch: str | None = None
|
|
last_accessed: datetime = field(default_factory=lambda: datetime.now(UTC))
|
|
size_bytes: int = 0
|
|
lock_holder: str | None = None
|
|
lock_expires: datetime | None = None
|
|
|
|
def to_dict(self) -> dict[str, Any]:
|
|
"""Convert to dictionary."""
|
|
return {
|
|
"project_id": self.project_id,
|
|
"path": self.path,
|
|
"state": self.state.value,
|
|
"repo_url": self.repo_url,
|
|
"current_branch": self.current_branch,
|
|
"last_accessed": self.last_accessed.isoformat(),
|
|
"size_bytes": self.size_bytes,
|
|
"lock_holder": self.lock_holder,
|
|
"lock_expires": self.lock_expires.isoformat()
|
|
if self.lock_expires
|
|
else None,
|
|
}
|
|
|
|
|
|
# Pydantic Request/Response Models
|
|
|
|
|
|
class CloneRequest(BaseModel):
|
|
"""Request to clone a repository."""
|
|
|
|
project_id: str = Field(..., description="Project ID for scoping")
|
|
agent_id: str = Field(..., description="Agent ID making the request")
|
|
repo_url: str = Field(..., description="Repository URL to clone")
|
|
branch: str | None = Field(
|
|
default=None, description="Branch to checkout after clone"
|
|
)
|
|
depth: int | None = Field(
|
|
default=None, ge=1, description="Shallow clone depth (None = full clone)"
|
|
)
|
|
|
|
|
|
class CloneResult(BaseModel):
|
|
"""Result of a clone operation."""
|
|
|
|
success: bool = Field(..., description="Whether clone succeeded")
|
|
project_id: str = Field(..., description="Project ID")
|
|
workspace_path: str = Field(..., description="Path to cloned workspace")
|
|
branch: str = Field(..., description="Current branch after clone")
|
|
commit_sha: str = Field(..., description="HEAD commit SHA")
|
|
error: str | None = Field(default=None, description="Error message if failed")
|
|
|
|
|
|
class StatusRequest(BaseModel):
|
|
"""Request for git status."""
|
|
|
|
project_id: str = Field(..., description="Project ID for scoping")
|
|
agent_id: str = Field(..., description="Agent ID making the request")
|
|
include_untracked: bool = Field(default=True, description="Include untracked files")
|
|
|
|
|
|
class StatusResult(BaseModel):
|
|
"""Result of a status operation."""
|
|
|
|
project_id: str = Field(..., description="Project ID")
|
|
branch: str = Field(..., description="Current branch")
|
|
commit_sha: str = Field(..., description="HEAD commit SHA")
|
|
is_clean: bool = Field(..., description="Whether working tree is clean")
|
|
staged: list[dict[str, Any]] = Field(
|
|
default_factory=list, description="Staged changes"
|
|
)
|
|
unstaged: list[dict[str, Any]] = Field(
|
|
default_factory=list, description="Unstaged changes"
|
|
)
|
|
untracked: list[str] = Field(default_factory=list, description="Untracked files")
|
|
ahead: int = Field(default=0, description="Commits ahead of upstream")
|
|
behind: int = Field(default=0, description="Commits behind upstream")
|
|
|
|
|
|
class BranchRequest(BaseModel):
|
|
"""Request for branch operations."""
|
|
|
|
project_id: str = Field(..., description="Project ID for scoping")
|
|
agent_id: str = Field(..., description="Agent ID making the request")
|
|
branch_name: str = Field(..., description="Branch name")
|
|
from_ref: str | None = Field(
|
|
default=None, description="Reference to create branch from"
|
|
)
|
|
checkout: bool = Field(default=True, description="Checkout after creation")
|
|
|
|
|
|
class BranchResult(BaseModel):
|
|
"""Result of a branch operation."""
|
|
|
|
success: bool = Field(..., description="Whether operation succeeded")
|
|
branch: str = Field(..., description="Branch name")
|
|
commit_sha: str | None = Field(default=None, description="HEAD commit SHA")
|
|
is_current: bool = Field(default=False, description="Whether branch is checked out")
|
|
error: str | None = Field(default=None, description="Error message if failed")
|
|
|
|
|
|
class ListBranchesRequest(BaseModel):
|
|
"""Request to list branches."""
|
|
|
|
project_id: str = Field(..., description="Project ID for scoping")
|
|
agent_id: str = Field(..., description="Agent ID making the request")
|
|
include_remote: bool = Field(default=False, description="Include remote branches")
|
|
|
|
|
|
class ListBranchesResult(BaseModel):
|
|
"""Result of listing branches."""
|
|
|
|
project_id: str = Field(..., description="Project ID")
|
|
current_branch: str = Field(..., description="Currently checked out branch")
|
|
local_branches: list[dict[str, Any]] = Field(
|
|
default_factory=list, description="Local branches"
|
|
)
|
|
remote_branches: list[dict[str, Any]] = Field(
|
|
default_factory=list, description="Remote branches"
|
|
)
|
|
|
|
|
|
class CheckoutRequest(BaseModel):
|
|
"""Request to checkout a branch or ref."""
|
|
|
|
project_id: str = Field(..., description="Project ID for scoping")
|
|
agent_id: str = Field(..., description="Agent ID making the request")
|
|
ref: str = Field(..., description="Branch, tag, or commit to checkout")
|
|
create_branch: bool = Field(default=False, description="Create new branch")
|
|
force: bool = Field(default=False, description="Force checkout (discard changes)")
|
|
|
|
|
|
class CheckoutResult(BaseModel):
|
|
"""Result of a checkout operation."""
|
|
|
|
success: bool = Field(..., description="Whether checkout succeeded")
|
|
ref: str = Field(..., description="Checked out reference")
|
|
commit_sha: str | None = Field(default=None, description="HEAD commit SHA")
|
|
error: str | None = Field(default=None, description="Error message if failed")
|
|
|
|
|
|
class CommitRequest(BaseModel):
|
|
"""Request to create a commit."""
|
|
|
|
project_id: str = Field(..., description="Project ID for scoping")
|
|
agent_id: str = Field(..., description="Agent ID making the request")
|
|
message: str = Field(..., description="Commit message")
|
|
files: list[str] | None = Field(
|
|
default=None, description="Files to commit (None = all staged)"
|
|
)
|
|
author_name: str | None = Field(default=None, description="Author name override")
|
|
author_email: str | None = Field(default=None, description="Author email override")
|
|
allow_empty: bool = Field(default=False, description="Allow empty commit")
|
|
|
|
|
|
class CommitResult(BaseModel):
|
|
"""Result of a commit operation."""
|
|
|
|
success: bool = Field(..., description="Whether commit succeeded")
|
|
commit_sha: str | None = Field(default=None, description="New commit SHA")
|
|
short_sha: str | None = Field(default=None, description="Short commit SHA")
|
|
message: str | None = Field(default=None, description="Commit message")
|
|
files_changed: int = Field(default=0, description="Number of files changed")
|
|
insertions: int = Field(default=0, description="Lines added")
|
|
deletions: int = Field(default=0, description="Lines removed")
|
|
error: str | None = Field(default=None, description="Error message if failed")
|
|
|
|
|
|
class PushRequest(BaseModel):
|
|
"""Request to push to remote."""
|
|
|
|
project_id: str = Field(..., description="Project ID for scoping")
|
|
agent_id: str = Field(..., description="Agent ID making the request")
|
|
branch: str | None = Field(
|
|
default=None, description="Branch to push (None = current)"
|
|
)
|
|
remote: str = Field(default="origin", description="Remote name")
|
|
force: bool = Field(default=False, description="Force push")
|
|
set_upstream: bool = Field(default=True, description="Set upstream tracking")
|
|
|
|
|
|
class PushResult(BaseModel):
|
|
"""Result of a push operation."""
|
|
|
|
success: bool = Field(..., description="Whether push succeeded")
|
|
branch: str = Field(..., description="Pushed branch")
|
|
remote: str = Field(..., description="Remote name")
|
|
commits_pushed: int = Field(default=0, description="Number of commits pushed")
|
|
error: str | None = Field(default=None, description="Error message if failed")
|
|
|
|
|
|
class PullRequest(BaseModel):
|
|
"""Request to pull from remote."""
|
|
|
|
project_id: str = Field(..., description="Project ID for scoping")
|
|
agent_id: str = Field(..., description="Agent ID making the request")
|
|
branch: str | None = Field(
|
|
default=None, description="Branch to pull (None = current)"
|
|
)
|
|
remote: str = Field(default="origin", description="Remote name")
|
|
rebase: bool = Field(default=False, description="Rebase instead of merge")
|
|
|
|
|
|
class PullResult(BaseModel):
|
|
"""Result of a pull operation."""
|
|
|
|
success: bool = Field(..., description="Whether pull succeeded")
|
|
branch: str = Field(..., description="Pulled branch")
|
|
commits_received: int = Field(default=0, description="New commits received")
|
|
fast_forward: bool = Field(default=False, description="Was fast-forward")
|
|
conflicts: list[str] = Field(
|
|
default_factory=list, description="Conflicting files if any"
|
|
)
|
|
error: str | None = Field(default=None, description="Error message if failed")
|
|
|
|
|
|
class DiffRequest(BaseModel):
|
|
"""Request for diff."""
|
|
|
|
project_id: str = Field(..., description="Project ID for scoping")
|
|
agent_id: str = Field(..., description="Agent ID making the request")
|
|
base: str | None = Field(
|
|
default=None, description="Base reference (None = working tree)"
|
|
)
|
|
head: str | None = Field(default=None, description="Head reference (None = HEAD)")
|
|
files: list[str] | None = Field(default=None, description="Specific files to diff")
|
|
context_lines: int = Field(default=3, ge=0, description="Context lines")
|
|
|
|
|
|
class DiffResult(BaseModel):
|
|
"""Result of a diff operation."""
|
|
|
|
project_id: str = Field(..., description="Project ID")
|
|
base: str | None = Field(default=None, description="Base reference")
|
|
head: str | None = Field(default=None, description="Head reference")
|
|
files: list[dict[str, Any]] = Field(default_factory=list, description="File diffs")
|
|
total_additions: int = Field(default=0, description="Total lines added")
|
|
total_deletions: int = Field(default=0, description="Total lines removed")
|
|
files_changed: int = Field(default=0, description="Number of files changed")
|
|
|
|
|
|
class LogRequest(BaseModel):
|
|
"""Request for commit log."""
|
|
|
|
project_id: str = Field(..., description="Project ID for scoping")
|
|
agent_id: str = Field(..., description="Agent ID making the request")
|
|
ref: str | None = Field(default=None, description="Reference to start from")
|
|
limit: int = Field(default=20, ge=1, le=100, description="Max commits to return")
|
|
skip: int = Field(default=0, ge=0, description="Commits to skip")
|
|
path: str | None = Field(default=None, description="Filter by path")
|
|
|
|
|
|
class LogResult(BaseModel):
|
|
"""Result of a log operation."""
|
|
|
|
project_id: str = Field(..., description="Project ID")
|
|
commits: list[dict[str, Any]] = Field(
|
|
default_factory=list, description="Commit history"
|
|
)
|
|
total_commits: int = Field(default=0, description="Total commits in range")
|
|
|
|
|
|
# PR Operations
|
|
|
|
|
|
class CreatePRRequest(BaseModel):
|
|
"""Request to create a pull request."""
|
|
|
|
project_id: str = Field(..., description="Project ID for scoping")
|
|
agent_id: str = Field(..., description="Agent ID making the request")
|
|
title: str = Field(..., description="PR title")
|
|
body: str = Field(default="", description="PR description")
|
|
source_branch: str = Field(..., description="Source branch")
|
|
target_branch: str = Field(default="main", description="Target branch")
|
|
draft: bool = Field(default=False, description="Create as draft")
|
|
labels: list[str] = Field(default_factory=list, description="Labels to add")
|
|
assignees: list[str] = Field(default_factory=list, description="Assignees")
|
|
reviewers: list[str] = Field(default_factory=list, description="Reviewers")
|
|
|
|
|
|
class CreatePRResult(BaseModel):
|
|
"""Result of creating a pull request."""
|
|
|
|
success: bool = Field(..., description="Whether creation succeeded")
|
|
pr_number: int | None = Field(default=None, description="PR number")
|
|
pr_url: str | None = Field(default=None, description="PR URL")
|
|
error: str | None = Field(default=None, description="Error message if failed")
|
|
|
|
|
|
class GetPRRequest(BaseModel):
|
|
"""Request to get a pull request."""
|
|
|
|
project_id: str = Field(..., description="Project ID for scoping")
|
|
agent_id: str = Field(..., description="Agent ID making the request")
|
|
pr_number: int = Field(..., description="PR number")
|
|
|
|
|
|
class GetPRResult(BaseModel):
|
|
"""Result of getting a pull request."""
|
|
|
|
success: bool = Field(..., description="Whether fetch succeeded")
|
|
pr: dict[str, Any] | None = Field(default=None, description="PR info")
|
|
error: str | None = Field(default=None, description="Error message if failed")
|
|
|
|
|
|
class ListPRsRequest(BaseModel):
|
|
"""Request to list pull requests."""
|
|
|
|
project_id: str = Field(..., description="Project ID for scoping")
|
|
agent_id: str = Field(..., description="Agent ID making the request")
|
|
state: PRState | None = Field(default=None, description="Filter by state")
|
|
author: str | None = Field(default=None, description="Filter by author")
|
|
limit: int = Field(default=20, ge=1, le=100, description="Max PRs to return")
|
|
|
|
|
|
class ListPRsResult(BaseModel):
|
|
"""Result of listing pull requests."""
|
|
|
|
success: bool = Field(..., description="Whether list succeeded")
|
|
pull_requests: list[dict[str, Any]] = Field(default_factory=list, description="PRs")
|
|
total_count: int = Field(default=0, description="Total matching PRs")
|
|
error: str | None = Field(default=None, description="Error message if failed")
|
|
|
|
|
|
class MergePRRequest(BaseModel):
|
|
"""Request to merge a pull request."""
|
|
|
|
project_id: str = Field(..., description="Project ID for scoping")
|
|
agent_id: str = Field(..., description="Agent ID making the request")
|
|
pr_number: int = Field(..., description="PR number")
|
|
merge_strategy: MergeStrategy = Field(
|
|
default=MergeStrategy.MERGE, description="Merge strategy"
|
|
)
|
|
commit_message: str | None = Field(
|
|
default=None, description="Custom merge commit message"
|
|
)
|
|
delete_branch: bool = Field(
|
|
default=True, description="Delete source branch after merge"
|
|
)
|
|
|
|
|
|
class MergePRResult(BaseModel):
|
|
"""Result of merging a pull request."""
|
|
|
|
success: bool = Field(..., description="Whether merge succeeded")
|
|
merge_commit_sha: str | None = Field(default=None, description="Merge commit SHA")
|
|
branch_deleted: bool = Field(
|
|
default=False, description="Whether branch was deleted"
|
|
)
|
|
error: str | None = Field(default=None, description="Error message if failed")
|
|
|
|
|
|
class UpdatePRRequest(BaseModel):
|
|
"""Request to update a pull request."""
|
|
|
|
project_id: str = Field(..., description="Project ID for scoping")
|
|
agent_id: str = Field(..., description="Agent ID making the request")
|
|
pr_number: int = Field(..., description="PR number")
|
|
title: str | None = Field(default=None, description="New title")
|
|
body: str | None = Field(default=None, description="New description")
|
|
state: PRState | None = Field(default=None, description="New state")
|
|
labels: list[str] | None = Field(default=None, description="Replace labels")
|
|
assignees: list[str] | None = Field(default=None, description="Replace assignees")
|
|
|
|
|
|
class UpdatePRResult(BaseModel):
|
|
"""Result of updating a pull request."""
|
|
|
|
success: bool = Field(..., description="Whether update succeeded")
|
|
pr: dict[str, Any] | None = Field(default=None, description="Updated PR info")
|
|
error: str | None = Field(default=None, description="Error message if failed")
|
|
|
|
|
|
# Workspace Operations
|
|
|
|
|
|
class GetWorkspaceRequest(BaseModel):
|
|
"""Request to get or create workspace."""
|
|
|
|
project_id: str = Field(..., description="Project ID")
|
|
agent_id: str = Field(..., description="Agent ID making the request")
|
|
|
|
|
|
class GetWorkspaceResult(BaseModel):
|
|
"""Result of getting workspace."""
|
|
|
|
success: bool = Field(..., description="Whether operation succeeded")
|
|
workspace: dict[str, Any] | None = Field(default=None, description="Workspace info")
|
|
error: str | None = Field(default=None, description="Error message if failed")
|
|
|
|
|
|
class LockWorkspaceRequest(BaseModel):
|
|
"""Request to lock a workspace."""
|
|
|
|
project_id: str = Field(..., description="Project ID")
|
|
agent_id: str = Field(..., description="Agent ID requesting lock")
|
|
timeout: int = Field(
|
|
default=300, ge=10, le=3600, description="Lock timeout seconds"
|
|
)
|
|
|
|
|
|
class LockWorkspaceResult(BaseModel):
|
|
"""Result of locking workspace."""
|
|
|
|
success: bool = Field(..., description="Whether lock acquired")
|
|
lock_holder: str | None = Field(default=None, description="Current lock holder")
|
|
lock_expires: str | None = Field(
|
|
default=None, description="Lock expiry ISO timestamp"
|
|
)
|
|
error: str | None = Field(default=None, description="Error message if failed")
|
|
|
|
|
|
class UnlockWorkspaceRequest(BaseModel):
|
|
"""Request to unlock a workspace."""
|
|
|
|
project_id: str = Field(..., description="Project ID")
|
|
agent_id: str = Field(..., description="Agent ID releasing lock")
|
|
force: bool = Field(default=False, description="Force unlock (admin only)")
|
|
|
|
|
|
class UnlockWorkspaceResult(BaseModel):
|
|
"""Result of unlocking workspace."""
|
|
|
|
success: bool = Field(..., description="Whether unlock succeeded")
|
|
error: str | None = Field(default=None, description="Error message if failed")
|
|
|
|
|
|
# Health and Status
|
|
|
|
|
|
class HealthStatus(BaseModel):
|
|
"""Health status response."""
|
|
|
|
status: str = Field(..., description="Health status")
|
|
version: str = Field(..., description="Server version")
|
|
workspace_count: int = Field(default=0, description="Active workspaces")
|
|
gitea_connected: bool = Field(default=False, description="Gitea connectivity")
|
|
github_connected: bool = Field(default=False, description="GitHub connectivity")
|
|
gitlab_connected: bool = Field(default=False, description="GitLab connectivity")
|
|
redis_connected: bool = Field(default=False, description="Redis connectivity")
|
|
|
|
|
|
class ProviderStatus(BaseModel):
|
|
"""Provider connection status."""
|
|
|
|
provider: str = Field(..., description="Provider name")
|
|
connected: bool = Field(..., description="Connection status")
|
|
url: str | None = Field(default=None, description="Provider URL")
|
|
user: str | None = Field(default=None, description="Authenticated user")
|
|
error: str | None = Field(default=None, description="Error if not connected")
|