This commit is contained in:
@@ -0,0 +1,116 @@
|
||||
package providermemory
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"data-terminal-cloud/internal/outbox"
|
||||
)
|
||||
|
||||
type Memory struct {
|
||||
TenantID string `json:"tenant_id"`
|
||||
HouseholdID string `json:"household_id"`
|
||||
SubjectID string `json:"subject_id"`
|
||||
MemoryID string `json:"memory_id"`
|
||||
MemoryVersion string `json:"memory_version_id"`
|
||||
VersionNo int64 `json:"version_no"`
|
||||
Content string `json:"content"`
|
||||
ContentDigest string `json:"content_digest"`
|
||||
IdempotencyKey string `json:"idempotency_key"`
|
||||
}
|
||||
|
||||
type Client interface {
|
||||
Upsert(context.Context, Memory) (string, error)
|
||||
Delete(context.Context, string, string) error
|
||||
}
|
||||
|
||||
type HTTPClient struct {
|
||||
baseURL string
|
||||
apiKey string
|
||||
client *http.Client
|
||||
}
|
||||
|
||||
func NewHTTPClient(baseURL, apiKey string, timeout time.Duration) (*HTTPClient, error) {
|
||||
parsed, err := url.Parse(strings.TrimRight(strings.TrimSpace(baseURL), "/"))
|
||||
if err != nil || parsed.Scheme == "" || parsed.Host == "" || (parsed.Scheme != "http" && parsed.Scheme != "https") {
|
||||
return nil, errors.New("provider memory base URL must be an absolute HTTP(S) URL")
|
||||
}
|
||||
if strings.TrimSpace(apiKey) == "" || timeout <= 0 {
|
||||
return nil, errors.New("provider memory API key and positive timeout are required")
|
||||
}
|
||||
return &HTTPClient{baseURL: parsed.String(), apiKey: strings.TrimSpace(apiKey), client: &http.Client{Timeout: timeout}}, nil
|
||||
}
|
||||
|
||||
func (c *HTTPClient) Upsert(ctx context.Context, memory Memory) (string, error) {
|
||||
payload, err := json.Marshal(memory)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
request, err := http.NewRequestWithContext(ctx, http.MethodPost, c.baseURL+"/v1/memories:upsert", bytes.NewReader(payload))
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
request.Header.Set("Authorization", "Bearer "+c.apiKey)
|
||||
request.Header.Set("Content-Type", "application/json")
|
||||
request.Header.Set("Idempotency-Key", memory.IdempotencyKey)
|
||||
response, err := c.client.Do(request)
|
||||
if err != nil {
|
||||
return "", outbox.Retryable(fmt.Errorf("call provider memory upsert: %w", err))
|
||||
}
|
||||
defer response.Body.Close()
|
||||
if response.StatusCode < 200 || response.StatusCode >= 300 {
|
||||
return "", providerError("upsert", response)
|
||||
}
|
||||
var result struct {
|
||||
ProviderRef string `json:"provider_ref"`
|
||||
}
|
||||
if err := json.NewDecoder(io.LimitReader(response.Body, 64*1024)).Decode(&result); err != nil {
|
||||
return "", outbox.Retryable(errors.New("provider memory upsert returned invalid JSON"))
|
||||
}
|
||||
result.ProviderRef = strings.TrimSpace(result.ProviderRef)
|
||||
if result.ProviderRef == "" || len(result.ProviderRef) > 300 {
|
||||
return "", outbox.Retryable(errors.New("provider memory upsert returned an invalid reference"))
|
||||
}
|
||||
return result.ProviderRef, nil
|
||||
}
|
||||
|
||||
func (c *HTTPClient) Delete(ctx context.Context, providerRef, idempotencyKey string) error {
|
||||
providerRef = strings.TrimSpace(providerRef)
|
||||
if providerRef == "" {
|
||||
return nil
|
||||
}
|
||||
request, err := http.NewRequestWithContext(ctx, http.MethodDelete, c.baseURL+"/v1/memories/"+url.PathEscape(providerRef), nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
request.Header.Set("Authorization", "Bearer "+c.apiKey)
|
||||
request.Header.Set("Idempotency-Key", idempotencyKey)
|
||||
response, err := c.client.Do(request)
|
||||
if err != nil {
|
||||
return outbox.Retryable(fmt.Errorf("call provider memory delete: %w", err))
|
||||
}
|
||||
defer response.Body.Close()
|
||||
if response.StatusCode == http.StatusNotFound || response.StatusCode == http.StatusGone {
|
||||
return nil
|
||||
}
|
||||
if response.StatusCode < 200 || response.StatusCode >= 300 {
|
||||
return providerError("delete", response)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func providerError(action string, response *http.Response) error {
|
||||
err := fmt.Errorf("provider memory %s returned HTTP %d", action, response.StatusCode)
|
||||
if response.StatusCode == http.StatusTooManyRequests || response.StatusCode >= 500 {
|
||||
return outbox.Retryable(err)
|
||||
}
|
||||
return err
|
||||
}
|
||||
Reference in New Issue
Block a user