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
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,9 @@ Thumbs.db
*.swo
*~

# Scratch files
scratch_*

# Logs and temp
*.log
npm-debug.log*
Expand Down
34 changes: 9 additions & 25 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,36 +23,20 @@ This starts a WebSocket server on port 8080 using the OpenAI agent handler by de

```python
import asyncio
import json
from runtimeuse_client import RuntimeUseClient, InvocationMessage, ResultMessageInterface
from runtimeuse_client import RuntimeUseClient, QueryOptions

async def main():
client = RuntimeUseClient(ws_url="ws://localhost:8080")

invocation = InvocationMessage(
message_type="invocation_message",
source_id="my-run-001",
model="gpt-4.1",
system_prompt="You are a helpful assistant.",
user_prompt="What is 2 + 2?",
output_format_json_schema_str=json.dumps({
"type": "json_schema",
"schema": {
"type": "object",
"properties": {"answer": {"type": "string"}},
},
}),
secrets_to_redact=[],
result = await client.query(
prompt="What is 2 + 2?",
options=QueryOptions(
system_prompt="You are a helpful assistant.",
model="gpt-4.1",
),
)

async def on_result(result: ResultMessageInterface):
print(result.structured_output)

await client.invoke(
invocation=invocation,
on_result_message=on_result,
result_message_cls=ResultMessageInterface,
)
print(result.data.text)

asyncio.run(main())
```
Expand All @@ -63,7 +47,7 @@ asyncio.run(main())
import { RuntimeUseServer, openaiHandler } from "runtimeuse";

const server = new RuntimeUseServer({ handler: openaiHandler, port: 8080 });
await server.start();
await server.startListening();
```

## How It Works
Expand Down
127 changes: 65 additions & 62 deletions packages/runtimeuse-client-python/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ Handles the WebSocket connection lifecycle, message dispatch, artifact upload ha
## Installation

```bash
pip install runtimeuse
pip install runtimeuse-client
```

## Quick Start
Expand All @@ -16,36 +16,39 @@ Start the runtime inside any sandbox, then connect from outside:

```python
import asyncio
from runtimeuse import RuntimeUseClient, InvocationMessage, ResultMessageInterface
from runtimeuse_client import RuntimeUseClient, QueryOptions, TextResult, StructuredOutputResult

async def main():
# Start the runtime in a sandbox (provider-specific)
sandbox = Sandbox.create()
sandbox.run("npx -y runtimeuse")
ws_url = sandbox.get_url(8080)

# Connect and invoke
client = RuntimeUseClient(ws_url=ws_url)

