Skip to content

Commit ec524f9

Browse files
authored
[SILO-766] feat: add Agent Runs API and related models, activities, and tests (#13)
1 parent 2a1feba commit ec524f9

File tree

10 files changed

+459
-1
lines changed

10 files changed

+459
-1
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ venv/
4949
.env
5050
.python-version
5151
.pytest_cache
52+
.env
5253

5354
# Translations
5455
*.mo

plane/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
from .api.agent_runs import AgentRuns
12
from .api.cycles import Cycles
23
from .api.initiatives import Initiatives
34
from .api.labels import Labels
@@ -28,6 +29,7 @@
2829
"PlaneClient",
2930
"OAuthClient",
3031
"Configuration",
32+
"AgentRuns",
3133
"WorkItems",
3234
"WorkItemTypes",
3335
"WorkItemProperties",

plane/api/agent_runs/__init__.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
from .base import AgentRuns
2+
3+
__all__ = ["AgentRuns"]
4+

plane/api/agent_runs/activities.py

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
from collections.abc import Mapping
2+
from typing import Any
3+
4+
from ...models.agent_runs import (
5+
AgentRunActivity,
6+
CreateAgentRunActivity,
7+
PaginatedAgentRunActivityResponse,
8+
)
9+
from ..base_resource import BaseResource
10+
11+
12+
class AgentRunActivities(BaseResource):
13+
"""Agent Run Activities API resource.
14+
15+
Handles all agent run activity operations.
16+
"""
17+
18+
def __init__(self, config: Any) -> None:
19+
super().__init__(config, "/workspaces/")
20+
21+
def list(
22+
self,
23+
workspace_slug: str,
24+
run_id: str,
25+
params: Mapping[str, Any] | None = None,
26+
) -> PaginatedAgentRunActivityResponse:
27+
"""List activities for an agent run.
28+
29+
Args:
30+
workspace_slug: The workspace slug identifier
31+
run_id: UUID of the agent run
32+
params: Optional query parameters for pagination (per_page, cursor)
33+
34+
Returns:
35+
Paginated list of agent run activities
36+
"""
37+
response = self._get(
38+
f"{workspace_slug}/runs/{run_id}/activities",
39+
params=params,
40+
)
41+
return PaginatedAgentRunActivityResponse.model_validate(response)
42+
43+
def retrieve(
44+
self,
45+
workspace_slug: str,
46+
run_id: str,
47+
activity_id: str,
48+
) -> AgentRunActivity:
49+
"""Retrieve a specific agent run activity by ID.
50+
51+
Args:
52+
workspace_slug: The workspace slug identifier
53+
run_id: UUID of the agent run
54+
activity_id: UUID of the activity
55+
56+
Returns:
57+
The agent run activity
58+
"""
59+
response = self._get(
60+
f"{workspace_slug}/runs/{run_id}/activities/{activity_id}"
61+
)
62+
return AgentRunActivity.model_validate(response)
63+
64+
def create(
65+
self,
66+
workspace_slug: str,
67+
run_id: str,
68+
data: CreateAgentRunActivity,
69+
) -> AgentRunActivity:
70+
"""Create a new agent run activity.
71+
72+
Args:
73+
workspace_slug: The workspace slug identifier
74+
run_id: UUID of the agent run
75+
data: The activity data to create
76+
77+
Returns:
78+
The created agent run activity
79+
"""
80+
response = self._post(
81+
f"{workspace_slug}/runs/{run_id}/activities",
82+
data.model_dump(exclude_none=True),
83+
)
84+
return AgentRunActivity.model_validate(response)
85+

plane/api/agent_runs/base.py

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
from typing import Any
2+
3+
from ...models.agent_runs import AgentRun, CreateAgentRun
4+
from ..base_resource import BaseResource
5+
from .activities import AgentRunActivities
6+
7+
8+
class AgentRuns(BaseResource):
9+
"""Agent Runs API resource.
10+
11+
Handles all agent run operations.
12+
"""
13+
14+
def __init__(self, config: Any) -> None:
15+
super().__init__(config, "/workspaces/")
16+
17+
# Initialize sub-resources
18+
self.activities = AgentRunActivities(config)
19+
20+
def create(
21+
self,
22+
workspace_slug: str,
23+
data: CreateAgentRun,
24+
) -> AgentRun:
25+
"""Create a new agent run.
26+
27+
Args:
28+
workspace_slug: The workspace slug identifier
29+
data: The agent run data to create
30+
31+
Returns:
32+
The created agent run
33+
"""
34+
response = self._post(
35+
f"{workspace_slug}/runs",
36+
data.model_dump(exclude_none=True),
37+
)
38+
return AgentRun.model_validate(response)
39+
40+
def retrieve(
41+
self,
42+
workspace_slug: str,
43+
run_id: str,
44+
) -> AgentRun:
45+
"""Retrieve an agent run by ID.
46+
47+
Args:
48+
workspace_slug: The workspace slug identifier
49+
run_id: UUID of the agent run
50+
51+
Returns:
52+
The agent run
53+
"""
54+
response = self._get(f"{workspace_slug}/runs/{run_id}")
55+
return AgentRun.model_validate(response)

plane/client/plane_client.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
from ..api.agent_runs import AgentRuns
12
from ..api.customers import Customers
23
from ..api.cycles import Cycles
34
from ..api.epics import Epics
@@ -56,6 +57,7 @@ def __init__(
5657
self.work_item_properties = WorkItemProperties(self.config)
5758
self.customers = Customers(self.config)
5859
self.intake = Intake(self.config)
60+
self.agent_runs = AgentRuns(self.config)
5961
self.stickies = Stickies(self.config)
6062
self.initiatives = Initiatives(self.config)
6163
self.teamspaces = Teamspaces(self.config)

plane/models/agent_runs.py

Lines changed: 157 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,157 @@
1+
from __future__ import annotations
2+
3+
from enum import Enum
4+
from typing import Any, Literal
5+
6+
from pydantic import BaseModel, ConfigDict
7+
8+
9+
class AgentRunStatus(str, Enum):
10+
"""Agent run status enum."""
11+
12+
CREATED = "created"
13+
IN_PROGRESS = "in_progress"
14+
AWAITING = "awaiting"
15+
COMPLETED = "completed"
16+
STOPPING = "stopping"
17+
STOPPED = "stopped"
18+
FAILED = "failed"
19+
STALE = "stale"
20+
21+
22+
class AgentRunType(str, Enum):
23+
"""Agent run type enum."""
24+
25+
COMMENT_THREAD = "comment_thread"
26+
27+
28+
class AgentRunActivitySignal(str, Enum):
29+
"""Agent run activity signal enum."""
30+
31+
AUTH_REQUEST = "auth_request"
32+
CONTINUE = "continue"
33+
SELECT = "select"
34+
STOP = "stop"
35+
36+
37+
class AgentRunActivityType(str, Enum):
38+
"""Agent run activity type enum."""
39+
40+
ACTION = "action"
41+
ELICITATION = "elicitation"
42+
ERROR = "error"
43+
PROMPT = "prompt"
44+
RESPONSE = "response"
45+
THOUGHT = "thought"
46+
47+
48+
class AgentRun(BaseModel):
49+
"""Agent Run model."""
50+
51+
model_config = ConfigDict(extra="allow", populate_by_name=True)
52+
53+
id: str
54+
agent_user: str
55+
comment: str | None = None
56+
source_comment: str | None = None
57+
creator: str
58+
stopped_at: str | None = None
59+
stopped_by: str | None = None
60+
started_at: str
61+
ended_at: str | None = None
62+
external_link: str | None = None
63+
issue: str | None = None
64+
workspace: str
65+
project: str | None = None
66+
status: AgentRunStatus
67+
error_metadata: dict[str, Any] | None = None
68+
type: AgentRunType
69+
created_at: str | None = None
70+
updated_at: str | None = None
71+
72+
73+
class CreateAgentRun(BaseModel):
74+
"""Create agent run request model."""
75+
76+
model_config = ConfigDict(extra="ignore", populate_by_name=True)
77+
78+
agent_slug: str
79+
issue: str | None = None
80+
project: str | None = None
81+
comment: str | None = None
82+
source_comment: str | None = None
83+
external_link: str | None = None
84+
type: AgentRunType | None = None
85+
86+
87+
class AgentRunActivityActionContent(BaseModel):
88+
"""Agent run activity content for action type."""
89+
90+
model_config = ConfigDict(extra="allow", populate_by_name=True)
91+
92+
type: Literal["action"]
93+
action: str
94+
parameters: dict[str, str]
95+
96+
97+
class AgentRunActivityTextContent(BaseModel):
98+
"""Agent run activity content for non-action types."""
99+
100+
model_config = ConfigDict(extra="allow", populate_by_name=True)
101+
102+
type: Literal["elicitation", "error", "prompt", "response", "thought"]
103+
body: str
104+
105+
106+
AgentRunActivityContent = AgentRunActivityActionContent | AgentRunActivityTextContent
107+
108+
109+
class AgentRunActivity(BaseModel):
110+
"""Agent Run Activity model."""
111+
112+
model_config = ConfigDict(extra="allow", populate_by_name=True)
113+
114+
id: str
115+
agent_run: str
116+
content: AgentRunActivityContent
117+
content_metadata: dict[str, Any] | None = None
118+
ephemeral: bool
119+
signal: AgentRunActivitySignal
120+
signal_metadata: dict[str, Any] | None = None
121+
comment: str | None = None
122+
actor: str | None = None
123+
type: AgentRunActivityType
124+
project: str | None = None
125+
workspace: str
126+
created_at: str | None = None
127+
updated_at: str | None = None
128+
129+
130+
class CreateAgentRunActivity(BaseModel):
131+
"""Create agent run activity request model."""
132+
133+
model_config = ConfigDict(extra="ignore", populate_by_name=True)
134+
135+
content: AgentRunActivityContent
136+
content_metadata: dict[str, Any] | None = None
137+
signal: AgentRunActivitySignal | None = None
138+
signal_metadata: dict[str, Any] | None = None
139+
type: Literal["action", "elicitation", "error", "response", "thought"]
140+
project: str | None = None
141+
142+
143+
class PaginatedAgentRunActivityResponse(BaseModel):
144+
"""Paginated agent run activity response."""
145+
146+
model_config = ConfigDict(extra="allow", populate_by_name=True)
147+
148+
results: list[AgentRunActivity]
149+
next_cursor: str | None = None
150+
prev_cursor: str | None = None
151+
next_page_results: bool | None = None
152+
prev_page_results: bool | None = None
153+
count: int | None = None
154+
total_pages: int | None = None
155+
total_results: int | None = None
156+
extra_stats: dict[str, Any] | None = None
157+

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
44

55
[project]
66
name = "plane-sdk"
7-
version = "0.2.3"
7+
version = "0.2.4"
88
description = "Python SDK for Plane API"
99
readme = "README.md"
1010
requires-python = ">=3.10"

tests/unit/conftest.py

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,9 @@
66
import pytest
77

88
from plane.client import PlaneClient
9+
from plane.models.agent_runs import AgentRun, CreateAgentRun
910
from plane.models.projects import CreateProject, Project
11+
from plane.models.work_items import CreateWorkItem, CreateWorkItemComment
1012

1113

1214
@pytest.fixture(scope="session")
@@ -73,3 +75,31 @@ def project(client: PlaneClient, workspace_slug: str) -> Project:
7375
client.projects.delete(workspace_slug, project.id)
7476
except Exception:
7577
pass
78+
79+
80+
@pytest.fixture(scope="session")
81+
def agent_run(client: PlaneClient, workspace_slug: str, project: Project) -> AgentRun:
82+
agent_slug = os.getenv("AGENT_SLUG")
83+
if not agent_slug:
84+
pytest.skip("AGENT_SLUG environment variable not set")
85+
"""Create a test agent run and yield it."""
86+
work_item = client.work_items.create(
87+
workspace_slug,
88+
project.id,
89+
CreateWorkItem(name="Test Work Item"),
90+
)
91+
comment = client.work_items.comments.create(
92+
workspace_slug,
93+
project.id,
94+
work_item.id,
95+
CreateWorkItemComment(comment_html="<p>This is a test comment</p>"),
96+
)
97+
agent_run = client.agent_runs.create(
98+
workspace_slug,
99+
CreateAgentRun(
100+
agent_slug=agent_slug,
101+
project=project.id,
102+
comment=comment.id,
103+
),
104+
)
105+
yield agent_run

0 commit comments

Comments
 (0)