Files

88 lines
2.4 KiB
Go
Raw Permalink Normal View History

package dify
import (
"bytes"
"context"
"encoding/json"
"errors"
"io"
"net/http"
"strconv"
"strings"
"time"
)
// Only the backend has the bridge credential. Dify receives a short-lived step
// token, and records counts and IDs rather than elder speech or image bytes.
type Bridge struct {
BaseURL, Secret string
HTTP *http.Client
}
type Ticket struct {
ID string `json:"context_id"`
Token string `json:"step_token"`
}
func NewBridge(base, secret string) *Bridge {
return &Bridge{strings.TrimRight(base, "/"), secret, &http.Client{Timeout: 30 * time.Second}}
}
func (b *Bridge) request(ctx context.Context, method, path string, payload any, out any) error {
var body io.Reader
if payload != nil {
raw, _ := json.Marshal(payload)
body = bytes.NewReader(raw)
}
req, err := http.NewRequestWithContext(ctx, method, b.BaseURL+path, body)
if err != nil {
return err
}
req.Header.Set("Authorization", "Bearer "+b.Secret)
req.Header.Set("Content-Type", "application/json")
resp, err := b.HTTP.Do(req)
if err != nil {
return ErrUnavailable
}
defer resp.Body.Close()
if resp.StatusCode != 200 {
return ErrUnavailable
}
if out != nil {
return json.NewDecoder(io.LimitReader(resp.Body, 4<<20)).Decode(out)
}
return nil
}
func (b *Bridge) Register(ctx context.Context, kind string, input any) (Ticket, error) {
var t Ticket
err := b.request(ctx, "POST", "/contexts", map[string]any{"kind": kind, "input": input}, &t)
return t, err
}
func (b *Bridge) Result(ctx context.Context, t Ticket, out any) error {
return b.request(ctx, "GET", "/contexts/"+t.ID, nil, out)
}
func (b *Bridge) Forget(t Ticket) {
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
_ = b.request(ctx, "DELETE", "/contexts/"+t.ID, nil, nil)
}
func (b *Bridge) Image(ctx context.Context, t Ticket, index int) ([]byte, error) {
path := b.BaseURL + "/contexts/" + t.ID + "/images/" + strconv.Itoa(index)
req, _ := http.NewRequestWithContext(ctx, "GET", path, nil)
req.Header.Set("Authorization", "Bearer "+b.Secret)
resp, err := b.HTTP.Do(req)
if err != nil {
return nil, err
}
defer resp.Body.Close()
if resp.StatusCode == 404 {
return nil, nil
}
if resp.StatusCode != 200 {
return nil, ErrUnavailable
}
raw, err := io.ReadAll(io.LimitReader(resp.Body, (10<<20)+1))
if err != nil || len(raw) > 10<<20 || http.DetectContentType(raw) != "image/jpeg" {
return nil, errors.New("invalid workflow illustration")
}
return raw, nil
}