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.
122 lines
3.1 KiB
Go
122 lines
3.1 KiB
Go
// Package bot wires hanzo.bot (the in-app AI agent) into team-go.
|
|
//
|
|
// hanzo.bot is the unified Hanzo Bot framework (hanzoai/bot v1.0.0
|
|
// wrapper, runtimes in hanzobot/ts, hanzobot/go, etc.). This package
|
|
// exposes a single SSE endpoint that team's Svelte UI can talk to:
|
|
//
|
|
// POST /v1/bot/chat { messages: [...] } → SSE stream
|
|
// GET /v1/bot/skills → skill list (cached)
|
|
//
|
|
// Skills + auth + rate limits live inside hanzo.bot. team-go just
|
|
// forwards requests with the JWT-validated identity so the bot can
|
|
// scope tool access per user/org.
|
|
package bot
|
|
|
|
import (
|
|
"net/http"
|
|
"net/url"
|
|
"os"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/hanzoai/base/core"
|
|
"github.com/hanzoai/base/tools/hook"
|
|
)
|
|
|
|
const (
|
|
defaultEndpoint = "https://hanzo.bot"
|
|
envEndpoint = "BOT_ENDPOINT"
|
|
)
|
|
|
|
// Register binds /v1/bot/* onto app.
|
|
func Register(app core.App) {
|
|
endpoint, err := url.Parse(botEndpoint())
|
|
if err != nil {
|
|
app.Logger().Error("bot: invalid BOT_ENDPOINT", "err", err)
|
|
return
|
|
}
|
|
|
|
// SSE-friendly client: no global timeout; per-request stream.
|
|
client := &http.Client{}
|
|
|
|
app.OnServe().Bind(&hook.Handler[*core.ServeEvent]{
|
|
Func: func(e *core.ServeEvent) error {
|
|
e.Router.Any("/v1/bot/{path...}", func(re *core.RequestEvent) error {
|
|
return proxy(re, endpoint, client)
|
|
})
|
|
return e.Next()
|
|
},
|
|
})
|
|
}
|
|
|
|
func botEndpoint() string {
|
|
if v := os.Getenv(envEndpoint); v != "" {
|
|
return v
|
|
}
|
|
return defaultEndpoint
|
|
}
|
|
|
|
func proxy(re *core.RequestEvent, endpoint *url.URL, client *http.Client) error {
|
|
if re.Auth == nil {
|
|
return re.UnauthorizedError("bot requires auth", nil)
|
|
}
|
|
|
|
upstream := *endpoint
|
|
upstream.Path = strings.TrimRight(endpoint.Path, "/") + re.Request.URL.Path
|
|
upstream.RawQuery = re.Request.URL.RawQuery
|
|
|
|
req, err := http.NewRequestWithContext(re.Request.Context(),
|
|
re.Request.Method, upstream.String(), re.Request.Body)
|
|
if err != nil {
|
|
return re.InternalServerError("bot proxy build failed", err)
|
|
}
|
|
for k, v := range re.Request.Header {
|
|
if k == "X-User-Id" || k == "X-Org-Id" || k == "X-User-Email" {
|
|
continue
|
|
}
|
|
req.Header[k] = v
|
|
}
|
|
req.Header.Set("X-User-Id", re.Auth.Id)
|
|
req.Header.Set("X-User-Email", re.Auth.Email())
|
|
if owner := re.Auth.GetString("owner"); owner != "" {
|
|
req.Header.Set("X-Org-Id", owner)
|
|
}
|
|
// Force chunked transfer + disable proxy buffering so SSE streams
|
|
// reach the browser as they come off hanzo.bot.
|
|
req.Header.Set("X-Accel-Buffering", "no")
|
|
|
|
// Override client timeout for SSE-style streams.
|
|
if strings.Contains(req.URL.Path, "/chat") || strings.Contains(req.URL.Path, "/stream") {
|
|
client.Timeout = 0
|
|
} else {
|
|
client.Timeout = 30 * time.Second
|
|
}
|
|
|
|
resp, err := client.Do(req)
|
|
if err != nil {
|
|
return re.InternalServerError("bot unreachable", err)
|
|
}
|
|
defer resp.Body.Close()
|
|
|
|
for k, v := range resp.Header {
|
|
re.Response.Header()[k] = v
|
|
}
|
|
re.Response.WriteHeader(resp.StatusCode)
|
|
|
|
flusher, _ := re.Response.(http.Flusher)
|
|
buf := make([]byte, 4096)
|
|
for {
|
|
n, err := resp.Body.Read(buf)
|
|
if n > 0 {
|
|
_, _ = re.Response.Write(buf[:n])
|
|
if flusher != nil {
|
|
flusher.Flush()
|
|
}
|
|
}
|
|
if err != nil {
|
|
break
|
|
}
|
|
}
|
|
return nil
|
|
}
|