Changelog
All notable changes to this project are documented in this file.
The format is based on Keep a Changelog, and this project adheres to Semantic Versioning. While the version is 0.x, any change to an answer type or the wire format is at least a minor version bump.
Unreleased
0.1.0 - 2026-09-18
First release: an unofficial community Zig client for TypeSafe’s System One API (the Jev model). It is not an official TypeSafe SDK and is not affiliated with or endorsed by TypeSafe.
Install it with zig fetch --save git+https://github.com/mattneel/typesafe.zig#v0.1.0. It
requires Zig 0.16.0 or later and uses only the standard library.
Added
typesafe.ClientwithinitandinitFromEnv. Options resolve from the explicit option, thenTYPESAFE_API_KEY,TYPESAFE_BASE_URLorTYPESAFE_DEFAULT_MODEL(blank values ignored), then the default. Invalid configuration is anInitErrorat init, never at request time. The base URL’s scheme is lowercased; IPv6 literal hosts, whichstd.http.Client0.16 cannot connect to, and hosts over 255 bytes areInvalidBaseUrl.Client.askforPOST /v1/systemonewith typed questions built bytypesafe.noul,typesafe.choiceandtypesafe.score. The answers struct is derived from the questions at compile time: a Choice answer is the caller’s enum with one probability field per tag, and a Score answer has a[N]f64of probabilities. Missing questions, unknown options, non-enum option types, Scores with fewer than two levels, levels given as a string instead of a tuple, booleans or numbers as instructions, descriptions, Noul criteria or levels (including through an optional or a pointer), and a Noul withnullinstructions and no non-nullcriteria are compile errors. The comptime helpers raise@setEvalBranchQuotafor you, so question sets of a few dozen questions and Scores with dozens of levels compile as they are; an unusually large set can still reach the comptime branch limit, which the compiler reports and names@setEvalBranchQuotafor.Client.askDynamicandtypesafe.dynamicfor questions defined at run time, validated before sending.dynamic.Jsonvalues report their JSON type withkind()and absence withisEmpty().Client.listModelsforGET /v1/models.- JSON structure for state, instructions, descriptions, levels and criteria: struct literals,
tuples, slices,
std.json.Valueandtypesafe.RawJson. A strict encoder, which walksstd.json.Valuetoo, rejects invalid UTF-8, non-finite numbers, invalidnumber_stringvalues, nesting over 256 levels, invalid raw JSON, a Noul whose instructions and criteria are all empty at run time and anullScore level witherror.InvalidRequestand a path to the value. Types with their ownjsonStringifyare written by it unchecked; awriteTypesafeJsonmethod has the encoder check a type’s contents. - Strict, forward-compatible decoding in one pass over the response bytes (no intermediate
std.json.Valuetree), with field paths in errors, such asanswers.tone.confidence: expected a number from 0 to 1. Answer strings point into the response body, which the result keeps asbody. Unknown answers and fields are skipped, keys may arrive in any order, a repeated key keeps its last value, and a body that is not valid JSON, ends early or carries trailing data iserror.InvalidResponse. gzip and deflate response bodies are decoded; any other content encoding iserror.InvalidResponse. - Answer helpers:
NoulAnswer.isYes,ChoiceAnswer.probability,rankedandmargin, andScoreAnswer.probability,expectedLevel,maxLevelandranked. Dynamic answers haveprobability,rankedandmargin(Choice) andexpectedLevelandmaxLevel(Score).marginis rounded to 10 decimal places, as in the Elixir client. typesafe.Error, one error set for every call, andtypesafe.Diagnostics, an optional out-parameter with the status, request id, server error type and message, body, field path,retry_after_ms, attempt count and underlying cause, plus a one-line{f}format. On failure it describes the final attempt; after a success it holds only the final response’s status and request id and the attempt count.typesafe.Retry, a retry policy with the same defaults as TypeSafe’s official Python and JavaScript SDKs: 2 retries, exponential backoff from 500 ms to 5 s with 25% jitter, retries on 408, 429, 5xx and transport errors,retry-after-msandRetry-After(seconds or any RFC 9110 HTTP date), and a 30 s budget per call.isRetryableStatusreplaces the default retryable statuses, andretriesStatusandisRetryableStatusByDefaultexpose the status decision.- A per-attempt timeout (10 s by default, at most one year, disabled with
nullorIo.Duration.max) enforced withstd.Io.Select, and cancelation throughstd.Io: canceling the calling task cancels the request. - Connection handling: only a response read to the end returns its connection to the pool; a
pooled connection the server has closed is replaced without using an attempt; a body cut short
by a closed connection is a retryable
error.ConnectionFailed; a body of exactlymax_response_bytesis accepted. A long-running client reloads the clock and system root certificates it checks TLS certificates against every hour, behind-Dtls-trust-refresh(on by default;.tls_trust_refresh = falsethroughb.dependencyleaves it out). HTTPS through a proxy is refused witherror.InvalidOption, becausestd.http.Client0.16 would not encrypt the proxied connection. Per-call option and header mistakes areerror.InvalidOptiontoo, with the option inDiagnostics.path;error.InvalidRequestis only for a request that cannot be encoded. - Identification headers in the format of TypeSafe’s official SDKs (
User-Agent,X-TypeSafe-SDK,X-TypeSafe-Runtime,X-TypeSafe-Retry-Count), identifying this client astypesafe-zig/<version>. Redirects are never followed. typesafe.Hooksfor observability: request start, retry and request end events with status, request id, attempts, duration, token usage and error. Every call that fires the start event, including one rejected for invalid options, fires the end event.- Logging through
std.log.scoped(.typesafe): retries and schema mismatches at.debug, requests sent without a timeout and failed root certificate reloads at.warn. typesafe.testing.MockServer, a loopback HTTP server with scripted replies (answers, API errors, delays, dropped connections, unread request bodies, truncated bodies, connections closed after a reply) and recorded requests, for testing code that uses the client.- Examples (
route_ticket,structured,batch,dynamic,list_models), guides (installation, questions, confidence, concurrency, testing, observability) and an API reference generated withzig build docs. The guides, this changelog, the release checklist and the README are published as one site bybook/build.shand the Docs workflow.