typesafe

package module
v0.2.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Sep 27, 2026 License: MIT Imports: 17 Imported by: 0

README

typesafe-go

An unofficial, community-maintained Go SDK for the hosted TypeSafe API. It is an independent project by FelineStateMachine, with no official affiliation with or endorsement from TypeSafe. The SDK sends state and questions to the hosted service; it does not run inference locally.

The module is pure Go and uses only the standard library.

go get github.com/FelineStateMachine/typesafe-go@v0.2.0

Documentation: pkg.go.dev/github.com/FelineStateMachine/typesafe-go

Quickstart

Set TYPESAFE_API_KEY and create a client. NewClient also accepts WithAPIKey when applications prefer explicit configuration.

package main

import (
	"context"
	"fmt"
	"log"

	"github.com/FelineStateMachine/typesafe-go"
)

func main() {
	client, err := typesafe.NewClient()
	if err != nil {
		log.Fatal(err)
	}

	result, err := client.SystemOne(context.Background(), typesafe.SystemOneRequest{
		State: map[string]any{"text": "The package has tests and documentation."},
		Questions: map[string]typesafe.Question{
			"is_ready": typesafe.NoulQuestion{
				Instructions: "Is this ready to publish?",
				Criteria: &typesafe.NoulCriteria{
					True:  "The package is ready for users.",
					False: "The package still needs substantial work.",
				},
			},
			"release_channel": typesafe.ChoiceQuestion{
				Instructions: "Which release channel fits?",
				Criteria: map[string]any{
					"stable": "Ready for a stable release.",
					"beta":   "Useful, but still needs real-world feedback.",
				},
			},
			"readiness": typesafe.ScoreQuestion{
				Instructions: "Score the release readiness.",
				Criteria: []any{
					"Not ready",
					"Needs review",
					"Ready",
				},
			},
		},
	})
	if err != nil {
		log.Fatal(err)
	}

	isReady, err := result.Noul("is_ready")
	if err != nil {
		log.Fatal(err)
	}
	channel, err := result.Choice("release_channel")
	if err != nil {
		log.Fatal(err)
	}
	readiness, err := result.Score("readiness")
	if err != nil {
		log.Fatal(err)
	}
	fmt.Printf("ready=%v channel=%s score=%.1f\n", isReady.Noul >= 0.5, channel.Choice, readiness.Score)
}

The accessors check the answer's wire type and return an error when a question has a different type, is missing, or contains malformed known fields. Unknown answer types are retained as UnknownAnswer so a newer service response is not silently discarded.

NoulAnswer.Noul is the service's yes probability. ChoiceAnswer.Confidence and ScoreAnswer.Confidence describe confidence in the selected result; they are not probabilities for the answer's truth. Treat all model output as uncertain application data and choose thresholds appropriate to your use case.

Models

models, err := client.ListModels(context.Background())
if err != nil {
	// Handle *typesafe.APIError or *typesafe.ResponseError as appropriate.
	log.Fatal(err)
}
for _, model := range models.Models {
	fmt.Println(model.Name, model.Description)
}

Requests use jev-latest by default. Select another model with typesafe.WithModel("model-name"), or set TYPESAFE_DEFAULT_MODEL. A request's SystemOneRequest.Model takes precedence for that request.

Configuration

NewClient reads these environment variables when the corresponding option is not supplied:

Setting Option Environment Default
API key WithAPIKey TYPESAFE_API_KEY required
Base URL WithBaseURL TYPESAFE_BASE_URL https://api.typesafe.ai
Model WithModel TYPESAFE_DEFAULT_MODEL jev-latest
HTTP client WithHTTPClient — fresh http.Client
Attempt timeout WithTimeout — 10 seconds
Retries WithMaxRetries or WithRetryPolicy — 2 retries

Whitespace-only environment values are ignored. Explicit options take precedence over environment values. WithHeaders adds headers to requests; the SDK sets authentication and content headers itself.

Errors, context, and retries

Every request accepts a context.Context. Cancellation and an expired caller deadline stop the request and are returned through the wrapped error chain. WithTimeout limits each attempt; use the context for an overall deadline.

