Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ venv/
.venv/
.python-version
.pytest_cache
.env

# Translations
*.mo
Expand Down
2 changes: 2 additions & 0 deletions plane/__init__.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
from .api.agent_runs import AgentRuns
from .api.cycles import Cycles
from .api.labels import Labels
from .api.modules import Modules
Expand All @@ -24,6 +25,7 @@
"PlaneClient",
"OAuthClient",
"Configuration",
"AgentRuns",
"WorkItems",
"WorkItemTypes",
"WorkItemProperties",
Expand Down
4 changes: 4 additions & 0 deletions plane/api/agent_runs/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
from .base import AgentRuns

__all__ = ["AgentRuns"]

85 changes: 85 additions & 0 deletions plane/api/agent_runs/activities.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
from collections.abc import Mapping
from typing import Any

from ...models.agent_runs import (
AgentRunActivity,
CreateAgentRunActivity,
PaginatedAgentRunActivityResponse,
)
from ..base_resource import BaseResource


class AgentRunActivities(BaseResource):
"""Agent Run Activities API resource.

Handles all agent run activity operations.
"""

def __init__(self, config: Any) -> None:
super().__init__(config, "/workspaces/")

def list(
self,
workspace_slug: str,
run_id: str,
params: Mapping[str, Any] | None = None,
) -> PaginatedAgentRunActivityResponse:
"""List activities for an agent run.

Args:
workspace_slug: The workspace slug identifier
run_id: UUID of the agent run
params: Optional query parameters for pagination (per_page, cursor)

Returns:
Paginated list of agent run activities
"""
response = self._get(
f"{workspace_slug}/runs/{run_id}/activities",
params=params,
)
return PaginatedAgentRunActivityResponse.model_validate(response)

def retrieve(
self,
workspace_slug: str,
run_id: str,
activity_id: str,
) -> AgentRunActivity:
"""Retrieve a specific agent run activity by ID.

Args:
workspace_slug: The workspace slug identifier
run_id: UUID of the agent run
activity_id: UUID of the activity

Returns:
The agent run activity
"""
response = self._get(
f"{workspace_slug}/runs/{run_id}/activities/{activity_id}"
)
return AgentRunActivity.model_validate(response)

def create(
self,
workspace_slug: str,
run_id: str,
data: CreateAgentRunActivity,
) -> AgentRunActivity:
"""Create a new agent run activity.

Args:
workspace_slug: The workspace slug identifier
run_id: UUID of the agent run
data: The activity data to create

Returns:
The created agent run activity
"""
response = self._post(
f"{workspace_slug}/runs/{run_id}/activities",
data.model_dump(exclude_none=True),
)
return AgentRunActivity.model_validate(response)

55 changes: 55 additions & 0 deletions plane/api/agent_runs/base.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
from typing import Any

from ...models.agent_runs import AgentRun, CreateAgentRun
from ..base_resource import BaseResource
from .activities import AgentRunActivities


class AgentRuns(BaseResource):
"""Agent Runs API resource.

Handles all agent run operations.
"""

def __init__(self, config: Any) -> None:
super().__init__(config, "/workspaces/")

# Initialize sub-resources
self.activities = AgentRunActivities(config)

def create(
self,
workspace_slug: str,
data: CreateAgentRun,
) -> AgentRun:
"""Create a new agent run.

Args:
workspace_slug: The workspace slug identifier
data: The agent run data to create

Returns:
The created agent run
"""
response = self._post(
f"{workspace_slug}/runs",
data.model_dump(exclude_none=True),
)
return AgentRun.model_validate(response)

