package main import ( "bytes" "encoding/json" "fmt" "io" "net/http" "sync" "time" ) // OpenClaw HTTP API client — uses the OpenAI-compatible /v1/chat/completions endpoint type GatewayConfig struct { URL string // http://host:port Token string } type GatewayPool struct { mu sync.Mutex gateways map[string]*GatewayConfig client *http.Client } func NewGatewayPool() *GatewayPool { return &GatewayPool{ gateways: make(map[string]*GatewayConfig), client: &http.Client{Timeout: 120 * time.Second}, } } func (gp *GatewayPool) Register(host string, cfg *GatewayConfig) { gp.mu.Lock() defer gp.mu.Unlock() gp.gateways[host] = cfg } func (gp *GatewayPool) Get(host string) *GatewayConfig { gp.mu.Lock() defer gp.mu.Unlock() return gp.gateways[host] } // CallAgent sends a message (optionally with an image) to an agent via the OpenAI-compatible API. // imageDataURL, if non-empty, should be a data URL: "data:image/png;base64,..." func (gp *GatewayPool) CallAgent(host, agentID, message, session, imageDataURL string) (string, error) { cfg := gp.Get(host) if cfg == nil { return "", fmt.Errorf("no gateway configured for host %s", host) } // Build the message content — text only, or text + image var content any if imageDataURL != "" { content = []map[string]any{ {"type": "text", "text": message}, {"type": "image_url", "image_url": map[string]string{"url": imageDataURL}}, } } else { content = message } body, _ := json.Marshal(map[string]any{ "model": "openclaw:" + agentID, "user": session, "messages": []map[string]any{ {"role": "user", "content": content}, }, }) req, err := http.NewRequest("POST", cfg.URL+"/v1/chat/completions", bytes.NewReader(body)) if err != nil { return "", err } req.Header.Set("Content-Type", "application/json") req.Header.Set("Authorization", "Bearer "+cfg.Token) resp, err := gp.client.Do(req) if err != nil { return "", fmt.Errorf("http: %w", err) } defer resp.Body.Close() data, err := io.ReadAll(resp.Body) if err != nil { return "", fmt.Errorf("read body: %w", err) } if resp.StatusCode != 200 { return "", fmt.Errorf("http %d: %s", resp.StatusCode, string(data)) } var result struct { Choices []struct { Message struct { Content string `json:"content"` } `json:"message"` } `json:"choices"` } if err := json.Unmarshal(data, &result); err != nil { return "", fmt.Errorf("parse: %w", err) } if len(result.Choices) == 0 || result.Choices[0].Message.Content == "" { return "[completed, no text reply]", nil } return result.Choices[0].Message.Content, nil }