mirror of
https://github.com/hanzoai/team-go.git
synced 2026-10-30 20:22:42 +00:00
The Go transactor and account service for Hanzo Team. History starts here. The previous 107 commits carried the fork's origin in their messages and their tree, and none of it described this code: the Go under pkg/transactor is Hanzo's own work implementing a wire protocol, not a port of anyone's implementation. pkg/transactor/model.json remains, and remains attributed. It is a compiled platform model this transactor loads through go:embed, governed by EPL-2.0 and named in NOTICE. Removing it is a replacement project -- a new compiler and a move off the protocol -- not a deletion.
98 lines
3.2 KiB
Go
98 lines
3.2 KiB
Go
package bots
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"fmt"
|
|
"io"
|
|
"net/http"
|
|
"strings"
|
|
"time"
|
|
)
|
|
|
|
// cloudAgent is the subset of the cloud /v1/agents view we consume. The cloud
|
|
// registry (~/work/hanzo/cloud/clients/agents) is the canonical agent model;
|
|
// a bot is an agent bound to a workspace identity + membership. We fold cloud
|
|
// agents into the same ServiceAccount domain shape so reconcile treats IAM SAs
|
|
// and cloud agents uniformly (one reconcile, one way).
|
|
type cloudAgent struct {
|
|
ID string `json:"id"`
|
|
Name string `json:"name"`
|
|
Model string `json:"model"`
|
|
Status string `json:"status"`
|
|
}
|
|
|
|
type cloudAgentList struct {
|
|
Agents []cloudAgent `json:"agents"`
|
|
}
|
|
|
|
// agentIDPrefix namespaces cloud-agent ids in the members table so they never
|
|
// collide with raw IAM service-account ids. It is also the discriminator the
|
|
// reconcile uses to keep the two sources' removal logic in their own lanes.
|
|
const agentIDPrefix = "agent:"
|
|
|
|
// isAgentID reports whether a service_account_id originated from cloud agents.
|
|
func isAgentID(id string) bool { return strings.HasPrefix(id, agentIDPrefix) }
|
|
|
|
// agentsClient reads the cloud agent registry over its /v1/agents surface,
|
|
// forwarding the caller's identity so the registry scopes to the caller's org.
|
|
type agentsClient struct {
|
|
base string
|
|
client *http.Client
|
|
}
|
|
|
|
func newAgentsClient(base string) *agentsClient {
|
|
base = strings.TrimRight(base, "/")
|
|
if base == "" {
|
|
return nil
|
|
}
|
|
return &agentsClient{base: base, client: &http.Client{Timeout: 15 * time.Second}}
|
|
}
|
|
|
|
// list fetches the caller's org agents. Identity is carried by the same headers
|
|
// the cloud gateway trusts (X-User-Id / X-Org-Id) plus the forwarded bearer.
|
|
// Returns them as disabled=false ServiceAccounts keyed by a distinct id space
|
|
// ("agent:<id>") so they never collide with IAM SA ids.
|
|
func (c *agentsClient) list(ctx context.Context, org, userID, bearer string) ([]ServiceAccount, error) {
|
|
req, err := http.NewRequestWithContext(ctx, http.MethodGet, c.base+"/v1/agents", nil)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("bots: build agents request: %w", err)
|
|
}
|
|
req.Header.Set("Accept", "application/json")
|
|
if bearer != "" {
|
|
req.Header.Set("Authorization", "Bearer "+bearer)
|
|
}
|
|
if userID != "" {
|
|
req.Header.Set("X-User-Id", userID)
|
|
}
|
|
if org != "" {
|
|
req.Header.Set("X-Org-Id", org)
|
|
}
|
|
resp, err := c.client.Do(req)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("bots: agents request: %w", err)
|
|
}
|
|
defer resp.Body.Close()
|
|
body, _ := io.ReadAll(io.LimitReader(resp.Body, 4<<20))
|
|
if resp.StatusCode != http.StatusOK {
|
|
return nil, fmt.Errorf("bots: agents status %d: %s", resp.StatusCode, strings.TrimSpace(string(body)))
|
|
}
|
|
var out cloudAgentList
|
|
if err := json.Unmarshal(body, &out); err != nil {
|
|
return nil, fmt.Errorf("bots: agents decode: %w", err)
|
|
}
|
|
sas := make([]ServiceAccount, 0, len(out.Agents))
|
|
for _, a := range out.Agents {
|
|
sas = append(sas, ServiceAccount{
|
|
ID: agentIDPrefix + a.ID,
|
|
Name: a.Name,
|
|
Organization: org,
|
|
DisplayName: a.Name,
|
|
AgentModel: a.Model,
|
|
// A retired/archived agent is disabled → reconcile removes its member.
|
|
Disabled: a.Status != "" && a.Status != "active" && a.Status != "ready",
|
|
})
|
|
}
|
|
return sas, nil
|
|
}
|