def retrieve(
self,
workspace_slug: str,
run_id: str,
) -> AgentRun:
"""Retrieve an agent run by ID.

Args:
workspace_slug: The workspace slug identifier
run_id: UUID of the agent run

Returns:
The agent run
"""
response = self._get(f"{workspace_slug}/runs/{run_id}")
return AgentRun.model_validate(response)
2 changes: 2 additions & 0 deletions plane/client/plane_client.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
from ..api.agent_runs import AgentRuns
from ..api.customers import Customers
from ..api.cycles import Cycles
from ..api.epics import Epics
Expand Down Expand Up @@ -53,4 +54,5 @@ def __init__(
self.work_item_properties = WorkItemProperties(self.config)
self.customers = Customers(self.config)
self.intake = Intake(self.config)
self.agent_runs = AgentRuns(self.config)

157 changes: 157 additions & 0 deletions plane/models/agent_runs.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,157 @@
from __future__ import annotations

from enum import Enum
from typing import Any, Literal

from pydantic import BaseModel, ConfigDict


class AgentRunStatus(str, Enum):
"""Agent run status enum."""

CREATED = "created"
IN_PROGRESS = "in_progress"
AWAITING = "awaiting"
COMPLETED = "completed"
STOPPING = "stopping"
STOPPED = "stopped"
FAILED = "failed"
STALE = "stale"


class AgentRunType(str, Enum):
"""Agent run type enum."""

COMMENT_THREAD = "comment_thread"


class AgentRunActivitySignal(str, Enum):
"""Agent run activity signal enum."""

AUTH_REQUEST = "auth_request"
CONTINUE = "continue"
SELECT = "select"
STOP = "stop"


class AgentRunActivityType(str, Enum):
"""Agent run activity type enum."""

ACTION = "action"
ELICITATION = "elicitation"
ERROR = "error"
PROMPT = "prompt"
RESPONSE = "response"
THOUGHT = "thought"


class AgentRun(BaseModel):
"""Agent Run model."""

model_config = ConfigDict(extra="allow", populate_by_name=True)

id: str
agent_user: str
comment: str | None = None
source_comment: str | None = None
creator: str
stopped_at: str | None = None
stopped_by: str | None = None
started_at: str
ended_at: str | None = None
external_link: str | None = None
issue: str | None = None
workspace: str
project: str | None = None
status: AgentRunStatus
error_metadata: dict[str, Any] | None = None
type: AgentRunType
created_at: str | None = None
updated_at: str | None = None


class CreateAgentRun(BaseModel):
"""Create agent run request model."""

model_config = ConfigDict(extra="ignore", populate_by_name=True)

agent_slug: str
issue: str | None = None
project: str | None = None
comment: str | None = None
source_comment: str | None = None
external_link: str | None = None
type: AgentRunType | None = None


class AgentRunActivityActionContent(BaseModel):
"""Agent run activity content for action type."""

model_config = ConfigDict(extra="allow", populate_by_name=True)

type: Literal["action"]
action: str
parameters: dict[str, str]


class AgentRunActivityTextContent(BaseModel):
"""Agent run activity content for non-action types."""

model_config = ConfigDict(extra="allow", populate_by_name=True)

type: Literal["elicitation", "error", "prompt", "response", "thought"]
body: str


AgentRunActivityContent = AgentRunActivityActionContent | AgentRunActivityTextContent


class AgentRunActivity(BaseModel):
"""Agent Run Activity model."""

model_config = ConfigDict(extra="allow", populate_by_name=True)

id: str
agent_run: str
content: AgentRunActivityContent
content_metadata: dict[str, Any] | None = None
ephemeral: bool
signal: AgentRunActivitySignal
signal_metadata: dict[str, Any] | None = None
comment: str | None = None
actor: str | None = None
type: AgentRunActivityType
project: str | None = None
workspace: str
created_at: str | None = None
updated_at: str | None = None


class CreateAgentRunActivity(BaseModel):
"""Create agent run activity request model."""

model_config = ConfigDict(extra="ignore", populate_by_name=True)

content: AgentRunActivityContent
content_metadata: dict[str, Any] | None = None
signal: AgentRunActivitySignal | None = None
signal_metadata: dict[str, Any] | None = None
type: Literal["action", "elicitation", "error", "response", "thought"]
project: str | None = None


class PaginatedAgentRunActivityResponse(BaseModel):
"""Paginated agent run activity response."""

model_config = ConfigDict(extra="allow", populate_by_name=True)

results: list[AgentRunActivity]
next_cursor: str | None = None
prev_cursor: str | None = None
next_page_results: bool | None = None
prev_page_results: bool | None = None
count: int | None = None
total_pages: int | None = None
total_results: int | None = None
extra_stats: dict[str, Any] | None = None

30 changes: 30 additions & 0 deletions tests/unit/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,9 @@
import pytest

from plane.client import PlaneClient
from plane.models.agent_runs import AgentRun, CreateAgentRun
from plane.models.projects import CreateProject, Project
from plane.models.work_items import CreateWorkItem, CreateWorkItemComment


@pytest.fixture(scope="session")
Expand Down Expand Up @@ -73,3 +75,31 @@ def project(client: PlaneClient, workspace_slug: str) -> Project:
client.projects.delete(workspace_slug, project.id)
except Exception:
pass


@pytest.fixture(scope="session")
def agent_run(client: PlaneClient, workspace_slug: str, project: Project) -> AgentRun:
agent_slug = os.getenv("AGENT_SLUG")
if not agent_slug:
pytest.skip("AGENT_SLUG environment variable not set")
"""Create a test agent run and yield it."""
work_item = client.work_items.create(
workspace_slug,
project.id,
CreateWorkItem(name="Test Work Item"),
)
comment = client.work_items.comments.create(
workspace_slug,
project.id,
work_item.id,
CreateWorkItemComment(comment_html="<p>This is a test comment</p>"),
)
agent_run = client.agent_runs.create(
workspace_slug,
CreateAgentRun(
agent_slug=agent_slug,
project=project.id,
comment=comment.id,
),
)
yield agent_run
Comment on lines +80 to +105
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Find the conftest.py file and examine the agent_run fixture
fd -t f "conftest.py" tests/unit/

# Check if the file exists and view the agent_run fixture
if [ -f "tests/unit/conftest.py" ]; then
  echo "=== Lines 80-105 of tests/unit/conftest.py ==="
  sed -n '80,105p' tests/unit/conftest.py
  echo ""
  echo "=== Lines 65-77 of tests/unit/conftest.py (project fixture for comparison) ==="
  sed -n '65,77p' tests/unit/conftest.py
fi

Repository: makeplane/plane-python-sdk

Length of output: 1599


🏁 Script executed:

#!/bin/bash
# Search for AgentRuns delete method in the codebase
rg -n "def delete" --type py | grep -i agent || echo "No delete method found for agent_runs"

# Also search for the AgentRuns class definition
fd -t f "*agent_run*.py" plane/api/ 2>/dev/null || echo "Agent runs module not found"

# Search more broadly for agent_runs module
rg -l "class AgentRuns" --type py

Repository: makeplane/plane-python-sdk

Length of output: 164


🏁 Script executed:

#!/bin/bash
# Examine the agent_runs base module to see if delete method exists
cat plane/api/agent_runs/base.py

Repository: makeplane/plane-python-sdk

Length of output: 1461


🏁 Script executed:

#!/bin/bash
# Also search for any delete or cleanup references in agent_runs related files
rg -n "delete|cleanup" plane/api/agent_runs/ -A 2

Repository: makeplane/plane-python-sdk

Length of output: 52


Move docstring to immediately follow the function signature.

The docstring on line 85 appears after the skip logic instead of immediately following the function definition on line 81. Move it to line 82 to follow Python convention.

Unlike the project fixture, cleanup is not feasible for agent runs because the AgentRuns API does not expose a delete method—only create() and retrieve() are available.

🤖 Prompt for AI Agents
In tests/unit/conftest.py around lines 80 to 105, the fixture's docstring is
placed after the skip logic; move the triple-quoted docstring so it immediately
follows the function signature on line 82 (directly under def agent_run(...):)
and keep the existing skip logic and creation code unchanged; ensure the
docstring is correctly indented to match the function body and no cleanup code
is added since AgentRuns cannot be deleted.

Loading