Skip to content
Merged
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
40 changes: 38 additions & 2 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -89,13 +89,49 @@ Most tests require you to [set up a mock server](https://github.com/stoplightio/

```sh
# you will need npm installed
$ npx prism mock path/to/your/openapi.yml
npx prism mock path/to/your/openapi.yml
```

```sh
$ ./scripts/test
# run all tests
./scripts/test

# pass in pytest args, eg to show info on skipped tests:
./scripts/test -rs

# Run tests for only one python version
UV_PYTHON=3.13 ./scripts/test
```

### Running pytets

Assuming you have a uv virtual env set up in .venv, the following are helpful for more granular test running:

```sh
# Run all tests pytests
.venv/bin/pytest tests/

# Run all SDK tests with verbose info
.venv/bin/pytest tests/sdk/ -v

# Run specific test class
.venv/bin/pytest tests/sdk/test_clients.py::TestAgentClient -v

# Run specific test method
.venv/bin/pytest tests/sdk/test_clients.py::TestAgentClient::test_create_from_npm -v

# Run agent smoketests (requires RUNLOOP_API_KEY)
export RUNLOOP_API_KEY=your_key_here
.venv/bin/pytest tests/smoketests/sdk/test_agent.py -v

# Run tests matching a pattern
.venv/bin/pytest tests/sdk/ -k "agent" -v

# Run with coverage
.venv/bin/pytest tests/sdk/ --cov=src/runloop_api_client/sdk --cov-report=html
```


## Linting and formatting

This repository uses [ruff](https://github.com/astral-sh/ruff) and
Expand Down
6 changes: 6 additions & 0 deletions docs/Makefile
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
# Minimal makefile for Sphinx documentation
#
# To rebuild the html docs, first make sure you've got the right deps installed via
# uv sync --group docs
# then from the docs directory (this dir) rebuild with
# uv run make html
# Look for generated docs under _build/html.


# You can set these variables from the command line, and also
# from the environment for the first two.
Expand Down
20 changes: 19 additions & 1 deletion docs/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,25 @@
# https://www.sphinx-doc.org/en/master/usage/configuration.html#options-for-html-output

html_theme = "furo"
html_static_path = ["_static"]
# html_static_path = ["_static"]

# Furo theme options
html_theme_options = {
"navigation_with_keys": True,
"sidebar_hide_name": False,
}

# Show the global toctree in sidebar
html_sidebars = {
"**": [
"sidebar/scroll-start.html",
"sidebar/brand.html",
"sidebar/search.html",
"sidebar/navigation.html",
"sidebar/ethical-ads.html",
"sidebar/scroll-end.html",
]
}

# -- Extension configuration -------------------------------------------------

Expand Down
7 changes: 7 additions & 0 deletions docs/sdk/async/agent.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
Agent
======

The ``AsyncAgent`` class provides asynchronous methods for managing and interacting with stored Agents.

.. automodule:: runloop_api_client.sdk.async_agent
:members:
2 changes: 1 addition & 1 deletion docs/sdk/async/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -27,4 +27,4 @@ Asynchronous resource classes for working with devboxes, blueprints, snapshots,
snapshot
storage_object
scorer

agent
2 changes: 1 addition & 1 deletion docs/sdk/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,6 @@ The Runloop SDK provides both synchronous and asynchronous interfaces for managi
:maxdepth: 2
:caption: SDK Documentation

sync/index
async/index
sync/index
types
7 changes: 7 additions & 0 deletions docs/sdk/sync/agent.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
Agent
======

The ``Agent`` class provides synchronous methods for managing and interacting with stored Agents.

.. automodule:: runloop_api_client.sdk.agent
:members:
1 change: 1 addition & 0 deletions docs/sdk/sync/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -27,4 +27,5 @@ Synchronous resource classes for working with devboxes, blueprints, snapshots, a
snapshot
storage_object
scorer
agent

2 changes: 1 addition & 1 deletion scripts/lint
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ set -e
cd "$(dirname "$0")/.."

echo "==> Running lints"
uv run ruff check .
uv run ruff check . "$@"

echo "==> Making sure it imports"
uv run python -c 'import runloop_api_client'
9 changes: 8 additions & 1 deletion src/runloop_api_client/sdk/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,10 @@

from __future__ import annotations

from .sync import DevboxOps, ScorerOps, RunloopSDK, SnapshotOps, BlueprintOps, StorageObjectOps
from .sync import AgentOps, DevboxOps, ScorerOps, RunloopSDK, SnapshotOps, BlueprintOps, StorageObjectOps
from .agent import Agent
from .async_ import (
AsyncAgentOps,
AsyncDevboxOps,
AsyncScorerOps,
AsyncRunloopSDK,
Expand All @@ -19,6 +21,7 @@
from .snapshot import Snapshot
from .blueprint import Blueprint
from .execution import Execution
from .async_agent import AsyncAgent
from .async_devbox import AsyncDevbox, AsyncNamedShell
from .async_scorer import AsyncScorer
from .async_snapshot import AsyncSnapshot
Expand All @@ -34,6 +37,8 @@
"RunloopSDK",
"AsyncRunloopSDK",
# Management interfaces
"AgentOps",
"AsyncAgentOps",
"DevboxOps",
"AsyncDevboxOps",
"BlueprintOps",
Expand All @@ -45,6 +50,8 @@
"StorageObjectOps",
"AsyncStorageObjectOps",
# Resource classes
"Agent",
"AsyncAgent",
"Devbox",
"AsyncDevbox",
"Execution",
Expand Down
10 changes: 10 additions & 0 deletions src/runloop_api_client/sdk/_types.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,10 @@
from ..lib.polling import PollingConfig
from ..types.devboxes import DiskSnapshotListParams, DiskSnapshotUpdateParams
from ..types.scenarios import ScorerListParams, ScorerCreateParams, ScorerUpdateParams, ScorerValidateParams
from ..types.agent_list_params import AgentListParams
from ..types.devbox_list_params import DevboxListParams
from ..types.object_list_params import ObjectListParams
from ..types.agent_create_params import AgentCreateParams
from ..types.devbox_create_params import DevboxCreateParams, DevboxBaseCreateParams
from ..types.object_create_params import ObjectCreateParams
from ..types.blueprint_list_params import BlueprintListParams
Expand Down Expand Up @@ -157,3 +159,11 @@ class SDKScorerUpdateParams(ScorerUpdateParams, LongRequestOptions):

class SDKScorerValidateParams(ScorerValidateParams, LongRequestOptions):
pass


class SDKAgentCreateParams(AgentCreateParams, LongRequestOptions):
pass


class SDKAgentListParams(AgentListParams, BaseRequestOptions):
pass
75 changes: 75 additions & 0 deletions src/runloop_api_client/sdk/agent.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
"""Agent resource class for synchronous operations."""

from __future__ import annotations

from typing import Optional
from typing_extensions import Unpack, override

from ._types import (
BaseRequestOptions,
)
from .._client import Runloop
from ..types.agent_view import AgentView


class Agent:
"""Wrapper around synchronous agent operations.

This class provides a Pythonic interface for interacting with agents,
including retrieving agent information.

Example:
>>> agent = runloop.agent.create_from_npm(
... name="my-agent",
... package_name="@runloop/example-agent"
... )
>>> info = agent.get_info()
>>> print(info.name)
"""

def __init__(
self,
client: Runloop,
agent_id: str,
agent_view: Optional[AgentView] = None,
) -> None:
"""Initialize the wrapper.

:param client: Generated Runloop client
:type client: Runloop
:param agent_id: Agent identifier returned by the API
:type agent_id: str
"""
self._client = client
self._id = agent_id
self._agent_view = agent_view

@override
def __repr__(self) -> str:
return f"<Agent id={self._id!r}>"

@property
def id(self) -> str:
"""Return the agent identifier.

:return: Unique agent ID
:rtype: str
"""
return self._id

def get_info(
self,
**options: Unpack[BaseRequestOptions],
) -> AgentView:
"""Retrieve the latest agent information.

:param options: Optional request configuration
:return: Agent details
:rtype: AgentView
"""
if self._agent_view is None:
self._agent_view = self._client.agents.retrieve(
self._id,
**options,
)
return self._agent_view
Loading