The default policy retries connection errors, timeouts, HTTP 408, HTTP 429, and HTTP 5xx responses. It uses exponential backoff with jitter and honors retry-after-ms and Retry-After up to the policy's maximum. Set a custom RetryPolicy when the defaults do not fit, or WithMaxRetries(0) to disable retries. Caller cancellation is never retried. Redirects are rejected to avoid replaying API keys or state to another endpoint. When WithHTTPClient is used, the client configuration is copied before the SDK installs its redirect policy; the caller's *http.Client is never mutated.

SystemOne validates requests locally before sending them, matching the hosted service's limits: State must not be null, a Noul needs instructions or a non-null criterion, a Choice has 2 to MaxChoiceOptions (255) labels, and a Score has 2 to MaxScoreLevels (10) non-null levels. Call SystemOneRequest.Validate to check a request without sending it.

HTTP failures return *APIError, which exposes status, request ID, headers, method, URL, and response bytes without placing the body in its Error() string. Response decoding validates required and known answer fields and returns *ResponseError when it fails; the error retains the request ID when one was supplied. The typed accessors then check only whether an answer exists and has the requested type. Use errors.Is for context errors and errors.AsType for typed errors:

apiErr, ok := errors.AsType[*typesafe.APIError](err)
if ok {
	fmt.Println(apiErr.StatusCode, apiErr.RequestID)
}

Responses larger than 16 MiB are rejected before decoding. This bounds memory use when an endpoint returns an unexpectedly large payload.

Compatibility and scope

This release targets Go 1.27 and later, supports Go modules, and has no third-party dependencies. The public surface covers the documented SystemOne and models operations, typed Noul, Choice, and Score questions, and forward-compatible handling of unknown answer types. The API contract was checked against the TypeSafe API documentation; the documentation is a reference for wire compatibility and does not imply that this repository is an official TypeSafe SDK.

Go 1.27's encoding/json/v2 is used for strict response handling: duplicate object members and invalid UTF-8 are rejected, while unknown answer payloads are preserved as jsontext.Value. In addition to the named convenience methods, a response can be read with a generic accessor:

answer, err := result.Answer[typesafe.ChoiceAnswer]("release_channel")

Development

The default test suite is offline. Run the same checks used by CI with:

gofmt -w .
go test ./...
go test -race ./...
go vet ./...

The live API check makes billable requests and is explicitly gated. Run it only with a configured API key:

TYPESAFE_LIVE_TEST=1 go test -run TestLiveAPI -v

See CONTRIBUTING.md, CHANGELOG.md, and the TypeSafe API documentation for more context.

License

MIT. See LICENSE.

Documentation

Overview

Package typesafe provides a small, dependency-free Go client for the hosted TypeSafe API.

This is an unofficial community SDK maintained by FelineStateMachine. It sends state and questions to TypeSafe's hosted service; inference is performed by that service. See Client and SystemOneRequest for the main entry points.

Index

Examples

Constants

View Source
const (
	// MaxChoiceOptions is the maximum number of labels in a ChoiceQuestion.
	MaxChoiceOptions = 255
	// MaxScoreLevels is the maximum number of levels in a ScoreQuestion.
	MaxScoreLevels = 10
)

API limits enforced by the hosted service.

View Source
const DefaultBaseURL = "https://api.typesafe.ai"

DefaultBaseURL is the hosted TypeSafe API root.

View Source
const DefaultModel = "jev-latest"

DefaultModel is used when neither the request nor the client specifies a model.

View Source
const Version = "0.2.0"

Version is the SDK version sent in diagnostic request headers.

Variables

View Source
var ErrAnswerNotFound = errors.New("answer not found")

ErrAnswerNotFound indicates that a named answer is absent.

View Source
var ErrAnswerType = errors.New("answer has unexpected type")

ErrAnswerType indicates that an answer exists but has another type.

Functions

This section is empty.

Types

type APIError

type APIError struct {
	StatusCode int
	RequestID  string
	Header     http.Header
	Body       []byte
	Method     string
	URL        string
	Message    string
}

APIError describes a non-2xx HTTP response. Use errors.AsType to inspect it. Body and Header retain the server response and may contain sensitive data. Error deliberately excludes the body, credentials, and full URL.

func (*APIError) Error

func (e *APIError) Error() string

Error returns an HTTP status summary without exposing response body contents.

type Answer

type Answer interface {
	jsonv2.Marshaler
	// contains filtered or unexported methods
}

