1package typesafe23import (4 "context"5 "encoding/json/v2"6 "fmt"7 "net/http"8)910// Client calls the TypeSafe hosted API. A client can be shared by concurrent11// goroutines. Caller-owned request values and custom transports must also be12// safe for their usage; do not mutate a request while it is being marshaled.13// Construct clients with NewClient; the zero value is not ready for use.14type Client struct {15 config clientConfig16}1718// NewClient constructs a client using environment variables and options.19// Explicit options take precedence. An API key is required. Configuration is20// validated locally; construction does not make any network requests.21func NewClient(options ...Option) (*Client, error) {22 config := defaultConfig()23 for _, option := range options {24 if option == nil {25 return nil, fmt.Errorf("typesafe: option must not be nil")26 }27 if err := option(&config); err != nil {28 return nil, err29 }30 }31 if err := config.validate(); err != nil {32 return nil, err33 }34 client := *config.httpClient35 client.CheckRedirect = func(*http.Request, []*http.Request) error {36 return http.ErrUseLastResponse37 }38 config.httpClient = &client39 return &Client{config: config}, nil40}4142// SystemOne evaluates named questions against a shared state. Questions are43// validated before sending. A blank model uses the client's configured model.44// The context bounds the entire operation, including retries and backoff.45func (c *Client) SystemOne(ctx context.Context, request SystemOneRequest) (*SystemOneResponse, error) {46 if err := c.ready(ctx); err != nil {47 return nil, err48 }49 if request.Model == "" {50 request.Model = c.config.model51 }52 if err := request.Validate(); err != nil {53 return nil, fmt.Errorf("typesafe: invalid request: %w", err)54 }55 body, err := json.Marshal(request)56 if err != nil {57 return nil, fmt.Errorf("typesafe: encode request: %w", err)58 }59 wire, err := c.send(ctx, http.MethodPost, "/v1/systemone", body)60 if err != nil {61 return nil, err62 }63 var result SystemOneResponse64 if err := json.Unmarshal(wire.body, &result); err != nil {65 return nil, &ResponseError{RequestID: wire.requestID(), Err: err}66 }67 result.RequestID = wire.requestID()68 return &result, nil69}7071// ListModels lists models available to the authenticated account.72// An empty Models slice is a valid response.73func (c *Client) ListModels(ctx context.Context) (*ListModelsResponse, error) {74 if err := c.ready(ctx); err != nil {75 return nil, err76 }77 wire, err := c.send(ctx, http.MethodGet, "/v1/models", nil)78 if err != nil {79 return nil, err80 }81 var result ListModelsResponse82 if err := json.Unmarshal(wire.body, &result); err != nil {83 return nil, &ResponseError{RequestID: wire.requestID(), Err: err}84 }85 if result.Models == nil {86 return nil, &ResponseError{RequestID: wire.requestID(), Err: fmt.Errorf("missing models array")}87 }88 result.RequestID = wire.requestID()89 return &result, nil90}9192func (c *Client) ready(ctx context.Context) error {93 if c == nil || c.config.httpClient == nil {94 return fmt.Errorf("typesafe: construct the client with NewClient")95 }96 if ctx == nil {97 return fmt.Errorf("typesafe: context must not be nil")98 }99 return ctx.Err()100}