diff --git a/.github/workflows/create-release.yml b/.github/workflows/create-release.yml new file mode 100644 index 0000000..0afce07 --- /dev/null +++ b/.github/workflows/create-release.yml @@ -0,0 +1,267 @@ +# .github/workflows/create-release.yml +name: Create GitHub Release + +on: + push: + branches: [main] # Trigger on ANY push to main (including PR merges) + pull_request: + types: [closed] # Also trigger when PRs are merged + branches: [main] + +jobs: + detect-and-release: + runs-on: ubuntu-latest + if: github.event_name == 'push' || (github.event.pull_request.merged == true) + + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 # Need full history to find tags + + - name: Find new tags reachable from main + id: find-tags + run: | + echo "🔍 [MAIN RELEASE] Checking for version tags now reachable from main..." + + # Get all version tags + ALL_TAGS=$(git tag -l 'v*' | sort -V) + echo "All version tags: $ALL_TAGS" + + # Find tags that are reachable from main + REACHABLE_TAGS="" + for tag in $ALL_TAGS; do + TAG_COMMIT=$(git rev-list -n 1 $tag) + if git merge-base --is-ancestor $TAG_COMMIT HEAD; then + echo "✅ Tag $tag is reachable from main (commit: $TAG_COMMIT)" + REACHABLE_TAGS="$REACHABLE_TAGS $tag" + else + echo "❌ Tag $tag is NOT reachable from main (commit: $TAG_COMMIT)" + fi + done + + echo "reachable_tags=$REACHABLE_TAGS" >> $GITHUB_OUTPUT + + # Find the latest reachable tag + if [ -n "$REACHABLE_TAGS" ]; then + LATEST_TAG=$(echo $REACHABLE_TAGS | tr ' ' '\n' | sort -V | tail -1) + echo "latest_tag=$LATEST_TAG" >> $GITHUB_OUTPUT + echo "🏷️ Latest reachable tag: $LATEST_TAG" + + # Extract version + VERSION=${LATEST_TAG#v} + echo "version=$VERSION" >> $GITHUB_OUTPUT + else + echo "No version tags reachable from main" + echo "latest_tag=" >> $GITHUB_OUTPUT + echo "version=" >> $GITHUB_OUTPUT + fi + + - name: Check if this is a new release + id: check-release + run: | + LATEST_TAG="${{ steps.find-tags.outputs.latest_tag }}" + + if [ -z "$LATEST_TAG" ]; then + echo "No tags found, skipping release" + echo "should_release=false" >> $GITHUB_OUTPUT + exit 0 + fi + + # Check if we already created a release for this tag + if gh release view "$LATEST_TAG" >/dev/null 2>&1; then + echo "Release for $LATEST_TAG already exists, skipping" + echo "should_release=false" >> $GITHUB_OUTPUT + else + echo "🚀 New tag $LATEST_TAG detected, should create release" + echo "should_release=true" >> $GITHUB_OUTPUT + fi + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + - name: Install git-cliff for changelog generation + if: steps.check-release.outputs.should_release == 'true' + run: | + echo "📥 Installing git-cliff for changelog generation..." + curl -L https://github.com/orhun/git-cliff/releases/latest/download/git-cliff-x86_64-unknown-linux-gnu.tar.gz | tar -xz + sudo mv git-cliff-*/git-cliff /usr/local/bin/ + git-cliff --version + + - name: Generate changelog + if: steps.check-release.outputs.should_release == 'true' + id: changelog + run: | + echo "📝 Generating changelog for v${{ steps.find-tags.outputs.version }}..." + + # Find the last tag to show range + LAST_TAG=$(git describe --tags --abbrev=0 ${{ steps.find-tags.outputs.latest_tag }}^ 2>/dev/null || echo "") + + if [ -n "$LAST_TAG" ]; then + echo "📍 Generating changelog for range: $LAST_TAG → ${{ steps.find-tags.outputs.latest_tag }}" + COMMIT_COUNT=$(git rev-list --count $LAST_TAG..${{ steps.find-tags.outputs.latest_tag }}) + echo "📊 Found $COMMIT_COUNT commits since $LAST_TAG" + else + echo "📍 No previous tags found - generating changelog for initial release" + COMMIT_COUNT=$(git rev-list --count ${{ steps.find-tags.outputs.latest_tag }}) + echo "📊 Found $COMMIT_COUNT total commits" + fi + + # Generate changelog using git-cliff + git-cliff --tag "${{ steps.find-tags.outputs.latest_tag }}" --strip header --strip footer > current_changelog.md + + # If git-cliff output is empty or too short, create a basic changelog + if [ ! -s current_changelog.md ] || [ $(wc -l < current_changelog.md) -lt 3 ]; then + echo "⚠️ Git-cliff output was minimal, creating basic changelog" + + if [ -n "$LAST_TAG" ]; then + echo "## What's Changed" > current_changelog.md + echo "" >> current_changelog.md + + # Categorize commits + echo "### ✨ Features" >> current_changelog.md + git log --pretty=format:"- %s" $LAST_TAG..${{ steps.find-tags.outputs.latest_tag }} | grep -i "^- feat" | sed 's/^- feat[:(]/- /' | sed 's/^- feat: /- /' >> current_changelog.md || echo "- No new features" >> current_changelog.md + echo "" >> current_changelog.md + + echo "### 🐛 Bug Fixes" >> current_changelog.md + git log --pretty=format:"- %s" $LAST_TAG..${{ steps.find-tags.outputs.latest_tag }} | grep -i "^- fix" | sed 's/^- fix[:(]/- /' | sed 's/^- fix: /- /' >> current_changelog.md || echo "- No bug fixes" >> current_changelog.md + echo "" >> current_changelog.md + + echo "### 📚 Documentation" >> current_changelog.md + git log --pretty=format:"- %s" $LAST_TAG..${{ steps.find-tags.outputs.latest_tag }} | grep -i "^- docs" | sed 's/^- docs[:(]/- /' | sed 's/^- docs: /- /' >> current_changelog.md || echo "- No documentation changes" >> current_changelog.md + echo "" >> current_changelog.md + + echo "### Other Changes" >> current_changelog.md + git log --pretty=format:"- %s" $LAST_TAG..${{ steps.find-tags.outputs.latest_tag }} | grep -v -i "^- \(feat\|fix\|docs\)" >> current_changelog.md || echo "- No other changes" >> current_changelog.md + else + echo "## Initial Release" > current_changelog.md + echo "" >> current_changelog.md + echo "🎉 First release of RunAgent Universal AI Agent Platform!" >> current_changelog.md + echo "" >> current_changelog.md + echo "### Features" >> current_changelog.md + echo "- Universal AI agent platform supporting multiple languages" >> current_changelog.md + echo "- Python, JavaScript, Rust, and Go SDKs" >> current_changelog.md + echo "- Framework-agnostic agent deployment" >> current_changelog.md + echo "- Real-time streaming support" >> current_changelog.md + fi + fi + + # Read the changelog content + CHANGELOG_CONTENT=$(cat current_changelog.md) + + # Save changelog content for use in release body + echo "changelog<> $GITHUB_OUTPUT + echo "$CHANGELOG_CONTENT" >> $GITHUB_OUTPUT + echo "EOF" >> $GITHUB_OUTPUT + + echo "✅ Changelog generated successfully" + echo "📄 Preview:" + echo "----------" + head -15 current_changelog.md + if [ $(wc -l < current_changelog.md) -gt 15 ]; then + echo "... ($(wc -l < current_changelog.md) total lines)" + fi + + - name: Update CHANGELOG.md in repository + if: steps.check-release.outputs.should_release == 'true' + run: | + echo "📝 Updating CHANGELOG.md in repository..." + + # Create the new changelog entry + echo "# Changelog" > CHANGELOG_NEW.md + echo "" >> CHANGELOG_NEW.md + echo "## [v${{ steps.find-tags.outputs.version }}] - $(date +%Y-%m-%d)" >> CHANGELOG_NEW.md + echo "" >> CHANGELOG_NEW.md + cat current_changelog.md >> CHANGELOG_NEW.md + echo "" >> CHANGELOG_NEW.md + + # Append existing changelog if it exists + if [ -f "CHANGELOG.md" ] && [ -s "CHANGELOG.md" ]; then + # Skip the header of existing changelog + tail -n +2 CHANGELOG.md >> CHANGELOG_NEW.md + fi + + mv CHANGELOG_NEW.md CHANGELOG.md + + echo "✅ CHANGELOG.md updated" + + - name: Commit updated CHANGELOG.md + if: steps.check-release.outputs.should_release == 'true' + run: | + echo "💾 Committing updated CHANGELOG.md..." + + # Configure git + git config user.name "github-actions[bot]" + git config user.email "41898282+github-actions[bot]@users.noreply.github.com" + + # Add and commit the changelog + git add CHANGELOG.md + if git diff --staged --quiet; then + echo "No changes to commit" + else + git commit -m "docs: update CHANGELOG.md for v${{ steps.find-tags.outputs.version }}" + git push origin main + echo "✅ CHANGELOG.md committed and pushed" + fi + + - name: Create main GitHub Release + if: steps.check-release.outputs.should_release == 'true' + uses: softprops/action-gh-release@v1 + with: + tag_name: ${{ steps.find-tags.outputs.latest_tag }} + name: RunAgent v${{ steps.find-tags.outputs.version }} + body: | + # 🚀 RunAgent v${{ steps.find-tags.outputs.version }} + + **Universal AI Agent Platform - All SDKs synchronized at v${{ steps.find-tags.outputs.version }}** + + ## 📦 Installation + + Choose your preferred language: + + ### Python + ```bash + pip install runagent==${{ steps.find-tags.outputs.version }} + ``` + + ### JavaScript/TypeScript + ```bash + npm install runagent@${{ steps.find-tags.outputs.version }} + ``` + + ### Rust + ```toml + [dependencies] + runagent = "${{ steps.find-tags.outputs.version }}" + ``` + + ### Go + ```bash + go get github.com/runagent-dev/runagent/runagent-go@${{ steps.find-tags.outputs.latest_tag }} + ``` + + ## 📋 Changes in this Release + + ${{ steps.changelog.outputs.changelog }} + + ## 🔗 Package Links + + - 🐍 [PyPI Package](https://pypi.org/project/runagent/${{ steps.find-tags.outputs.version }}/) (Publishing...) + - 📦 [npm Package](https://www.npmjs.com/package/runagent/v/${{ steps.find-tags.outputs.version }}) (Publishing...) + - 🦀 [crates.io Package](https://crates.io/crates/runagent/${{ steps.find-tags.outputs.version }}) (Publishing...) + - 🐹 [Go Module](https://pkg.go.dev/github.com/runagent-dev/runagent/runagent-go@${{ steps.find-tags.outputs.latest_tag }}) ✅ + + ## 📚 Documentation + + - 📖 [Documentation](https://docs.run-agent.ai) + - 🚀 [Quick Start Guide](https://docs.run-agent.ai/get-started/quickstart) + - 💬 [Discord Community](https://discord.gg/runagent) + + --- + + **Note**: Individual SDK status will be updated below as packages are published. + draft: false + prerelease: false + generate_release_notes: false + files: | + CHANGELOG.md + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} \ No newline at end of file diff --git a/.gitignore b/.gitignore index 8904ed7..0d32626 100644 --- a/.gitignore +++ b/.gitignore @@ -5,7 +5,7 @@ __pycache__/ # C extensions *.so - +node_modules/ # Distribution / packaging .Python build/ diff --git a/pyproject.toml b/pyproject.toml index 30ff45f..ca4df23 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -5,7 +5,7 @@ build-backend = "hatchling.build" [project] name = "runagent" -version = "0.1.5" +version = "0.1.9" description = "A command-line tool and SDK for deploying, managing, and interacting with AI agents" readme = "README.md" requires-python = ">=3.9" @@ -103,7 +103,7 @@ line_length = 88 skip = ["docs"] [tool.mypy] -python_version = "3.9" +python_version = "0.1.9" warn_return_any = true warn_unused_configs = true disallow_untyped_defs = true @@ -159,7 +159,7 @@ fail_under = 80 [tool.ruff] line-length = 88 -target-version = "py39" +target-version = "0.1.9" select = [ "E", # pycodestyle errors "W", # pycodestyle warnings diff --git a/release.sh b/release.sh new file mode 100644 index 0000000..aa750bb --- /dev/null +++ b/release.sh @@ -0,0 +1,323 @@ +#!/bin/bash +# release.sh - Version bumping and tagging script + +set -e + +# Status tracking (compatible with older bash) +python_status=false +javascript_status=false +rust_status=false +go_status=false + +usage() { + echo "Usage: $0 " + echo "Version: semantic version (e.g., 1.2.3)" + echo "" + echo "This will:" + echo " 1. Update version in all SDK package files" + echo " 2. Commit the changes" + echo " 3. Create a git tag v" + echo " 4. Push everything to current branch" + echo "" + echo "Example:" + echo "bash ./release.sh 1.2.3" +} + +validate_version() { + local version=$1 + if [[ ! $version =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]; then + echo "Error: Version must be in format X.Y.Z (e.g., 1.2.3)" + return 1 + fi + return 0 +} + +verify_version_update() { + local file=$1 + local version=$2 + local pattern=$3 + + if [[ -f "$file" ]] && grep -q "$pattern" "$file" 2>/dev/null; then + return 0 + fi + return 1 +} + +update_python_version() { + local version=$1 + local pyproject_file="pyproject.toml" + local version_file="runagent/__version__.py" + local success=false + + # Update pyproject.toml + if [[ -f "$pyproject_file" ]]; then + if [[ "$OSTYPE" == "darwin"* ]]; then + sed -i '' "s/version = \".*\"/version = \"$version\"/" "$pyproject_file" + else + sed -i "s/version = \".*\"/version = \"$version\"/" "$pyproject_file" + fi + + if verify_version_update "$pyproject_file" "$version" "version = \"$version\""; then + success=true + fi + fi + + # Update __version__.py + if [[ -f "$version_file" ]]; then + if [[ "$OSTYPE" == "darwin"* ]]; then + sed -i '' "s/__version__ = \".*\"/__version__ = \"$version\"/" "$version_file" + else + sed -i "s/__version__ = \".*\"/__version__ = \"$version\"/" "$version_file" + fi + else + mkdir -p "$(dirname "$version_file")" + echo "__version__ = \"$version\"" > "$version_file" + fi + + if verify_version_update "$version_file" "$version" "__version__ = \"$version\""; then + success=true + fi + + python_status=$success +} + +update_javascript_version() { + local version=$1 + local file="runagent-ts/package.json" + + if [[ ! -f "$file" ]]; then + javascript_status=false + return + fi + + if command -v npm &> /dev/null; then + ( + cd runagent-ts + npm version "$version" --no-git-tag-version --allow-same-version &>/dev/null + ) + else + if [[ "$OSTYPE" == "darwin"* ]]; then + sed -i '' "s/\"version\": \".*\"/\"version\": \"$version\"/" "$file" + else + sed -i "s/\"version\": \".*\"/\"version\": \"$version\"/" "$file" + fi + fi + + if verify_version_update "$file" "$version" "\"version\": \"$version\""; then + javascript_status=true + else + javascript_status=false + fi +} + +update_rust_version() { + local version=$1 + local file="runagent-rust/runagent/Cargo.toml" + + if [[ ! -f "$file" ]]; then + rust_status=false + return + fi + + if [[ "$OSTYPE" == "darwin"* ]]; then + sed -i '' "s/^version = \".*\"/version = \"$version\"/" "$file" + else + sed -i "s/^version = \".*\"/version = \"$version\"/" "$file" + fi + + if verify_version_update "$file" "$version" "version = \"$version\""; then + rust_status=true + else + rust_status=false + fi +} + +update_go_version() { + local version=$1 + local version_file="runagent-go/runagent/version.go" + local go_mod_file="runagent-go/go.mod" + + mkdir -p "$(dirname "$version_file")" + cat > "$version_file" << EOF +package runagent + +// Version represents the current version of the RunAgent Go SDK +const Version = "$version" +EOF + + if [[ ! -f "$go_mod_file" ]]; then + mkdir -p "$(dirname "$go_mod_file")" + cat > "$go_mod_file" << EOF +module github.com/runagent-dev/runagent/runagent-go + +go 1.20 + +// Dependencies will be added here +EOF + fi + + if verify_version_update "$version_file" "$version" "const Version = \"$version\""; then + go_status=true + else + go_status=false + fi +} + +show_update_summary() { + echo "" + echo "📋 Update Summary:" + echo "-------------------" + + if [[ "$python_status" == "true" ]]; then + echo "✅ python" + else + echo "❌ python" + fi + + if [[ "$javascript_status" == "true" ]]; then + echo "✅ javascript" + else + echo "❌ javascript" + fi + + if [[ "$rust_status" == "true" ]]; then + echo "✅ rust" + else + echo "❌ rust" + fi + + if [[ "$go_status" == "true" ]]; then + echo "✅ go" + else + echo "❌ go" + fi + echo "" +} + +check_prerequisites() { + local current_branch + current_branch=$(git branch --show-current 2>/dev/null || git rev-parse --abbrev-ref HEAD 2>/dev/null || echo "unknown") + + if [[ "$current_branch" == "unknown" ]]; then + echo "❌ Error: Could not determine current branch" + exit 1 + fi + + if ! git status &>/dev/null; then + echo "❌ Error: Not in a git repository or git is not working" + exit 1 + fi +} + +handle_existing_tag() { + local version=$1 + local tag_name="v$version" + + if git tag -l | grep -q "^$tag_name$"; then + echo "" + echo "⚠️ Tag $tag_name already exists" + echo "" + echo "Do you want to move this tag to the current commit?" + echo "This will update the existing tag (potentially dangerous if already published)" + echo "" + read -p "Move tag to current commit? [y/N]: " -r + echo "" + + if [[ ! $REPLY =~ ^[Yy]$ ]]; then + echo "Release cancelled. Tag $tag_name remains unchanged." + exit 0 + fi + + echo "Moving tag $tag_name to current commit..." + git tag -f -a "$tag_name" -m "Release $tag_name (moved) + +RunAgent Universal Release $tag_name + +All SDKs updated to version $version" + return 0 + fi + + return 1 +} + +# Main script +if [[ $# -ne 1 ]]; then + usage + exit 1 +fi + +VERSION=$1 + +if ! validate_version "$VERSION"; then + exit 1 +fi + +echo "🚀 RunAgent Version Bump to v$VERSION" + +check_prerequisites + +# Update all package files +update_python_version "$VERSION" +update_javascript_version "$VERSION" +update_rust_version "$VERSION" +update_go_version "$VERSION" + +show_update_summary + +# Check if any updates succeeded +any_success=false +if [[ "$python_status" == "true" ]] || [[ "$javascript_status" == "true" ]] || [[ "$rust_status" == "true" ]] || [[ "$go_status" == "true" ]]; then + any_success=true +fi + +if [[ "$any_success" == "false" ]]; then + echo "❌ No version updates succeeded. Aborting release." + exit 1 +fi + +# Show git changes +if ! git diff --name-only --quiet 2>/dev/null; then + echo "Changes detected:" + git diff --name-only 2>/dev/null | sed 's/^/ /' +else + echo "⚠️ No git changes detected" +fi + +echo "" +read -p "Continue with commit and tag? [y/N]: " -r +echo "" +if [[ ! $REPLY =~ ^[Yy]$ ]]; then + echo "Release cancelled." + git checkout -- . 2>/dev/null || true + exit 0 +fi + +# Handle existing tag +if handle_existing_tag "$VERSION"; then + echo "✅ Tag v$VERSION updated successfully!" + exit 0 +fi + +# Stage and commit changes +git add . + +if git diff --staged --quiet; then + echo "⚠️ No changes to commit." + exit 1 +fi + +git commit -m "chore: bump version to v$VERSION" -q + +# Create new tag +git tag -a "v$VERSION" -m "Release v$VERSION + +RunAgent Universal Release v$VERSION" + +echo "✅ Tag v$VERSION created successfully!" + +CURRENT_BRANCH=$(git branch --show-current 2>/dev/null || git rev-parse --abbrev-ref HEAD 2>/dev/null) +echo "" +echo "📋 Next steps:" +echo " 1. Push changes: git push origin $CURRENT_BRANCH" +echo " 2. Push tag: git push origin v$VERSION" +echo " 3. Monitor workflows at: https://github.com/runagent-dev/runagent/actions" \ No newline at end of file diff --git a/runagent-go/runagent/version.go b/runagent-go/runagent/version.go new file mode 100644 index 0000000..26f0308 --- /dev/null +++ b/runagent-go/runagent/version.go @@ -0,0 +1,4 @@ +package runagent + +// Version represents the current version of the RunAgent Go SDK +const Version = "0.1.9" diff --git a/runagent-rust/runagent/Cargo.toml b/runagent-rust/runagent/Cargo.toml index 5e8fb77..d2b1f6f 100644 --- a/runagent-rust/runagent/Cargo.toml +++ b/runagent-rust/runagent/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "runagent" -version = "0.1.1" +version = "0.1.9" edition = "2021" description = "RunAgent SDK for Rust - Deploy and manage AI agents easily" license = "MIT" diff --git a/runagent-ts/package-lock.json b/runagent-ts/package-lock.json index 1688710..9e9d934 100644 --- a/runagent-ts/package-lock.json +++ b/runagent-ts/package-lock.json @@ -1,12 +1,12 @@ { "name": "runagent", - "version": "0.1.4", + "version": "0.1.9", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "runagent", - "version": "0.1.4", + "version": "0.1.9", "dependencies": { "better-sqlite3": "^12.2.0" }, diff --git a/runagent-ts/package.json b/runagent-ts/package.json index 2de85d5..4fb641e 100644 --- a/runagent-ts/package.json +++ b/runagent-ts/package.json @@ -1,6 +1,6 @@ { "name": "runagent", - "version": "0.1.4", + "version": "0.1.9", "type": "module", "files": [ "dist" diff --git a/runagent/__version__.py b/runagent/__version__.py index 3dc1f76..c11f861 100644 --- a/runagent/__version__.py +++ b/runagent/__version__.py @@ -1 +1 @@ -__version__ = "0.1.0" +__version__ = "0.1.9" diff --git a/runagent/cli/commands.py b/runagent/cli/commands.py index 06f7973..b0dcdef 100644 --- a/runagent/cli/commands.py +++ b/runagent/cli/commands.py @@ -197,6 +197,7 @@ def delete(agent_id, yes): @click.option("--letta", is_flag=True, help="Use Letta framework") @click.option("--llamaindex", is_flag=True, help="Use LlamaIndex framework") @click.option("--openai", is_flag=True, help="Use OpenAI framework") +@click.option("--n8n", is_flag=True, help="Use N8N workflows") @click.argument( "path", type=click.Path( @@ -222,6 +223,7 @@ def init( letta, llamaindex, openai, + n8n, path ): """Initialize a new RunAgent project""" @@ -240,6 +242,7 @@ def init( "letta": letta, "llamaindex": llamaindex, "openai": openai, + "n8n": n8n } total_flags = sum(flag for flag in framework_dict.values()) if total_flags > 1: @@ -300,6 +303,7 @@ def init( console.print(f" Framework: [magenta]{framework if framework else 'None'}[/magenta]") console.print(f" Template: [yellow]{template}[/yellow]") + print(">>", framework, ">>", template) # Initialize project success = sdk.init_project( folder_path=project_path, @@ -720,9 +724,9 @@ def deploy(folder, agent_id, local, framework, config): @click.option("--debug", is_flag=True, help="Run server in debug mode") @click.option("--replace", help="Replace existing agent with this agent ID") @click.option("--no-animation", is_flag=True, help="Skip startup animation") -@click.option("--animation-style", - type=click.Choice(["field", "ascii", "minimal", "quick"]), - default="field", +@click.option("--animation-style", + type=click.Choice(["field", "ascii", "minimal", "quick"]), + default="field", help="Animation style") @click.argument( "path", @@ -900,7 +904,7 @@ def run(ctx, agent_id, host, port, input_file, local, tag, timeout): raise click.UsageError( "Must specify either --agent-id or both --host and --port." ) - + # If using host/port, both must be provided if host_port_provided and (host is None or port is None): raise click.UsageError( diff --git a/runagent/client/client.py b/runagent/client/client.py index 03e9a9e..e5c88da 100644 --- a/runagent/client/client.py +++ b/runagent/client/client.py @@ -1,6 +1,5 @@ from runagent.sdk import RunAgentSDK from runagent.sdk.rest_client import RestClient -from runagent.utils.agent import detect_framework, validate_agent from runagent.sdk.socket_client import SocketClient from runagent.utils.serializer import CoreSerializer from rich.console import Console diff --git a/runagent/sdk/sdk.py b/runagent/sdk/sdk.py index 493d441..9e401b4 100644 --- a/runagent/sdk/sdk.py +++ b/runagent/sdk/sdk.py @@ -171,7 +171,7 @@ def init_project( ValidationError: If parameters are invalid FileExistsError: If folder exists and overwrite is False """ - return self.templates.init_project( + return self.templates.init_template( folder_path=folder_path, framework=framework, template=template, overwrite=overwrite ) diff --git a/runagent/sdk/server/framework/__init__.py b/runagent/sdk/server/framework/__init__.py index 0f4751e..b909474 100644 --- a/runagent/sdk/server/framework/__init__.py +++ b/runagent/sdk/server/framework/__init__.py @@ -1,6 +1,6 @@ from pathlib import Path -from typing import Dict - +# from typing import Dict +import typing as t from runagent.sdk.server.framework.langgraph import LangGraphExecutor from runagent.sdk.server.framework.langchain import LangChainExecutor from runagent.sdk.server.framework.openai import OpenAIExecutor @@ -10,11 +10,12 @@ from runagent.sdk.server.framework.autogen import AutogenExecutor from runagent.sdk.server.framework.crewai import CrewAIExecutor from runagent.sdk.server.framework.ag2 import AG2Executor -from runagent.utils.schema import EntryPoint +from runagent.sdk.server.framework.n8n import N8NExecutor +from runagent.utils.schema import PythonicEntryPoint, WebHookEntryPoint def get_executor( - agent_dir: Path, framework: str, agent_entrypoints: Dict[str, EntryPoint] + agent_dir: Path, framework: str, agent_entrypoints: t.Dict[str, t.Union[PythonicEntryPoint, WebHookEntryPoint]] ): executor_dict = { "default": GenericExecutor, @@ -27,6 +28,7 @@ def get_executor( "langchain": GenericExecutor, "letta": GenericExecutor, "llamaindex": LlamaIndexExecutor, + "n8n": N8NExecutor } framework_executor = executor_dict.get(framework) if framework_executor is None: diff --git a/runagent/sdk/server/framework/generic.py b/runagent/sdk/server/framework/generic.py index 5cd600e..a9e9c3a 100644 --- a/runagent/sdk/server/framework/generic.py +++ b/runagent/sdk/server/framework/generic.py @@ -4,7 +4,7 @@ from typing import Dict, List from rich.console import Console from runagent.utils.imports import PackageImporter -from runagent.utils.schema import EntryPoint, RunAgentConfig +from runagent.utils.schema import PythonicEntryPoint, RunAgentConfig from runagent.utils.serializer import CoreSerializer from runagent.utils.response import extract_jsonpath, to_dict @@ -19,7 +19,7 @@ def __init__(self, agent_dir: Path): self.agent_dir = agent_dir self.importer = PackageImporter(verbose=False) - def get_runner(self, entrypoint: EntryPoint): + def get_runner(self, entrypoint: PythonicEntryPoint): """ Always returns an async function, regardless of whether the underlying entrypoint is sync or async. @@ -50,7 +50,7 @@ async def normalized_runner(*input_args, **input_kwargs): return normalized_runner - def get_stream_runner(self, entrypoint: EntryPoint): + def get_stream_runner(self, entrypoint: PythonicEntryPoint): """ Always returns an async generator, regardless of whether the underlying entrypoint is sync or async. diff --git a/runagent/sdk/server/framework/n8n.py b/runagent/sdk/server/framework/n8n.py new file mode 100644 index 0000000..45a0025 --- /dev/null +++ b/runagent/sdk/server/framework/n8n.py @@ -0,0 +1,83 @@ +import asyncio +import httpx +import inspect +from pathlib import Path +from typing import Dict, List +from rich.console import Console +from runagent.utils.imports import PackageImporter +from runagent.utils.schema import WebHookEntryPoint, RunAgentConfig +from runagent.utils.serializer import CoreSerializer +from runagent.utils.response import extract_jsonpath, to_dict + +console = Console() + + +class N8NExecutor: + + rerserved_tags = list() + + def __init__(self, agent_dir: Path): + self.agent_dir = agent_dir + + def get_runner(self, entrypoint: WebHookEntryPoint): + """ + Always returns an async function, regardless of whether the + underlying entrypoint is sync or async. + """ + # resolved_entrypoint = self._entrypoint_resolver( + # entrypoint_filepath=self.agent_dir / entrypoint.file, + # entrypoint_module=entrypoint.module, + # ) + + async def normalized_runner(*input_args, **input_kwargs): + console.print(f"🔍 [cyan]Entrypoint:[/cyan] {entrypoint.tag}") + console.print(f"🔍 [cyan]Input data:[/cyan] *{input_args}, **{input_kwargs}") + + # Check for invalid argument combinations + if len(input_args) > 1: + raise ValueError("Too many positional arguments. Expected at most 1.") + + if input_args and input_kwargs: + raise ValueError("Cannot specify both positional and keyword arguments.") + + if len(input_kwargs) > 1 or (input_kwargs and 'payload' not in input_kwargs): + raise ValueError("Only 'payload' keyword argument is allowed.") + + # Extract payload + if input_args: + # normalized_runner({...}) + payload = input_args[0] + elif 'payload' in input_kwargs: + # normalized_runner(payload={...}) + payload = input_kwargs['payload'] + else: + # normalized_runner() + payload = None + + timeout = httpx.Timeout( + timeout=entrypoint.timeout, # 60 seconds total timeout + connect=10.0, # 10 seconds to connect + read=50.0, # 50 seconds to read response + write=10.0 # 10 seconds to write request + ) + async with httpx.AsyncClient(timeout=timeout) as client: + if entrypoint.method == "post": + # POST request with JSON payload + response = await client.post( + entrypoint.webhook_url, + json=payload, + headers={"Content-Type": "application/json"} + ) + elif entrypoint.method == "get": + # GET request when no payload + response = await client.get(entrypoint.webhook_url) + + response.raise_for_status() # Raise exception for HTTP errors + result = response.json() + + if entrypoint.extractor: + result = extract_jsonpath(result, entrypoint.extractor) + + return result + + return normalized_runner diff --git a/runagent/sdk/server/local_server.py b/runagent/sdk/server/local_server.py index 4509146..5e4da4e 100644 --- a/runagent/sdk/server/local_server.py +++ b/runagent/sdk/server/local_server.py @@ -27,6 +27,9 @@ from runagent.utils.port import PortManager from runagent.utils.serializer import CoreSerializer +from runagent.utils.port import PortManager +from runagent.utils.agent import detect_framework, get_agent_config + console = Console() @@ -53,7 +56,7 @@ def __init__( self.agent_name = self.agent_config.agent_name self.agent_version = self.agent_config.version - self.agent_framework = self.agent_config.framework + self.agent_framework = self.agent_config.framework.value self.agent_architecture = self.agent_config.agent_architecture # Install dependencies if requirements.txt exists @@ -177,10 +180,6 @@ def from_path( Returns: LocalServer instance """ - import uuid - from runagent.utils.port import PortManager - from runagent.utils.agent import detect_framework, get_agent_config - db_service = DBService() agent_path = agent_path.resolve() diff --git a/runagent/sdk/template_manager.py b/runagent/sdk/template_manager.py index de4bdab..24ddec9 100644 --- a/runagent/sdk/template_manager.py +++ b/runagent/sdk/template_manager.py @@ -77,7 +77,7 @@ def get_info(self, framework: str, template: str) -> t.Optional[t.Dict[str, t.An raise return None - def init_project( + def init_template( self, folder_path: Path, framework: str, template: str, overwrite: bool = False ) -> bool: """ diff --git a/runagent/utils/agent.py b/runagent/utils/agent.py index 51640ba..b7113e3 100644 --- a/runagent/utils/agent.py +++ b/runagent/utils/agent.py @@ -1,13 +1,10 @@ -import os -import importlib import json -import sys import typing as t from pathlib import Path - from runagent.constants import AGENT_CONFIG_FILE_NAME from runagent.utils.imports import PackageImporter from runagent.utils.schema import RunAgentConfig +from .enums import FrameworkType def get_agent_config(folder_path: Path) -> t.Optional[dict]: @@ -109,7 +106,7 @@ def detect_framework(folder_path: Path) -> str: config = get_agent_config(folder_path) framework = config.framework - return framework + return framework.value def validate_agent( @@ -131,6 +128,13 @@ def validate_agent( "error_msgs": [f"Agent Folder not found: {folder}"], } + config = get_agent_config(folder_path) + + is_valid, details = validate_pythonic_agent(config, dynamic_loading, folder_path) + + +def validate_pythonic_agent(config, dynamic_loading, folder_path): + validation_details = { "valid": False, "folder_exists": True, @@ -145,61 +149,51 @@ def validate_agent( unwanted_files = [".env"] # Check for runagent.config.json and validate schema - try: - config = get_agent_config(folder_path) - validation_details["files_found"].append(AGENT_CONFIG_FILE_NAME) - validation_details["success_msgs"].append( - f"Found and validated {AGENT_CONFIG_FILE_NAME}" - ) + # try: - # Validate each entrypoint dynamically - for entrypoint in config.agent_architecture.entrypoints: - entrypoint_file = folder_path / entrypoint.file - module_name = entrypoint.module + # Validate each entrypoint dynamically + for entrypoint in config.agent_architecture.entrypoints: + entrypoint_file = folder_path / entrypoint.file + module_name = entrypoint.module - if not entrypoint_file.exists(): - validation_details["error_msgs"].append( - f"Entrypoint file not found: {entrypoint_file}" + if not entrypoint_file.exists(): + validation_details["error_msgs"].append( + f"Entrypoint file not found: {entrypoint_file}" + ) + validation_details["missing_files"].append(entrypoint.file) + validation_details["valid"] = False + continue + + if dynamic_loading: + try: + importer = PackageImporter() + importer.resolve_import(entrypoint_file, module_name) + validation_details["success_msgs"].append( + f"Found {module_name} reference in {entrypoint_file}" ) - validation_details["missing_files"].append(entrypoint.file) + except Exception as e: + validation_details["error_msgs"].append(str(e)) validation_details["valid"] = False - continue - - if dynamic_loading: - try: - importer = PackageImporter() - importer.resolve_import(entrypoint_file, module_name) + else: + try: + content = entrypoint_file.read_text() + if module_name in content: validation_details["success_msgs"].append( f"Found {module_name} reference in {entrypoint_file}" ) - except Exception as e: - validation_details["error_msgs"].append(str(e)) - validation_details["valid"] = False - else: - try: - content = entrypoint_file.read_text() - if module_name in content: - validation_details["success_msgs"].append( - f"Found {module_name} reference in {entrypoint_file}" - ) - else: - validation_details["error_msgs"].append( - f"Module {module_name} not found in {entrypoint_file}" - ) - validation_details["missing_files"].append( - f"{module_name} module reference" - ) - validation_details["valid"] = False - except Exception as e: + else: validation_details["error_msgs"].append( - f"Failed to read {entrypoint_file}: {str(e)}" + f"Module {module_name} not found in {entrypoint_file}" + ) + validation_details["missing_files"].append( + f"{module_name} module reference" ) validation_details["valid"] = False - - except ValueError as e: - validation_details["error_msgs"].append(str(e)) - validation_details["missing_files"].append("valid runagent.config.json") - validation_details["valid"] = False + except Exception as e: + validation_details["error_msgs"].append( + f"Failed to read {entrypoint_file}: {str(e)}" + ) + validation_details["valid"] = False # Check for unwanted files for unwanted_file in unwanted_files: diff --git a/runagent/utils/enums.py b/runagent/utils/enums.py index 8603f12..956ed27 100644 --- a/runagent/utils/enums.py +++ b/runagent/utils/enums.py @@ -1,6 +1,26 @@ from enum import Enum +import typing as t class ResponseStatus(Enum): SUCCESS = "success" ERROR = "error" + + +class PythonicType(Enum): + AG2 = "ag2" + AGNO = "agno" + AUTOGEN = "autogen" + CREWAI = "crewai" + LANGCHAIN = "langchain" + LANGGRAPH = "langgraph" + LETTA = "letta" + LLAMAINDEX = "llamaindex" + OPENAI = "openai" + + +class WebhookType(Enum): + N8N = "n8n" + + +FrameworkType = t.Union[PythonicType, WebhookType] diff --git a/runagent/utils/schema.py b/runagent/utils/schema.py index cb73acc..972bda3 100644 --- a/runagent/utils/schema.py +++ b/runagent/utils/schema.py @@ -1,8 +1,10 @@ # Pydantic schema for runagent.config.json from datetime import datetime -from typing import Any, Dict, List, Optional, Union +import typing as t +# from typing import Any, Dict, List, Optional, Union from enum import Enum from pydantic import BaseModel, Field, validator +from .enums import FrameworkType class TemplateSource(BaseModel): @@ -13,13 +15,26 @@ class TemplateSource(BaseModel): path: str = Field(..., description="Path to template in repository") -class EntryPoint(BaseModel): - """Entrypoint configuration""" +class PythonicEntryPoint(BaseModel): + """Configuration for a Python-based entrypoint.""" - file: str = Field(..., description="Entrypoint file") - module: str = Field(..., description="Entrypoint module name") + file: str = Field(..., description="Path to the Python file containing the entrypoint") + module: str = Field(..., description="Python module name for the entrypoint") + tag: str = Field(..., description="Unique tag identifying this entrypoint") + extractor: t.Optional[t.Dict[str, str]] = Field( + default={}, + description="Optional mapping of output fields to JSONPath expressions for extracting data from the response" + ) + + +class WebHookEntryPoint(BaseModel): + """Configuration for a webhook-based entrypoint.""" + + webhook_url: str = Field(..., description="Webhook URL for the entrypoint") + method: str = Field(..., description="HTTP method to use for the webhook") tag: str = Field(..., description="Entrypoint tag") - extractor: Optional[Dict[str, str]] = Field( + timeout: int = Field(30, description="Timeout in seconds for the webhook request") + extractor: t.Optional[t.Dict[str, str]] = Field( default={}, description="JSONPath expression to extract data from the response" ) @@ -28,7 +43,9 @@ class EntryPoint(BaseModel): class AgentArchitecture(BaseModel): """Agent architecture configuration""" - entrypoints: List[EntryPoint] = Field( + entrypoints: t.Union[ + t.List[PythonicEntryPoint], t.List[WebHookEntryPoint] + ] = Field( ..., description="List of entrypoints" ) @@ -46,17 +63,17 @@ class RunAgentConfig(BaseModel): agent_name: str = Field(..., description="Name of the agent") description: str = Field(..., description="Description of the agent") - framework: str = Field(..., description="Framework used (langchain, etc)") + framework: FrameworkType = Field(..., description="Framework used (langchain, etc)") template: str = Field(..., description="Template name") version: str = Field(..., description="Agent version") created_at: datetime = Field(..., description="Creation timestamp") - template_source: Optional[TemplateSource] = Field( + template_source: t.Optional[TemplateSource] = Field( ..., description="Template source details" ) agent_architecture: AgentArchitecture = Field( ..., description="Agent architecture details" ) - env_vars: Optional[Dict[str, str]] = Field( + env_vars: t.Optional[t.Dict[str, str]] = Field( default_factory=dict, description="Environment variables" ) @@ -64,10 +81,10 @@ class RunAgentConfig(BaseModel): class AgentInputArgs(BaseModel): """Request model for agent execution""" - input_args: List[Any] = Field( + input_args: t.List[t.Any] = Field( default={}, description="Input data for agent invocation" ) - input_kwargs: Dict[str, Any] = Field( + input_kwargs: t.Dict[str, t.Any] = Field( default={}, description="Input data for agent invocation" ) @@ -83,7 +100,7 @@ class WebSocketAgentRequest(BaseModel): action: WebSocketActionType agent_id: str input_data: AgentInputArgs - stream_config: Optional[Dict[str, Any]] = Field(default_factory=dict) + stream_config: t.Optional[t.Dict[str, t.Any]] = Field(default_factory=dict) # Pydantic Models @@ -99,9 +116,9 @@ class AgentRunResponse(BaseModel): """Response model for agent execution""" success: bool - output_data: Optional[Any] = None - error: Optional[str] = None - execution_time: Optional[float] = None + output_data: t.Optional[t.Any] = None + error: t.Optional[str] = None + execution_time: t.Optional[float] = None agent_id: str @@ -112,7 +129,7 @@ class CapacityInfo(BaseModel): max_capacity: int remaining_slots: int is_full: bool - agents: List[Dict[str, Any]] + agents: t.List[t.Dict[str, t.Any]] class AgentInfo(BaseModel): @@ -122,8 +139,8 @@ class AgentInfo(BaseModel): version: str host: str port: int - config: Dict[str, Any] - endpoints: Dict[str, str] + config: t.Dict[str, t.Any] + endpoints: t.Dict[str, str] # Message types for different agentic framework responses @@ -143,11 +160,11 @@ class SafeMessage(BaseModel): id: str type: MessageType timestamp: str - data: Any - metadata: Optional[Dict[str, Any]] = None - error: Optional[str] = None + data: t.Any + metadata: t.Optional[t.Dict[str, t.Any]] = None + error: t.Optional[str] = None - def to_dict(self) -> Dict[str, Any]: + def to_dict(self) -> t.Dict[str, t.Any]: return { "id": self.id, "type": self.type.value, @@ -155,4 +172,4 @@ def to_dict(self) -> Dict[str, Any]: "data": self.data, "metadata": self.metadata, "error": self.error - } \ No newline at end of file + } diff --git a/templates/n8n/default/requirements.txt b/templates/n8n/default/requirements.txt new file mode 100644 index 0000000..e69de29 diff --git a/templates/n8n/default/runagent.config.json b/templates/n8n/default/runagent.config.json new file mode 100644 index 0000000..457a827 --- /dev/null +++ b/templates/n8n/default/runagent.config.json @@ -0,0 +1,23 @@ +{ + "agent_name": "Demo Agent", + "description": "A simple placeholder agent", + "framework": "n8n", + "template": "default", + "version": "1.0.0", + "created_at": "2025-06-25 13:42:03", + "template_source": { + "repo_url": "https://github.com/runagent-dev/runagent.git", + "path": "templates/default", + "author": "sawradip" + }, + "agent_architecture": { + "entrypoints": [ + { + "webhook_url": "https://taniatinni.app.n8n.cloud/webhook/recipe-nutrition", + "method": "post", + "timeout": 60, + "tag": "food_doctor" + } + ] + } +} \ No newline at end of file diff --git a/test_scripts/js/package.json b/test_scripts/js/package.json index e73d4b2..23a7c1d 100644 --- a/test_scripts/js/package.json +++ b/test_scripts/js/package.json @@ -10,6 +10,7 @@ "license": "ISC", "description": "", "dependencies": { - "better-sqlite3": "^12.2.0" + "better-sqlite3": "^12.2.0", + "runagent": "^0.1.4" } } diff --git a/test_scripts/js/pnpm-lock.yaml b/test_scripts/js/pnpm-lock.yaml new file mode 100644 index 0000000..acb9fa0 --- /dev/null +++ b/test_scripts/js/pnpm-lock.yaml @@ -0,0 +1,301 @@ +lockfileVersion: '9.0' + +settings: + autoInstallPeers: true + excludeLinksFromLockfile: false + +importers: + + .: + dependencies: + better-sqlite3: + specifier: ^12.2.0 + version: 12.2.0 + runagent: + specifier: ^0.1.4 + version: 0.1.4 + +packages: + + base64-js@1.5.1: + resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==} + + better-sqlite3@12.2.0: + resolution: {integrity: sha512-eGbYq2CT+tos1fBwLQ/tkBt9J5M3JEHjku4hbvQUePCckkvVf14xWj+1m7dGoK81M/fOjFT7yM9UMeKT/+vFLQ==} + engines: {node: 20.x || 22.x || 23.x || 24.x} + + bindings@1.5.0: + resolution: {integrity: sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==} + + bl@4.1.0: + resolution: {integrity: sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==} + + buffer@5.7.1: + resolution: {integrity: sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==} + + chownr@1.1.4: + resolution: {integrity: sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==} + + decompress-response@6.0.0: + resolution: {integrity: sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==} + engines: {node: '>=10'} + + deep-extend@0.6.0: + resolution: {integrity: sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==} + engines: {node: '>=4.0.0'} + + detect-libc@2.0.4: + resolution: {integrity: sha512-3UDv+G9CsCKO1WKMGw9fwq/SWJYbI0c5Y7LU1AXYoDdbhE2AHQ6N6Nb34sG8Fj7T5APy8qXDCKuuIHd1BR0tVA==} + engines: {node: '>=8'} + + end-of-stream@1.4.5: + resolution: {integrity: sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg==} + + expand-template@2.0.3: + resolution: {integrity: sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg==} + engines: {node: '>=6'} + + file-uri-to-path@1.0.0: + resolution: {integrity: sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==} + + fs-constants@1.0.0: + resolution: {integrity: sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==} + + github-from-package@0.0.0: + resolution: {integrity: sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw==} + + ieee754@1.2.1: + resolution: {integrity: sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==} + + inherits@2.0.4: + resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==} + + ini@1.3.8: + resolution: {integrity: sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==} + + mimic-response@3.1.0: + resolution: {integrity: sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==} + engines: {node: '>=10'} + + minimist@1.2.8: + resolution: {integrity: sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==} + + mkdirp-classic@0.5.3: + resolution: {integrity: sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==} + + napi-build-utils@2.0.0: + resolution: {integrity: sha512-GEbrYkbfF7MoNaoh2iGG84Mnf/WZfB0GdGEsM8wz7Expx/LlWf5U8t9nvJKXSp3qr5IsEbK04cBGhol/KwOsWA==} + + node-abi@3.75.0: + resolution: {integrity: sha512-OhYaY5sDsIka7H7AtijtI9jwGYLyl29eQn/W623DiN/MIv5sUqc4g7BIDThX+gb7di9f6xK02nkp8sdfFWZLTg==} + engines: {node: '>=10'} + + once@1.4.0: + resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==} + + prebuild-install@7.1.3: + resolution: {integrity: sha512-8Mf2cbV7x1cXPUILADGI3wuhfqWvtiLA1iclTDbFRZkgRQS0NqsPZphna9V+HyTEadheuPmjaJMsbzKQFOzLug==} + engines: {node: '>=10'} + hasBin: true + + pump@3.0.3: + resolution: {integrity: sha512-todwxLMY7/heScKmntwQG8CXVkWUOdYxIvY2s0VWAAMh/nd8SoYiRaKjlr7+iCs984f2P8zvrfWcDDYVb73NfA==} + + rc@1.2.8: + resolution: {integrity: sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==} + hasBin: true + + readable-stream@3.6.2: + resolution: {integrity: sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==} + engines: {node: '>= 6'} + + runagent@0.1.4: + resolution: {integrity: sha512-KvD1AEIswFvPrhtp9RoPyLIbkwzy4AMWj8G9CgIiyggROTnMWj7IvwYvKmDkXC4vx/MnaQt9muguSpKG/LYLAA==} + peerDependencies: + ws: ^8.18.3 + peerDependenciesMeta: + ws: + optional: true + + safe-buffer@5.2.1: + resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==} + + semver@7.7.2: + resolution: {integrity: sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==} + engines: {node: '>=10'} + hasBin: true + + simple-concat@1.0.1: + resolution: {integrity: sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q==} + + simple-get@4.0.1: + resolution: {integrity: sha512-brv7p5WgH0jmQJr1ZDDfKDOSeWWg+OVypG99A/5vYGPqJ6pxiaHLy8nxtFjBA7oMa01ebA9gfh1uMCFqOuXxvA==} + + string_decoder@1.3.0: + resolution: {integrity: sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==} + + strip-json-comments@2.0.1: + resolution: {integrity: sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==} + engines: {node: '>=0.10.0'} + + tar-fs@2.1.3: + resolution: {integrity: sha512-090nwYJDmlhwFwEW3QQl+vaNnxsO2yVsd45eTKRBzSzu+hlb1w2K9inVq5b0ngXuLVqQ4ApvsUHHnu/zQNkWAg==} + + tar-stream@2.2.0: + resolution: {integrity: sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==} + engines: {node: '>=6'} + + tunnel-agent@0.6.0: + resolution: {integrity: sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==} + + util-deprecate@1.0.2: + resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==} + + wrappy@1.0.2: + resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==} + +snapshots: + + base64-js@1.5.1: {} + + better-sqlite3@12.2.0: + dependencies: + bindings: 1.5.0 + prebuild-install: 7.1.3 + + bindings@1.5.0: + dependencies: + file-uri-to-path: 1.0.0 + + bl@4.1.0: + dependencies: + buffer: 5.7.1 + inherits: 2.0.4 + readable-stream: 3.6.2 + + buffer@5.7.1: + dependencies: + base64-js: 1.5.1 + ieee754: 1.2.1 + + chownr@1.1.4: {} + + decompress-response@6.0.0: + dependencies: + mimic-response: 3.1.0 + + deep-extend@0.6.0: {} + + detect-libc@2.0.4: {} + + end-of-stream@1.4.5: + dependencies: + once: 1.4.0 + + expand-template@2.0.3: {} + + file-uri-to-path@1.0.0: {} + + fs-constants@1.0.0: {} + + github-from-package@0.0.0: {} + + ieee754@1.2.1: {} + + inherits@2.0.4: {} + + ini@1.3.8: {} + + mimic-response@3.1.0: {} + + minimist@1.2.8: {} + + mkdirp-classic@0.5.3: {} + + napi-build-utils@2.0.0: {} + + node-abi@3.75.0: + dependencies: + semver: 7.7.2 + + once@1.4.0: + dependencies: + wrappy: 1.0.2 + + prebuild-install@7.1.3: + dependencies: + detect-libc: 2.0.4 + expand-template: 2.0.3 + github-from-package: 0.0.0 + minimist: 1.2.8 + mkdirp-classic: 0.5.3 + napi-build-utils: 2.0.0 + node-abi: 3.75.0 + pump: 3.0.3 + rc: 1.2.8 + simple-get: 4.0.1 + tar-fs: 2.1.3 + tunnel-agent: 0.6.0 + + pump@3.0.3: + dependencies: + end-of-stream: 1.4.5 + once: 1.4.0 + + rc@1.2.8: + dependencies: + deep-extend: 0.6.0 + ini: 1.3.8 + minimist: 1.2.8 + strip-json-comments: 2.0.1 + + readable-stream@3.6.2: + dependencies: + inherits: 2.0.4 + string_decoder: 1.3.0 + util-deprecate: 1.0.2 + + runagent@0.1.4: + dependencies: + better-sqlite3: 12.2.0 + + safe-buffer@5.2.1: {} + + semver@7.7.2: {} + + simple-concat@1.0.1: {} + + simple-get@4.0.1: + dependencies: + decompress-response: 6.0.0 + once: 1.4.0 + simple-concat: 1.0.1 + + string_decoder@1.3.0: + dependencies: + safe-buffer: 5.2.1 + + strip-json-comments@2.0.1: {} + + tar-fs@2.1.3: + dependencies: + chownr: 1.1.4 + mkdirp-classic: 0.5.3 + pump: 3.0.3 + tar-stream: 2.2.0 + + tar-stream@2.2.0: + dependencies: + bl: 4.1.0 + end-of-stream: 1.4.5 + fs-constants: 1.0.0 + inherits: 2.0.4 + readable-stream: 3.6.2 + + tunnel-agent@0.6.0: + dependencies: + safe-buffer: 5.2.1 + + util-deprecate@1.0.2: {} + + wrappy@1.0.2: {} diff --git a/test_scripts/js/test-node-n8n.mjs b/test_scripts/js/test-node-n8n.mjs new file mode 100644 index 0000000..475f079 --- /dev/null +++ b/test_scripts/js/test-node-n8n.mjs @@ -0,0 +1,37 @@ +import { RunAgentClient } from 'runagent'; + +// Example 1: Non-streaming +async function testStandard() { + console.log('=== Testing Standard Execution ==='); + + const ra = new RunAgentClient({ + agentId: '95d60499-b9c1-4cc4-8e7a-0382b6e085d7', + entrypointTag: 'food_doctor', + // host: 'localhost', + // port: 8451, + local: true, + }); + + await ra.initialize(); + console.log(`✅ Environment: ${ra.environment}`); + + const result = await ra.run({ + payload: { + "recipe_name": "Chocolate Chip Pancakes", + "servings": 4, + "ingredients": [ + "2 cups all-purpose flour", + "2 tablespoons sugar", + "2 teaspoons baking powder", + "1 teaspoon salt", + "2 large eggs", + "1.5 cups whole milk", + "1/4 cup melted butter", + "1/2 cup chocolate chips" + ]} + }); + + console.log('Result:', result); +} + +await testStandard(); diff --git a/test_scripts/python/client_test_n8n.py b/test_scripts/python/client_test_n8n.py new file mode 100644 index 0000000..6b63e9d --- /dev/null +++ b/test_scripts/python/client_test_n8n.py @@ -0,0 +1,27 @@ +from pprint import pprint +from runagent import RunAgentClient + +ra = RunAgentClient( + agent_id="95d60499-b9c1-4cc4-8e7a-0382b6e085d7", + entrypoint_tag="food_doctor", + local=True + ) + + +agent_results = ra.run({ + "recipe_name": "Chocolate Chip Pancakes", + "servings": 4, + "ingredients": [ + "2 cups all-purpose flour", + "2 tablespoons sugar", + "2 teaspoons baking powder", + "1 teaspoon salt", + "2 large eggs", + "1.5 cups whole milk", + "1/4 cup melted butter", + "1/2 cup chocolate chips" + ] + } +) + +pprint(agent_results)