Answer is a discriminated answer returned by System One.

func UnmarshalAnswer

func UnmarshalAnswer(data []byte) (Answer, error)

UnmarshalAnswer decodes a discriminated answer and preserves unknown types.

type ChoiceAnswer

type ChoiceAnswer struct {
	Choice        string             `json:"choice"`
	Confidence    float64            `json:"confidence"`
	Probabilities map[string]float64 `json:"probabilities"`
}

ChoiceAnswer contains the selected label, confidence, and label probabilities.

func (ChoiceAnswer) MarshalJSON

func (a ChoiceAnswer) MarshalJSON() ([]byte, error)

MarshalJSON encodes a ChoiceAnswer with its discriminator.

type ChoiceQuestion

type ChoiceQuestion struct {
	Instructions any            `json:"instructions"`
	Criteria     map[string]any `json:"criteria"`
}

ChoiceQuestion selects one label from Criteria, which must contain between 2 and MaxChoiceOptions labels. A label's description may be nil.

func (ChoiceQuestion) MarshalJSON

func (q ChoiceQuestion) MarshalJSON() ([]byte, error)

MarshalJSON encodes a ChoiceQuestion with its discriminator.

type Client

type Client struct {
	// contains filtered or unexported fields
}

Client calls the TypeSafe hosted API. A client can be shared by concurrent goroutines. Caller-owned request values and custom transports must also be safe for their usage; do not mutate a request while it is being marshaled. Construct clients with NewClient; the zero value is not ready for use.

func NewClient

func NewClient(options ...Option) (*Client, error)

NewClient constructs a client using environment variables and options. Explicit options take precedence. An API key is required. Configuration is validated locally; construction does not make any network requests.

func (*Client) ListModels

func (c *Client) ListModels(ctx context.Context) (*ListModelsResponse, error)

ListModels lists models available to the authenticated account. An empty Models slice is a valid response.

func (*Client) SystemOne

func (c *Client) SystemOne(ctx context.Context, request SystemOneRequest) (*SystemOneResponse, error)

SystemOne evaluates named questions against a shared state. Questions are validated before sending. A blank model uses the client's configured model. The context bounds the entire operation, including retries and backoff.

Example
package main

import (
	"context"
	json "encoding/json/v2"
	"fmt"
	"net/http"
	"net/http/httptest"

	"github.com/FelineStateMachine/typesafe-go"
)

func main() {
	server := httptest.NewServer(http.HandlerFunc(func(writer http.ResponseWriter, request *http.Request) {
		writer.Header().Set("Content-Type", "application/json")
		if err := json.MarshalWrite(writer, map[string]any{
			"model": "jev-latest",
			"answers": map[string]any{
				"ready": map[string]any{"type": "noul", "noul": 0.9},
				"kind":  map[string]any{"type": "choice", "choice": "stable", "confidence": 0.8, "probabilities": map[string]float64{"stable": 0.8, "beta": 0.2}},
				"score": map[string]any{"type": "score", "score": 1.6, "confidence": 0.7, "legend": map[string]any{"0": "not ready", "1": "review", "2": "ready"}, "probabilities": map[string]float64{"0": 0.1, "1": 0.2, "2": 0.7}},
			},
			"usage": map[string]int{"input_tokens": 12, "output_tokens": 3},
		}); err != nil {
			http.Error(writer, err.Error(), http.StatusInternalServerError)
		}
	}))
	defer server.Close()
	client, err := typesafe.NewClient(
		typesafe.WithAPIKey("test-key"),
		typesafe.WithBaseURL(server.URL),
	)
	if err != nil {
		panic(err)
	}
	response, err := client.SystemOne(context.Background(), typesafe.SystemOneRequest{
		State: map[string]any{"text": "ready"},
		Questions: map[string]typesafe.Question{
			"ready": typesafe.NoulQuestion{Instructions: "Is it ready?", Criteria: &typesafe.NoulCriteria{True: "yes", False: "no"}},
			"kind":  typesafe.ChoiceQuestion{Instructions: "Which kind?", Criteria: map[string]any{"stable": "stable", "beta": "beta"}},
			"score": typesafe.ScoreQuestion{Instructions: "How ready?", Criteria: []any{"not ready", "review", "ready"}},
		},
	})
	if err != nil {
		panic(err)
	}
	ready, err := response.Noul("ready")
	if err != nil {
		panic(err)
	}
	kind, err := response.Choice("kind")
	if err != nil {
		panic(err)
	}
	score, err := response.Score("score")
	if err != nil {
		panic(err)
	}
	fmt.Printf("%.1f %s %.1f\n", ready.Noul, kind.Choice, score.Score)
}
Output:
0.9 stable 1.6

