From 6b75fb7e9799a556badd9a18ff40bb153a8c3ffb Mon Sep 17 00:00:00 2001 From: openhands Date: Thu, 19 Feb 2026 21:14:38 +0000 Subject: [PATCH 1/2] Add plugin-test tool for testing plugin loading Add a generalized plugin testing tool that can: - Launch conversations with any plugin from a configurable marketplace - Wait for sandbox startup and agent response - Verify response matches an expected string or regex pattern - Support CI/CD integration with proper exit codes Plugin structure: - scripts/test_plugin.sh - Main test execution script - commands/test.md - Slash command for /plugin-test:test - README.md - Comprehensive documentation Environment variables: - OPENHANDS_API_KEY (required) - OPENHANDS_URL (default: https://app.all-hands.dev) - MARKETPLACE_REPO (default: github:OpenHands/extensions) - MARKETPLACE_REF (default: main) Co-authored-by: openhands --- .plugin/marketplace.json | 13 + .../plugin-test/.claude-plugin/plugin.json | 13 + plugins/plugin-test/README.md | 202 ++++++++ plugins/plugin-test/commands/test.md | 101 ++++ plugins/plugin-test/scripts/test_plugin.sh | 452 ++++++++++++++++++ 5 files changed, 781 insertions(+) create mode 100644 plugins/plugin-test/.claude-plugin/plugin.json create mode 100644 plugins/plugin-test/README.md create mode 100644 plugins/plugin-test/commands/test.md create mode 100755 plugins/plugin-test/scripts/test_plugin.sh diff --git a/.plugin/marketplace.json b/.plugin/marketplace.json index 14acf5a..592321c 100644 --- a/.plugin/marketplace.json +++ b/.plugin/marketplace.json @@ -376,6 +376,19 @@ "preview", "hosting" ] + }, + { + "name": "plugin-test", + "source": "../plugins/plugin-test", + "description": "Test plugin loading via OpenHands Cloud API - launch conversations with plugins and verify responses match expected patterns.", + "category": "testing", + "keywords": [ + "test", + "plugin", + "verification", + "api", + "conversation" + ] } ] } diff --git a/plugins/plugin-test/.claude-plugin/plugin.json b/plugins/plugin-test/.claude-plugin/plugin.json new file mode 100644 index 0000000..f92baef --- /dev/null +++ b/plugins/plugin-test/.claude-plugin/plugin.json @@ -0,0 +1,13 @@ +{ + "name": "plugin-test", + "version": "1.0.0", + "description": "Test plugin loading via OpenHands Cloud API - launch conversations with plugins and verify responses", + "author": { + "name": "OpenHands", + "email": "contact@all-hands.dev" + }, + "homepage": "https://github.com/OpenHands/extensions", + "repository": "https://github.com/OpenHands/extensions", + "license": "MIT", + "keywords": ["test", "plugin", "verification", "conversation", "api"] +} diff --git a/plugins/plugin-test/README.md b/plugins/plugin-test/README.md new file mode 100644 index 0000000..9d9be14 --- /dev/null +++ b/plugins/plugin-test/README.md @@ -0,0 +1,202 @@ +# Plugin Test Tool + +Test plugin loading via the OpenHands Cloud API. Launch conversations with any plugin from a marketplace, wait for the agent to respond, and verify the response matches an expected string or pattern. + +## Features + +- **Automated testing**: Launch conversations and verify responses programmatically +- **Flexible matching**: Substring or regex pattern matching +- **Configurable marketplace**: Test plugins from any marketplace repository +- **Detailed output**: Verbose mode for debugging API interactions +- **CI/CD ready**: Exit codes indicate pass/fail for integration into pipelines + +## Quick Start + +### Prerequisites + +- `curl` and `jq` installed +- OpenHands API key (from [app.all-hands.dev](https://app.all-hands.dev)) + +### Basic Usage + +```bash +export OPENHANDS_API_KEY="sk-oh-your-api-key" + +# Test the city-weather plugin +./scripts/test_plugin.sh \ + --plugin "plugins/city-weather" \ + --message "/city-weather:now Tokyo" \ + --expect "Weather Report" +``` + +### Using the Slash Command + +If you have this plugin loaded, you can invoke it directly: + +``` +/plugin-test:test --plugin "plugins/city-weather" --message "/city-weather:now Tokyo" --expect "Weather Report" +``` + +--- + +## Command Reference + +``` +Usage: test_plugin.sh [OPTIONS] + +Required Arguments: + --plugin PATH Path to plugin within the marketplace repo + --message TEXT Initial message to send + --expect STRING String or regex pattern to expect in the response + +Options: + --regex Treat --expect as a regex pattern (default: substring) + --open Open the conversation in a browser when ready + --verbose Show detailed output including API responses + --max-wait SECONDS Maximum time to wait for conversation (default: 180) + --poll SECONDS Polling interval in seconds (default: 3) + --help Show help message +``` + +--- + +## Environment Variables + +| Variable | Default | Description | +|----------|---------|-------------| +| `OPENHANDS_API_KEY` | (required) | Your OpenHands API key | +| `OPENHANDS_URL` | `https://app.all-hands.dev` | OpenHands Cloud URL | +| `MARKETPLACE_REPO` | `github:OpenHands/extensions` | Plugin marketplace repository | +| `MARKETPLACE_REF` | `main` | Git ref (branch/tag) for marketplace | + +--- + +## Examples + +### Test city-weather plugin + +```bash +export OPENHANDS_API_KEY="sk-oh-..." + +./scripts/test_plugin.sh \ + --plugin "plugins/city-weather" \ + --message "/city-weather:now Tokyo" \ + --expect "Weather Report" +``` + +### Test magic-test plugin with regex + +```bash +./scripts/test_plugin.sh \ + --plugin "plugins/magic-test" \ + --message "What happens if I say alakazam?" \ + --expect "Plugin loaded successfully" \ + --regex +``` + +### Test with custom marketplace + +```bash +export MARKETPLACE_REPO="github:myorg/my-plugins" +export MARKETPLACE_REF="develop" + +./scripts/test_plugin.sh \ + --plugin "plugins/my-plugin" \ + --message "/my-plugin:hello" \ + --expect "Hello World" +``` + +### Verbose mode for debugging + +```bash +./scripts/test_plugin.sh \ + --plugin "plugins/city-weather" \ + --message "/city-weather:now London" \ + --expect "Temperature" \ + --verbose +``` + +### Open in browser after test + +```bash +./scripts/test_plugin.sh \ + --plugin "plugins/city-weather" \ + --message "/city-weather:now Paris" \ + --expect "Weather" \ + --open +``` + +--- + +## CI/CD Integration + +The script returns exit codes suitable for CI pipelines: + +| Exit Code | Meaning | +|-----------|---------| +| 0 | Test passed - response matched expected pattern | +| 1 | Test failed - response didn't match, or error occurred | + +### GitHub Actions Example + +```yaml +- name: Test city-weather plugin + env: + OPENHANDS_API_KEY: ${{ secrets.OPENHANDS_API_KEY }} + run: | + ./plugins/plugin-test/scripts/test_plugin.sh \ + --plugin "plugins/city-weather" \ + --message "/city-weather:now Tokyo" \ + --expect "Weather Report" +``` + +--- + +## How It Works + +1. **Create Conversation**: POST to `/api/v1/app-conversations` with the plugin spec +2. **Wait for Sandbox**: Poll `/api/v1/app-conversations/start-tasks/search` until status is `READY` +3. **Fetch Events**: Query the Agent Server for conversation events +4. **Verify Response**: Check if the agent's response matches the expected pattern + +### Architecture + +``` +┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐ +│ test_plugin │────▶│ App Server │────▶│ Agent Server │ +│ script │ │ (Cloud URL) │ │ (In Sandbox) │ +└─────────────────┘ └─────────────────┘ └─────────────────┘ + │ │ │ + │ 1. Create conv │ 2. Start sandbox │ + │ with plugin │ load plugin │ + │ │ │ + │ 3. Poll for ready │ │ + │ │ │ 4. Agent runs, + │ 5. Fetch events ◀────────────────────────────│ responds + │ │ + │ 6. Verify response │ + └───────────────────────────────────────────────┘ +``` + +--- + +## Plugin Structure + +``` +plugin-test/ +├── .claude-plugin/ +│ └── plugin.json # Plugin manifest +├── commands/ +│ └── test.md # Slash command definition +├── scripts/ +│ └── test_plugin.sh # Test execution script +└── README.md +``` + +--- + +## Related Resources + +- [OpenHands Cloud](https://app.all-hands.dev) +- [OpenHands API Documentation](https://docs.openhands.dev) +- [Plugin Marketplace](https://github.com/OpenHands/extensions) diff --git a/plugins/plugin-test/commands/test.md b/plugins/plugin-test/commands/test.md new file mode 100644 index 0000000..302ade1 --- /dev/null +++ b/plugins/plugin-test/commands/test.md @@ -0,0 +1,101 @@ +--- +allowed-tools: Bash(*) +argument-hint: --plugin --message --expect [--regex] [--open] +description: Test plugin loading via OpenHands Cloud API - creates a conversation with the specified plugin and verifies the response +--- + +# Test Plugin Loading + +Launch a conversation with a specified plugin loaded via the OpenHands Cloud API, wait for the agent response, and verify it matches an expected string or pattern. + +## Instructions + +Run the test script with the provided arguments: **$ARGUMENTS** + +```bash +# Navigate to the plugin-test scripts directory and run the test +cd /path/to/extensions/plugins/plugin-test/scripts + +# Run the test with the provided arguments +./test_plugin.sh $ARGUMENTS +``` + +If the script is not available locally, you can download and run it: + +```bash +curl -sL "https://raw.githubusercontent.com/OpenHands/extensions/main/plugins/plugin-test/scripts/test_plugin.sh" -o /tmp/test_plugin.sh +chmod +x /tmp/test_plugin.sh +/tmp/test_plugin.sh $ARGUMENTS +``` + +## Required Environment Variables + +Before running, ensure these are set: + +```bash +export OPENHANDS_API_KEY="sk-oh-your-api-key" +# Optional: override defaults +export OPENHANDS_URL="https://app.all-hands.dev" +export MARKETPLACE_REPO="github:OpenHands/extensions" +export MARKETPLACE_REF="main" +``` + +## Arguments + +| Argument | Required | Description | +|----------|----------|-------------| +| `--plugin PATH` | Yes | Path to plugin within marketplace (e.g., `plugins/city-weather`) | +| `--message TEXT` | Yes | Initial message to send (e.g., `/city-weather:now Tokyo`) | +| `--expect STRING` | Yes | String or regex pattern to expect in the response | +| `--regex` | No | Treat `--expect` as a regex pattern | +| `--open` | No | Open the conversation in a browser when ready | +| `--verbose` | No | Show detailed debug output | + +## Example Usage + +```bash +# Test the city-weather plugin +/plugin-test:test --plugin "plugins/city-weather" --message "/city-weather:now Tokyo" --expect "Weather Report" + +# Test the magic-test plugin with regex matching +/plugin-test:test --plugin "plugins/magic-test" --message "alakazam" --expect "Plugin loaded successfully" --regex + +# Test with verbose output and open browser +/plugin-test:test --plugin "plugins/city-weather" --message "/city-weather:now London" --expect "Temperature" --verbose --open +``` + +## Expected Output + +On success: +``` +=== Plugin Test === + +Configuration: + OpenHands URL: https://app.all-hands.dev + Marketplace: github:OpenHands/extensions (ref: main) + Plugin: plugins/city-weather + Message: /city-weather:now Tokyo + Expect: Weather Report + +[INFO] Creating conversation with plugin: plugins/city-weather +[INFO] Task created: abc123... +[INFO] Waiting for conversation to start (max 180s)... +[PASS] Conversation ready: def456... +[INFO] Fetching conversation events... +[INFO] Agent responded (1234 chars) +[PASS] Response matches expected pattern: "Weather Report" + +=== TEST PASSED === + +View conversation: + https://app.all-hands.dev/conversations/def456... +``` + +On failure, the script exits with code 1 and shows the actual response for debugging. + +## Notes + +- Sandbox startup typically takes 30-90 seconds +- The script polls the API every 3 seconds by default +- Maximum wait time is 180 seconds (configurable with `--max-wait`) +- Use `--verbose` to see full API responses for debugging diff --git a/plugins/plugin-test/scripts/test_plugin.sh b/plugins/plugin-test/scripts/test_plugin.sh new file mode 100755 index 0000000..6355632 --- /dev/null +++ b/plugins/plugin-test/scripts/test_plugin.sh @@ -0,0 +1,452 @@ +#!/bin/bash +# +# Test Plugin Loading via OpenHands Cloud API +# +# This script creates a conversation with a specified plugin loaded, +# waits for the sandbox to be ready, and verifies the agent response +# matches an expected string or pattern. +# +# Usage: +# ./test_plugin.sh --plugin --message --expect +# +# Examples: +# # Test city-weather plugin +# ./test_plugin.sh \ +# --plugin "plugins/city-weather" \ +# --message "/city-weather:now Tokyo" \ +# --expect "Weather Report" +# +# # Test magic-test plugin with regex +# ./test_plugin.sh \ +# --plugin "plugins/magic-test" \ +# --message "alakazam" \ +# --expect "Plugin loaded successfully" \ +# --regex +# +# Environment Variables: +# OPENHANDS_URL - OpenHands Cloud URL (default: https://app.all-hands.dev) +# OPENHANDS_API_KEY - Your OpenHands API key (required) +# MARKETPLACE_REPO - Plugin marketplace repo (default: github:OpenHands/extensions) +# MARKETPLACE_REF - Git ref for marketplace (default: main) +# + +set -e + +# ============================================================================ +# DEFAULTS +# ============================================================================ + +OPENHANDS_URL="${OPENHANDS_URL:-https://app.all-hands.dev}" +OPENHANDS_API_KEY="${OPENHANDS_API_KEY:-}" +MARKETPLACE_REPO="${MARKETPLACE_REPO:-github:OpenHands/extensions}" +MARKETPLACE_REF="${MARKETPLACE_REF:-main}" + +# Script arguments +PLUGIN_PATH="" +INITIAL_MESSAGE="" +EXPECT_STRING="" +USE_REGEX=false +OPEN_BROWSER=false +VERBOSE=false +MAX_WAIT=180 # Maximum seconds to wait for conversation +POLL_INTERVAL=3 + +# ============================================================================ +# COLORS +# ============================================================================ + +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +BLUE='\033[0;34m' +CYAN='\033[0;36m' +NC='\033[0m' # No Color + +# ============================================================================ +# FUNCTIONS +# ============================================================================ + +usage() { + cat << EOF +Usage: $(basename "$0") [OPTIONS] + +Test plugin loading via OpenHands Cloud API. + +Required Arguments: + --plugin PATH Path to plugin within the marketplace repo (e.g., plugins/city-weather) + --message TEXT Initial message to send (e.g., "/city-weather:now Tokyo") + --expect STRING String or regex pattern to expect in the response + +Options: + --regex Treat --expect as a regex pattern (default: substring match) + --open Open the conversation in a browser when ready + --verbose Show detailed output including API responses + --max-wait SECONDS Maximum time to wait for conversation (default: 180) + --poll SECONDS Polling interval in seconds (default: 3) + --help Show this help message + +Environment Variables: + OPENHANDS_URL OpenHands Cloud URL (default: https://app.all-hands.dev) + OPENHANDS_API_KEY Your OpenHands API key (required) + MARKETPLACE_REPO Plugin marketplace repo (default: github:OpenHands/extensions) + MARKETPLACE_REF Git ref for marketplace (default: main) + +Examples: + # Test city-weather plugin + export OPENHANDS_API_KEY="sk-oh-..." + $(basename "$0") \\ + --plugin "plugins/city-weather" \\ + --message "/city-weather:now Tokyo" \\ + --expect "Weather Report" + + # Test with custom marketplace + MARKETPLACE_REPO="github:myorg/my-plugins" $(basename "$0") \\ + --plugin "plugins/my-plugin" \\ + --message "/my-plugin:test" \\ + --expect "success" +EOF + exit 0 +} + +log_info() { + echo -e "${BLUE}[INFO]${NC} $1" +} + +log_success() { + echo -e "${GREEN}[PASS]${NC} $1" +} + +log_error() { + echo -e "${RED}[FAIL]${NC} $1" +} + +log_warn() { + echo -e "${YELLOW}[WARN]${NC} $1" +} + +log_verbose() { + if [ "$VERBOSE" = true ]; then + echo -e "${CYAN}[DEBUG]${NC} $1" + fi +} + +check_dependencies() { + local missing=() + + if ! command -v curl &> /dev/null; then + missing+=("curl") + fi + + if ! command -v jq &> /dev/null; then + missing+=("jq") + fi + + if [ ${#missing[@]} -gt 0 ]; then + log_error "Missing required dependencies: ${missing[*]}" + echo " Install with:" + echo " brew install ${missing[*]} # macOS" + echo " apt install ${missing[*]} # Ubuntu/Debian" + exit 1 + fi +} + +validate_config() { + if [ -z "$OPENHANDS_API_KEY" ]; then + log_error "OPENHANDS_API_KEY environment variable is required" + echo " export OPENHANDS_API_KEY='sk-oh-your-key-here'" + exit 1 + fi + + if [ -z "$PLUGIN_PATH" ]; then + log_error "--plugin argument is required" + exit 1 + fi + + if [ -z "$INITIAL_MESSAGE" ]; then + log_error "--message argument is required" + exit 1 + fi + + if [ -z "$EXPECT_STRING" ]; then + log_error "--expect argument is required" + exit 1 + fi +} + +create_conversation() { + log_info "Creating conversation with plugin: $PLUGIN_PATH" + log_verbose "Marketplace: $MARKETPLACE_REPO (ref: $MARKETPLACE_REF)" + log_verbose "Message: $INITIAL_MESSAGE" + + local payload + payload=$(cat << EOF +{ + "initial_message": { + "content": [ + { + "type": "text", + "text": "$INITIAL_MESSAGE" + } + ] + }, + "plugins": [{ + "source": "$MARKETPLACE_REPO", + "ref": "$MARKETPLACE_REF", + "repo_path": "$PLUGIN_PATH" + }] +} +EOF +) + + log_verbose "Request payload: $payload" + + RESPONSE=$(curl -s -X POST "${OPENHANDS_URL}/api/v1/app-conversations" \ + -H "Authorization: Bearer ${OPENHANDS_API_KEY}" \ + -H "Content-Type: application/json" \ + -d "$payload") + + log_verbose "Response: $RESPONSE" + + TASK_ID=$(echo "$RESPONSE" | jq -r '.id // empty') + + if [ -z "$TASK_ID" ]; then + log_error "Failed to create conversation" + echo "Response: $RESPONSE" + exit 1 + fi + + log_info "Task created: $TASK_ID" +} + +wait_for_conversation() { + log_info "Waiting for conversation to start (max ${MAX_WAIT}s)..." + + local elapsed=0 + local conversation_id="" + + while [ $elapsed -lt $MAX_WAIT ]; do + # Poll the start-tasks endpoint + TASK_RESPONSE=$(curl -s -X GET "${OPENHANDS_URL}/api/v1/app-conversations/start-tasks/search" \ + -H "Authorization: Bearer ${OPENHANDS_API_KEY}") + + # Find our task by ID + TASK_INFO=$(echo "$TASK_RESPONSE" | jq -r --arg id "$TASK_ID" '.items[] | select(.id == $id)') + TASK_STATUS=$(echo "$TASK_INFO" | jq -r '.status // "WORKING"') + conversation_id=$(echo "$TASK_INFO" | jq -r '.app_conversation_id // empty') + + printf "\r Elapsed: %ds | Status: %-10s" "$elapsed" "$TASK_STATUS" + + if [ "$TASK_STATUS" == "READY" ] && [ -n "$conversation_id" ] && [ "$conversation_id" != "null" ]; then + echo "" + CONVERSATION_ID="$conversation_id" + log_success "Conversation ready: $CONVERSATION_ID" + return 0 + fi + + if [ "$TASK_STATUS" == "ERROR" ]; then + echo "" + local detail + detail=$(echo "$TASK_INFO" | jq -r '.detail // "Unknown error"') + log_error "Task failed: $detail" + exit 1 + fi + + sleep "$POLL_INTERVAL" + elapsed=$((elapsed + POLL_INTERVAL)) + done + + echo "" + log_error "Timeout waiting for conversation to start" + exit 1 +} + +get_agent_response() { + log_info "Fetching conversation events..." + + # Get conversation details including runtime URL and session key + CONV_RESPONSE=$(curl -s -X GET "${OPENHANDS_URL}/api/v1/app-conversations/search" \ + -H "Authorization: Bearer ${OPENHANDS_API_KEY}") + + CONV_INFO=$(echo "$CONV_RESPONSE" | jq -r --arg id "$CONVERSATION_ID" '.items[] | select(.id == $id)') + CONVERSATION_URL=$(echo "$CONV_INFO" | jq -r '.conversation_url // empty') + SESSION_API_KEY=$(echo "$CONV_INFO" | jq -r '.session_api_key // empty') + + if [ -z "$CONVERSATION_URL" ] || [ "$CONVERSATION_URL" == "null" ]; then + log_error "Could not get conversation URL" + log_verbose "Conv info: $CONV_INFO" + exit 1 + fi + + log_verbose "Conversation URL: $CONVERSATION_URL" + + # Wait a bit for agent to process and respond + log_info "Waiting for agent response..." + sleep 10 + + # Query events from the Agent Server + EVENTS_RESPONSE=$(curl -s "${CONVERSATION_URL}/events/search" \ + -H "X-Session-API-Key: ${SESSION_API_KEY}") + + log_verbose "Events: $EVENTS_RESPONSE" + + # Extract agent messages + AGENT_RESPONSE=$(echo "$EVENTS_RESPONSE" | jq -r ' + .items[] + | select(.kind == "MessageEvent" and .source == "agent") + | .llm_message.content[0].text // empty + ' 2>/dev/null | head -1) + + if [ -z "$AGENT_RESPONSE" ]; then + # Try alternative event structure + AGENT_RESPONSE=$(echo "$EVENTS_RESPONSE" | jq -r ' + .items[] + | select(.source == "agent") + | .message // .content // empty + ' 2>/dev/null | head -1) + fi + + if [ -n "$AGENT_RESPONSE" ]; then + log_info "Agent responded (${#AGENT_RESPONSE} chars)" + log_verbose "Response preview: ${AGENT_RESPONSE:0:200}..." + else + log_warn "No agent response captured yet" + fi +} + +verify_response() { + log_info "Verifying response against expected pattern..." + + if [ -z "$AGENT_RESPONSE" ]; then + log_error "No agent response to verify" + return 1 + fi + + local match_found=false + + if [ "$USE_REGEX" = true ]; then + if echo "$AGENT_RESPONSE" | grep -qE "$EXPECT_STRING"; then + match_found=true + fi + else + if echo "$AGENT_RESPONSE" | grep -qF "$EXPECT_STRING"; then + match_found=true + fi + fi + + if [ "$match_found" = true ]; then + log_success "Response matches expected pattern: \"$EXPECT_STRING\"" + return 0 + else + log_error "Response does not match expected pattern: \"$EXPECT_STRING\"" + echo "" + echo "Agent response:" + echo "----------------------------------------" + echo "$AGENT_RESPONSE" | head -20 + echo "----------------------------------------" + return 1 + fi +} + +open_in_browser() { + local url="${OPENHANDS_URL}/conversations/${CONVERSATION_ID}" + + if [ "$OPEN_BROWSER" = true ]; then + log_info "Opening in browser: $url" + + if command -v open &> /dev/null; then + open "$url" + elif command -v xdg-open &> /dev/null; then + xdg-open "$url" + else + echo "Open in browser: $url" + fi + else + echo "" + echo "View conversation:" + echo " $url" + fi +} + +# ============================================================================ +# PARSE ARGUMENTS +# ============================================================================ + +while [[ $# -gt 0 ]]; do + case $1 in + --plugin) + PLUGIN_PATH="$2" + shift 2 + ;; + --message) + INITIAL_MESSAGE="$2" + shift 2 + ;; + --expect) + EXPECT_STRING="$2" + shift 2 + ;; + --regex) + USE_REGEX=true + shift + ;; + --open) + OPEN_BROWSER=true + shift + ;; + --verbose|-v) + VERBOSE=true + shift + ;; + --max-wait) + MAX_WAIT="$2" + shift 2 + ;; + --poll) + POLL_INTERVAL="$2" + shift 2 + ;; + --help|-h) + usage + ;; + *) + log_error "Unknown argument: $1" + echo "Use --help for usage information" + exit 1 + ;; + esac +done + +# ============================================================================ +# MAIN +# ============================================================================ + +echo "" +echo -e "${GREEN}=== Plugin Test ===${NC}" +echo "" + +check_dependencies +validate_config + +echo -e "${BLUE}Configuration:${NC}" +echo " OpenHands URL: $OPENHANDS_URL" +echo " Marketplace: $MARKETPLACE_REPO (ref: $MARKETPLACE_REF)" +echo " Plugin: $PLUGIN_PATH" +echo " Message: $INITIAL_MESSAGE" +echo " Expect: $EXPECT_STRING $([ "$USE_REGEX" = true ] && echo "(regex)")" +echo "" + +create_conversation +wait_for_conversation +get_agent_response + +if verify_response; then + echo "" + echo -e "${GREEN}=== TEST PASSED ===${NC}" + open_in_browser + exit 0 +else + echo "" + echo -e "${RED}=== TEST FAILED ===${NC}" + open_in_browser + exit 1 +fi From c7b40c17805caf8fd100a52a8be1153a5495398c Mon Sep 17 00:00:00 2001 From: openhands Date: Thu, 19 Feb 2026 21:56:41 +0000 Subject: [PATCH 2/2] Use OH_API_KEY as primary env var with OPENHANDS_API_KEY fallback Support both OH_API_KEY (preferred) and OPENHANDS_API_KEY (fallback) for the API key environment variable. Co-authored-by: openhands --- plugins/plugin-test/README.md | 9 ++++---- plugins/plugin-test/commands/test.md | 4 +++- plugins/plugin-test/scripts/test_plugin.sh | 24 +++++++++++++--------- 3 files changed, 22 insertions(+), 15 deletions(-) diff --git a/plugins/plugin-test/README.md b/plugins/plugin-test/README.md index 9d9be14..65072eb 100644 --- a/plugins/plugin-test/README.md +++ b/plugins/plugin-test/README.md @@ -20,7 +20,7 @@ Test plugin loading via the OpenHands Cloud API. Launch conversations with any p ### Basic Usage ```bash -export OPENHANDS_API_KEY="sk-oh-your-api-key" +export OH_API_KEY="sk-oh-your-api-key" # Test the city-weather plugin ./scripts/test_plugin.sh \ @@ -64,7 +64,8 @@ Options: | Variable | Default | Description | |----------|---------|-------------| -| `OPENHANDS_API_KEY` | (required) | Your OpenHands API key | +| `OH_API_KEY` | (required) | Your OpenHands API key | +| `OPENHANDS_API_KEY` | - | Alternative name for API key (fallback) | | `OPENHANDS_URL` | `https://app.all-hands.dev` | OpenHands Cloud URL | | `MARKETPLACE_REPO` | `github:OpenHands/extensions` | Plugin marketplace repository | | `MARKETPLACE_REF` | `main` | Git ref (branch/tag) for marketplace | @@ -76,7 +77,7 @@ Options: ### Test city-weather plugin ```bash -export OPENHANDS_API_KEY="sk-oh-..." +export OH_API_KEY="sk-oh-..." ./scripts/test_plugin.sh \ --plugin "plugins/city-weather" \ @@ -142,7 +143,7 @@ The script returns exit codes suitable for CI pipelines: ```yaml - name: Test city-weather plugin env: - OPENHANDS_API_KEY: ${{ secrets.OPENHANDS_API_KEY }} + OH_API_KEY: ${{ secrets.OH_API_KEY }} run: | ./plugins/plugin-test/scripts/test_plugin.sh \ --plugin "plugins/city-weather" \ diff --git a/plugins/plugin-test/commands/test.md b/plugins/plugin-test/commands/test.md index 302ade1..5f285d4 100644 --- a/plugins/plugin-test/commands/test.md +++ b/plugins/plugin-test/commands/test.md @@ -33,7 +33,9 @@ chmod +x /tmp/test_plugin.sh Before running, ensure these are set: ```bash -export OPENHANDS_API_KEY="sk-oh-your-api-key" +export OH_API_KEY="sk-oh-your-api-key" +# Alternative: OPENHANDS_API_KEY is also accepted as a fallback + # Optional: override defaults export OPENHANDS_URL="https://app.all-hands.dev" export MARKETPLACE_REPO="github:OpenHands/extensions" diff --git a/plugins/plugin-test/scripts/test_plugin.sh b/plugins/plugin-test/scripts/test_plugin.sh index 6355632..b5bd402 100755 --- a/plugins/plugin-test/scripts/test_plugin.sh +++ b/plugins/plugin-test/scripts/test_plugin.sh @@ -25,7 +25,8 @@ # # Environment Variables: # OPENHANDS_URL - OpenHands Cloud URL (default: https://app.all-hands.dev) -# OPENHANDS_API_KEY - Your OpenHands API key (required) +# OH_API_KEY - Your OpenHands API key (required) +# OPENHANDS_API_KEY - Alternative name for API key (fallback if OH_API_KEY not set) # MARKETPLACE_REPO - Plugin marketplace repo (default: github:OpenHands/extensions) # MARKETPLACE_REF - Git ref for marketplace (default: main) # @@ -37,7 +38,8 @@ set -e # ============================================================================ OPENHANDS_URL="${OPENHANDS_URL:-https://app.all-hands.dev}" -OPENHANDS_API_KEY="${OPENHANDS_API_KEY:-}" +# Support both OH_API_KEY (preferred) and OPENHANDS_API_KEY (fallback) +OH_API_KEY="${OH_API_KEY:-${OPENHANDS_API_KEY:-}}" MARKETPLACE_REPO="${MARKETPLACE_REPO:-github:OpenHands/extensions}" MARKETPLACE_REF="${MARKETPLACE_REF:-main}" @@ -87,13 +89,14 @@ Options: Environment Variables: OPENHANDS_URL OpenHands Cloud URL (default: https://app.all-hands.dev) - OPENHANDS_API_KEY Your OpenHands API key (required) + OH_API_KEY Your OpenHands API key (required) + OPENHANDS_API_KEY Alternative name for API key (fallback if OH_API_KEY not set) MARKETPLACE_REPO Plugin marketplace repo (default: github:OpenHands/extensions) MARKETPLACE_REF Git ref for marketplace (default: main) Examples: # Test city-weather plugin - export OPENHANDS_API_KEY="sk-oh-..." + export OH_API_KEY="sk-oh-..." $(basename "$0") \\ --plugin "plugins/city-weather" \\ --message "/city-weather:now Tokyo" \\ @@ -151,9 +154,10 @@ check_dependencies() { } validate_config() { - if [ -z "$OPENHANDS_API_KEY" ]; then - log_error "OPENHANDS_API_KEY environment variable is required" - echo " export OPENHANDS_API_KEY='sk-oh-your-key-here'" + if [ -z "$OH_API_KEY" ]; then + log_error "OH_API_KEY environment variable is required" + echo " export OH_API_KEY='sk-oh-your-key-here'" + echo " (OPENHANDS_API_KEY is also accepted)" exit 1 fi @@ -201,7 +205,7 @@ EOF log_verbose "Request payload: $payload" RESPONSE=$(curl -s -X POST "${OPENHANDS_URL}/api/v1/app-conversations" \ - -H "Authorization: Bearer ${OPENHANDS_API_KEY}" \ + -H "Authorization: Bearer ${OH_API_KEY}" \ -H "Content-Type: application/json" \ -d "$payload") @@ -227,7 +231,7 @@ wait_for_conversation() { while [ $elapsed -lt $MAX_WAIT ]; do # Poll the start-tasks endpoint TASK_RESPONSE=$(curl -s -X GET "${OPENHANDS_URL}/api/v1/app-conversations/start-tasks/search" \ - -H "Authorization: Bearer ${OPENHANDS_API_KEY}") + -H "Authorization: Bearer ${OH_API_KEY}") # Find our task by ID TASK_INFO=$(echo "$TASK_RESPONSE" | jq -r --arg id "$TASK_ID" '.items[] | select(.id == $id)') @@ -265,7 +269,7 @@ get_agent_response() { # Get conversation details including runtime URL and session key CONV_RESPONSE=$(curl -s -X GET "${OPENHANDS_URL}/api/v1/app-conversations/search" \ - -H "Authorization: Bearer ${OPENHANDS_API_KEY}") + -H "Authorization: Bearer ${OH_API_KEY}") CONV_INFO=$(echo "$CONV_RESPONSE" | jq -r --arg id "$CONVERSATION_ID" '.items[] | select(.id == $id)') CONVERSATION_URL=$(echo "$CONV_INFO" | jq -r '.conversation_url // empty')