Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
8 changes: 6 additions & 2 deletions src/runloop_api_client/resources/axons.py
Original file line number Diff line number Diff line change
Expand Up @@ -227,10 +227,12 @@ def subscribe_sse(
"""
if not id:
raise ValueError(f"Expected a non-empty value for `id` but received {id!r}")
default_headers: Headers = {"Accept": "text/event-stream"}
merged_headers = default_headers if extra_headers is None else {**default_headers, **extra_headers}
return self._get(
path_template("/v1/axons/{id}/subscribe/sse", id=id),
options=make_request_options(
extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
extra_headers=merged_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
),
cast_to=AxonEventView,
stream=True,
Expand Down Expand Up @@ -437,10 +439,12 @@ async def subscribe_sse(
"""
if not id:
raise ValueError(f"Expected a non-empty value for `id` but received {id!r}")
default_headers: Headers = {"Accept": "text/event-stream"}
merged_headers = default_headers if extra_headers is None else {**default_headers, **extra_headers}
return await self._get(
path_template("/v1/axons/{id}/subscribe/sse", id=id),
options=make_request_options(
extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
extra_headers=merged_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
),
cast_to=AxonEventView,
stream=True,
Expand Down
8 changes: 8 additions & 0 deletions src/runloop_api_client/sdk/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,9 @@

from __future__ import annotations

from .axon import Axon
from .sync import (
AxonOps,
AgentOps,
DevboxOps,
ScorerOps,
Expand All @@ -23,6 +25,7 @@
from .agent import Agent
from ._types import ScenarioPreview
from .async_ import (
AsyncAxonOps,
AsyncAgentOps,
AsyncDevboxOps,
AsyncScorerOps,
Expand All @@ -45,6 +48,7 @@
from .benchmark import Benchmark
from .blueprint import Blueprint
from .execution import Execution
from .async_axon import AsyncAxon
from .mcp_config import McpConfig
from .async_agent import AsyncAgent
from .async_devbox import AsyncDevbox, AsyncNamedShell
Expand Down Expand Up @@ -78,6 +82,8 @@
# Management interfaces
"AgentOps",
"AsyncAgentOps",
"AxonOps",
"AsyncAxonOps",
"BenchmarkOps",
"AsyncBenchmarkOps",
"DevboxOps",
Expand All @@ -103,6 +109,8 @@
# Resource classes
"Agent",
"AsyncAgent",
"Axon",
"AsyncAxon",
"AsyncSecret",
"Benchmark",
"AsyncBenchmark",
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,9 +5,11 @@
InputContext,
ScenarioView,
AgentListParams,
AxonCreateParams,
DevboxListParams,
ObjectListParams,
AgentCreateParams,
AxonPublishParams,
DevboxCreateParams,
ObjectCreateParams,
ScenarioListParams,
Expand Down Expand Up @@ -186,6 +188,14 @@ class SDKAgentListParams(AgentListParams, BaseRequestOptions):
pass


class SDKAxonCreateParams(AxonCreateParams, LongRequestOptions):
pass


class SDKAxonPublishParams(AxonPublishParams, LongRequestOptions):
pass


class SDKScenarioListParams(ScenarioListParams, BaseRequestOptions):
pass

Expand Down
33 changes: 33 additions & 0 deletions src/runloop_api_client/sdk/async_.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
BaseRequestOptions,
LongRequestOptions,
SDKAgentListParams,
SDKAxonCreateParams,
SDKDevboxListParams,
SDKObjectListParams,
SDKScorerListParams,
Expand All @@ -38,6 +39,7 @@
from .._types import Timeout, NotGiven, not_given
from .._client import DEFAULT_MAX_RETRIES, AsyncRunloop
from ._helpers import detect_content_type
from .async_axon import AsyncAxon
from .async_agent import AsyncAgent
from .async_devbox import AsyncDevbox
from .async_scorer import AsyncScorer
Expand Down Expand Up @@ -521,6 +523,33 @@ async def upload_from_bytes(
return obj


class AsyncAxonOps:
"""[Beta] Create and manage axons (async). Access via ``runloop.axon``.

Example:
>>> runloop = AsyncRunloopSDK()
>>> axon = await runloop.axon.create()
>>> await axon.publish(event_type="test", origin="USER_EVENT", payload="{}", source="sdk")
"""

def __init__(self, client: AsyncRunloop) -> None:
self._client = client

async def create(self, **params: Unpack[SDKAxonCreateParams]) -> AsyncAxon:
"""[Beta] Create a new axon."""
response = await self._client.axons.create(**params)
return AsyncAxon(self._client, response.id)

def from_id(self, axon_id: str) -> AsyncAxon:
"""Get an AsyncAxon instance for an existing axon ID."""
return AsyncAxon(self._client, axon_id)

async def list(self, **options: Unpack[BaseRequestOptions]) -> list[AsyncAxon]:
"""[Beta] List all active axons."""
result = await self._client.axons.list(**options)
return [AsyncAxon(self._client, axon.id) for axon in result.axons]


class AsyncScorerOps:
"""Create and manage custom scorers (async). Access via ``runloop.scorer``.

Expand Down Expand Up @@ -1205,6 +1234,8 @@ class AsyncRunloopSDK:
:vartype api: AsyncRunloop
:ivar agent: High-level async interface for agent management.
:vartype agent: AsyncAgentOps
:ivar axon: [Beta] High-level async interface for axon management
:vartype axon: AsyncAxonOps
:ivar benchmark: High-level async interface for benchmark management
:vartype benchmark: AsyncBenchmarkOps
:ivar devbox: High-level async interface for devbox management
Expand Down Expand Up @@ -1238,6 +1269,7 @@ class AsyncRunloopSDK:

api: AsyncRunloop
agent: AsyncAgentOps
axon: AsyncAxonOps
benchmark: AsyncBenchmarkOps
devbox: AsyncDevboxOps
blueprint: AsyncBlueprintOps
Expand Down Expand Up @@ -1289,6 +1321,7 @@ def __init__(
)

self.agent = AsyncAgentOps(self.api)
self.axon = AsyncAxonOps(self.api)
self.benchmark = AsyncBenchmarkOps(self.api)
self.devbox = AsyncDevboxOps(self.api)
self.blueprint = AsyncBlueprintOps(self.api)
Expand Down
53 changes: 53 additions & 0 deletions src/runloop_api_client/sdk/async_axon.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
"""Axon resource class for asynchronous operations."""

from __future__ import annotations

from typing_extensions import Unpack, override

from ._types import (
BaseRequestOptions,
SDKAxonPublishParams,
)
from .._client import AsyncRunloop
from .._streaming import AsyncStream
from ..types.axon_view import AxonView
from ..types.axon_event_view import AxonEventView
from ..types.publish_result_view import PublishResultView


class AsyncAxon:
"""[Beta] Wrapper around asynchronous axon operations.

Axons are event communication channels that support publishing events
and subscribing to event streams via server-sent events (SSE).
Obtain instances via ``runloop.axon.create()`` or ``runloop.axon.from_id()``.

Example:
>>> runloop = AsyncRunloopSDK()
>>> axon = await runloop.axon.create()
>>> await axon.publish(event_type="task_done", origin="AGENT_EVENT", payload="{}", source="my-agent")
"""

def __init__(self, client: AsyncRunloop, axon_id: str) -> None:
self._client = client
self._id = axon_id

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

@property
def id(self) -> str:
return self._id

async def get_info(self, **options: Unpack[BaseRequestOptions]) -> AxonView:
"""[Beta] Retrieve the latest axon information."""
return await self._client.axons.retrieve(self._id, **options)

async def publish(self, **params: Unpack[SDKAxonPublishParams]) -> PublishResultView:
"""[Beta] Publish an event to this axon."""
return await self._client.axons.publish(self._id, **params)

async def subscribe_sse(self, **options: Unpack[BaseRequestOptions]) -> AsyncStream[AxonEventView]:
"""[Beta] Subscribe to this axon's event stream via SSE."""
return await self._client.axons.subscribe_sse(self._id, **options)
53 changes: 53 additions & 0 deletions src/runloop_api_client/sdk/axon.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
"""Axon resource class for synchronous operations."""

from __future__ import annotations

from typing_extensions import Unpack, override

from ._types import (
BaseRequestOptions,
SDKAxonPublishParams,
)
from .._client import Runloop
from .._streaming import Stream
from ..types.axon_view import AxonView
from ..types.axon_event_view import AxonEventView
from ..types.publish_result_view import PublishResultView


class Axon:
"""[Beta] Wrapper around synchronous axon operations.

Axons are event communication channels that support publishing events
and subscribing to event streams via server-sent events (SSE).
Obtain instances via ``runloop.axon.create()`` or ``runloop.axon.from_id()``.

Example:
>>> runloop = RunloopSDK()
>>> axon = runloop.axon.create()
>>> axon.publish(event_type="task_done", origin="AGENT_EVENT", payload="{}", source="my-agent")
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

show how to listen on it too

"""

def __init__(self, client: Runloop, axon_id: str) -> None:
self._client = client
self._id = axon_id

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

@property
def id(self) -> str:
return self._id

def get_info(self, **options: Unpack[BaseRequestOptions]) -> AxonView:
"""[Beta] Retrieve the latest axon information."""
return self._client.axons.retrieve(self._id, **options)

def publish(self, **params: Unpack[SDKAxonPublishParams]) -> PublishResultView:
"""[Beta] Publish an event to this axon."""
return self._client.axons.publish(self._id, **params)

def subscribe_sse(self, **options: Unpack[BaseRequestOptions]) -> Stream[AxonEventView]:
"""[Beta] Subscribe to this axon's event stream via SSE."""
return self._client.axons.subscribe_sse(self._id, **options)
33 changes: 33 additions & 0 deletions src/runloop_api_client/sdk/sync.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,13 @@

import httpx

from .axon import Axon
from .agent import Agent
from ._types import (
BaseRequestOptions,
LongRequestOptions,
SDKAgentListParams,
SDKAxonCreateParams,
SDKDevboxListParams,
SDKObjectListParams,
SDKScorerListParams,
Expand Down Expand Up @@ -516,6 +518,33 @@ def upload_from_bytes(
return obj


class AxonOps:
"""[Beta] Create and manage axons. Access via ``runloop.axon``.

Example:
>>> runloop = RunloopSDK()
>>> axon = runloop.axon.create()
>>> axon.publish(event_type="test", origin="USER_EVENT", payload="{}", source="sdk")
"""

def __init__(self, client: Runloop) -> None:
self._client = client

def create(self, **params: Unpack[SDKAxonCreateParams]) -> Axon:
"""[Beta] Create a new axon."""
response = self._client.axons.create(**params)
return Axon(self._client, response.id)

def from_id(self, axon_id: str) -> Axon:
"""Get an Axon instance for an existing axon ID."""
return Axon(self._client, axon_id)

def list(self, **options: Unpack[BaseRequestOptions]) -> list[Axon]:
"""[Beta] List all active axons."""
result = self._client.axons.list(**options)
return [Axon(self._client, axon.id) for axon in result.axons]


class ScorerOps:
"""Create and manage custom scorers. Access via ``runloop.scorer``.

Expand Down Expand Up @@ -1230,6 +1259,8 @@ class RunloopSDK:
:vartype api: Runloop
:ivar agent: High-level interface for agent management.
:vartype agent: AgentOps
:ivar axon: [Beta] High-level interface for axon management
:vartype axon: AxonOps
:ivar benchmark: High-level interface for benchmark management
:vartype benchmark: BenchmarkOps
:ivar devbox: High-level interface for devbox management
Expand Down Expand Up @@ -1263,6 +1294,7 @@ class RunloopSDK:

api: Runloop
agent: AgentOps
axon: AxonOps
benchmark: BenchmarkOps
devbox: DevboxOps
blueprint: BlueprintOps
Expand Down Expand Up @@ -1314,6 +1346,7 @@ def __init__(
)

self.agent = AgentOps(self.api)
self.axon = AxonOps(self.api)
self.benchmark = BenchmarkOps(self.api)
self.devbox = DevboxOps(self.api)
self.blueprint = BlueprintOps(self.api)
Expand Down
24 changes: 24 additions & 0 deletions tests/sdk/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
"object": "obj_123",
"scorer": "sco_123",
"agent": "agt_123",
"axon": "axn_123",
"scenario": "scn_123",
"scenario_run": "scr_123",
"benchmark": "bmd_123",
Expand Down Expand Up @@ -115,6 +116,23 @@ class MockAgentView:
source: Any = None


@dataclass
class MockAxonView:
"""Mock AxonView for testing."""

id: str = TEST_IDS["axon"]
created_at_ms: int = 1234567890000
name: str = "test-axon"


@dataclass
class MockPublishResultView:
"""Mock PublishResultView for testing."""

sequence: int = 1
timestamp_ms: int = 1234567890000


@dataclass
class MockScenarioView:
"""Mock ScenarioView for testing."""
Expand Down Expand Up @@ -307,6 +325,12 @@ def agent_view() -> MockAgentView:
return MockAgentView()


@pytest.fixture
def axon_view() -> MockAxonView:
"""Create a mock AxonView."""
return MockAxonView()


@pytest.fixture
def scenario_view() -> MockScenarioView:
"""Create a mock ScenarioView."""
Expand Down
Loading