type ListModelsResponse

type ListModelsResponse struct {
	Models    []ModelCard `json:"models"`
	RequestID string      `json:"-"`
}

ListModelsResponse contains the models available to the account.

func (*ListModelsResponse) UnmarshalJSON

func (r *ListModelsResponse) UnmarshalJSON(data []byte) error

UnmarshalJSON decodes and validates a model list response.

type ModelCard

type ModelCard struct {
	Name        string `json:"name"`
	Description string `json:"description"`
	ReleaseDate string `json:"release_date"`
}

ModelCard describes an available model.

type NoulAnswer

type NoulAnswer struct {
	Noul float64 `json:"noul"`
}

NoulAnswer contains the probability of a yes answer.

func (NoulAnswer) MarshalJSON

func (a NoulAnswer) MarshalJSON() ([]byte, error)

MarshalJSON encodes a NoulAnswer with its discriminator.

type NoulCriteria

type NoulCriteria struct {
	True  any `json:"true"`
	False any `json:"false"`
}

NoulCriteria describes the true and false outcomes of a Noul question.

type NoulQuestion

type NoulQuestion struct {
	Instructions any           `json:"instructions"`
	Criteria     *NoulCriteria `json:"criteria"`
}

NoulQuestion asks for the probability of a yes answer. It requires Instructions, a non-null criterion, or both.

func (NoulQuestion) MarshalJSON

func (q NoulQuestion) MarshalJSON() ([]byte, error)

MarshalJSON encodes a NoulQuestion with its discriminator.

type Option

type Option func(*clientConfig) error

Option configures a client. Use the With functions to construct options. Options are applied in order, after environment variables and defaults.

func WithAPIKey

func WithAPIKey(key string) Option

WithAPIKey sets the API key, overriding TYPESAFE_API_KEY.

func WithBaseURL

func WithBaseURL(baseURL string) Option

WithBaseURL sets the API root, overriding TYPESAFE_BASE_URL. A path prefix is supported. The URL must use HTTP or HTTPS and contain no credentials, query, or fragment. Use HTTPS for remote services.

func WithHTTPClient

func WithHTTPClient(client *http.Client) Option

WithHTTPClient sets the HTTP client. The SDK takes a shallow copy and disables redirects; it does not mutate or close the caller's client or transport. A shorter HTTP client timeout also applies to each attempt.

func WithHeaders

func WithHeaders(headers http.Header) Option

WithHeaders copies additional headers. Authentication, JSON content headers, SDK identity headers, and retry count are always controlled by the SDK.

func WithMaxRetries

func WithMaxRetries(maxRetries int) Option

WithMaxRetries sets retries after the initial attempt. Zero disables retries. It changes only MaxRetries in the current retry policy.

func WithModel

func WithModel(model string) Option

WithModel sets the default model, overriding TYPESAFE_DEFAULT_MODEL. A nonempty SystemOneRequest.Model takes precedence over this setting.

func WithRetryPolicy

func WithRetryPolicy(policy RetryPolicy) Option

WithRetryPolicy replaces the complete retry policy. Start from DefaultRetryPolicy to change selected settings while retaining the defaults.

func WithTimeout

func WithTimeout(timeout time.Duration) Option

WithTimeout sets the positive timeout for one HTTP attempt, including reading the response body. The default is 10 seconds. Use a context deadline to bound the whole operation, including retries and backoff.

type Question

type Question interface {
	jsonv2.Marshaler
	// contains filtered or unexported methods
}

Question is a typed System One question.

The concrete question types are NoulQuestion, ChoiceQuestion, and ScoreQuestion. Question values always include their wire type discriminator.

func UnmarshalQuestion

func UnmarshalQuestion(data []byte) (Question, error)

UnmarshalQuestion decodes a discriminated question.

type ResponseError

type ResponseError struct {
	RequestID string
	Err       error
}