invocation = InvocationMessage(
message_type="invocation_message",
source_id="my-run-001",
model="gpt-4.1",
system_prompt="You are a helpful assistant.",
user_prompt="Do the thing and return the result.",
output_format_json_schema_str='{"type":"json_schema","schema":{"type":"object"}}',
secrets_to_redact=["sk-secret-key"],
# Text response (no output schema)
result = await client.query(
prompt="What is the capital of France?",
options=QueryOptions(
system_prompt="You are a helpful assistant.",
model="gpt-4.1",
),
)

async def on_result(result: ResultMessageInterface):
print(f"Success: {result.structured_output.get('success')}")
print(f"Output: {result.structured_output}")

await client.invoke(
invocation=invocation,
on_result_message=on_result,
result_message_cls=ResultMessageInterface,
assert isinstance(result.data, TextResult)
print(result.data.text)

# Structured response (with output schema)
result = await client.query(
prompt="Return the capital of France.",
options=QueryOptions(
system_prompt="You are a helpful assistant.",
model="gpt-4.1",
output_format_json_schema_str='{"type":"json_schema","schema":{"type":"object"}}',
),
)
assert isinstance(result.data, StructuredOutputResult)
print(result.data.structured_output)
print(result.metadata) # execution metadata

asyncio.run(main())
```
Expand All @@ -60,29 +63,39 @@ client = RuntimeUseClient(ws_url="ws://localhost:8080")

### RuntimeUseClient

Manages the WebSocket connection to the agent runtime and runs the message loop: sends an invocation, iterates the response stream, and dispatches typed messages to your callbacks.
Manages the WebSocket connection to the agent runtime and runs the message loop: sends a prompt, iterates the response stream, and returns a `QueryResult`. Raises `AgentRuntimeError` if the runtime returns an error.

`query()` returns a `QueryResult` with `.data` (a `TextResult` or `StructuredOutputResult`) and `.metadata`.

```python
client = RuntimeUseClient(ws_url="ws://localhost:8080")

await client.invoke(
invocation=invocation,
on_result_message=on_result,
result_message_cls=ResultMessageInterface,
on_assistant_message=on_assistant, # optional
on_artifact_upload_request=on_artifact, # optional -- return ArtifactUploadResult
on_error_message=on_error, # optional
is_cancelled=check_cancelled, # optional -- async () -> bool
timeout=300, # optional -- seconds
result = await client.query(
prompt="Do the thing.",
options=QueryOptions(
system_prompt="You are a helpful assistant.",
model="gpt-4.1",
output_format_json_schema_str='...', # optional -- omit for text response
on_assistant_message=on_assistant, # optional
on_artifact_upload_request=on_artifact, # optional -- return ArtifactUploadResult
timeout=300, # optional -- seconds
),
)

if isinstance(result.data, TextResult):
print(result.data.text)
elif isinstance(result.data, StructuredOutputResult):
print(result.data.structured_output)

print(result.metadata) # execution metadata
```

### Artifact Upload Handshake

When the agent runtime requests an artifact upload, provide a callback that returns a presigned URL and content type. The client sends the response back automatically.

```python
from runtimeuse import ArtifactUploadResult
from runtimeuse_client import ArtifactUploadResult

async def on_artifact(request: ArtifactUploadRequestMessageInterface) -> ArtifactUploadResult:
presigned_url = await my_storage.create_presigned_url(request.filename)
Expand All @@ -92,60 +105,50 @@ async def on_artifact(request: ArtifactUploadRequestMessageInterface) -> Artifac

### Cancellation

Pass an `is_cancelled` callback to cancel a running invocation. When it returns `True`, the client sends a cancel message to the runtime and raises `CancelledException`.
Call `client.abort()` from any coroutine to cancel a running query. The client sends a cancel message to the runtime and `query` raises `CancelledException`.

```python
from runtimeuse import CancelledException
from runtimeuse_client import CancelledException

async def check_cancelled() -> bool:
return await db.is_run_cancelled(run_id)
async def cancel_after_delay(client, seconds):
await asyncio.sleep(seconds)
client.abort()

try:
await client.invoke(
invocation=invocation,
on_result_message=on_result,
result_message_cls=ResultMessageInterface,
is_cancelled=check_cancelled,
asyncio.create_task(cancel_after_delay(client, 30))
result = await client.query(
prompt="Do the thing.",
options=QueryOptions(
system_prompt="You are a helpful assistant.",
model="gpt-4.1",
),
)
except CancelledException:
print("Run was cancelled")
```

### Custom Result Types

Subclass `ResultMessageInterface` to add domain-specific fields:

```python
from runtimeuse import ResultMessageInterface

class MyResultMessage(ResultMessageInterface):
custom_score: float | None = None

await client.invoke(
invocation=invocation,
on_result_message=handle_my_result,
result_message_cls=MyResultMessage,
)
```

## API Reference

### Message Types
### Types

| Class | Description |
| ----------------------------------------- | ------------------------------------------------------ |
| `InvocationMessage` | Sent to the runtime to start an agent invocation |
| `ResultMessageInterface` | Structured result from the agent |
| `QueryOptions` | Configuration for `client.query()` (prompt options, callbacks, timeout) |
| `QueryResult` | Return type of `query()` (`.data`, `.metadata`) |
| `ResultMessageInterface` | Wire-format result message from the runtime |
| `TextResult` | Result variant when no output schema is specified (`.text`) |
| `StructuredOutputResult` | Result variant when an output schema is specified (`.structured_output`) |

| `AssistantMessageInterface` | Intermediate assistant text messages |
| `ArtifactUploadRequestMessageInterface` | Runtime requesting a presigned URL for artifact upload |
| `ArtifactUploadResponseMessageInterface` | Response with presigned URL sent back to runtime |
| `ErrorMessageInterface` | Error from the agent runtime |
| `CancelMessage` | Sent to cancel a running invocation |
| `CommandInterface` | Pre/post invocation shell command |
| `RuntimeEnvironmentDownloadableInterface` | File to download into the runtime before invocation |

### Exceptions

| Class | Description |
| -------------------- | ------------------------------------------- |
| `CancelledException` | Raised when `is_cancelled()` returns `True` |
| `AgentRuntimeError` | Raised when the agent runtime returns an error (carries `.error` and `.metadata`) |
| `CancelledException` | Raised when `client.abort()` is called during a query |
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,10 @@

from src.runtimeuse_client import (
AssistantMessageInterface,
ErrorMessageInterface,
AgentRuntimeError,
RuntimeUseClient,
InvocationMessage,
ResultMessageInterface,
QueryOptions,
StructuredOutputResult,
CommandInterface,
)

Expand All @@ -19,41 +19,34 @@ class Answer(BaseModel):

async def main():
client = RuntimeUseClient(ws_url="ws://localhost:8080")
invocation = InvocationMessage(
message_type="invocation_message",
source_id="my-source",
model="gpt-5.4",
pre_agent_invocation_commands=[
CommandInterface(
command="echo 'Hello, world!'",
cwd=os.getcwd(),
env={},
)
],
system_prompt="You are a helpful assistant.",
user_prompt="Search the web to find the answer to the question: 'What is the population of France grouped by region? Once you find the answer, run a python script to compute the sum of the total population of France.'",
output_format_json_schema_str=json.dumps(
{"type": "json_schema", "schema": Answer.model_json_schema()}
),
secrets_to_redact=[],
)

async def on_result(result: ResultMessageInterface):
print(f"Result: {result.structured_output}")

async def on_assistant_message(message: AssistantMessageInterface):
print(f"Assistant message: {message.text_blocks}")

async def on_error_message(message: ErrorMessageInterface):
print(f"Error message: {message.error}")

await client.invoke(
invocation=invocation,
on_result_message=on_result,
result_message_cls=ResultMessageInterface,
on_assistant_message=on_assistant_message,
on_error_message=on_error_message,
)
try:
result = await client.query(
prompt="Search the web to find the answer to the question: 'What is the population of France grouped by region? Once you find the answer, run a python script to compute the sum of the total population of France.'",
options=QueryOptions(
system_prompt="You are a helpful assistant.",
model="gpt-5.4",
output_format_json_schema_str=json.dumps(
{"type": "json_schema", "schema": Answer.model_json_schema()}
),
source_id="my-source",
pre_agent_invocation_commands=[
CommandInterface(
command="echo 'Hello, world!'",
cwd=os.getcwd(),
env={},
)
],
on_assistant_message=on_assistant_message,
),
)
assert isinstance(result.data, StructuredOutputResult)
print(f"Result: {result.data.structured_output}")
except AgentRuntimeError as e:
print(f"Error: {e.error}")


if __name__ == "__main__":
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,16 @@
from .client import RuntimeUseClient
from .transports import Transport, WebSocketTransport
from .exceptions import CancelledException
from .exceptions import AgentRuntimeError, CancelledException
from .types import (
AgentRuntimeMessageInterface,
RuntimeEnvironmentDownloadableInterface,
CommandInterface,
InvocationMessage,
QueryOptions,
QueryResult,
ResultMessageInterface,
TextResult,
StructuredOutputResult,
AssistantMessageInterface,
ArtifactUploadRequestMessageInterface,
ArtifactUploadResponseMessageInterface,
Expand All @@ -15,20 +19,23 @@
ArtifactUploadResult,
OnAssistantMessageCallback,
OnArtifactUploadRequestCallback,
OnErrorMessageCallback,
IsCancelledCallback,
)

__all__ = [
"RuntimeUseClient",
"Transport",
"WebSocketTransport",
"AgentRuntimeError",
"CancelledException",
"AgentRuntimeMessageInterface",
"RuntimeEnvironmentDownloadableInterface",
"CommandInterface",
"InvocationMessage",
"QueryOptions",
"QueryResult",
"ResultMessageInterface",
"TextResult",
"StructuredOutputResult",
"AssistantMessageInterface",
"ArtifactUploadRequestMessageInterface",
"ArtifactUploadResponseMessageInterface",
Expand All @@ -37,6 +44,4 @@
"ArtifactUploadResult",
"OnAssistantMessageCallback",
"OnArtifactUploadRequestCallback",
"OnErrorMessageCallback",
"IsCancelledCallback",
]
Loading