mirror of
https://github.com/hanzoai/agent.git
synced 2026-10-30 14:43:55 +00:00
hanzoai/agents carried the reconciled Go, TypeScript and Python SDKs: memory in Base with one record schema across all three, scope ids resolved the way the control plane resolves them, /v1 paths, one address per vector operation, agents that start under Express 5, and suites that run (Go green, 92 TypeScript, 527 Python). They come home here. The Go module is github.com/hanzoai/agent/sdk/go, because the path has to name the repository it is fetched from. The TypeScript manifest names its versions directly and keeps npm's lockfile; this repo is not a pnpm workspace. Versions are 0.2.10. Co-authored-by: Hanzo Dev <[email protected]>
130 lines
3.6 KiB
Go
130 lines
3.6 KiB
Go
package agent
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"encoding/json"
|
|
"fmt"
|
|
"net/http"
|
|
"strings"
|
|
"time"
|
|
)
|
|
|
|
// notePayload represents the JSON payload sent to the HanzoAgents server.
|
|
type notePayload struct {
|
|
Message string `json:"message"`
|
|
Tags []string `json:"tags"`
|
|
Timestamp float64 `json:"timestamp"`
|
|
AgentNodeID string `json:"agent_node_id"`
|
|
}
|
|
|
|
// Note sends a progress/status message to the HanzoAgents server.
|
|
// This is useful for debugging and tracking agent execution progress
|
|
// in the HanzoAgents UI.
|
|
//
|
|
// Notes are sent asynchronously (fire-and-forget) and will not block
|
|
// the handler or raise errors that interrupt the workflow.
|
|
//
|
|
// Example usage:
|
|
//
|
|
// agent.Note(ctx, "Starting data processing", "debug", "processing")
|
|
// // ... do work ...
|
|
// agent.Note(ctx, "Processing completed", "info")
|
|
func (a *Agent) Note(ctx context.Context, message string, tags ...string) {
|
|
if tags == nil {
|
|
tags = []string{}
|
|
}
|
|
|
|
// Fire-and-forget: send note in a goroutine
|
|
go a.sendNote(ctx, message, tags)
|
|
}
|
|
|
|
// Notef sends a formatted progress/status message to the HanzoAgents server.
|
|
// This is a convenience method that formats the message using fmt.Sprintf.
|
|
//
|
|
// Example usage:
|
|
//
|
|
// agent.Notef(ctx, "Processing %d items...", len(items))
|
|
func (a *Agent) Notef(ctx context.Context, format string, args ...any) {
|
|
a.Note(ctx, fmt.Sprintf(format, args...))
|
|
}
|
|
|
|
// sendNote performs the actual HTTP request to send the note.
|
|
func (a *Agent) sendNote(ctx context.Context, message string, tags []string) {
|
|
// Check if HanzoAgents URL is configured
|
|
baseURL := strings.TrimSpace(a.cfg.HanzoAgentsURL)
|
|
if baseURL == "" {
|
|
// No server configured, silently skip
|
|
return
|
|
}
|
|
|
|
// Get execution context from the provided context
|
|
execCtx := ExecutionContextFrom(ctx)
|
|
|
|
// Notes go to the /ui subtree of the version prefix. A base that already
|
|
// carries its version loses it before one is appended.
|
|
noteURL := strings.TrimSuffix(strings.TrimSuffix(baseURL, "/"), "/v1") + "/v1/ui/executions/note"
|
|
|
|
// Build payload
|
|
payload := notePayload{
|
|
Message: message,
|
|
Tags: tags,
|
|
Timestamp: float64(time.Now().UnixNano()) / 1e9, // Unix timestamp as float
|
|
AgentNodeID: a.cfg.NodeID,
|
|
}
|
|
|
|
body, err := json.Marshal(payload)
|
|
if err != nil {
|
|
a.logger.Printf("note: failed to marshal payload: %v", err)
|
|
return
|
|
}
|
|
|
|
// Build request with execution context headers
|
|
reqCtx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
|
defer cancel()
|
|
|
|
req, err := http.NewRequestWithContext(reqCtx, http.MethodPost, noteURL, bytes.NewReader(body))
|
|
if err != nil {
|
|
a.logger.Printf("note: failed to create request: %v", err)
|
|
return
|
|
}
|
|
|
|
// Set headers
|
|
req.Header.Set("Content-Type", "application/json")
|
|
if a.cfg.Token != "" {
|
|
req.Header.Set("Authorization", "Bearer "+a.cfg.Token)
|
|
}
|
|
|
|
// Add execution context headers
|
|
if execCtx.RunID != "" {
|
|
req.Header.Set("X-Run-ID", execCtx.RunID)
|
|
}
|
|
if execCtx.ExecutionID != "" {
|
|
req.Header.Set("X-Execution-ID", execCtx.ExecutionID)
|
|
}
|
|
if execCtx.SessionID != "" {
|
|
req.Header.Set("X-Session-ID", execCtx.SessionID)
|
|
}
|
|
if execCtx.ActorID != "" {
|
|
req.Header.Set("X-Actor-ID", execCtx.ActorID)
|
|
}
|
|
if execCtx.WorkflowID != "" {
|
|
req.Header.Set("X-Workflow-ID", execCtx.WorkflowID)
|
|
}
|
|
req.Header.Set("X-Agent-Node-ID", a.cfg.NodeID)
|
|
|
|
// Send request
|
|
resp, err := a.httpClient.Do(req)
|
|
if err != nil {
|
|
// Silently fail - notes should not interrupt workflow
|
|
return
|
|
}
|
|
defer resp.Body.Close()
|
|
|
|
// We don't care about the response for fire-and-forget notes
|
|
// but we could log errors for debugging
|
|
if resp.StatusCode >= 400 {
|
|
a.logger.Printf("note: server returned status %d", resp.StatusCode)
|
|
}
|
|
}
|