ResponseError reports an invalid or oversized API response. RequestID can be used to correlate the failure with the API service.

func (*ResponseError) Error

func (e *ResponseError) Error() string

Error returns a summary. Inspect Unwrap for the detailed decoding error.

func (*ResponseError) Unwrap

func (e *ResponseError) Unwrap() error

Unwrap exposes the underlying response decoding or validation error.

type RetryPolicy

type RetryPolicy struct {
	MaxRetries            int
	InitialDelay          time.Duration
	MaxDelay              time.Duration
	MaxRetryAfter         time.Duration
	Jitter                float64
	RetryConnectionErrors bool
	RetryTimeouts         bool
}

RetryPolicy controls retries after transient failures. All delays must be nonnegative, MaxDelay must be at least InitialDelay, and Jitter is in [0, 1]. Retryable HTTP statuses are 408, 429, and 500–599. A caller's cancellation or deadline always stops the operation, regardless of this policy.

func DefaultRetryPolicy

func DefaultRetryPolicy() RetryPolicy

DefaultRetryPolicy returns an independent copy of the default settings: two retries, 500ms initial backoff, 5s maximum backoff, up to 25% downward jitter, and server-requested delays up to 60s. Network failures and attempt timeouts are retried. No total time budget is imposed; use a context deadline.

type ScoreAnswer

type ScoreAnswer struct {
	Score         float64            `json:"score"`
	Confidence    float64            `json:"confidence"`
	Legend        map[string]any     `json:"legend"`
	Probabilities map[string]float64 `json:"probabilities"`
}

ScoreAnswer contains an expected score, rubric legend, confidence, and probabilities.

func (ScoreAnswer) MarshalJSON

func (a ScoreAnswer) MarshalJSON() ([]byte, error)

MarshalJSON encodes a ScoreAnswer with its discriminator.

type ScoreQuestion

type ScoreQuestion struct {
	Instructions any   `json:"instructions"`
	Criteria     []any `json:"criteria"`
}

ScoreQuestion estimates a score using ordered rubric levels in Criteria, which must contain between 2 and MaxScoreLevels non-null levels.

func (ScoreQuestion) MarshalJSON

func (q ScoreQuestion) MarshalJSON() ([]byte, error)

MarshalJSON encodes a ScoreQuestion with its discriminator.

type SystemOneRequest

type SystemOneRequest struct {
	State     any                 `json:"state"`
	Questions map[string]Question `json:"questions"`
	Model     string              `json:"model,omitempty"`
}

SystemOneRequest contains state and named questions to evaluate.

func (SystemOneRequest) Validate

func (r SystemOneRequest) Validate() error

Validate checks the locally enforceable System One request constraints.

type SystemOneResponse

type SystemOneResponse struct {
	Model     string            `json:"model"`
	Answers   map[string]Answer `json:"answers"`
	Usage     Usage             `json:"usage"`
	RequestID string            `json:"-"`
}

SystemOneResponse contains all named answers and request usage metadata.

func (SystemOneResponse) Answer

func (r SystemOneResponse) Answer[T Answer](name string) (T, error)

Answer returns a named answer as the requested concrete answer type.

func (SystemOneResponse) Choice

func (r SystemOneResponse) Choice(name string) (ChoiceAnswer, error)

Choice returns the named Choice answer.

func (SystemOneResponse) Noul

func (r SystemOneResponse) Noul(name string) (NoulAnswer, error)

Noul returns the named Noul answer.

func (SystemOneResponse) Score

func (r SystemOneResponse) Score(name string) (ScoreAnswer, error)

Score returns the named Score answer.

func (*SystemOneResponse) UnmarshalJSON

func (r *SystemOneResponse) UnmarshalJSON(data []byte) error

UnmarshalJSON decodes and validates a SystemOneResponse.

type UnknownAnswer

type UnknownAnswer struct {
	Type string
	Raw  jsontext.Value
}

UnknownAnswer preserves an answer type this SDK does not know yet.

func (UnknownAnswer) MarshalJSON

func (a UnknownAnswer) MarshalJSON() ([]byte, error)

type Usage

type Usage struct {
	InputTokens  int `json:"input_tokens"`
	OutputTokens int `json:"output_tokens"`
}

Usage reports token counts for a System One request.

Directories

Path Synopsis
examples
classify command

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL