diff --git a/.gitignore b/.gitignore index 28d9bef..ca8f753 100644 --- a/.gitignore +++ b/.gitignore @@ -14,7 +14,6 @@ dist/ downloads/ eggs/ .eggs/ -lib/ lib64/ parts/ sdist/ diff --git a/README.md b/README.md index ba2d56c..75a45dd 100644 --- a/README.md +++ b/README.md @@ -26,6 +26,7 @@ runagent-js runagent-rs runagent-go + runagent-dart @@ -50,6 +51,11 @@ GitHub stars + + + pub.dev downloads + + @@ -66,6 +72,11 @@ Go Reference + + + pub.dev version + + @@ -219,14 +230,15 @@ async def solve_problem_stream(query, num_solutions, constraints): **๐ŸŒ Access from any language:** -RunAgent offers multi-language SDKs : Rust, TypeScript, JavaScript, Go, and beyondโ€”so you can integrate seamlessly without ever rewriting your agents for different stacks. +RunAgent offers multi-language SDKs : Rust, TypeScript, JavaScript, Go, Dart, and beyondโ€”so you can integrate seamlessly without ever rewriting your agents for different stacks. - - - - + + + + + +
Python SDKJavaScript SDKRust SDKGo SDKPython SDKJavaScript SDKRust SDKGo SDKDart SDK
@@ -381,6 +393,40 @@ func main() { } ``` + + +```dart +import 'package:runagent/runagent.dart'; + +void main() async { + final client = await RunAgentClient.create( + RunAgentClientConfig.create( + agentId: "lg-solver-123", + entrypointTag: "solve_problem", + local: true, + ), + ); + + final result = await client.run({ + "query": "My laptop is slow", + "num_solutions": 3, + "constraints": [ + {"type": "budget", "value": 100} + ], + }); + print(result); + + // Streaming + await for (final chunk in client.runStream({ + "query": "Fix my phone", + "num_solutions": 4, + })) { + print(chunk); + } +} +``` +
diff --git a/examples/journalist_agent/agent/main.py b/examples/journalist_agent/agent/main.py new file mode 100644 index 0000000..61b440d --- /dev/null +++ b/examples/journalist_agent/agent/main.py @@ -0,0 +1,70 @@ +from agno.agent import Agent +from agno.team import Team +from agno.tools.serper import SerperTools +from agno.tools.newspaper4k import Newspaper4kTools +from textwrap import dedent + +# Model string format: "provider:model_id" +model_string = "openai:gpt-4o-mini" + +# Create journalist team +journalist_team = Team( + model=model_string, + members=[ + Agent( + model=model_string, + name="Researcher", + role="Research Specialist", + tools=[SerperTools()], + instructions=dedent("""\ + You are a research specialist for the New York Times. + - Generate 3-5 relevant search terms for any given topic + - Use search_web to find authoritative, high-quality sources + - Analyze results and identify the 10 most credible URLs + - Prioritize official sources, academic papers, and reputable news outlets + """), + ), + Agent( + model=model_string, + name="Writer", + role="Senior Writer", + tools=[Newspaper4kTools()], + instructions=dedent("""\ + You are a senior writer for the New York Times. + - Use get_article_text to read content from provided URLs + - Write comprehensive articles with 15+ paragraphs + - Include proper citations and balanced perspectives + - Maintain NYT's high standards for clarity and engagement + - Never plagiarize or fabricate information + """), + ), + ], + instructions=dedent("""\ + You are the Editor-in-Chief of a journalism team at the New York Times. + Coordinate your team to produce high-quality articles: + 1. Direct the Researcher to find authoritative sources on the topic + 2. Have the Writer create a comprehensive article using those sources + 3. Review and refine the final article for accuracy, clarity, and engagement + Ensure every article meets NYT's prestigious standards. + """), +) + + +def create_article(topic: str): + """ + Create a high-quality news article on a given topic + + Args: + topic: The topic to write about + """ + response = journalist_team.run(topic, stream=False) + return { + "article": response.content, + "success": True + } + + +def create_article_stream(topic: str): + """Streaming version of article creation""" + for chunk in journalist_team.run(topic, stream=True): + yield {"content": chunk if hasattr(chunk, 'content') else str(chunk)} \ No newline at end of file diff --git a/examples/journalist_agent/agent/requirements.txt b/examples/journalist_agent/agent/requirements.txt new file mode 100644 index 0000000..493b942 --- /dev/null +++ b/examples/journalist_agent/agent/requirements.txt @@ -0,0 +1,6 @@ +streamlit +agno>=2.2.10 +openai +google-search-results +newspaper4k +lxml_html_clean \ No newline at end of file diff --git a/examples/journalist_agent/agent/runagent.config.json b/examples/journalist_agent/agent/runagent.config.json new file mode 100644 index 0000000..098a392 --- /dev/null +++ b/examples/journalist_agent/agent/runagent.config.json @@ -0,0 +1,32 @@ +{ + "agent_name": "Dart SDK based journalist", + "description": "My AI agent", + "framework": "default", + "template": "", + "version": "1.0.0", + "created_at": "2025-11-24T14:16:50.661307", + "template_source": { + "repo_url": "https://github.com/runagent-dev/runagent.git", + "author": "runagent-cli", + "path": "/home/azureuser/runagent/examples/journalist_agent/agent" + }, + "agent_architecture": { + "entrypoints": [ + { + "file": "main.py", + "module": "create_article", + "tag": "create_article" + }, + { + "file": "main.py", + "module": "create_article_stream", + "tag": "create_article_stream" + } + ] + }, + "env_vars": {}, + "agent_id": "9fac4988-d88e-4d6c-994c-7495c11de8b9", + "auth_settings": { + "type": "api_key" + } +} \ No newline at end of file diff --git a/examples/journalist_agent/dart_sdk/.dart_tool/package_config.json b/examples/journalist_agent/dart_sdk/.dart_tool/package_config.json new file mode 100644 index 0000000..158d6cc --- /dev/null +++ b/examples/journalist_agent/dart_sdk/.dart_tool/package_config.json @@ -0,0 +1,106 @@ +{ + "configVersion": 2, + "packages": [ + { + "name": "async", + "rootUri": "file:///home/azureuser/.pub-cache/hosted/pub.dev/async-2.13.0", + "packageUri": "lib/", + "languageVersion": "3.4" + }, + { + "name": "collection", + "rootUri": "file:///home/azureuser/.pub-cache/hosted/pub.dev/collection-1.19.1", + "packageUri": "lib/", + "languageVersion": "3.4" + }, + { + "name": "crypto", + "rootUri": "file:///home/azureuser/.pub-cache/hosted/pub.dev/crypto-3.0.7", + "packageUri": "lib/", + "languageVersion": "3.4" + }, + { + "name": "http", + "rootUri": "file:///home/azureuser/.pub-cache/hosted/pub.dev/http-1.6.0", + "packageUri": "lib/", + "languageVersion": "3.4" + }, + { + "name": "http_parser", + "rootUri": "file:///home/azureuser/.pub-cache/hosted/pub.dev/http_parser-4.1.2", + "packageUri": "lib/", + "languageVersion": "3.4" + }, + { + "name": "meta", + "rootUri": "file:///home/azureuser/.pub-cache/hosted/pub.dev/meta-1.17.0", + "packageUri": "lib/", + "languageVersion": "3.5" + }, + { + "name": "path", + "rootUri": "file:///home/azureuser/.pub-cache/hosted/pub.dev/path-1.9.1", + "packageUri": "lib/", + "languageVersion": "3.4" + }, + { + "name": "runagent", + "rootUri": "file:///home/azureuser/runagent/runagent-dart", + "packageUri": "lib/", + "languageVersion": "3.0" + }, + { + "name": "source_span", + "rootUri": "file:///home/azureuser/.pub-cache/hosted/pub.dev/source_span-1.10.1", + "packageUri": "lib/", + "languageVersion": "3.1" + }, + { + "name": "stream_channel", + "rootUri": "file:///home/azureuser/.pub-cache/hosted/pub.dev/stream_channel-2.1.4", + "packageUri": "lib/", + "languageVersion": "3.3" + }, + { + "name": "string_scanner", + "rootUri": "file:///home/azureuser/.pub-cache/hosted/pub.dev/string_scanner-1.4.1", + "packageUri": "lib/", + "languageVersion": "3.1" + }, + { + "name": "term_glyph", + "rootUri": "file:///home/azureuser/.pub-cache/hosted/pub.dev/term_glyph-1.2.2", + "packageUri": "lib/", + "languageVersion": "3.1" + }, + { + "name": "typed_data", + "rootUri": "file:///home/azureuser/.pub-cache/hosted/pub.dev/typed_data-1.4.0", + "packageUri": "lib/", + "languageVersion": "3.5" + }, + { + "name": "web", + "rootUri": "file:///home/azureuser/.pub-cache/hosted/pub.dev/web-0.5.1", + "packageUri": "lib/", + "languageVersion": "3.3" + }, + { + "name": "web_socket_channel", + "rootUri": "file:///home/azureuser/.pub-cache/hosted/pub.dev/web_socket_channel-2.4.5", + "packageUri": "lib/", + "languageVersion": "3.3" + }, + { + "name": "test_journalist", + "rootUri": "../", + "packageUri": "lib/", + "languageVersion": "3.0" + } + ], + "generator": "pub", + "generatorVersion": "3.10.0", + "flutterRoot": "file:///home/azureuser/snap/flutter/common/flutter", + "flutterVersion": "3.38.2", + "pubCache": "file:///home/azureuser/.pub-cache" +} diff --git a/examples/journalist_agent/dart_sdk/.dart_tool/package_graph.json b/examples/journalist_agent/dart_sdk/.dart_tool/package_graph.json new file mode 100644 index 0000000..d4c9016 --- /dev/null +++ b/examples/journalist_agent/dart_sdk/.dart_tool/package_graph.json @@ -0,0 +1,124 @@ +{ + "roots": [ + "test_journalist" + ], + "packages": [ + { + "name": "test_journalist", + "version": "0.1.0", + "dependencies": [ + "runagent" + ], + "devDependencies": [] + }, + { + "name": "runagent", + "version": "0.1.0", + "dependencies": [ + "http", + "web_socket_channel" + ] + }, + { + "name": "web_socket_channel", + "version": "2.4.5", + "dependencies": [ + "async", + "crypto", + "stream_channel", + "web" + ] + }, + { + "name": "web", + "version": "0.5.1", + "dependencies": [] + }, + { + "name": "stream_channel", + "version": "2.1.4", + "dependencies": [ + "async" + ] + }, + { + "name": "crypto", + "version": "3.0.7", + "dependencies": [ + "typed_data" + ] + }, + { + "name": "typed_data", + "version": "1.4.0", + "dependencies": [ + "collection" + ] + }, + { + "name": "collection", + "version": "1.19.1", + "dependencies": [] + }, + { + "name": "http", + "version": "1.6.0", + "dependencies": [ + "async", + "http_parser", + "meta", + "web" + ] + }, + { + "name": "http_parser", + "version": "4.1.2", + "dependencies": [ + "collection", + "source_span", + "string_scanner", + "typed_data" + ] + }, + { + "name": "string_scanner", + "version": "1.4.1", + "dependencies": [ + "source_span" + ] + }, + { + "name": "source_span", + "version": "1.10.1", + "dependencies": [ + "collection", + "path", + "term_glyph" + ] + }, + { + "name": "term_glyph", + "version": "1.2.2", + "dependencies": [] + }, + { + "name": "path", + "version": "1.9.1", + "dependencies": [] + }, + { + "name": "async", + "version": "2.13.0", + "dependencies": [ + "collection", + "meta" + ] + }, + { + "name": "meta", + "version": "1.17.0", + "dependencies": [] + } + ], + "configVersion": 1 +} \ No newline at end of file diff --git a/examples/journalist_agent/dart_sdk/Readme.md b/examples/journalist_agent/dart_sdk/Readme.md new file mode 100644 index 0000000..9bdb6d9 --- /dev/null +++ b/examples/journalist_agent/dart_sdk/Readme.md @@ -0,0 +1,113 @@ +# AI Journalist Agent - News Article Generator + +Generate high-quality, NYT-worthy articles on any topic using AI agents with web research capabilities. + + +## Setup + +### 1. Agent Setup + +```bash +cd agents +pip install -r requirements.txt +``` + +Set environment variables: +```bash +export OPENAI_API_KEY="your-openai-key" +export SERPER_API_KEY="your-serper-key" +``` + +### 2. Deploy Agent with RunAgent Local + +```bash +cd agents +runagent serve . +``` + +After deployment, you'll get an `agent_id`. Update this in: +- `backend/app.py` (both client instances) +- `test_dart/lib/main.dart` + +### 2.1 Local vs Cloud Runs + +- **Local:** Keep the `local` flag in `test_dart/lib/main.dart` (or your Dart sample) and make sure `host`/`port` point to your `runagent serve` process (defaults: `127.0.0.1:8451`). +- **Cloud:** Comment out the `local` override, then authenticate once per shell: + +```bash +export RUNAGENT_API_KEY="your-runagent-api-key" +``` + +Use the cloud agent ID returned from the dashboard when you deploy remotely. + +### 3. Backend Setup (Optional - for HTTP testing) + +```bash +cd backend +pip install -r requirements.txt +python app.py +``` + +### 4. Dart SDK Test + +```bash +cd test_dart +dart pub get +dart run lib/main.dart +``` + +## Agent Functions + +### Non-Streaming +**Entrypoint:** `article_create` +```dart +final result = await client.run({'topic': 'AI developments'}); +``` + +### Streaming +**Entrypoint:** `article_stream` +```dart +await for (final chunk in client.runStream({'topic': 'AI developments'})) { + print(chunk); +} +``` + +## Features + +- **AI-Powered Research**: Automatically searches web for relevant sources +- **Content Extraction**: Reads and analyzes articles from top sources +- **High-Quality Output**: NYT-worthy articles with proper attribution +- **Streaming Support**: Real-time article generation +- **Dart SDK Compatible**: Works with RunAgent Dart SDK + +## Configuration + +After running `runagent serve .`, a `runagent.config.json` will be automatically generated with your agent architecture and entrypoints. + +## Testing + +**Dart SDK (Recommended):** +```bash +cd test_dart +dart run lib/main.dart +``` + +**Backend API:** +```bash +# Non-streaming +curl -X POST http://localhost:5001/api/article \ + -H "Content-Type: application/json" \ + -d '{"topic": "Latest in quantum computing"}' + +# Streaming +curl -X POST http://localhost:5001/api/article/stream \ + -H "Content-Type: application/json" \ + -d '{"topic": "Climate change solutions"}' +``` + +## Notes + +- The agent requires OpenAI API key and Serper API key +- Articles are typically 15+ paragraphs with proper citations +- Processing time varies based on topic complexity (usually 1-3 minutes) +- Streaming provides real-time updates as the article is written \ No newline at end of file diff --git a/examples/journalist_agent/dart_sdk/lib/main.dart b/examples/journalist_agent/dart_sdk/lib/main.dart new file mode 100644 index 0000000..a3f6873 --- /dev/null +++ b/examples/journalist_agent/dart_sdk/lib/main.dart @@ -0,0 +1,36 @@ +import 'package:runagent/runagent.dart'; + +Future main() async { + try { + // Create client (update agent_id after deployment) + final client = await RunAgentClient.create( + RunAgentClientConfig.create( + agentId: '9fac4988-d88e-4d6c-994c-7495c11de8b9', + entrypointTag: 'create_article' + // local: true, host: '127.0.0.1', port: 8451, + ), + ); + + print('๐Ÿ—ž๏ธ Testing AI Journalist Agent (Streaming)'); + print('=' * 50); + + // Run article creation once (non-streaming) + final response = await client.run({ + 'topic': 'Recent developments in electric bike in south asia', + }); + + print('\nResponse Map:'); + print(response); + print('\n' + '=' * 50); + print('โœ… Article generation completed!'); + } catch (e) { + if (e is RunAgentError) { + print('โŒ Error: ${e.message}'); + if (e.suggestion != null) { + print('Suggestion: ${e.suggestion}'); + } + } else { + print('โŒ Unexpected error: $e'); + } + } +} \ No newline at end of file diff --git a/examples/journalist_agent/dart_sdk/pubspec.yaml b/examples/journalist_agent/dart_sdk/pubspec.yaml new file mode 100644 index 0000000..73b21f1 --- /dev/null +++ b/examples/journalist_agent/dart_sdk/pubspec.yaml @@ -0,0 +1,10 @@ +name: test_journalist +description: Test script for AI Journalist Agent with Dart SDK +version: 0.1.0 + +environment: + sdk: '>=3.0.0 <4.0.0' + +dependencies: + runagent: + path: /home/azureuser/runagent/runagent-dart \ No newline at end of file diff --git a/runagent-dart/.dart_tool/package_config.json b/runagent-dart/.dart_tool/package_config.json new file mode 100644 index 0000000..d619e8f --- /dev/null +++ b/runagent-dart/.dart_tool/package_config.json @@ -0,0 +1,304 @@ +{ + "configVersion": 2, + "packages": [ + { + "name": "_fe_analyzer_shared", + "rootUri": "file:///home/azureuser/.pub-cache/hosted/pub.dev/_fe_analyzer_shared-92.0.0", + "packageUri": "lib/", + "languageVersion": "3.9" + }, + { + "name": "analyzer", + "rootUri": "file:///home/azureuser/.pub-cache/hosted/pub.dev/analyzer-9.0.0", + "packageUri": "lib/", + "languageVersion": "3.9" + }, + { + "name": "args", + "rootUri": "file:///home/azureuser/.pub-cache/hosted/pub.dev/args-2.7.0", + "packageUri": "lib/", + "languageVersion": "3.3" + }, + { + "name": "async", + "rootUri": "file:///home/azureuser/.pub-cache/hosted/pub.dev/async-2.13.0", + "packageUri": "lib/", + "languageVersion": "3.4" + }, + { + "name": "boolean_selector", + "rootUri": "file:///home/azureuser/.pub-cache/hosted/pub.dev/boolean_selector-2.1.2", + "packageUri": "lib/", + "languageVersion": "3.1" + }, + { + "name": "cli_config", + "rootUri": "file:///home/azureuser/.pub-cache/hosted/pub.dev/cli_config-0.2.0", + "packageUri": "lib/", + "languageVersion": "3.0" + }, + { + "name": "collection", + "rootUri": "file:///home/azureuser/.pub-cache/hosted/pub.dev/collection-1.19.1", + "packageUri": "lib/", + "languageVersion": "3.4" + }, + { + "name": "convert", + "rootUri": "file:///home/azureuser/.pub-cache/hosted/pub.dev/convert-3.1.2", + "packageUri": "lib/", + "languageVersion": "3.4" + }, + { + "name": "coverage", + "rootUri": "file:///home/azureuser/.pub-cache/hosted/pub.dev/coverage-1.15.0", + "packageUri": "lib/", + "languageVersion": "3.4" + }, + { + "name": "crypto", + "rootUri": "file:///home/azureuser/.pub-cache/hosted/pub.dev/crypto-3.0.7", + "packageUri": "lib/", + "languageVersion": "3.4" + }, + { + "name": "file", + "rootUri": "file:///home/azureuser/.pub-cache/hosted/pub.dev/file-7.0.1", + "packageUri": "lib/", + "languageVersion": "3.0" + }, + { + "name": "frontend_server_client", + "rootUri": "file:///home/azureuser/.pub-cache/hosted/pub.dev/frontend_server_client-4.0.0", + "packageUri": "lib/", + "languageVersion": "3.0" + }, + { + "name": "glob", + "rootUri": "file:///home/azureuser/.pub-cache/hosted/pub.dev/glob-2.1.3", + "packageUri": "lib/", + "languageVersion": "3.3" + }, + { + "name": "http", + "rootUri": "file:///home/azureuser/.pub-cache/hosted/pub.dev/http-1.6.0", + "packageUri": "lib/", + "languageVersion": "3.4" + }, + { + "name": "http_multi_server", + "rootUri": "file:///home/azureuser/.pub-cache/hosted/pub.dev/http_multi_server-3.2.2", + "packageUri": "lib/", + "languageVersion": "3.2" + }, + { + "name": "http_parser", + "rootUri": "file:///home/azureuser/.pub-cache/hosted/pub.dev/http_parser-4.1.2", + "packageUri": "lib/", + "languageVersion": "3.4" + }, + { + "name": "io", + "rootUri": "file:///home/azureuser/.pub-cache/hosted/pub.dev/io-1.0.5", + "packageUri": "lib/", + "languageVersion": "3.4" + }, + { + "name": "lints", + "rootUri": "file:///home/azureuser/.pub-cache/hosted/pub.dev/lints-3.0.0", + "packageUri": "lib/", + "languageVersion": "3.0" + }, + { + "name": "logging", + "rootUri": "file:///home/azureuser/.pub-cache/hosted/pub.dev/logging-1.3.0", + "packageUri": "lib/", + "languageVersion": "3.4" + }, + { + "name": "matcher", + "rootUri": "file:///home/azureuser/.pub-cache/hosted/pub.dev/matcher-0.12.18", + "packageUri": "lib/", + "languageVersion": "3.7" + }, + { + "name": "meta", + "rootUri": "file:///home/azureuser/.pub-cache/hosted/pub.dev/meta-1.17.0", + "packageUri": "lib/", + "languageVersion": "3.5" + }, + { + "name": "mime", + "rootUri": "file:///home/azureuser/.pub-cache/hosted/pub.dev/mime-2.0.0", + "packageUri": "lib/", + "languageVersion": "3.2" + }, + { + "name": "node_preamble", + "rootUri": "file:///home/azureuser/.pub-cache/hosted/pub.dev/node_preamble-2.0.2", + "packageUri": "lib/", + "languageVersion": "2.12" + }, + { + "name": "package_config", + "rootUri": "file:///home/azureuser/.pub-cache/hosted/pub.dev/package_config-2.2.0", + "packageUri": "lib/", + "languageVersion": "3.4" + }, + { + "name": "path", + "rootUri": "file:///home/azureuser/.pub-cache/hosted/pub.dev/path-1.9.1", + "packageUri": "lib/", + "languageVersion": "3.4" + }, + { + "name": "pool", + "rootUri": "file:///home/azureuser/.pub-cache/hosted/pub.dev/pool-1.5.2", + "packageUri": "lib/", + "languageVersion": "3.4" + }, + { + "name": "pub_semver", + "rootUri": "file:///home/azureuser/.pub-cache/hosted/pub.dev/pub_semver-2.2.0", + "packageUri": "lib/", + "languageVersion": "3.4" + }, + { + "name": "shelf", + "rootUri": "file:///home/azureuser/.pub-cache/hosted/pub.dev/shelf-1.4.2", + "packageUri": "lib/", + "languageVersion": "3.4" + }, + { + "name": "shelf_packages_handler", + "rootUri": "file:///home/azureuser/.pub-cache/hosted/pub.dev/shelf_packages_handler-3.0.2", + "packageUri": "lib/", + "languageVersion": "2.17" + }, + { + "name": "shelf_static", + "rootUri": "file:///home/azureuser/.pub-cache/hosted/pub.dev/shelf_static-1.1.3", + "packageUri": "lib/", + "languageVersion": "3.3" + }, + { + "name": "shelf_web_socket", + "rootUri": "file:///home/azureuser/.pub-cache/hosted/pub.dev/shelf_web_socket-3.0.0", + "packageUri": "lib/", + "languageVersion": "3.5" + }, + { + "name": "source_map_stack_trace", + "rootUri": "file:///home/azureuser/.pub-cache/hosted/pub.dev/source_map_stack_trace-2.1.2", + "packageUri": "lib/", + "languageVersion": "3.3" + }, + { + "name": "source_maps", + "rootUri": "file:///home/azureuser/.pub-cache/hosted/pub.dev/source_maps-0.10.13", + "packageUri": "lib/", + "languageVersion": "3.3" + }, + { + "name": "source_span", + "rootUri": "file:///home/azureuser/.pub-cache/hosted/pub.dev/source_span-1.10.1", + "packageUri": "lib/", + "languageVersion": "3.1" + }, + { + "name": "stack_trace", + "rootUri": "file:///home/azureuser/.pub-cache/hosted/pub.dev/stack_trace-1.12.1", + "packageUri": "lib/", + "languageVersion": "3.4" + }, + { + "name": "stream_channel", + "rootUri": "file:///home/azureuser/.pub-cache/hosted/pub.dev/stream_channel-2.1.4", + "packageUri": "lib/", + "languageVersion": "3.3" + }, + { + "name": "string_scanner", + "rootUri": "file:///home/azureuser/.pub-cache/hosted/pub.dev/string_scanner-1.4.1", + "packageUri": "lib/", + "languageVersion": "3.1" + }, + { + "name": "term_glyph", + "rootUri": "file:///home/azureuser/.pub-cache/hosted/pub.dev/term_glyph-1.2.2", + "packageUri": "lib/", + "languageVersion": "3.1" + }, + { + "name": "test", + "rootUri": "file:///home/azureuser/.pub-cache/hosted/pub.dev/test-1.28.0", + "packageUri": "lib/", + "languageVersion": "3.7" + }, + { + "name": "test_api", + "rootUri": "file:///home/azureuser/.pub-cache/hosted/pub.dev/test_api-0.7.8", + "packageUri": "lib/", + "languageVersion": "3.7" + }, + { + "name": "test_core", + "rootUri": "file:///home/azureuser/.pub-cache/hosted/pub.dev/test_core-0.6.14", + "packageUri": "lib/", + "languageVersion": "3.7" + }, + { + "name": "typed_data", + "rootUri": "file:///home/azureuser/.pub-cache/hosted/pub.dev/typed_data-1.4.0", + "packageUri": "lib/", + "languageVersion": "3.5" + }, + { + "name": "vm_service", + "rootUri": "file:///home/azureuser/.pub-cache/hosted/pub.dev/vm_service-15.0.2", + "packageUri": "lib/", + "languageVersion": "3.5" + }, + { + "name": "watcher", + "rootUri": "file:///home/azureuser/.pub-cache/hosted/pub.dev/watcher-1.1.4", + "packageUri": "lib/", + "languageVersion": "3.1" + }, + { + "name": "web", + "rootUri": "file:///home/azureuser/.pub-cache/hosted/pub.dev/web-0.5.1", + "packageUri": "lib/", + "languageVersion": "3.3" + }, + { + "name": "web_socket_channel", + "rootUri": "file:///home/azureuser/.pub-cache/hosted/pub.dev/web_socket_channel-2.4.5", + "packageUri": "lib/", + "languageVersion": "3.3" + }, + { + "name": "webkit_inspection_protocol", + "rootUri": "file:///home/azureuser/.pub-cache/hosted/pub.dev/webkit_inspection_protocol-1.2.1", + "packageUri": "lib/", + "languageVersion": "3.0" + }, + { + "name": "yaml", + "rootUri": "file:///home/azureuser/.pub-cache/hosted/pub.dev/yaml-3.1.3", + "packageUri": "lib/", + "languageVersion": "3.4" + }, + { + "name": "runagent", + "rootUri": "../", + "packageUri": "lib/", + "languageVersion": "3.0" + } + ], + "generator": "pub", + "generatorVersion": "3.10.0", + "flutterRoot": "file:///home/azureuser/snap/flutter/common/flutter", + "flutterVersion": "3.38.2", + "pubCache": "file:///home/azureuser/.pub-cache" +} diff --git a/runagent-dart/.dart_tool/package_graph.json b/runagent-dart/.dart_tool/package_graph.json new file mode 100644 index 0000000..1b63185 --- /dev/null +++ b/runagent-dart/.dart_tool/package_graph.json @@ -0,0 +1,452 @@ +{ + "roots": [ + "runagent" + ], + "packages": [ + { + "name": "runagent", + "version": "0.1.41", + "dependencies": [ + "http", + "web_socket_channel" + ], + "devDependencies": [ + "lints", + "test" + ] + }, + { + "name": "lints", + "version": "3.0.0", + "dependencies": [] + }, + { + "name": "test", + "version": "1.28.0", + "dependencies": [ + "analyzer", + "async", + "boolean_selector", + "collection", + "coverage", + "http_multi_server", + "io", + "matcher", + "node_preamble", + "package_config", + "path", + "pool", + "shelf", + "shelf_packages_handler", + "shelf_static", + "shelf_web_socket", + "source_span", + "stack_trace", + "stream_channel", + "test_api", + "test_core", + "typed_data", + "web_socket_channel", + "webkit_inspection_protocol", + "yaml" + ] + }, + { + "name": "web_socket_channel", + "version": "2.4.5", + "dependencies": [ + "async", + "crypto", + "stream_channel", + "web" + ] + }, + { + "name": "http", + "version": "1.6.0", + "dependencies": [ + "async", + "http_parser", + "meta", + "web" + ] + }, + { + "name": "yaml", + "version": "3.1.3", + "dependencies": [ + "collection", + "source_span", + "string_scanner" + ] + }, + { + "name": "webkit_inspection_protocol", + "version": "1.2.1", + "dependencies": [ + "logging" + ] + }, + { + "name": "typed_data", + "version": "1.4.0", + "dependencies": [ + "collection" + ] + }, + { + "name": "test_core", + "version": "0.6.14", + "dependencies": [ + "analyzer", + "args", + "async", + "boolean_selector", + "collection", + "coverage", + "frontend_server_client", + "glob", + "io", + "meta", + "package_config", + "path", + "pool", + "source_map_stack_trace", + "source_maps", + "source_span", + "stack_trace", + "stream_channel", + "test_api", + "vm_service", + "yaml" + ] + }, + { + "name": "test_api", + "version": "0.7.8", + "dependencies": [ + "async", + "boolean_selector", + "collection", + "meta", + "source_span", + "stack_trace", + "stream_channel", + "string_scanner", + "term_glyph" + ] + }, + { + "name": "stream_channel", + "version": "2.1.4", + "dependencies": [ + "async" + ] + }, + { + "name": "stack_trace", + "version": "1.12.1", + "dependencies": [ + "path" + ] + }, + { + "name": "source_span", + "version": "1.10.1", + "dependencies": [ + "collection", + "path", + "term_glyph" + ] + }, + { + "name": "shelf_web_socket", + "version": "3.0.0", + "dependencies": [ + "shelf", + "stream_channel", + "web_socket_channel" + ] + }, + { + "name": "shelf_static", + "version": "1.1.3", + "dependencies": [ + "convert", + "http_parser", + "mime", + "path", + "shelf" + ] + }, + { + "name": "shelf_packages_handler", + "version": "3.0.2", + "dependencies": [ + "path", + "shelf", + "shelf_static" + ] + }, + { + "name": "shelf", + "version": "1.4.2", + "dependencies": [ + "async", + "collection", + "http_parser", + "path", + "stack_trace", + "stream_channel" + ] + }, + { + "name": "pool", + "version": "1.5.2", + "dependencies": [ + "async", + "stack_trace" + ] + }, + { + "name": "path", + "version": "1.9.1", + "dependencies": [] + }, + { + "name": "package_config", + "version": "2.2.0", + "dependencies": [ + "path" + ] + }, + { + "name": "node_preamble", + "version": "2.0.2", + "dependencies": [] + }, + { + "name": "matcher", + "version": "0.12.18", + "dependencies": [ + "async", + "meta", + "stack_trace", + "term_glyph", + "test_api" + ] + }, + { + "name": "io", + "version": "1.0.5", + "dependencies": [ + "meta", + "path", + "string_scanner" + ] + }, + { + "name": "http_multi_server", + "version": "3.2.2", + "dependencies": [ + "async" + ] + }, + { + "name": "coverage", + "version": "1.15.0", + "dependencies": [ + "args", + "cli_config", + "glob", + "logging", + "meta", + "package_config", + "path", + "source_maps", + "stack_trace", + "vm_service", + "yaml" + ] + }, + { + "name": "collection", + "version": "1.19.1", + "dependencies": [] + }, + { + "name": "boolean_selector", + "version": "2.1.2", + "dependencies": [ + "source_span", + "string_scanner" + ] + }, + { + "name": "async", + "version": "2.13.0", + "dependencies": [ + "collection", + "meta" + ] + }, + { + "name": "analyzer", + "version": "9.0.0", + "dependencies": [ + "_fe_analyzer_shared", + "collection", + "convert", + "crypto", + "glob", + "meta", + "package_config", + "path", + "pub_semver", + "source_span", + "watcher", + "yaml" + ] + }, + { + "name": "web", + "version": "0.5.1", + "dependencies": [] + }, + { + "name": "crypto", + "version": "3.0.7", + "dependencies": [ + "typed_data" + ] + }, + { + "name": "meta", + "version": "1.17.0", + "dependencies": [] + }, + { + "name": "http_parser", + "version": "4.1.2", + "dependencies": [ + "collection", + "source_span", + "string_scanner", + "typed_data" + ] + }, + { + "name": "string_scanner", + "version": "1.4.1", + "dependencies": [ + "source_span" + ] + }, + { + "name": "logging", + "version": "1.3.0", + "dependencies": [] + }, + { + "name": "vm_service", + "version": "15.0.2", + "dependencies": [] + }, + { + "name": "source_maps", + "version": "0.10.13", + "dependencies": [ + "source_span" + ] + }, + { + "name": "source_map_stack_trace", + "version": "2.1.2", + "dependencies": [ + "path", + "source_maps", + "stack_trace" + ] + }, + { + "name": "glob", + "version": "2.1.3", + "dependencies": [ + "async", + "collection", + "file", + "path", + "string_scanner" + ] + }, + { + "name": "frontend_server_client", + "version": "4.0.0", + "dependencies": [ + "async", + "path" + ] + }, + { + "name": "args", + "version": "2.7.0", + "dependencies": [] + }, + { + "name": "term_glyph", + "version": "1.2.2", + "dependencies": [] + }, + { + "name": "mime", + "version": "2.0.0", + "dependencies": [] + }, + { + "name": "convert", + "version": "3.1.2", + "dependencies": [ + "typed_data" + ] + }, + { + "name": "cli_config", + "version": "0.2.0", + "dependencies": [ + "args", + "yaml" + ] + }, + { + "name": "watcher", + "version": "1.1.4", + "dependencies": [ + "async", + "path" + ] + }, + { + "name": "pub_semver", + "version": "2.2.0", + "dependencies": [ + "collection" + ] + }, + { + "name": "_fe_analyzer_shared", + "version": "92.0.0", + "dependencies": [ + "meta" + ] + }, + { + "name": "file", + "version": "7.0.1", + "dependencies": [ + "meta", + "path" + ] + } + ], + "configVersion": 1 +} \ No newline at end of file diff --git a/runagent-dart/.dart_tool/pub/bin/test/test.dart-3.10.0.snapshot b/runagent-dart/.dart_tool/pub/bin/test/test.dart-3.10.0.snapshot new file mode 100644 index 0000000..ad5f772 Binary files /dev/null and b/runagent-dart/.dart_tool/pub/bin/test/test.dart-3.10.0.snapshot differ diff --git a/runagent-dart/.dart_tool/test/incremental_kernel.Ly9AZGFydD0zLjA= b/runagent-dart/.dart_tool/test/incremental_kernel.Ly9AZGFydD0zLjA= new file mode 100644 index 0000000..1f2b9f6 Binary files /dev/null and b/runagent-dart/.dart_tool/test/incremental_kernel.Ly9AZGFydD0zLjA= differ diff --git a/runagent-dart/CHANGELOG.md b/runagent-dart/CHANGELOG.md new file mode 100644 index 0000000..61f781f --- /dev/null +++ b/runagent-dart/CHANGELOG.md @@ -0,0 +1,26 @@ +# Changelog + +All notable changes to this project will be documented in this file. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), +and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + +## [0.1.41] - 2024-XX-XX + +### Added +- Initial release of RunAgent Dart SDK +- Support for REST and WebSocket communication with deployed AI agents +- `RunAgentClient` with support for local and cloud agents +- Streaming and non-streaming execution modes +- Type definitions for agent configuration and architecture +- Error handling with custom error types +- Unit tests for core type definitions + +### Features +- REST client for synchronous agent execution +- WebSocket client for streaming agent execution +- Local agent support with registry lookup +- Cloud agent support with API key authentication +- Agent architecture introspection +- Comprehensive error handling + diff --git a/runagent-dart/LICENSE b/runagent-dart/LICENSE new file mode 100644 index 0000000..fda5283 --- /dev/null +++ b/runagent-dart/LICENSE @@ -0,0 +1,91 @@ +Elastic License 2.0 + +URL: https://www.elastic.co/licensing/elastic-license + +## Acceptance + +By using the software, you agree to all of the terms and conditions below. + +## Copyright License + +The licensor grants you a non-exclusive, royalty-free, worldwide, +non-sublicensable, non-transferable license to use, copy, distribute, make +available, and prepare derivative works of the software, in each case subject to +the limitations and conditions below. + +## Limitations + +You may not provide the software to third parties as a hosted or managed +service, where the service provides users with access to any substantial set of +the features or functionality of the software. + +You may not move, change, disable, or circumvent the license key functionality +in the software, and you may not remove or obscure any functionality in the +software that is protected by the license key. + +You may not alter, remove, or obscure any licensing, copyright, or other notices +of the licensor in the software. Any use of the licensor's trademarks is subject +to applicable law. + +## Patents + +The licensor grants you a license, under any patent claims the licensor can +license, or becomes able to license, to make, have made, use, sell, offer for +sale, import and have imported the software, in each case subject to the +limitations and conditions in this license. This license does not cover any +patent claims that you cause to be infringed by modifications or additions to +the software. If you or your company make any written claim that the software +infringes or contributes to infringement of any patent, your patent license for +the software granted under these terms immediately terminates. + +## Notices + +You must ensure that anyone who gets a copy of any part of the software from you +also gets a copy of these terms. + +If you modify the software, you must include in any modified copies of the +software prominent notices stating that you have modified the software. + +## No Other Rights + +These terms do not imply any licenses other than those expressly granted in +these terms. + +## Termination + +If you use the software in violation of these terms, such use is not licensed, +and your licenses will automatically terminate. If the licensor provides you +with a notice of your violation, and you cease all violation of this license no +later than 30 days after you receive that notice, your licenses will be +reinstated retroactively. However, if you violate these terms after such +reinstatement, any additional violation of these terms will cause your licenses +to terminate automatically and permanently. + +## No Liability + +*As far as the law allows, the software comes as is, without any warranty or +condition, and the licensor will not be liable to you for any damages arising +out of these terms or the use or nature of the software, under any kind of +legal claim.* + +## Definitions + +The **licensor** is the entity offering these terms, and the **software** is the +software the licensor makes available under these terms, including any portion +of the software. + +**You** refers to the individual or entity agreeing to these terms. + +**Your company** is any legal entity, sole proprietorship, or other kind of +organization that you work for, plus all organizations that have control over, +are controlled by, or are in common control with that organization. **Control** +means ownership of substantially all the assets of an entity, or the power to +direct its management and policies by vote, contract, or otherwise. Control can +be direct or indirect. + +**Your licenses** are all the licenses granted to you for the software under +these terms. + +**Use** means anything you do with the software requiring one of your licenses. + +**Trademark** means trademarks, service marks, and similar rights. \ No newline at end of file diff --git a/runagent-dart/PUBLISH.md b/runagent-dart/PUBLISH.md new file mode 100644 index 0000000..87f1b2f --- /dev/null +++ b/runagent-dart/PUBLISH.md @@ -0,0 +1,105 @@ +## Publishing `runagent-dart` + +The Dart SDK is distributed through pub.dev. Releasing a new version requires updating the version in `pubspec.yaml` and publishing to pub.dev. + +--- + +### 1. Prerequisites + +- Dart 3.0+ installed locally. +- Write access to the pub.dev package (or be added as an uploader). +- Clean working tree (`git status` should be clean or contain only staged release commits). +- `dart pub publish --dry-run` should pass without errors. + +--- + +### 2. Preflight Checklist + +1. **Bump the SDK version** + - Update `version` in `pubspec.yaml` in the root directory. + - Follow semver (increment patch for fixes, minor for new features, major for breaking changes). +2. **Changelog / release notes** + - Update the main repo changelog or docs to record the release. +3. **Verify dependencies** + - Run `dart pub get` from `runagent-dart/`. + - Ensure `pubspec.yaml` contains only the needed deps. + - Run `dart pub outdated` to check for dependency updates. + +--- + +### 3. Build & Test + +```bash +# From runagent-dart/ +dart analyze +dart test +dart pub publish --dry-run +``` + +For extra assurance, run the example files: + +```bash +dart run example/basic_example.dart +dart run example/streaming_example.dart +dart run example/local_example.dart +``` + +--- + +### 4. Commit & Tag + +```bash +git add . +git commit -m "chore(dart): release v0.1.0" + +# Tag with version prefix +git tag runagent-dart-v0.1.0 +git push origin main +git push origin runagent-dart-v0.1.0 +``` + +> If releasing from a feature branch, merge it first (or push the tag from the release branch) so `main` reflects the published state. + +--- + +### 5. Publish to pub.dev + +```bash +# From runagent-dart/ +dart pub publish +``` + +Follow the prompts to confirm publishing. You'll need to authenticate with pub.dev if not already logged in. + +--- + +### 6. Post-Publish + +- Announce the release internally and update documentation links (docs site, README tables, etc.). +- Monitor pub.dev (usually available within minutes after publishing). +- Verify `dart pub add runagent` resolves to the new version. + +--- + +### Troubleshooting + +- **`Package already exists`**: ensure the version in `pubspec.yaml` is incremented. +- **`Upload failed`**: check your pub.dev credentials and ensure you're an uploader for the package. +- **`Validation errors`**: run `dart pub publish --dry-run` to see what needs to be fixed. +- **Forgot to bump version**: create a new patch release (e.g., `v0.1.1`) with the correct version. + +--- + +### Version Format + +Follow semantic versioning: +- **MAJOR**: Breaking changes +- **MINOR**: New features (backward compatible) +- **PATCH**: Bug fixes + +Example: `0.1.0` โ†’ `0.1.1` (patch), `0.1.0` โ†’ `0.2.0` (minor), `0.1.0` โ†’ `1.0.0` (major) + +--- + +You're done! ๐Ÿš€ + diff --git a/runagent-dart/README.md b/runagent-dart/README.md new file mode 100644 index 0000000..9753749 --- /dev/null +++ b/runagent-dart/README.md @@ -0,0 +1,228 @@ +## RunAgent Dart/Flutter SDK + +The Dart SDK mirrors the Python CLI client so Dart/Flutter services can trigger hosted or local RunAgent deployments. It wraps the `/api/v1/agents/{agent_id}/run` and `/run-stream` endpoints, handles auth/discovery, and translates responses into Dart-friendly types. + +--- + +### Feature Overview + +- **Native Dart arguments**: Pass maps and lists directly +- **Streaming and non-streaming guardrails**: + - `run()` rejects `*_stream` tags with a helpful error + - `runStream()` rejects non-stream tags with a helpful error +- **Local vs Remote**: + - Local DB discovery from `~/.runagent/runagent_local.db` (override with `host`/`port`) + - Remote uses `RUNAGENT_BASE_URL` (default `https://backend.run-agent.ai`) and Bearer token +- **Authentication**: + - `Authorization: Bearer RUNAGENT_API_KEY` automatically for remote calls + - WS token fallback `?token=...` for streams +- **Error taxonomy**: + - `AUTHENTICATION_ERROR`, `CONNECTION_ERROR`, `VALIDATION_ERROR`, `SERVER_ERROR`, `UNKNOWN_ERROR` + - Execution errors include `code`, `suggestion`, `details` when provided by backend +- **Architecture**: + - `getAgentArchitecture()` normalizes envelope and legacy formats and enforces `ARCHITECTURE_MISSING` when needed +- **Config precedence**: + - Explicit `RunAgentClientConfig` fields โ†’ environment โ†’ defaults +- **Extra params**: + - `RunAgentClientConfig.extraParams` stored and retrievable via `client.getExtraParams()` + +--- + +### Installation + +#### Option 1: Local Development (Current Setup) + +If you're developing locally or the package isn't published to pub.dev yet, use a path dependency: + +```yaml +dependencies: + runagent: + path: ../runagent-dart # Adjust path relative to your project +``` + +Or use an absolute path: + +```yaml +dependencies: + runagent: + path: /home/azureuser/runagent/runagent-dart +``` + +#### Option 2: From pub.dev (When Published) + +Once the package is published to pub.dev, you can use: + +```yaml +dependencies: + runagent: ^0.1.0 +``` + +Then run: + +```bash +flutter pub get +# or +dart pub get +``` + +**Note:** Requires Dart 3.0+. + +--- + +### Configuration Precedence + +1. Explicit `RunAgentClientConfig` fields +2. Environment variables + - `RUNAGENT_API_KEY` + - `RUNAGENT_BASE_URL` (defaults to `https://backend.run-agent.ai`) + - `RUNAGENT_LOCAL`, `RUNAGENT_HOST`, `RUNAGENT_PORT`, `RUNAGENT_TIMEOUT` +3. Library defaults (e.g., local DB discovery, 300 s timeout) + +When `local` is `true` (or `RUNAGENT_LOCAL=true`), the SDK reads `~/.runagent/runagent_local.db` to discover the host/port unless they're provided directly. + +--- + +### Local vs Remote: Host/Port Optionality + +- **Remote** (cloud or self-hosted base URL): + - Do not set `host`/`port`. Provide `apiKey` (or set `RUNAGENT_API_KEY`), and optionally `baseUrl`. + - Example: + ```dart + final client = await RunAgentClient.create( + RunAgentClientConfig.create( + agentId: 'id', + entrypointTag: 'minimal', + apiKey: Platform.environment['RUNAGENT_API_KEY'], + // baseUrl optional; defaults to https://backend.run-agent.ai + ), + ); + ``` +- **Local**: + - `host`/`port` are optional. If either is missing, the SDK discovers the value(s) from `~/.runagent/runagent_local.db` for the given `agentId`. + - If discovery fails (agent not registered), you'll get a clear `VALIDATION_ERROR` suggesting to pass `host`/`port` or register the agent locally. + - Examples: + ```dart + // Rely fully on DB discovery (no host/port) + final client = await RunAgentClient.create( + RunAgentClientConfig.create( + agentId: 'local-id', + entrypointTag: 'generic', + local: true, + ), + ); + + // Provide only host, let port be discovered + final client = await RunAgentClient.create( + RunAgentClientConfig.create( + agentId: 'local-id', + entrypointTag: 'generic', + local: true, + host: '127.0.0.1', + ), + ); + ``` + +--- + +### Quickstart (Remote) + +```dart +import 'dart:io'; +import 'package:runagent/runagent.dart'; + +Future main() async { + final client = await RunAgentClient.create( + RunAgentClientConfig.create( + agentId: 'YOUR_AGENT_ID', + entrypointTag: 'minimal', + apiKey: Platform.environment['RUNAGENT_API_KEY'], + ), + ); + + try { + final result = await client.run({ + 'message': 'Summarize Q4 retention metrics', + }); + print('Response: $result'); + } catch (e) { + if (e is RunAgentError) { + print('Error: ${e.message}'); + if (e.suggestion != null) { + print('Suggestion: ${e.suggestion}'); + } + } + } +} +``` + +--- + +### Quickstart (Local) + +```dart +final client = await RunAgentClient.create( + RunAgentClientConfig.create( + agentId: 'local-agent-id', + entrypointTag: 'generic', + local: true, + host: '127.0.0.1', // optional: falls back to DB entry + port: 8450, + ), +); +``` + +If `host`/`port` are omitted, the SDK looks up the agent in `~/.runagent/runagent_local.db`. Missing entries yield a helpful `VALIDATION_ERROR`. + +--- + +### Streaming Responses + +```dart +await for (final chunk in client.runStream({ + 'prompt': 'Stream a haiku about Dart', +})) { + print(chunk); +} +``` + +- Local streams connect to `ws://{host}:{port}/api/v1/agents/{id}/run-stream`. +- Remote streams upgrade to `wss://backend.run-agent.ai/api/v1/...` and append `?token=RUNAGENT_API_KEY`. + +--- + +### Extra Params & Metadata + +`RunAgentClientConfig.extraParams` accepts arbitrary metadata; call `client.getExtraParams()` to retrieve a copy. Reserved for future features (tracing, tags) without breaking the API. + +--- + +### Error Handling + +All SDK errors extend `RunAgentError` and expose concrete error types: + +| Type | Meaning | Typical Fix | +| --- | --- | --- | +| `AUTHENTICATION_ERROR` | API key missing/invalid | Set `RUNAGENT_API_KEY` or `Config.apiKey` | +| `CONNECTION_ERROR` | Network/DNS/TLS issues | Verify network, agent uptime | +| `VALIDATION_ERROR` | Bad config or missing agent | Check `agentId`, entrypoint, local DB | +| `SERVER_ERROR` | Upstream failure (5xx) | Retry or inspect agent logs | + +Remote responses that return a structured `error` block become `RunAgentExecutionError` with `code`, `suggestion`, and `details` copied directly. + +Use `catch (e)` and check `e is RunAgentError` to inspect fields. + +--- + +### Testing & Troubleshooting + +- `dart test` exercises the SDK build. +- Enable debug logging in your application to capture request IDs. +- For local issues, run `runagent cli agents list` to confirm the SQLite database contains the agent and the host/port match. +- For remote failures, confirm the agent is deployed and the entrypoint tag is enabled in the RunAgent Cloud dashboard. + +--- + +### Publishing + +See `PUBLISH.md` in this directory for release instructions (version bumps, tagging, and pub.dev publishing). + diff --git a/runagent-dart/example/basic_example.dart b/runagent-dart/example/basic_example.dart new file mode 100644 index 0000000..e3cbe1b --- /dev/null +++ b/runagent-dart/example/basic_example.dart @@ -0,0 +1,34 @@ +import 'dart:io'; +import 'package:runagent/runagent.dart'; + +/// Basic example of using RunAgent SDK +Future main() async { + // Create a client for a remote agent + final client = await RunAgentClient.create( + RunAgentClientConfig.create( + agentId: 'YOUR_AGENT_ID', + entrypointTag: 'generic', + apiKey: Platform.environment['RUNAGENT_API_KEY'], + ), + ); + + try { + // Run the agent with input + final result = await client.run({ + 'message': 'Hello, world!', + 'temperature': 0.7, + }); + + print('Response: $result'); + } catch (e) { + if (e is RunAgentError) { + print('Error: ${e.message}'); + if (e.suggestion != null) { + print('Suggestion: ${e.suggestion}'); + } + } else { + print('Unexpected error: $e'); + } + } +} + diff --git a/runagent-dart/example/local_example.dart b/runagent-dart/example/local_example.dart new file mode 100644 index 0000000..89d78d6 --- /dev/null +++ b/runagent-dart/example/local_example.dart @@ -0,0 +1,34 @@ +import 'package:runagent/runagent.dart'; + +/// Example of using RunAgent SDK with a local agent +Future main() async { + // Create a client for a local agent + final client = await RunAgentClient.create( + RunAgentClientConfig.create( + agentId: 'local-agent-id', + entrypointTag: 'generic', + local: true, + host: '127.0.0.1', + port: 8450, + ), + ); + + try { + // Run the agent with input + final result = await client.run({ + 'message': 'Hello from local agent!', + }); + + print('Response: $result'); + } catch (e) { + if (e is RunAgentError) { + print('Error: ${e.message}'); + if (e.suggestion != null) { + print('Suggestion: ${e.suggestion}'); + } + } else { + print('Unexpected error: $e'); + } + } +} + diff --git a/runagent-dart/example/streaming_example.dart b/runagent-dart/example/streaming_example.dart new file mode 100644 index 0000000..e36c515 --- /dev/null +++ b/runagent-dart/example/streaming_example.dart @@ -0,0 +1,33 @@ +import 'dart:io'; +import 'package:runagent/runagent.dart'; + +/// Example of using RunAgent SDK with streaming +Future main() async { + // Create a client for a streaming agent + final client = await RunAgentClient.create( + RunAgentClientConfig.create( + agentId: 'YOUR_AGENT_ID', + entrypointTag: 'generic_stream', // Note: _stream suffix + apiKey: Platform.environment['RUNAGENT_API_KEY'], + ), + ); + + try { + // Run the agent with streaming + await for (final chunk in client.runStream({ + 'message': 'Tell me a story', + })) { + print('Chunk: $chunk'); + } + } catch (e) { + if (e is RunAgentError) { + print('Error: ${e.message}'); + if (e.suggestion != null) { + print('Suggestion: ${e.suggestion}'); + } + } else { + print('Unexpected error: $e'); + } + } +} + diff --git a/runagent-dart/lib/runagent.dart b/runagent-dart/lib/runagent.dart new file mode 100644 index 0000000..cc6f351 --- /dev/null +++ b/runagent-dart/lib/runagent.dart @@ -0,0 +1,12 @@ +/// RunAgent SDK for Dart/Flutter +/// +/// A comprehensive SDK for deploying and managing AI agents with support for +/// multiple frameworks including LangChain, LangGraph, LlamaIndex, and more. +library runagent; + +export 'src/client/runagent_client.dart'; +export 'src/errors/errors.dart'; +export 'src/types/types.dart'; +export 'src/utils/config.dart'; +export 'src/utils/constants.dart'; + diff --git a/runagent-dart/lib/src/client/rest_client.dart b/runagent-dart/lib/src/client/rest_client.dart new file mode 100644 index 0000000..dfdbe4d --- /dev/null +++ b/runagent-dart/lib/src/client/rest_client.dart @@ -0,0 +1,190 @@ +import 'dart:async'; +import 'dart:convert'; +import 'package:http/http.dart' as http; +import 'package:runagent/src/errors/errors.dart'; +import 'package:runagent/src/utils/constants.dart'; + +/// REST client for RunAgent API +class RestClient { + final String baseUrl; + final String? apiKey; + final bool isLocal; + + RestClient({ + required this.baseUrl, + this.apiKey, + this.isLocal = false, + }); + + /// Get agent architecture + Future> getAgentArchitecture(String agentId) async { + final url = Uri.parse('$baseUrl${RunAgentConstants.defaultApiPrefix}/agents/$agentId/architecture'); + + final headers = { + 'Content-Type': 'application/json', + 'User-Agent': RunAgentConstants.userAgent(), + }; + + if (!isLocal && apiKey != null) { + headers['Authorization'] = 'Bearer $apiKey'; + } + + try { + final response = await http.get(url, headers: headers); + + if (response.statusCode != 200) { + throw _translateHttpError(response.statusCode, response.body); + } + + final json = jsonDecode(response.body) as Map; + return json; + } catch (e) { + if (e is RunAgentError) rethrow; + throw ConnectionError( + message: 'Failed to reach RunAgent service: $e', + suggestion: 'Check your network connection or agent status', + ); + } + } + + /// Run agent via REST + Future> runAgent( + String agentId, + String entrypointTag, + List inputArgs, + Map inputKwargs, { + int timeoutSeconds = 300, + bool asyncExecution = false, + }) async { + final url = Uri.parse('$baseUrl${RunAgentConstants.defaultApiPrefix}/agents/$agentId/run'); + + final headers = { + 'Content-Type': 'application/json', + 'User-Agent': RunAgentConstants.userAgent(), + }; + + if (!isLocal && apiKey != null) { + headers['Authorization'] = 'Bearer $apiKey'; + } else if (!isLocal && apiKey == null) { + throw AuthenticationError( + message: 'api_key is required for remote runs', + suggestion: 'Set RUNAGENT_API_KEY or pass Config.APIKey', + ); + } + + final payload = { + 'entrypoint_tag': entrypointTag, + 'input_args': inputArgs, + 'input_kwargs': inputKwargs, + 'timeout_seconds': timeoutSeconds, + 'async_execution': asyncExecution, + }; + + try { + final response = await http.post( + url, + headers: headers, + body: jsonEncode(payload), + ).timeout(Duration(seconds: timeoutSeconds + 10)); + + if (response.statusCode != 200) { + throw _translateHttpError(response.statusCode, response.body); + } + + final json = jsonDecode(response.body) as Map; + return json; + } catch (e) { + if (e is RunAgentError) rethrow; + if (e is TimeoutException) { + throw ConnectionError( + message: 'Request timed out after ${timeoutSeconds}s', + suggestion: 'Increase timeout or check agent status', + ); + } + throw ConnectionError( + message: 'Failed to reach RunAgent service: $e', + suggestion: 'Check your network connection or agent status', + ); + } + } + + /// Health check + Future healthCheck() async { + try { + final url = Uri.parse('$baseUrl${RunAgentConstants.defaultApiPrefix}/health'); + final response = await http.get(url); + return response.statusCode == 200; + } catch (e) { + return false; + } + } + + /// Translate HTTP error to RunAgent error + RunAgentError _translateHttpError(int statusCode, String body) { + Map? errorPayload; + + try { + final json = jsonDecode(body) as Map?; + if (json != null && json.containsKey('error')) { + errorPayload = _parseApiError(json['error']); + } + } catch (e) { + // Ignore JSON parse errors + } + + final error = errorPayload ?? { + 'type': 'SERVER_ERROR', + 'message': 'Server returned status $statusCode', + }; + + final errorMessage = error['message']; + final errorSuggestion = error['suggestion']; + final errorDetails = error['details']; + + if (statusCode == 401 || statusCode == 403) { + return AuthenticationError( + message: errorMessage is String ? errorMessage : 'Authentication failed', + suggestion: errorSuggestion is String ? (errorSuggestion.isEmpty ? 'Set RUNAGENT_API_KEY or pass Config.APIKey' : errorSuggestion) : 'Set RUNAGENT_API_KEY or pass Config.APIKey', + details: errorDetails is Map ? errorDetails : null, + ); + } else if (statusCode >= 500) { + return ServerError( + message: errorMessage is String ? errorMessage : 'Server error', + suggestion: errorSuggestion is String ? errorSuggestion : null, + details: errorDetails is Map ? errorDetails : null, + ); + } else { + return ValidationError( + message: errorMessage is String ? errorMessage : 'Validation error', + suggestion: errorSuggestion is String ? errorSuggestion : null, + details: errorDetails is Map ? errorDetails : null, + ); + } + } + + /// Parse API error from response + Map? _parseApiError(dynamic rawError) { + if (rawError == null) return null; + + if (rawError is String) { + return { + 'type': 'SERVER_ERROR', + 'message': rawError, + }; + } + + if (rawError is Map) { + final message = rawError['message']; + return { + 'type': rawError['type'] is String ? rawError['type'] : 'SERVER_ERROR', + 'message': message is String ? message : 'Unknown error', + 'code': rawError['code'] is String ? rawError['code'] : null, + 'suggestion': rawError['suggestion'] is String ? rawError['suggestion'] : null, + 'details': rawError['details'] is Map ? rawError['details'] : null, + }; + } + + return null; + } +} + diff --git a/runagent-dart/lib/src/client/runagent_client.dart b/runagent-dart/lib/src/client/runagent_client.dart new file mode 100644 index 0000000..485478c --- /dev/null +++ b/runagent-dart/lib/src/client/runagent_client.dart @@ -0,0 +1,527 @@ +import 'dart:async'; +import 'dart:convert'; +import 'package:runagent/src/client/rest_client.dart'; +import 'package:runagent/src/client/socket_client.dart'; +import 'package:runagent/src/errors/errors.dart'; +import 'package:runagent/src/types/types.dart'; +import 'package:runagent/src/utils/config.dart'; +import 'package:runagent/src/utils/constants.dart'; + +/// Main client for interacting with RunAgent deployments +class RunAgentClient { + final String agentId; + final String entrypointTag; + final bool local; + final RestClient restClient; + final SocketClient socketClient; + final Map? extraParams; + + AgentArchitecture? _architecture; + + RunAgentClient._({ + required this.agentId, + required this.entrypointTag, + required this.local, + required this.restClient, + required this.socketClient, + this.extraParams, + }); + + /// Create a new RunAgent client from configuration + static Future create(RunAgentClientConfig config) async { + // Validate required fields + if (config.agentId.trim().isEmpty) { + throw ValidationError(message: 'agent_id is required'); + } + if (config.entrypointTag.trim().isEmpty) { + throw ValidationError(message: 'entrypoint_tag is required'); + } + + // Resolve configuration with precedence: explicit > env > default + final local = ConfigUtils.resolveBool( + config.local, + ConfigUtils.getLocal(), + false, + ); + + final enableRegistry = config.enableRegistry ?? local; + + // Resolve host/port for local agents + String? host; + int? port; + + if (local) { + host = ConfigUtils.firstNonEmpty([ + config.host, + ConfigUtils.getHost(), + ]); + port = ConfigUtils.firstNonZero([ + config.port, + ConfigUtils.getPort(), + ]); + + // Try database lookup if enabled and host/port not provided + if (enableRegistry && (host == null || port == null)) { + try { + // TODO: Implement database lookup when sqflite is available + // For now, we'll require explicit host/port or use defaults + if (host == null) host = RunAgentConstants.defaultLocalHost; + if (port == null) port = RunAgentConstants.defaultLocalPort; + } catch (e) { + // Database lookup failed, use defaults or throw + if (host == null || port == null) { + throw ValidationError( + message: 'Unable to resolve local host/port', + suggestion: 'Pass Config.Host/Config.Port or ensure the agent is registered locally', + ); + } + } + } + + if (host == null || port == null) { + throw ValidationError( + message: 'Host and port are required for local agents', + suggestion: 'Pass Config.Host/Config.Port or enable registry for database lookup', + ); + } + } + + // Resolve API key (config > env var) + final apiKey = ConfigUtils.firstNonEmpty([ + config.apiKey, + ConfigUtils.getApiKey(), + ]); + + // Resolve base URL (config > env var > default) + final baseUrl = ConfigUtils.firstNonEmpty([ + config.baseUrl, + ConfigUtils.getBaseUrl(), + ]) ?? RunAgentConstants.defaultBaseUrl; + + // Create REST and WebSocket clients + final (restClient, socketClient) = local + ? _createLocalClients(host!, port!, apiKey) + : _createRemoteClients(baseUrl, apiKey); + + final client = RunAgentClient._( + agentId: config.agentId, + entrypointTag: config.entrypointTag, + local: local, + restClient: restClient, + socketClient: socketClient, + extraParams: config.extraParams, + ); + + // Initialize architecture and validate entrypoint + await client._initializeArchitecture(); + + return client; + } + + /// Create local clients + static (RestClient, SocketClient) _createLocalClients( + String host, + int port, + String? apiKey, + ) { + final restBase = 'http://$host:$port'; + final socketBase = 'ws://$host:$port'; + + final restClient = RestClient( + baseUrl: restBase, + apiKey: apiKey, + isLocal: true, + ); + + final socketClient = SocketClient( + baseSocketUrl: socketBase, + apiKey: apiKey, + isLocal: true, + ); + + return (restClient, socketClient); + } + + /// Create remote clients + static (RestClient, SocketClient) _createRemoteClients( + String baseUrl, + String? apiKey, + ) { + // Normalize base URL + String normalizedBase = baseUrl.trim(); + if (!normalizedBase.startsWith('http://') && !normalizedBase.startsWith('https://')) { + normalizedBase = 'https://$normalizedBase'; + } + normalizedBase = normalizedBase.replaceAll(RegExp(r'/$'), ''); + + // Convert to WebSocket URL + String socketBase; + if (normalizedBase.startsWith('https://')) { + socketBase = normalizedBase.replaceFirst('https://', 'wss://'); + } else if (normalizedBase.startsWith('http://')) { + socketBase = normalizedBase.replaceFirst('http://', 'ws://'); + } else { + socketBase = 'wss://$normalizedBase'; + } + + final restClient = RestClient( + baseUrl: normalizedBase, + apiKey: apiKey, + isLocal: false, + ); + + final socketClient = SocketClient( + baseSocketUrl: socketBase, + apiKey: apiKey, + isLocal: false, + ); + + return (restClient, socketClient); + } + + /// Initialize architecture and validate entrypoint + Future _initializeArchitecture() async { + final architectureJson = await restClient.getAgentArchitecture(agentId); + + // Handle envelope format + if (architectureJson.containsKey('success')) { + final success = architectureJson['success'] as bool?; + if (success == false) { + final error = architectureJson['error']; + if (error is Map) { + final errorMessage = error['message']; + final errorCode = error['code']; + throw RunAgentExecutionError( + code: errorCode is String ? errorCode : ErrorCodes.serverError, + message: errorMessage is String ? errorMessage : 'Failed to retrieve agent architecture', + suggestion: error['suggestion'] is String ? error['suggestion'] : null, + details: error['details'] is Map ? error['details'] : null, + ); + } + final message = architectureJson['message']; + throw ServerError( + message: message is String ? message : 'Failed to retrieve agent architecture', + ); + } + + final data = architectureJson['data'] as Map?; + if (data != null) { + _architecture = AgentArchitecture.fromJson(data); + } + } else { + // Legacy format + _architecture = AgentArchitecture.fromJson(architectureJson); + } + + // Validate entrypoint exists + if (_architecture == null || _architecture!.entrypoints.isEmpty) { + throw ValidationError( + message: 'Architecture missing entrypoints', + suggestion: 'Redeploy the agent with entrypoints configured', + ); + } + + final found = _architecture!.entrypoints.any( + (ep) => ep.tag == entrypointTag, + ); + + if (!found) { + final available = _architecture!.entrypoints.map((ep) => ep.tag).join(', '); + throw ValidationError( + message: 'Entrypoint `$entrypointTag` not found in agent $agentId', + suggestion: 'Available entrypoints: $available', + ); + } + + // Validate stream vs non-stream guardrails + final isStreamTag = entrypointTag.toLowerCase().endsWith('_stream'); + if (isStreamTag) { + // This is a stream tag - should use runStream + } else { + // This is a non-stream tag - should use run + } + } + + /// Run the agent with keyword arguments + Future run([Map? inputKwargs]) async { + return runWithArgs([], inputKwargs ?? {}); + } + + /// Run the agent with both positional and keyword arguments + Future runWithArgs( + List inputArgs, + Map inputKwargs, + ) async { + // Guardrail: non-stream only + if (entrypointTag.toLowerCase().endsWith('_stream')) { + throw ValidationError( + message: 'Stream entrypoint must be invoked with runStream', + suggestion: 'Use client.runStream(...) for *_stream tags', + ); + } + + final response = await restClient.runAgent( + agentId, + entrypointTag, + inputArgs, + inputKwargs, + ); + + if (response['success'] == true) { + // Process response data + dynamic payload; + + final data = response['data']; + if (data is String) { + // Case 1: data is a string (could be structured JSON string with {type, payload}) + payload = _deserializeString(data); + } else if (data is Map) { + // Case 2: data has result_data.data (legacy detailed execution payload) + if (data.containsKey('result_data')) { + final resultData = data['result_data'] as Map?; + final innerData = resultData?['data']; + // Check if innerData is a string that needs deserialization + if (innerData is String) { + payload = _deserializeString(innerData); + } else { + payload = _deserializeObject(innerData); + } + } else { + // Case 3: data is an object (could be {type, payload} structure) + payload = _deserializeObject(data); + } + } else if (data != null) { + payload = _deserializeObject(data); + } else if (response.containsKey('output_data')) { + // Case 4: Fallback to output_data (backward compatibility) + final outputData = response['output_data']; + if (outputData is String) { + payload = _deserializeString(outputData); + } else { + payload = _deserializeObject(outputData); + } + } + + if (payload == null) { + return null; + } + + // Check for generator object warning + if (payload is String) { + final lowerStr = payload.toLowerCase(); + if (lowerStr.contains('generator object') || lowerStr.contains(') { + final errorMessage = error['message']; + final errorSuggestion = error['suggestion']; + final errorDetails = error['details']; + throw RunAgentExecutionError( + code: error['code'] is String ? error['code'] : ErrorCodes.serverError, + message: errorMessage is String ? errorMessage : 'Unknown error', + suggestion: errorSuggestion is String ? errorSuggestion : null, + details: errorDetails is Map ? errorDetails : null, + ); + } else if (error is String) { + throw RunAgentExecutionError( + code: ErrorCodes.serverError, + message: error, + ); + } else { + throw RunAgentExecutionError( + code: ErrorCodes.serverError, + message: 'Unknown error', + ); + } + } + } + + /// Run the agent and return a stream of responses + Stream runStream([Map? inputKwargs]) { + return runStreamWithArgs([], inputKwargs ?? {}); + } + + /// Run the agent with streaming and both positional and keyword arguments + Stream runStreamWithArgs( + List inputArgs, + Map inputKwargs, + ) { + // Guardrail: stream only + if (!entrypointTag.toLowerCase().endsWith('_stream')) { + throw ValidationError( + message: 'Non-stream entrypoint must be invoked with run', + suggestion: 'Use client.run(...) for non-stream tags', + ); + } + + return socketClient.runStream( + agentId, + entrypointTag, + inputArgs, + inputKwargs, + ); + } + + /// Get the agent's architecture information + Future getAgentArchitecture() async { + if (_architecture != null) { + return _architecture!; + } + + final architectureJson = await restClient.getAgentArchitecture(agentId); + + // Handle envelope format + if (architectureJson.containsKey('success')) { + final success = architectureJson['success'] as bool?; + if (success == false) { + final error = architectureJson['error']; + if (error is Map) { + final errorMessage = error['message']; + final errorCode = error['code']; + throw RunAgentExecutionError( + code: errorCode is String ? errorCode : ErrorCodes.serverError, + message: errorMessage is String ? errorMessage : 'Failed to retrieve agent architecture', + suggestion: error['suggestion'] is String ? error['suggestion'] : null, + details: error['details'] is Map ? error['details'] : null, + ); + } + final message = architectureJson['message']; + throw ServerError( + message: message is String ? message : 'Failed to retrieve agent architecture', + ); + } + + final data = architectureJson['data'] as Map?; + if (data != null) { + _architecture = AgentArchitecture.fromJson(data); + } + } else { + // Legacy format + _architecture = AgentArchitecture.fromJson(architectureJson); + } + + if (_architecture == null || _architecture!.entrypoints.isEmpty) { + throw ValidationError( + message: 'Architecture missing entrypoints', + suggestion: 'Redeploy the agent with entrypoints configured', + ); + } + + return _architecture!; + } + + /// Check if the agent is available + Future healthCheck() async { + try { + return await restClient.healthCheck(); + } catch (e) { + return false; + } + } + + /// Get agent ID + String getAgentId() => agentId; + + /// Get entrypoint tag + String getEntrypointTag() => entrypointTag; + + /// Get any extra params supplied during initialization + Map? getExtraParams() => extraParams; + + /// Check if using local deployment + bool isLocal() => local; + + /// Deserialize string payload + dynamic _deserializeString(String data) { + try { + // Try to parse as JSON + final parsed = jsonDecode(data); + + // If it's a structured format {type, payload}, deserialize it + if (parsed is Map) { + return _deserializeObject(parsed); + } + + return parsed; + } catch (e) { + // Return as plain string if JSON parsing fails + return data; + } + } + + /// Deserialize object payload + dynamic _deserializeObject(dynamic data) { + // Handle null + if (data == null) return null; + + // If it's not a Map, return as-is + if (data is! Map) { + return data; + } + + // Check for structured object with payload field (matching Python SDK) + if (data.containsKey('type') && data.containsKey('payload')) { + final type = data['type']; + final payload = data['payload']; + + // Parse payload based on type (matching Python serializer logic) + if (type == 'null') { + return null; + } else if (type == 'string') { + // Payload is a JSON string, parse it to get the actual string + try { + return jsonDecode(payload as String); + } catch (e) { + return payload; + } + } else if (type == 'integer') { + try { + final value = jsonDecode(payload as String); + return value is int ? value : int.parse(value.toString()); + } catch (e) { + return payload; + } + } else if (type == 'number') { + try { + final value = jsonDecode(payload as String); + return value is num ? value : double.parse(value.toString()); + } catch (e) { + return payload; + } + } else if (type == 'boolean') { + try { + return jsonDecode(payload as String); + } catch (e) { + return payload; + } + } else if (type == 'array' || type == 'object') { + // Parse JSON to get structured data + try { + return jsonDecode(payload as String); + } catch (e) { + return payload; + } + } else { + // Unknown type, try to parse as JSON + try { + return jsonDecode(payload as String); + } catch (e) { + return payload; + } + } + } + + return data; + } +} + diff --git a/runagent-dart/lib/src/client/socket_client.dart b/runagent-dart/lib/src/client/socket_client.dart new file mode 100644 index 0000000..958c7e4 --- /dev/null +++ b/runagent-dart/lib/src/client/socket_client.dart @@ -0,0 +1,218 @@ +import 'dart:async'; +import 'dart:convert'; +import 'package:web_socket_channel/web_socket_channel.dart'; +import 'package:runagent/src/errors/errors.dart'; +import 'package:runagent/src/utils/constants.dart'; + +/// WebSocket client for RunAgent streaming +class SocketClient { + final String baseSocketUrl; + final String? apiKey; + final bool isLocal; + + SocketClient({ + required this.baseSocketUrl, + this.apiKey, + this.isLocal = false, + }); + + /// Run agent with streaming + Stream runStream( + String agentId, + String entrypointTag, + List inputArgs, + Map inputKwargs, { + int timeoutSeconds = 600, + }) async* { + // Build WebSocket URL + String uri; + if (isLocal) { + uri = '$baseSocketUrl${RunAgentConstants.defaultApiPrefix}/agents/$agentId/run-stream'; + } else { + if (apiKey == null) { + throw AuthenticationError( + message: 'api_key is required for remote streaming', + suggestion: 'Set RUNAGENT_API_KEY or pass Config.APIKey', + ); + } + uri = '$baseSocketUrl${RunAgentConstants.defaultApiPrefix}/agents/$agentId/run-stream?token=$apiKey'; + } + + // Ensure proper WebSocket protocol + String wsUri = uri; + if (!wsUri.startsWith('ws://') && !wsUri.startsWith('wss://')) { + if (wsUri.startsWith('http://')) { + wsUri = wsUri.replaceFirst('http://', 'ws://'); + } else if (wsUri.startsWith('https://')) { + wsUri = wsUri.replaceFirst('https://', 'wss://'); + } else { + wsUri = 'wss://$wsUri'; + } + } + + WebSocketChannel? channel; + try { + // Create WebSocket connection + channel = WebSocketChannel.connect( + Uri.parse(wsUri), + ); + + // Send initial request + final requestData = { + 'entrypoint_tag': entrypointTag, + 'input_args': inputArgs, + 'input_kwargs': inputKwargs, + 'timeout_seconds': timeoutSeconds, + }; + + channel.sink.add(jsonEncode(requestData)); + + // Stream responses + await for (final message in channel.stream) { + try { + final json = jsonDecode(message) as Map; + final type = json['type'] as String?; + + if (type == 'error') { + final errorData = json['data'] ?? json['error']; + String errorMessage = 'Stream error'; + String? errorCode; + String? suggestion; + + if (errorData is Map) { + errorMessage = errorData['message'] ?? errorMessage; + errorCode = errorData['code']; + suggestion = errorData['suggestion']; + } else if (errorData is String) { + errorMessage = errorData; + } + + throw RunAgentExecutionError( + code: errorCode ?? ErrorCodes.serverError, + message: errorMessage, + suggestion: suggestion, + ); + } else if (type == 'status') { + final status = json['status'] as String?; + if (status == 'stream_completed') { + break; + } + // Continue for other status messages + } else if (type == 'data') { + final data = json['data'] ?? json['content']; + if (data != null) { + // Deserialize structured format if present + yield _deserializeChunk(data); + } + } else { + // Unknown type, yield the whole message + yield json; + } + } catch (e) { + if (e is RunAgentExecutionError) rethrow; + // Try to parse as plain string + if (message is String) { + yield message; + } else { + yield message; + } + } + } + } catch (e) { + if (e is RunAgentExecutionError) rethrow; + throw ConnectionError( + message: 'Failed to open WebSocket connection: $e', + suggestion: 'Check your network connection or agent status', + ); + } finally { + await channel?.sink.close(); + } + } + + /// Deserialize chunk data (handles structured format {type, payload}) + dynamic _deserializeChunk(dynamic data) { + // If data is a string, try to parse as JSON + if (data is String) { + try { + final parsed = jsonDecode(data); + return _deserializeStructured(parsed); + } catch (e) { + return data; + } + } + + // If data is already a Map, check for structured format + if (data is Map) { + return _deserializeStructured(data); + } + + return data; + } + + /// Deserialize structured format {type, payload} + dynamic _deserializeStructured(dynamic data) { + if (data is! Map) { + return data; + } + + // Check for structured format {type, payload} + if (data.containsKey('type') && data.containsKey('payload')) { + final type = data['type']; + final payload = data['payload']; + + // Parse payload based on type (matching Python serializer logic) + if (type == 'null') { + return null; + } else if (type == 'string') { + // Payload is a JSON string, parse it to get the actual string + try { + return jsonDecode(payload as String); + } catch (e) { + return payload; + } + } else if (type == 'integer') { + try { + final value = jsonDecode(payload as String); + return value is int ? value : int.parse(value.toString()); + } catch (e) { + return payload; + } + } else if (type == 'number') { + try { + final value = jsonDecode(payload as String); + return value is num ? value : double.parse(value.toString()); + } catch (e) { + return payload; + } + } else if (type == 'boolean') { + try { + return jsonDecode(payload as String); + } catch (e) { + return payload; + } + } else if (type == 'array' || type == 'object') { + // Parse JSON to get structured data + try { + final parsed = jsonDecode(payload as String); + // If the parsed object also has {content} field, extract it + if (parsed is Map && parsed.containsKey('content')) { + return parsed['content']; + } + return parsed; + } catch (e) { + return payload; + } + } else { + // Unknown type, try to parse as JSON + try { + return jsonDecode(payload as String); + } catch (e) { + return payload; + } + } + } + + return data; + } +} + diff --git a/runagent-dart/lib/src/errors/errors.dart b/runagent-dart/lib/src/errors/errors.dart new file mode 100644 index 0000000..79df1bb --- /dev/null +++ b/runagent-dart/lib/src/errors/errors.dart @@ -0,0 +1,99 @@ +/// RunAgent SDK error types and exceptions + +/// Base exception class for all RunAgent errors +class RunAgentError implements Exception { + final String code; + final String message; + final String? suggestion; + final Map? details; + + RunAgentError({ + required this.code, + required this.message, + this.suggestion, + this.details, + }); + + @override + String toString() => '[$code] $message${suggestion != null ? '\nSuggestion: $suggestion' : ''}'; +} + +/// Execution error raised when agent execution fails +class RunAgentExecutionError extends RunAgentError { + RunAgentExecutionError({ + required super.code, + required super.message, + super.suggestion, + super.details, + }); +} + +/// Authentication error (401, 403) +class AuthenticationError extends RunAgentError { + AuthenticationError({ + required super.message, + super.suggestion, + super.details, + }) : super(code: 'AUTHENTICATION_ERROR'); +} + +/// Permission error (403) +class PermissionError extends RunAgentError { + PermissionError({ + required super.message, + super.suggestion, + super.details, + }) : super(code: 'PERMISSION_ERROR'); +} + +/// Connection error (network, DNS, TLS issues) +class ConnectionError extends RunAgentError { + ConnectionError({ + required super.message, + super.suggestion, + super.details, + }) : super(code: 'CONNECTION_ERROR'); +} + +/// Validation error (400, 422, bad config) +class ValidationError extends RunAgentError { + ValidationError({ + required super.message, + super.suggestion, + super.details, + }) : super(code: 'VALIDATION_ERROR'); +} + +/// Server error (5xx) +class ServerError extends RunAgentError { + ServerError({ + required super.message, + super.suggestion, + super.details, + }) : super(code: 'SERVER_ERROR'); +} + +/// Unknown error +class UnknownError extends RunAgentError { + UnknownError({ + required super.message, + super.suggestion, + super.details, + }) : super(code: 'UNKNOWN_ERROR'); +} + +/// Error codes matching the checklist taxonomy +class ErrorCodes { + static const String authenticationError = 'AUTHENTICATION_ERROR'; + static const String permissionError = 'PERMISSION_ERROR'; + static const String connectionError = 'CONNECTION_ERROR'; + static const String validationError = 'VALIDATION_ERROR'; + static const String serverError = 'SERVER_ERROR'; + static const String unknownError = 'UNKNOWN_ERROR'; + static const String agentNotFoundLocal = 'AGENT_NOT_FOUND_LOCAL'; + static const String agentNotFoundRemote = 'AGENT_NOT_FOUND_REMOTE'; + static const String streamEntrypoint = 'STREAM_ENTRYPOINT'; + static const String nonStreamEntrypoint = 'NON_STREAM_ENTRYPOINT'; + static const String architectureMissing = 'ARCHITECTURE_MISSING'; +} + diff --git a/runagent-dart/lib/src/types/types.dart b/runagent-dart/lib/src/types/types.dart new file mode 100644 index 0000000..00a378d --- /dev/null +++ b/runagent-dart/lib/src/types/types.dart @@ -0,0 +1,148 @@ +/// Type definitions for RunAgent SDK + +/// Configuration for RunAgentClient +class RunAgentClientConfig { + /// Agent ID (required) + final String agentId; + + /// Entrypoint tag (required) + final String entrypointTag; + + /// Whether this is a local agent (default: false) + final bool? local; + + /// Host for local agents (optional, will lookup from DB if not provided and local=true) + final String? host; + + /// Port for local agents (optional, will lookup from DB if not provided and local=true) + final int? port; + + /// API key for remote agents (optional, can also use RUNAGENT_API_KEY env var) + final String? apiKey; + + /// Base URL for remote agents (optional, defaults to https://backend.run-agent.ai) + final String? baseUrl; + + /// Extra parameters for future use + final Map? extraParams; + + /// Enable database registry lookup (default: true for local agents) + final bool? enableRegistry; + + RunAgentClientConfig({ + required this.agentId, + required this.entrypointTag, + this.local, + this.host, + this.port, + this.apiKey, + this.baseUrl, + this.extraParams, + this.enableRegistry, + }); + + /// Create a config with required fields + factory RunAgentClientConfig.create({ + required String agentId, + required String entrypointTag, + bool? local, + String? host, + int? port, + String? apiKey, + String? baseUrl, + Map? extraParams, + bool? enableRegistry, + }) { + return RunAgentClientConfig( + agentId: agentId, + entrypointTag: entrypointTag, + local: local, + host: host, + port: port, + apiKey: apiKey, + baseUrl: baseUrl, + extraParams: extraParams, + enableRegistry: enableRegistry, + ); + } +} + +/// Agent architecture information +class AgentArchitecture { + final String agentId; + final List entrypoints; + + AgentArchitecture({ + required this.agentId, + required this.entrypoints, + }); + + factory AgentArchitecture.fromJson(Map json) { + final agentIdValue = json['agent_id'] ?? json['agentId']; + final entrypointsValue = json['entrypoints']; + + return AgentArchitecture( + agentId: agentIdValue is String ? agentIdValue : (agentIdValue?.toString() ?? ''), + entrypoints: entrypointsValue is List + ? entrypointsValue + .whereType>() + .map((e) => EntryPoint.fromJson(e)) + .toList() + : [], + ); + } +} + +/// Entry point definition +class EntryPoint { + final String tag; + final String? file; + final String? module; + final String? extractor; + final String? description; + + EntryPoint({ + required this.tag, + this.file, + this.module, + this.extractor, + this.description, + }); + + factory EntryPoint.fromJson(Map json) { + final tagValue = json['tag']; + return EntryPoint( + tag: tagValue is String ? tagValue : (tagValue?.toString() ?? ''), + file: json['file'] is String ? json['file'] : null, + module: json['module'] is String ? json['module'] : null, + extractor: json['extractor'] is String ? json['extractor'] : null, + description: json['description'] is String ? json['description'] : null, + ); + } +} + +/// Run input payload +class RunInput { + final List inputArgs; + final Map inputKwargs; + final int timeoutSeconds; + final bool asyncExecution; + + RunInput({ + this.inputArgs = const [], + this.inputKwargs = const {}, + this.timeoutSeconds = 300, + this.asyncExecution = false, + }); + + Map toJson() { + return { + 'entrypoint_tag': '', // Will be set by client + 'input_args': inputArgs, + 'input_kwargs': inputKwargs, + 'timeout_seconds': timeoutSeconds, + 'async_execution': asyncExecution, + }; + } +} + diff --git a/runagent-dart/lib/src/utils/config.dart b/runagent-dart/lib/src/utils/config.dart new file mode 100644 index 0000000..5e0acbf --- /dev/null +++ b/runagent-dart/lib/src/utils/config.dart @@ -0,0 +1,70 @@ +import 'dart:io'; +import 'package:runagent/src/utils/constants.dart'; + +/// Configuration utilities for loading from environment variables +class ConfigUtils { + /// Get API key from environment or return null + static String? getApiKey() { + return Platform.environment[RunAgentConstants.envApiKey]; + } + + /// Get base URL from environment or return default + static String getBaseUrl() { + return Platform.environment[RunAgentConstants.envBaseUrl] ?? + RunAgentConstants.defaultBaseUrl; + } + + /// Get local flag from environment + static bool? getLocal() { + final value = Platform.environment[RunAgentConstants.envLocalAgent]; + if (value == null) return null; + return value.toLowerCase() == 'true'; + } + + /// Get host from environment + static String? getHost() { + return Platform.environment[RunAgentConstants.envAgentHost]; + } + + /// Get port from environment + static int? getPort() { + final value = Platform.environment[RunAgentConstants.envAgentPort]; + if (value == null) return null; + return int.tryParse(value); + } + + /// Get timeout from environment + static int? getTimeout() { + final value = Platform.environment[RunAgentConstants.envTimeout]; + if (value == null) return null; + return int.tryParse(value); + } + + /// Resolve boolean with precedence: explicit > env > default + static bool resolveBool(bool? explicit, bool? env, bool defaultValue) { + if (explicit != null) return explicit; + if (env != null) return env; + return defaultValue; + } + + /// Get first non-empty string from list + static String? firstNonEmpty(List values) { + for (final value in values) { + if (value != null && value.trim().isNotEmpty) { + return value.trim(); + } + } + return null; + } + + /// Get first non-zero integer from list + static int? firstNonZero(List values) { + for (final value in values) { + if (value != null && value > 0) { + return value; + } + } + return null; + } +} + diff --git a/runagent-dart/lib/src/utils/constants.dart b/runagent-dart/lib/src/utils/constants.dart new file mode 100644 index 0000000..b077f84 --- /dev/null +++ b/runagent-dart/lib/src/utils/constants.dart @@ -0,0 +1,38 @@ +/// Constants used throughout the RunAgent SDK +class RunAgentConstants { + /// Default base URL for remote RunAgent service + static const String defaultBaseUrl = 'https://backend.run-agent.ai'; + + /// Default API prefix + static const String defaultApiPrefix = '/api/v1'; + + /// Default port for local agents + static const int defaultLocalPort = 8450; + + /// Default host for local agents + static const String defaultLocalHost = '127.0.0.1'; + + /// Default timeout in seconds + static const int defaultTimeoutSeconds = 300; + + /// Default stream timeout in seconds + static const int defaultStreamTimeout = 600; + + /// Local cache directory path (relative to home) + static const String localCacheDirectory = '.runagent'; + + /// Database file name + static const String databaseFileName = 'runagent_local.db'; + + /// Environment variable names + static const String envApiKey = 'RUNAGENT_API_KEY'; + static const String envBaseUrl = 'RUNAGENT_BASE_URL'; + static const String envLocalAgent = 'RUNAGENT_LOCAL'; + static const String envAgentHost = 'RUNAGENT_HOST'; + static const String envAgentPort = 'RUNAGENT_PORT'; + static const String envTimeout = 'RUNAGENT_TIMEOUT'; + + /// User agent string + static String userAgent() => 'runagent-dart/0.1.0'; +} + diff --git a/runagent-dart/pubspec.yaml b/runagent-dart/pubspec.yaml new file mode 100644 index 0000000..f58b819 --- /dev/null +++ b/runagent-dart/pubspec.yaml @@ -0,0 +1,16 @@ +name: runagent +description: RunAgent SDK for Dart/Flutter - Interact with deployed AI agents via REST and WebSocket +version: 0.1.41 +homepage: https://github.com/runagent-dev/runagent-dart + +environment: + sdk: '>=3.0.0 <4.0.0' + +dependencies: + http: ^1.1.0 + web_socket_channel: ^2.4.0 + +dev_dependencies: + test: ^1.24.0 + lints: ^3.0.0 + diff --git a/runagent-dart/test/runagent_types_test.dart b/runagent-dart/test/runagent_types_test.dart new file mode 100644 index 0000000..91d6327 --- /dev/null +++ b/runagent-dart/test/runagent_types_test.dart @@ -0,0 +1,76 @@ +import 'package:runagent/runagent.dart'; +import 'package:test/test.dart'; + +void main() { + group('RunAgentClientConfig', () { + test('create factory mirrors constructor arguments', () { + final config = RunAgentClientConfig.create( + agentId: 'agent-123', + entrypointTag: 'article_create', + local: true, + host: '127.0.0.1', + port: 8451, + apiKey: 'secret', + baseUrl: 'https://example.com', + extraParams: {'foo': 'bar'}, + enableRegistry: false, + ); + + expect(config.agentId, 'agent-123'); + expect(config.entrypointTag, 'article_create'); + expect(config.local, isTrue); + expect(config.host, '127.0.0.1'); + expect(config.port, 8451); + expect(config.apiKey, 'secret'); + expect(config.baseUrl, 'https://example.com'); + expect(config.extraParams, {'foo': 'bar'}); + expect(config.enableRegistry, isFalse); + }); + }); + + group('AgentArchitecture', () { + test('fromJson normalizes agentId and entrypoints', () { + final architecture = AgentArchitecture.fromJson({ + 'agent_id': 42, + 'entrypoints': [ + { + 'tag': 'article_create', + 'file': 'main.py', + 'description': 'Create article', + }, + { + 'tag': 123, + 'module': 'agents.writer', + }, + ], + }); + + expect(architecture.agentId, '42'); + expect(architecture.entrypoints, hasLength(2)); + expect(architecture.entrypoints.first.tag, 'article_create'); + expect(architecture.entrypoints.first.file, 'main.py'); + expect(architecture.entrypoints.last.tag, '123'); + expect(architecture.entrypoints.last.module, 'agents.writer'); + }); + }); + + group('RunInput', () { + test('toJson includes defaults and provided values', () { + final runInput = RunInput( + inputArgs: ['foo'], + inputKwargs: {'bar': 1}, + timeoutSeconds: 120, + asyncExecution: true, + ); + + final json = runInput.toJson(); + + expect(json['entrypoint_tag'], ''); + expect(json['input_args'], ['foo']); + expect(json['input_kwargs'], {'bar': 1}); + expect(json['timeout_seconds'], 120); + expect(json['async_execution'], isTrue); + }); + }); +} + diff --git a/test/runagent_types_test.dart b/test/runagent_types_test.dart new file mode 100644 index 0000000..79f86b5 --- /dev/null +++ b/test/runagent_types_test.dart @@ -0,0 +1,151 @@ +import 'package:runagent/runagent.dart'; +import 'package:test/test.dart'; + +void main() { + group('RunAgentClientConfig', () { + test('create factory mirrors constructor arguments', () { + final config = RunAgentClientConfig.create( + agentId: 'agent-123', + entrypointTag: 'article_create', + local: true, + host: '127.0.0.1', + port: 8451, + apiKey: 'secret', + baseUrl: 'https://example.com', + extraParams: {'foo': 'bar'}, + enableRegistry: false, + ); + + expect(config.agentId, 'agent-123'); + expect(config.entrypointTag, 'article_create'); + expect(config.local, isTrue); + expect(config.host, '127.0.0.1'); + expect(config.port, 8451); + expect(config.apiKey, 'secret'); + expect(config.baseUrl, 'https://example.com'); + expect(config.extraParams, {'foo': 'bar'}); + expect(config.enableRegistry, isFalse); + }); + }); + + group('AgentArchitecture', () { + test('fromJson normalizes agentId and entrypoints', () { + final architecture = AgentArchitecture.fromJson({ + 'agent_id': 42, + 'entrypoints': [ + { + 'tag': 'article_create', + 'file': 'main.py', + 'description': 'Create article', + }, + { + 'tag': 123, + 'module': 'agents.writer', + }, + ], + }); + + expect(architecture.agentId, '42'); + expect(architecture.entrypoints, hasLength(2)); + expect(architecture.entrypoints.first.tag, 'article_create'); + expect(architecture.entrypoints.first.file, 'main.py'); + expect(architecture.entrypoints.last.tag, '123'); + expect(architecture.entrypoints.last.module, 'agents.writer'); + }); + }); + + group('RunInput', () { + test('toJson includes defaults and provided values', () { + final runInput = RunInput( + inputArgs: ['foo'], + inputKwargs: {'bar': 1}, + timeoutSeconds: 120, + asyncExecution: true, + ); + + final json = runInput.toJson(); + + expect(json['entrypoint_tag'], ''); + expect(json['input_args'], ['foo']); + expect(json['input_kwargs'], {'bar': 1}); + expect(json['timeout_seconds'], 120); + expect(json['async_execution'], isTrue); + }); + }); +} +import 'package:runagent/runagent.dart'; +import 'package:test/test.dart'; + +void main() { + group('RunAgentClientConfig', () { + test('create factory mirrors constructor arguments', () { + final config = RunAgentClientConfig.create( + agentId: 'agent-123', + entrypointTag: 'article_create', + local: true, + host: '127.0.0.1', + port: 8451, + apiKey: 'secret', + baseUrl: 'https://example.com', + extraParams: {'foo': 'bar'}, + enableRegistry: false, + ); + + expect(config.agentId, 'agent-123'); + expect(config.entrypointTag, 'article_create'); + expect(config.local, isTrue); + expect(config.host, '127.0.0.1'); + expect(config.port, 8451); + expect(config.apiKey, 'secret'); + expect(config.baseUrl, 'https://example.com'); + expect(config.extraParams, {'foo': 'bar'}); + expect(config.enableRegistry, isFalse); + }); + }); + + group('AgentArchitecture', () { + test('fromJson normalizes agentId and entrypoints', () { + final architecture = AgentArchitecture.fromJson({ + 'agent_id': 42, + 'entrypoints': [ + { + 'tag': 'article_create', + 'file': 'main.py', + 'description': 'Create article', + }, + { + 'tag': 123, + 'module': 'agents.writer', + }, + ], + }); + + expect(architecture.agentId, '42'); + expect(architecture.entrypoints, hasLength(2)); + expect(architecture.entrypoints.first.tag, 'article_create'); + expect(architecture.entrypoints.first.file, 'main.py'); + expect(architecture.entrypoints.last.tag, '123'); + expect(architecture.entrypoints.last.module, 'agents.writer'); + }); + }); + + group('RunInput', () { + test('toJson includes defaults and provided values', () { + final runInput = RunInput( + inputArgs: ['foo'], + inputKwargs: {'bar': 1}, + timeoutSeconds: 120, + asyncExecution: true, + ); + + final json = runInput.toJson(); + + expect(json['entrypoint_tag'], ''); + expect(json['input_args'], ['foo']); + expect(json['input_kwargs'], {'bar': 1}); + expect(json['timeout_seconds'], 120); + expect(json['async_execution'], isTrue); + }); + }); +} + diff --git a/test_scripts/dart/test_agno/.dart_tool/package_config.json b/test_scripts/dart/test_agno/.dart_tool/package_config.json new file mode 100644 index 0000000..a6401df --- /dev/null +++ b/test_scripts/dart/test_agno/.dart_tool/package_config.json @@ -0,0 +1,106 @@ +{ + "configVersion": 2, + "packages": [ + { + "name": "async", + "rootUri": "file:///home/azureuser/.pub-cache/hosted/pub.dev/async-2.13.0", + "packageUri": "lib/", + "languageVersion": "3.4" + }, + { + "name": "collection", + "rootUri": "file:///home/azureuser/.pub-cache/hosted/pub.dev/collection-1.19.1", + "packageUri": "lib/", + "languageVersion": "3.4" + }, + { + "name": "crypto", + "rootUri": "file:///home/azureuser/.pub-cache/hosted/pub.dev/crypto-3.0.7", + "packageUri": "lib/", + "languageVersion": "3.4" + }, + { + "name": "http", + "rootUri": "file:///home/azureuser/.pub-cache/hosted/pub.dev/http-1.6.0", + "packageUri": "lib/", + "languageVersion": "3.4" + }, + { + "name": "http_parser", + "rootUri": "file:///home/azureuser/.pub-cache/hosted/pub.dev/http_parser-4.1.2", + "packageUri": "lib/", + "languageVersion": "3.4" + }, + { + "name": "meta", + "rootUri": "file:///home/azureuser/.pub-cache/hosted/pub.dev/meta-1.17.0", + "packageUri": "lib/", + "languageVersion": "3.5" + }, + { + "name": "path", + "rootUri": "file:///home/azureuser/.pub-cache/hosted/pub.dev/path-1.9.1", + "packageUri": "lib/", + "languageVersion": "3.4" + }, + { + "name": "runagent", + "rootUri": "file:///home/azureuser/.pub-cache/hosted/pub.dev/runagent-0.1.41", + "packageUri": "lib/", + "languageVersion": "3.0" + }, + { + "name": "source_span", + "rootUri": "file:///home/azureuser/.pub-cache/hosted/pub.dev/source_span-1.10.1", + "packageUri": "lib/", + "languageVersion": "3.1" + }, + { + "name": "stream_channel", + "rootUri": "file:///home/azureuser/.pub-cache/hosted/pub.dev/stream_channel-2.1.4", + "packageUri": "lib/", + "languageVersion": "3.3" + }, + { + "name": "string_scanner", + "rootUri": "file:///home/azureuser/.pub-cache/hosted/pub.dev/string_scanner-1.4.1", + "packageUri": "lib/", + "languageVersion": "3.1" + }, + { + "name": "term_glyph", + "rootUri": "file:///home/azureuser/.pub-cache/hosted/pub.dev/term_glyph-1.2.2", + "packageUri": "lib/", + "languageVersion": "3.1" + }, + { + "name": "typed_data", + "rootUri": "file:///home/azureuser/.pub-cache/hosted/pub.dev/typed_data-1.4.0", + "packageUri": "lib/", + "languageVersion": "3.5" + }, + { + "name": "web", + "rootUri": "file:///home/azureuser/.pub-cache/hosted/pub.dev/web-0.5.1", + "packageUri": "lib/", + "languageVersion": "3.3" + }, + { + "name": "web_socket_channel", + "rootUri": "file:///home/azureuser/.pub-cache/hosted/pub.dev/web_socket_channel-2.4.5", + "packageUri": "lib/", + "languageVersion": "3.3" + }, + { + "name": "test_agno", + "rootUri": "../", + "packageUri": "lib/", + "languageVersion": "3.0" + } + ], + "generator": "pub", + "generatorVersion": "3.10.0", + "flutterRoot": "file:///home/azureuser/snap/flutter/common/flutter", + "flutterVersion": "3.38.2", + "pubCache": "file:///home/azureuser/.pub-cache" +} diff --git a/test_scripts/dart/test_agno/.dart_tool/package_graph.json b/test_scripts/dart/test_agno/.dart_tool/package_graph.json new file mode 100644 index 0000000..3b9e91c --- /dev/null +++ b/test_scripts/dart/test_agno/.dart_tool/package_graph.json @@ -0,0 +1,124 @@ +{ + "roots": [ + "test_agno" + ], + "packages": [ + { + "name": "test_agno", + "version": "0.1.0", + "dependencies": [ + "runagent" + ], + "devDependencies": [] + }, + { + "name": "runagent", + "version": "0.1.41", + "dependencies": [ + "http", + "web_socket_channel" + ] + }, + { + "name": "web_socket_channel", + "version": "2.4.5", + "dependencies": [ + "async", + "crypto", + "stream_channel", + "web" + ] + }, + { + "name": "http", + "version": "1.6.0", + "dependencies": [ + "async", + "http_parser", + "meta", + "web" + ] + }, + { + "name": "web", + "version": "0.5.1", + "dependencies": [] + }, + { + "name": "stream_channel", + "version": "2.1.4", + "dependencies": [ + "async" + ] + }, + { + "name": "crypto", + "version": "3.0.7", + "dependencies": [ + "typed_data" + ] + }, + { + "name": "async", + "version": "2.13.0", + "dependencies": [ + "collection", + "meta" + ] + }, + { + "name": "meta", + "version": "1.17.0", + "dependencies": [] + }, + { + "name": "http_parser", + "version": "4.1.2", + "dependencies": [ + "collection", + "source_span", + "string_scanner", + "typed_data" + ] + }, + { + "name": "typed_data", + "version": "1.4.0", + "dependencies": [ + "collection" + ] + }, + { + "name": "collection", + "version": "1.19.1", + "dependencies": [] + }, + { + "name": "string_scanner", + "version": "1.4.1", + "dependencies": [ + "source_span" + ] + }, + { + "name": "source_span", + "version": "1.10.1", + "dependencies": [ + "collection", + "path", + "term_glyph" + ] + }, + { + "name": "term_glyph", + "version": "1.2.2", + "dependencies": [] + }, + { + "name": "path", + "version": "1.9.1", + "dependencies": [] + } + ], + "configVersion": 1 +} \ No newline at end of file diff --git a/test_scripts/dart/test_agno/README.md b/test_scripts/dart/test_agno/README.md new file mode 100644 index 0000000..8ef72ea --- /dev/null +++ b/test_scripts/dart/test_agno/README.md @@ -0,0 +1,45 @@ +# Test Agno - Dart SDK Example + +This is a test script for the RunAgent Dart SDK using the Agno agent. + +## Setup + +1. Make sure you have Dart 3.0+ installed +2. Install dependencies: + ```bash + dart pub get + ``` + +## Running + +### Async Non-Streaming (Active) +```bash +dart run lib/main.dart +``` + +This will run the async non-streaming example that's currently active in `lib/main.dart`. + +### Other Examples + +The file contains commented-out examples for: +- Async streaming +- Sync non-streaming (using Future.then) +- Sync streaming (using Future.then) + +To use any of these, uncomment the desired example and comment out the active one. + +## Configuration + +Update the `agentId` and `entrypointTag` in `lib/main.dart` to match your agent configuration. + +For local agents, you can also set: +```dart +RunAgentClientConfig.create( + agentId: 'your-agent-id', + entrypointTag: 'agno_print_response', + local: true, + host: '127.0.0.1', + port: 8450, +) +``` + diff --git a/test_scripts/dart/test_agno/lib/main.dart b/test_scripts/dart/test_agno/lib/main.dart new file mode 100644 index 0000000..2c1e7cb --- /dev/null +++ b/test_scripts/dart/test_agno/lib/main.dart @@ -0,0 +1,69 @@ +// // async version non-streaming +// import 'dart:io'; +// import 'package:runagent/runagent.dart'; + +// Future main() async { +// try { +// // Direct config construction +// final client = await RunAgentClient.create( +// RunAgentClientConfig.create( +// agentId: 'ae29bd73-b3d3-42c8-a98f-5d7aec7ee919', +// entrypointTag: 'agno_print_response', +// ), +// ); + +// final response = await client.run({ +// 'prompt': 'which is better toyota or land rover', +// }); + +// // Response is a Map, not a String +// print('Response: $response'); // <-- Added semicolon here +// } catch (e) { +// if (e is RunAgentError) { +// print('Error: ${e.message}'); +// if (e.suggestion != null) { +// print('Suggestion: ${e.suggestion}'); +// } +// } else { +// print('Unexpected error: $e'); +// } +// } +// } + +// ******************************Streaming Part with agno**************************************** +// async version streaming (Flutter/Dart idiomatic approach) + +import 'dart:io'; +import 'package:runagent/runagent.dart'; + +Future main() async { + try { + final client = await RunAgentClient.create( + RunAgentClientConfig.create( + agentId: 'ae29bd73-b3d3-42c8-a98f-5d7aec7ee919', + entrypointTag: 'agno_print_response_stream', + ), + ); + + // Real streaming - processes chunks as they arrive (idiomatic Dart/Flutter) + // This is the recommended approach for Flutter developers + await for (final chunk in client.runStream({ + 'prompt': 'tell me a short story about scotland', + })) { + print('Response: $chunk'); + } + } catch (e) { + if (e is RunAgentError) { + print('Error: ${e.message}'); + if (e.suggestion != null) { + print('Suggestion: ${e.suggestion}'); + } + exit(1); + } else { + print('Unexpected error: $e'); + exit(1); + } + } +} + + diff --git a/test_scripts/dart/test_agno/pubspec.yaml b/test_scripts/dart/test_agno/pubspec.yaml new file mode 100644 index 0000000..ab671e2 --- /dev/null +++ b/test_scripts/dart/test_agno/pubspec.yaml @@ -0,0 +1,10 @@ +name: test_agno +description: Test script for RunAgent Dart SDK with Agno agent +version: 0.1.0 + +environment: + sdk: '>=3.0.0 <4.0.0' + +dependencies: + runagent: ^0.1.41 +