diff --git a/CHANGELOG.md b/CHANGELOG.md index f9d85b1..4cf70c2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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 diff --git a/pyproject.toml b/pyproject.toml index 4780878..b3a757d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -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" @@ -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 @@ -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 diff --git a/runagent-go/runagent/version.go b/runagent-go/runagent/version.go index b38a704..1f83b04 100644 --- a/runagent-go/runagent/version.go +++ b/runagent-go/runagent/version.go @@ -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" diff --git a/runagent-rust/runagent/Cargo.toml b/runagent-rust/runagent/Cargo.toml index 1a75289..8a731fd 100644 --- a/runagent-rust/runagent/Cargo.toml +++ b/runagent-rust/runagent/Cargo.toml @@ -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" diff --git a/runagent-ts/package-lock.json b/runagent-ts/package-lock.json index aae5f2d..e1b407d 100644 --- a/runagent-ts/package-lock.json +++ b/runagent-ts/package-lock.json @@ -1,12 +1,12 @@ { "name": "runagent", - "version": "0.1.32", + "version": "0.1.33", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "runagent", - "version": "0.1.32", + "version": "0.1.33", "dependencies": { "better-sqlite3": "^12.2.0" }, diff --git a/runagent-ts/package.json b/runagent-ts/package.json index 48e4144..c710750 100644 --- a/runagent-ts/package.json +++ b/runagent-ts/package.json @@ -1,6 +1,6 @@ { "name": "runagent", - "version": "0.1.32", + "version": "0.1.33", "type": "module", "files": [ "dist" diff --git a/runagent-ts/src/client/index.ts b/runagent-ts/src/client/index.ts index 8bf45fc..a47664b 100644 --- a/runagent-ts/src/client/index.ts +++ b/runagent-ts/src/client/index.ts @@ -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}` ); diff --git a/runagent/__init__.py b/runagent/__init__.py index 8dcaab7..800b4ff 100644 --- a/runagent/__init__.py +++ b/runagent/__init__.py @@ -5,7 +5,7 @@ built with frameworks like LangGraph, LangChain, and LlamaIndex. """ -__version__ = "0.1.32" +__version__ = "0.1.33" from .client import RunAgentClient diff --git a/runagent/__version__.py b/runagent/__version__.py index 57cc51c..c2e5539 100644 --- a/runagent/__version__.py +++ b/runagent/__version__.py @@ -1 +1 @@ -__version__ = "0.1.32" +__version__ = "0.1.33" diff --git a/runagent/cli/commands/run_stream.py b/runagent/cli/commands/run_stream.py index 3393526..da92bf1 100644 --- a/runagent/cli/commands/run_stream.py +++ b/runagent/cli/commands/run_stream.py @@ -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 @@ -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 diff --git a/runagent/client/client.py b/runagent/client/client.py index b3aa582..ef84e4f 100644 --- a/runagent/client/client.py +++ b/runagent/client/client.py @@ -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") @@ -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) @@ -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""" @@ -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