@agentspan/agentspan 0.0.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +227 -0
- package/build.sh +35 -0
- package/cli.js +28 -0
- package/client/client.go +365 -0
- package/cmd/agent.go +120 -0
- package/cmd/compile.go +37 -0
- package/cmd/configure.go +48 -0
- package/cmd/delete.go +44 -0
- package/cmd/doctor.go +486 -0
- package/cmd/execution.go +155 -0
- package/cmd/get.go +40 -0
- package/cmd/helpers.go +21 -0
- package/cmd/init.go +83 -0
- package/cmd/list.go +55 -0
- package/cmd/respond.go +56 -0
- package/cmd/root.go +44 -0
- package/cmd/run.go +116 -0
- package/cmd/server.go +446 -0
- package/cmd/server_unix.go +36 -0
- package/cmd/server_windows.go +37 -0
- package/cmd/status.go +72 -0
- package/cmd/stream.go +32 -0
- package/cmd/update.go +91 -0
- package/config/config.go +78 -0
- package/dist/agentspan_darwin_amd64 +0 -0
- package/dist/agentspan_darwin_arm64 +0 -0
- package/dist/agentspan_linux_amd64 +0 -0
- package/dist/agentspan_linux_arm64 +0 -0
- package/dist/agentspan_windows_amd64.exe +0 -0
- package/dist/agentspan_windows_arm64.exe +0 -0
- package/examples/multi-agent.yaml +22 -0
- package/examples/simple-agent.yaml +7 -0
- package/go.mod +17 -0
- package/go.sum +24 -0
- package/install.js +122 -0
- package/install.sh +104 -0
- package/internal/progress/bar.go +121 -0
- package/main.go +10 -0
- package/package.json +43 -0
package/README.md
ADDED
|
@@ -0,0 +1,227 @@
|
|
|
1
|
+
# AgentSpan CLI
|
|
2
|
+
|
|
3
|
+
Command-line interface for building, running, and managing AI agents powered by the AgentSpan runtime.
|
|
4
|
+
|
|
5
|
+
## Install
|
|
6
|
+
|
|
7
|
+
### npm (recommended)
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
npm install -g @agentspan/agentspan
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
### Homebrew
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
brew tap agentspan/agentspan
|
|
17
|
+
brew install agentspan
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
### Shell script
|
|
21
|
+
|
|
22
|
+
```bash
|
|
23
|
+
curl -fsSL https://raw.githubusercontent.com/agentspan/agentspan/main/cli/install.sh | sh
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
### From source
|
|
27
|
+
|
|
28
|
+
```bash
|
|
29
|
+
cd cli
|
|
30
|
+
go build -o agentspan .
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
## Quickstart
|
|
34
|
+
|
|
35
|
+
```bash
|
|
36
|
+
# Start the runtime server (downloads automatically)
|
|
37
|
+
agentspan server start
|
|
38
|
+
|
|
39
|
+
# Create an agent config
|
|
40
|
+
agentspan agent init mybot
|
|
41
|
+
|
|
42
|
+
# Run an agent from a config file
|
|
43
|
+
agentspan agent run --config mybot.yaml "What is the weather in NYC?"
|
|
44
|
+
|
|
45
|
+
# Run a registered agent by name
|
|
46
|
+
agentspan agent run --name mybot "What is the weather in NYC?"
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
## Commands
|
|
50
|
+
|
|
51
|
+
### Server Management
|
|
52
|
+
|
|
53
|
+
```bash
|
|
54
|
+
# Start the server (downloads latest JAR if needed)
|
|
55
|
+
agentspan server start
|
|
56
|
+
|
|
57
|
+
# Start a specific version
|
|
58
|
+
agentspan server start --version 0.1.0
|
|
59
|
+
|
|
60
|
+
# Start on a custom port with a default model
|
|
61
|
+
agentspan server start --port 9090 --model openai/gpt-4o
|
|
62
|
+
|
|
63
|
+
# Stop the server
|
|
64
|
+
agentspan server stop
|
|
65
|
+
|
|
66
|
+
# View server logs
|
|
67
|
+
agentspan server logs
|
|
68
|
+
|
|
69
|
+
# Follow server logs in real-time
|
|
70
|
+
agentspan server logs -f
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
The server JAR is downloaded from GitHub releases and cached in `~/.agentspan/server/`. On each `server start`, the CLI checks GitHub for updates and re-downloads if a newer version is available.
|
|
74
|
+
|
|
75
|
+
### Agent Operations
|
|
76
|
+
|
|
77
|
+
```bash
|
|
78
|
+
# Create a new agent config file
|
|
79
|
+
agentspan agent init mybot
|
|
80
|
+
agentspan agent init mybot --model anthropic/claude-sonnet-4-20250514 --format json
|
|
81
|
+
|
|
82
|
+
# Run an agent
|
|
83
|
+
agentspan agent run --name mybot "Hello, what can you do?"
|
|
84
|
+
agentspan agent run --config mybot.yaml "Hello, what can you do?"
|
|
85
|
+
agentspan agent run --name mybot --no-stream "Fire and forget"
|
|
86
|
+
|
|
87
|
+
# List all registered agents
|
|
88
|
+
agentspan agent list
|
|
89
|
+
|
|
90
|
+
# Get agent definition as JSON
|
|
91
|
+
agentspan agent get mybot
|
|
92
|
+
agentspan agent get mybot --version 2
|
|
93
|
+
|
|
94
|
+
# Delete an agent
|
|
95
|
+
agentspan agent delete mybot
|
|
96
|
+
agentspan agent delete mybot --version 1
|
|
97
|
+
|
|
98
|
+
# Check execution status
|
|
99
|
+
agentspan agent status <execution-id>
|
|
100
|
+
|
|
101
|
+
# Search execution history
|
|
102
|
+
agentspan agent execution
|
|
103
|
+
agentspan agent execution --name mybot
|
|
104
|
+
agentspan agent execution --status COMPLETED --since 1h
|
|
105
|
+
agentspan agent execution --since 7d
|
|
106
|
+
agentspan agent execution --window now-30m
|
|
107
|
+
|
|
108
|
+
# Stream events from a running agent
|
|
109
|
+
agentspan agent stream <execution-id>
|
|
110
|
+
|
|
111
|
+
# Respond to human-in-the-loop tasks
|
|
112
|
+
agentspan agent respond <execution-id> --approve
|
|
113
|
+
agentspan agent respond <execution-id> --deny --reason "Amount too high"
|
|
114
|
+
|
|
115
|
+
# Compile agent config to workflow definition (inspect only)
|
|
116
|
+
agentspan agent compile mybot.yaml
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
### Time Filters
|
|
120
|
+
|
|
121
|
+
The `--since` and `--window` flags accept human-readable time specs:
|
|
122
|
+
|
|
123
|
+
| Format | Meaning |
|
|
124
|
+
|--------|---------|
|
|
125
|
+
| `30s` | 30 seconds |
|
|
126
|
+
| `5m` | 5 minutes |
|
|
127
|
+
| `1h` | 1 hour |
|
|
128
|
+
| `1d` | 1 day |
|
|
129
|
+
| `7d` | 7 days |
|
|
130
|
+
| `1mo` | 1 month (30 days) |
|
|
131
|
+
| `1y` | 1 year (365 days) |
|
|
132
|
+
|
|
133
|
+
### CLI Self-Update
|
|
134
|
+
|
|
135
|
+
```bash
|
|
136
|
+
agentspan update
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
### Configuration
|
|
140
|
+
|
|
141
|
+
```bash
|
|
142
|
+
# Set server URL and auth credentials
|
|
143
|
+
agentspan configure --url http://myserver:8080
|
|
144
|
+
agentspan configure --auth-key KEY --auth-secret SECRET
|
|
145
|
+
|
|
146
|
+
# Override server URL for a single command
|
|
147
|
+
agentspan --server http://other:8080 agent list
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
Configuration is stored in `~/.agentspan/config.json`. Environment variables take precedence:
|
|
151
|
+
|
|
152
|
+
| Variable | Description |
|
|
153
|
+
|----------|-------------|
|
|
154
|
+
| `AGENT_SERVER_URL` | Server URL (default: `http://localhost:8080`) |
|
|
155
|
+
| `CONDUCTOR_AUTH_KEY` | Auth key |
|
|
156
|
+
| `CONDUCTOR_AUTH_SECRET` | Auth secret |
|
|
157
|
+
|
|
158
|
+
### Version
|
|
159
|
+
|
|
160
|
+
```bash
|
|
161
|
+
agentspan version
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
## Agent Config Format
|
|
165
|
+
|
|
166
|
+
YAML or JSON. See [examples/](examples/) for samples.
|
|
167
|
+
|
|
168
|
+
```yaml
|
|
169
|
+
name: my-agent
|
|
170
|
+
description: A helpful assistant
|
|
171
|
+
model: openai/gpt-4o
|
|
172
|
+
instructions: You are a helpful assistant.
|
|
173
|
+
maxTurns: 25
|
|
174
|
+
tools:
|
|
175
|
+
- name: web_search
|
|
176
|
+
type: worker
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
## Distribution
|
|
180
|
+
|
|
181
|
+
The CLI is distributed through three channels:
|
|
182
|
+
|
|
183
|
+
1. **npm** (`@agentspan/agentspan`) -- Node.js wrapper downloads the Go binary on install
|
|
184
|
+
2. **Homebrew** (`agentspan/agentspan` tap) -- Pre-built binaries for macOS and Linux
|
|
185
|
+
3. **Shell installer** -- Direct binary download to `/usr/local/bin`
|
|
186
|
+
4. **GitHub Releases** -- Pre-built binaries for all platforms
|
|
187
|
+
|
|
188
|
+
### Supported Platforms
|
|
189
|
+
|
|
190
|
+
| OS | Architecture |
|
|
191
|
+
|----|-------------|
|
|
192
|
+
| macOS | x86_64, ARM64 (Apple Silicon) |
|
|
193
|
+
| Linux | x86_64, ARM64 |
|
|
194
|
+
| Windows | x86_64, ARM64 |
|
|
195
|
+
|
|
196
|
+
## Development
|
|
197
|
+
|
|
198
|
+
### Building
|
|
199
|
+
|
|
200
|
+
```bash
|
|
201
|
+
cd cli
|
|
202
|
+
go build -o agentspan .
|
|
203
|
+
```
|
|
204
|
+
|
|
205
|
+
### Cross-platform build
|
|
206
|
+
|
|
207
|
+
```bash
|
|
208
|
+
cd cli
|
|
209
|
+
VERSION=0.1.0 ./build.sh
|
|
210
|
+
```
|
|
211
|
+
|
|
212
|
+
Produces binaries in `cli/dist/` for all 6 platform/arch combinations.
|
|
213
|
+
|
|
214
|
+
### Release
|
|
215
|
+
|
|
216
|
+
Push a tag matching `cli-v*` to trigger the release workflow:
|
|
217
|
+
|
|
218
|
+
```bash
|
|
219
|
+
git tag cli-v0.1.0
|
|
220
|
+
git push origin cli-v0.1.0
|
|
221
|
+
```
|
|
222
|
+
|
|
223
|
+
This builds all binaries, creates a GitHub release, publishes to npm, and updates the Homebrew tap.
|
|
224
|
+
|
|
225
|
+
## License
|
|
226
|
+
|
|
227
|
+
Apache 2.0
|
package/build.sh
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
set -e
|
|
3
|
+
|
|
4
|
+
VERSION="${VERSION:-dev}"
|
|
5
|
+
COMMIT="${COMMIT:-$(git rev-parse --short HEAD 2>/dev/null || echo 'none')}"
|
|
6
|
+
DATE="${DATE:-$(date -u +%Y-%m-%dT%H:%M:%SZ)}"
|
|
7
|
+
LDFLAGS="-X github.com/agentspan/agentspan/cli/cmd.Version=${VERSION} -X github.com/agentspan/agentspan/cli/cmd.Commit=${COMMIT} -X github.com/agentspan/agentspan/cli/cmd.Date=${DATE}"
|
|
8
|
+
|
|
9
|
+
mkdir -p dist
|
|
10
|
+
|
|
11
|
+
echo "Building agentspan CLI v${VERSION}..."
|
|
12
|
+
|
|
13
|
+
GOOS=darwin GOARCH=amd64 go build -ldflags "$LDFLAGS" -o dist/agentspan_darwin_amd64 .
|
|
14
|
+
echo " Built: darwin/amd64"
|
|
15
|
+
|
|
16
|
+
GOOS=darwin GOARCH=arm64 go build -ldflags "$LDFLAGS" -o dist/agentspan_darwin_arm64 .
|
|
17
|
+
echo " Built: darwin/arm64"
|
|
18
|
+
|
|
19
|
+
GOOS=linux GOARCH=amd64 go build -ldflags "$LDFLAGS" -o dist/agentspan_linux_amd64 .
|
|
20
|
+
echo " Built: linux/amd64"
|
|
21
|
+
|
|
22
|
+
GOOS=linux GOARCH=arm64 go build -ldflags "$LDFLAGS" -o dist/agentspan_linux_arm64 .
|
|
23
|
+
echo " Built: linux/arm64"
|
|
24
|
+
|
|
25
|
+
GOOS=windows GOARCH=amd64 go build -ldflags "$LDFLAGS" -o dist/agentspan_windows_amd64.exe .
|
|
26
|
+
echo " Built: windows/amd64"
|
|
27
|
+
|
|
28
|
+
GOOS=windows GOARCH=arm64 go build -ldflags "$LDFLAGS" -o dist/agentspan_windows_arm64.exe .
|
|
29
|
+
echo " Built: windows/arm64"
|
|
30
|
+
|
|
31
|
+
chmod +x dist/agentspan_*
|
|
32
|
+
|
|
33
|
+
echo ""
|
|
34
|
+
echo "Build complete! Binaries in dist/"
|
|
35
|
+
ls -lh dist/
|
package/cli.js
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// Copyright (c) 2025 AgentSpan
|
|
4
|
+
// Licensed under the MIT License. See LICENSE file in the project root for details.
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
const { spawnSync } = require('child_process');
|
|
8
|
+
const path = require('path');
|
|
9
|
+
const fs = require('fs');
|
|
10
|
+
|
|
11
|
+
const platform = process.platform;
|
|
12
|
+
const binaryName = platform === 'win32' ? 'agentspan.exe' : 'agentspan';
|
|
13
|
+
const binaryPath = path.join(__dirname, 'bin', binaryName);
|
|
14
|
+
|
|
15
|
+
// Check if binary exists
|
|
16
|
+
if (!fs.existsSync(binaryPath)) {
|
|
17
|
+
console.error('Error: Binary not found. Please reinstall the package.');
|
|
18
|
+
console.error('Run: npm uninstall -g @agentspan/agentspan && npm install -g @agentspan/agentspan');
|
|
19
|
+
process.exit(1);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
// Execute the binary with all arguments
|
|
23
|
+
const result = spawnSync(binaryPath, process.argv.slice(2), {
|
|
24
|
+
stdio: 'inherit',
|
|
25
|
+
shell: false
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
process.exit(result.status);
|
package/client/client.go
ADDED
|
@@ -0,0 +1,365 @@
|
|
|
1
|
+
// Copyright (c) 2025 AgentSpan
|
|
2
|
+
// Licensed under the MIT License. See LICENSE file in the project root for details.
|
|
3
|
+
|
|
4
|
+
package client
|
|
5
|
+
|
|
6
|
+
import (
|
|
7
|
+
"bufio"
|
|
8
|
+
"bytes"
|
|
9
|
+
"encoding/json"
|
|
10
|
+
"fmt"
|
|
11
|
+
"io"
|
|
12
|
+
"net/http"
|
|
13
|
+
"net/url"
|
|
14
|
+
"strings"
|
|
15
|
+
"time"
|
|
16
|
+
|
|
17
|
+
"github.com/agentspan/agentspan/cli/config"
|
|
18
|
+
)
|
|
19
|
+
|
|
20
|
+
type Client struct {
|
|
21
|
+
baseURL string
|
|
22
|
+
httpClient *http.Client
|
|
23
|
+
authKey string
|
|
24
|
+
authSecret string
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
func New(cfg *config.Config) *Client {
|
|
28
|
+
return &Client{
|
|
29
|
+
baseURL: strings.TrimRight(cfg.ServerURL, "/"),
|
|
30
|
+
httpClient: &http.Client{Timeout: 30 * time.Second},
|
|
31
|
+
authKey: cfg.AuthKey,
|
|
32
|
+
authSecret: cfg.AuthSecret,
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
func (c *Client) doRequest(method, path string, body interface{}) (*http.Response, error) {
|
|
37
|
+
var bodyReader io.Reader
|
|
38
|
+
if body != nil {
|
|
39
|
+
data, err := json.Marshal(body)
|
|
40
|
+
if err != nil {
|
|
41
|
+
return nil, fmt.Errorf("marshal request: %w", err)
|
|
42
|
+
}
|
|
43
|
+
bodyReader = bytes.NewReader(data)
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
req, err := http.NewRequest(method, c.baseURL+path, bodyReader)
|
|
47
|
+
if err != nil {
|
|
48
|
+
return nil, err
|
|
49
|
+
}
|
|
50
|
+
if body != nil {
|
|
51
|
+
req.Header.Set("Content-Type", "application/json")
|
|
52
|
+
}
|
|
53
|
+
if c.authKey != "" {
|
|
54
|
+
req.Header.Set("X-Auth-Key", c.authKey)
|
|
55
|
+
}
|
|
56
|
+
if c.authSecret != "" {
|
|
57
|
+
req.Header.Set("X-Auth-Secret", c.authSecret)
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
resp, err := c.httpClient.Do(req)
|
|
61
|
+
if err != nil {
|
|
62
|
+
return nil, fmt.Errorf("request failed: %w", err)
|
|
63
|
+
}
|
|
64
|
+
if resp.StatusCode >= 400 {
|
|
65
|
+
defer resp.Body.Close()
|
|
66
|
+
bodyBytes, _ := io.ReadAll(resp.Body)
|
|
67
|
+
return nil, fmt.Errorf("HTTP %d: %s", resp.StatusCode, string(bodyBytes))
|
|
68
|
+
}
|
|
69
|
+
return resp, nil
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// HealthCheck pings the server
|
|
73
|
+
func (c *Client) HealthCheck() error {
|
|
74
|
+
resp, err := c.doRequest("GET", "/api/agent", nil)
|
|
75
|
+
if err != nil {
|
|
76
|
+
return err
|
|
77
|
+
}
|
|
78
|
+
resp.Body.Close()
|
|
79
|
+
return nil
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// StartRequest is the payload for starting an agent
|
|
83
|
+
type StartRequest struct {
|
|
84
|
+
AgentConfig map[string]interface{} `json:"agentConfig,omitempty"`
|
|
85
|
+
Prompt string `json:"prompt"`
|
|
86
|
+
SessionID string `json:"sessionId,omitempty"`
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// StartResponse from the runtime
|
|
90
|
+
type StartResponse struct {
|
|
91
|
+
WorkflowID string `json:"workflowId"`
|
|
92
|
+
WorkflowName string `json:"workflowName"`
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// Start compiles, registers, and starts an agent workflow
|
|
96
|
+
func (c *Client) Start(req *StartRequest) (*StartResponse, error) {
|
|
97
|
+
resp, err := c.doRequest("POST", "/api/agent/start", req)
|
|
98
|
+
if err != nil {
|
|
99
|
+
return nil, err
|
|
100
|
+
}
|
|
101
|
+
defer resp.Body.Close()
|
|
102
|
+
var result StartResponse
|
|
103
|
+
if err := json.NewDecoder(resp.Body).Decode(&result); err != nil {
|
|
104
|
+
return nil, fmt.Errorf("decode response: %w", err)
|
|
105
|
+
}
|
|
106
|
+
return &result, nil
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// Compile compiles an agent config to a workflow definition
|
|
110
|
+
func (c *Client) Compile(agentConfig map[string]interface{}) (map[string]interface{}, error) {
|
|
111
|
+
body := map[string]interface{}{"agentConfig": agentConfig}
|
|
112
|
+
resp, err := c.doRequest("POST", "/api/agent/compile", body)
|
|
113
|
+
if err != nil {
|
|
114
|
+
return nil, err
|
|
115
|
+
}
|
|
116
|
+
defer resp.Body.Close()
|
|
117
|
+
var result map[string]interface{}
|
|
118
|
+
if err := json.NewDecoder(resp.Body).Decode(&result); err != nil {
|
|
119
|
+
return nil, fmt.Errorf("decode response: %w", err)
|
|
120
|
+
}
|
|
121
|
+
return result, nil
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// AgentSummary represents a registered agent
|
|
125
|
+
type AgentSummary struct {
|
|
126
|
+
Name string `json:"name"`
|
|
127
|
+
Version int `json:"version"`
|
|
128
|
+
Type string `json:"type"`
|
|
129
|
+
Tags []string `json:"tags"`
|
|
130
|
+
CreateTime *int64 `json:"createTime"`
|
|
131
|
+
UpdateTime *int64 `json:"updateTime"`
|
|
132
|
+
Description string `json:"description"`
|
|
133
|
+
Checksum string `json:"checksum"`
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// ListAgents returns all registered agents
|
|
137
|
+
func (c *Client) ListAgents() ([]AgentSummary, error) {
|
|
138
|
+
resp, err := c.doRequest("GET", "/api/agent/list", nil)
|
|
139
|
+
if err != nil {
|
|
140
|
+
return nil, err
|
|
141
|
+
}
|
|
142
|
+
defer resp.Body.Close()
|
|
143
|
+
var result []AgentSummary
|
|
144
|
+
if err := json.NewDecoder(resp.Body).Decode(&result); err != nil {
|
|
145
|
+
return nil, fmt.Errorf("decode response: %w", err)
|
|
146
|
+
}
|
|
147
|
+
return result, nil
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
// GetAgent returns the workflow definition for a named agent
|
|
151
|
+
func (c *Client) GetAgent(name string, version *int) (map[string]interface{}, error) {
|
|
152
|
+
path := "/api/agent/get/" + url.PathEscape(name)
|
|
153
|
+
if version != nil {
|
|
154
|
+
path += fmt.Sprintf("?version=%d", *version)
|
|
155
|
+
}
|
|
156
|
+
resp, err := c.doRequest("GET", path, nil)
|
|
157
|
+
if err != nil {
|
|
158
|
+
return nil, err
|
|
159
|
+
}
|
|
160
|
+
defer resp.Body.Close()
|
|
161
|
+
var result map[string]interface{}
|
|
162
|
+
if err := json.NewDecoder(resp.Body).Decode(&result); err != nil {
|
|
163
|
+
return nil, fmt.Errorf("decode response: %w", err)
|
|
164
|
+
}
|
|
165
|
+
return result, nil
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
// DeleteAgent removes an agent workflow definition
|
|
169
|
+
func (c *Client) DeleteAgent(name string, version *int) error {
|
|
170
|
+
path := "/api/agent/delete/" + url.PathEscape(name)
|
|
171
|
+
if version != nil {
|
|
172
|
+
path += fmt.Sprintf("?version=%d", *version)
|
|
173
|
+
}
|
|
174
|
+
resp, err := c.doRequest("DELETE", path, nil)
|
|
175
|
+
if err != nil {
|
|
176
|
+
return err
|
|
177
|
+
}
|
|
178
|
+
resp.Body.Close()
|
|
179
|
+
return nil
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
// ExecutionSearchResult from the search endpoint
|
|
183
|
+
type ExecutionSearchResult struct {
|
|
184
|
+
TotalHits int64 `json:"totalHits"`
|
|
185
|
+
Results []AgentExecutionSummary `json:"results"`
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
// AgentExecutionSummary represents one execution in search results
|
|
189
|
+
type AgentExecutionSummary struct {
|
|
190
|
+
WorkflowID string `json:"workflowId"`
|
|
191
|
+
AgentName string `json:"agentName"`
|
|
192
|
+
Version int `json:"version"`
|
|
193
|
+
Status string `json:"status"`
|
|
194
|
+
StartTime string `json:"startTime"`
|
|
195
|
+
EndTime string `json:"endTime"`
|
|
196
|
+
UpdateTime string `json:"updateTime"`
|
|
197
|
+
ExecutionTime int64 `json:"executionTime"`
|
|
198
|
+
Input string `json:"input"`
|
|
199
|
+
Output string `json:"output"`
|
|
200
|
+
CreatedBy string `json:"createdBy"`
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
// SearchExecutions searches agent executions with optional filters
|
|
204
|
+
func (c *Client) SearchExecutions(start, size int, agentName, status, freeText string) (*ExecutionSearchResult, error) {
|
|
205
|
+
params := url.Values{}
|
|
206
|
+
params.Set("start", fmt.Sprintf("%d", start))
|
|
207
|
+
params.Set("size", fmt.Sprintf("%d", size))
|
|
208
|
+
params.Set("sort", "startTime:DESC")
|
|
209
|
+
if agentName != "" {
|
|
210
|
+
params.Set("agentName", agentName)
|
|
211
|
+
}
|
|
212
|
+
if status != "" {
|
|
213
|
+
params.Set("status", status)
|
|
214
|
+
}
|
|
215
|
+
if freeText != "" {
|
|
216
|
+
params.Set("freeText", freeText)
|
|
217
|
+
}
|
|
218
|
+
resp, err := c.doRequest("GET", "/api/agent/executions?"+params.Encode(), nil)
|
|
219
|
+
if err != nil {
|
|
220
|
+
return nil, err
|
|
221
|
+
}
|
|
222
|
+
defer resp.Body.Close()
|
|
223
|
+
var result ExecutionSearchResult
|
|
224
|
+
if err := json.NewDecoder(resp.Body).Decode(&result); err != nil {
|
|
225
|
+
return nil, fmt.Errorf("decode response: %w", err)
|
|
226
|
+
}
|
|
227
|
+
return &result, nil
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
// ExecutionDetail represents detailed execution status
|
|
231
|
+
type ExecutionDetail struct {
|
|
232
|
+
WorkflowID string `json:"workflowId"`
|
|
233
|
+
AgentName string `json:"agentName"`
|
|
234
|
+
Version int `json:"version"`
|
|
235
|
+
Status string `json:"status"`
|
|
236
|
+
Input map[string]interface{} `json:"input"`
|
|
237
|
+
Output map[string]interface{} `json:"output"`
|
|
238
|
+
CurrentTask *CurrentTask `json:"currentTask"`
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
type CurrentTask struct {
|
|
242
|
+
TaskRefName string `json:"taskRefName"`
|
|
243
|
+
TaskType string `json:"taskType"`
|
|
244
|
+
Status string `json:"status"`
|
|
245
|
+
InputData map[string]interface{} `json:"inputData"`
|
|
246
|
+
OutputData map[string]interface{} `json:"outputData"`
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
// GetExecutionDetail returns detailed status for an execution
|
|
250
|
+
func (c *Client) GetExecutionDetail(executionId string) (*ExecutionDetail, error) {
|
|
251
|
+
resp, err := c.doRequest("GET", "/api/agent/executions/"+url.PathEscape(executionId), nil)
|
|
252
|
+
if err != nil {
|
|
253
|
+
return nil, err
|
|
254
|
+
}
|
|
255
|
+
defer resp.Body.Close()
|
|
256
|
+
var result ExecutionDetail
|
|
257
|
+
if err := json.NewDecoder(resp.Body).Decode(&result); err != nil {
|
|
258
|
+
return nil, fmt.Errorf("decode response: %w", err)
|
|
259
|
+
}
|
|
260
|
+
return &result, nil
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
// Status gets the workflow execution status (legacy endpoint)
|
|
264
|
+
func (c *Client) Status(workflowID string) (map[string]interface{}, error) {
|
|
265
|
+
resp, err := c.doRequest("GET", "/api/agent/"+workflowID+"/status", nil)
|
|
266
|
+
if err != nil {
|
|
267
|
+
return nil, err
|
|
268
|
+
}
|
|
269
|
+
defer resp.Body.Close()
|
|
270
|
+
var result map[string]interface{}
|
|
271
|
+
if err := json.NewDecoder(resp.Body).Decode(&result); err != nil {
|
|
272
|
+
return nil, fmt.Errorf("decode response: %w", err)
|
|
273
|
+
}
|
|
274
|
+
return result, nil
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
// Respond sends a HITL response
|
|
278
|
+
func (c *Client) Respond(workflowID string, approved bool, reason, message string) error {
|
|
279
|
+
body := map[string]interface{}{
|
|
280
|
+
"approved": approved,
|
|
281
|
+
"reason": reason,
|
|
282
|
+
"message": message,
|
|
283
|
+
}
|
|
284
|
+
resp, err := c.doRequest("POST", "/api/agent/"+workflowID+"/respond", body)
|
|
285
|
+
if err != nil {
|
|
286
|
+
return err
|
|
287
|
+
}
|
|
288
|
+
resp.Body.Close()
|
|
289
|
+
return nil
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
// SSEEvent represents a server-sent event
|
|
293
|
+
type SSEEvent struct {
|
|
294
|
+
ID string
|
|
295
|
+
Event string
|
|
296
|
+
Data string
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
// Stream opens an SSE connection and sends events to the channel
|
|
300
|
+
func (c *Client) Stream(workflowID string, lastEventID string, events chan<- SSEEvent, done chan<- error) {
|
|
301
|
+
go func() {
|
|
302
|
+
defer close(events)
|
|
303
|
+
defer close(done)
|
|
304
|
+
|
|
305
|
+
streamClient := &http.Client{Timeout: 0} // no timeout for SSE
|
|
306
|
+
|
|
307
|
+
req, err := http.NewRequest("GET", c.baseURL+"/api/agent/stream/"+workflowID, nil)
|
|
308
|
+
if err != nil {
|
|
309
|
+
done <- err
|
|
310
|
+
return
|
|
311
|
+
}
|
|
312
|
+
req.Header.Set("Accept", "text/event-stream")
|
|
313
|
+
if lastEventID != "" {
|
|
314
|
+
req.Header.Set("Last-Event-ID", lastEventID)
|
|
315
|
+
}
|
|
316
|
+
if c.authKey != "" {
|
|
317
|
+
req.Header.Set("X-Auth-Key", c.authKey)
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
resp, err := streamClient.Do(req)
|
|
321
|
+
if err != nil {
|
|
322
|
+
done <- err
|
|
323
|
+
return
|
|
324
|
+
}
|
|
325
|
+
defer resp.Body.Close()
|
|
326
|
+
|
|
327
|
+
if resp.StatusCode >= 400 {
|
|
328
|
+
body, _ := io.ReadAll(resp.Body)
|
|
329
|
+
done <- fmt.Errorf("HTTP %d: %s", resp.StatusCode, string(body))
|
|
330
|
+
return
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
scanner := bufio.NewScanner(resp.Body)
|
|
334
|
+
scanner.Buffer(make([]byte, 0, 1024*1024), 1024*1024)
|
|
335
|
+
|
|
336
|
+
var current SSEEvent
|
|
337
|
+
for scanner.Scan() {
|
|
338
|
+
line := scanner.Text()
|
|
339
|
+
|
|
340
|
+
if line == "" {
|
|
341
|
+
// Empty line = end of event
|
|
342
|
+
if current.Data != "" || current.Event != "" {
|
|
343
|
+
events <- current
|
|
344
|
+
current = SSEEvent{}
|
|
345
|
+
}
|
|
346
|
+
continue
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
if strings.HasPrefix(line, ":") {
|
|
350
|
+
// Comment (heartbeat), skip
|
|
351
|
+
continue
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
if strings.HasPrefix(line, "id:") {
|
|
355
|
+
current.ID = strings.TrimSpace(strings.TrimPrefix(line, "id:"))
|
|
356
|
+
} else if strings.HasPrefix(line, "event:") {
|
|
357
|
+
current.Event = strings.TrimSpace(strings.TrimPrefix(line, "event:"))
|
|
358
|
+
} else if strings.HasPrefix(line, "data:") {
|
|
359
|
+
current.Data = strings.TrimSpace(strings.TrimPrefix(line, "data:"))
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
done <- scanner.Err()
|
|
364
|
+
}()
|
|
365
|
+
}
|