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
10 changes: 6 additions & 4 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
# Changelog
All notable changes to this project's latest version.

## [0.1.31] - 2025-11-13
## [0.1.32] - 2025-11-14

### Features

- Added /architecture endpoint support to all server & sdk

### Miscellaneous Tasks

- Add repository information to package.json
- Simplify create-release workflow by removing pull request trigger
- Bump version to v0.1.31
- Bump version to v0.1.32

<!-- generated by git-cliff -->
6 changes: 3 additions & 3 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ build-backend = "hatchling.build"

[project]
name = "runagent"
version = "0.1.32"
version = "0.1.33"
description = "A command-line tool and SDK for deploying, managing, and interacting with AI agents"
readme = "README.md"
requires-python = ">=3.9"
Expand Down Expand Up @@ -103,7 +103,7 @@ line_length = 88
skip = ["docs"]

[tool.mypy]
python_version = "0.1.32"
python_version = "0.1.33"
warn_return_any = true
warn_unused_configs = true
disallow_untyped_defs = true
Expand Down Expand Up @@ -159,7 +159,7 @@ fail_under = 80

[tool.ruff]
line-length = 88
target-version = "0.1.32"
target-version = "0.1.33"
select = [
"E", # pycodestyle errors
"W", # pycodestyle warnings
Expand Down
2 changes: 1 addition & 1 deletion runagent-go/runagent/version.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package runagent

// Version represents the current version of the RunAgent Go SDK
const Version = "0.1.32"
const Version = "0.1.33"
2 changes: 1 addition & 1 deletion runagent-rust/runagent/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "runagent"
version = "0.1.32"
version = "0.1.33"
edition = "2021"
description = "RunAgent SDK for Rust - Client SDK for interacting with deployed AI agents"
license = "MIT"
Expand Down
4 changes: 2 additions & 2 deletions runagent-ts/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion runagent-ts/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "runagent",
"version": "0.1.32",
"version": "0.1.33",
"type": "module",
"files": [
"dist"
Expand Down
12 changes: 12 additions & 0 deletions runagent-ts/src/client/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -271,6 +271,18 @@ export class RunAgentClient {
);

if (!selectedEntrypoint) {
const availableEntrypoints =
this.agentArchitecture.entrypoints
?.map((entrypoint) => entrypoint.tag)
.filter(Boolean) || [];

console.error('[RunAgentClient] Entrypoint not found.', {
requested: this.entrypointTag,
agentId: this.agentId,
availableEntrypoints,
architecture: this.agentArchitecture,
});

throw new Error(
`Entrypoint \`${this.entrypointTag}\` not found in agent ${this.agentId}`
);
Expand Down
2 changes: 1 addition & 1 deletion runagent/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
built with frameworks like LangGraph, LangChain, and LlamaIndex.
"""

__version__ = "0.1.32"
__version__ = "0.1.33"

from .client import RunAgentClient

Expand Down
2 changes: 1 addition & 1 deletion runagent/__version__.py
Original file line number Diff line number Diff line change
@@ -1 +1 @@
__version__ = "0.1.32"
__version__ = "0.1.33"
11 changes: 10 additions & 1 deletion runagent/cli/commands/run_stream.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
TemplateError,
ValidationError,
)
from runagent.client.client import RunAgentClient
from runagent.client.client import RunAgentClient, RunAgentExecutionError
from runagent.sdk.server.local_server import LocalServer
from runagent.utils.agent import detect_framework
from runagent.utils.animation import show_subtle_robotic_runner, show_quick_runner
Expand Down Expand Up @@ -193,6 +193,15 @@ def run_stream(ctx, agent_id, host, port, input_file, local, tag, timeout):
for chunk in ra_client.run_stream(**input_params):
console.print(chunk)

except RunAgentExecutionError as e:
if os.getenv('DISABLE_TRY_CATCH'):
raise
console.print(f"[bold red]❌ {e.message}[/bold red]")
if e.suggestion:
console.print(f"[cyan]Suggestion: {e.suggestion}[/cyan]")
import sys
sys.exit(1)

except Exception as e:
if os.getenv('DISABLE_TRY_CATCH'):
raise
Expand Down
112 changes: 103 additions & 9 deletions runagent/client/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,9 @@ def __init__(self, agent_id: str, entrypoint_tag: str, local: bool = True, host:
self.local = local
self.agent_id = agent_id
self.entrypoint_tag = entrypoint_tag
self.dashboard_url = f"https://app.run-agent.ai/dashboard/agents/{self.agent_id}"
self.agent_host = None
self.agent_port = None

# FIXED: Detect if this is a streaming entrypoint
self.is_streaming = entrypoint_tag.endswith("_stream")
Expand All @@ -48,12 +51,17 @@ def __init__(self, agent_id: str, entrypoint_tag: str, local: bool = True, host:

console.print(f"🔍 [cyan]Auto-resolved address for agent {agent_id}: {agent_host}:{agent_port}[/cyan]")

self.agent_host = agent_host
self.agent_port = agent_port

agent_base_url_local = f"http://{agent_host}:{agent_port}"
agent_socket_url_local = f"ws://{agent_host}:{agent_port}"

self.rest_client = RestClient(base_url=agent_base_url_local)
self.socket_client = SocketClient(base_socket_url=agent_socket_url_local)
else:
self.agent_host = None
self.agent_port = None
self.rest_client = RestClient(is_local=False)
self.socket_client = SocketClient(is_local=False)

Expand Down Expand Up @@ -119,15 +127,26 @@ def run(self, *input_args, **input_kwargs):

def run_stream(self, *input_args, **input_kwargs):
"""Stream agent execution results in real-time via WebSocket"""
try:
# FIXED: Return the generator directly, don't try to iterate here
return self.socket_client.run_stream(
self.agent_id, self.entrypoint_tag, input_args=input_args, input_kwargs=input_kwargs
)
except Exception as e:
# Error message is already cleaned by socket_client._clean_error_message
# Just re-raise with the cleaned message
raise Exception(str(e))

socket_iterator = self.socket_client.run_stream(
self.agent_id, self.entrypoint_tag, input_args=input_args, input_kwargs=input_kwargs
)

def _iterator():
try:
for chunk in socket_iterator:
yield chunk
except Exception as e:
raw_message = str(e)
friendly_message, code, suggestion = self._classify_stream_error(raw_message)
raise RunAgentExecutionError(
code=code,
message=friendly_message,
suggestion=suggestion,
details={"raw_error": raw_message} if raw_message else None,
)

return _iterator()

def _run_stream(self, *input_args, **input_kwargs):
"""Legacy method - use run_stream instead"""
Expand Down Expand Up @@ -180,6 +199,81 @@ def _build_suggestion(self, code: str, message: str) -> str | None:

return None

def _classify_stream_error(self, raw_message: str) -> tuple[str, str, str | None]:
"""Derive a friendly error message, error code, and suggestion for streaming failures."""
message = raw_message or "Unknown streaming error"
lower_msg = message.lower()

# Connection issues
connection_keywords = [
"connection refused",
"failed to connect",
"cannot connect",
"did not respond",
"connection closed",
]
if any(keyword in lower_msg for keyword in connection_keywords):
friendly = "Unable to connect to the streaming endpoint."
suggestion = self._connection_hint()
return friendly, "CONNECTION_ERROR", suggestion

# Authentication / permission issues
if any(keyword in lower_msg for keyword in ["unauthorized", "invalid token", "401"]):
friendly = "Authentication failed for the streaming request."
suggestion = self._build_suggestion("AUTHENTICATION_ERROR", raw_message)
return friendly, "AUTHENTICATION_ERROR", suggestion

if any(keyword in lower_msg for keyword in ["permission", "forbidden", "403", "access denied"]):
friendly = "You do not have permission to stream this agent."
suggestion = self._build_suggestion("PERMISSION_ERROR", raw_message)
return friendly, "PERMISSION_ERROR", suggestion

# Not found errors
if "not found" in lower_msg or "404" in lower_msg:
friendly = "The agent or entrypoint tag was not found."
suggestion = self._build_suggestion("NOT_FOUND", raw_message)
if not suggestion:
suggestion = (
f"Confirm the agent ID and entrypoint `{self.entrypoint_tag}` exist. "
f"Review the agent in the dashboard: {self.dashboard_url}."
)
return friendly, "NOT_FOUND", suggestion

# Timeout
if "timeout" in lower_msg or "timed out" in lower_msg:
friendly = "The streaming connection timed out."
suggestion = (
"Ensure the agent is still running and producing output, or increase the timeout value."
)
return friendly, "TIMEOUT", suggestion

# Server errors
if any(keyword in lower_msg for keyword in ["500", "internal server error", "bad gateway", "502", "503"]):
friendly = "The server returned an error while streaming."
suggestion = (
"Try the request again. If the issue persists, inspect the agent logs or redeploy the agent."
)
return friendly, "SERVER_ERROR", suggestion

# Default fallback
suggestion = self._build_suggestion("STREAM_ERROR", raw_message)
return message, "STREAM_ERROR", suggestion

def _connection_hint(self) -> str:
if self.local:
if self.agent_host and self.agent_port:
return (
f"Ensure the local agent is running at ws://{self.agent_host}:{self.agent_port} "
f"and that entrypoint `{self.entrypoint_tag}` exposes streaming output. "
"Restart the agent with `runagent serve` if needed."
)
return "Ensure the local agent is running and reachable before streaming."

return (
"Check your internet connection and confirm the agent is deployed in RunAgent Cloud. "
f"Review its status in the dashboard: {self.dashboard_url}."
)

def _build_error_from_string(self, error_value) -> RunAgentExecutionError:
if isinstance(error_value, RunAgentExecutionError):
return error_value
Expand Down