Skip to content

Repository files navigation

TypeSafe SDK for Java

A community Java client for the TypeSafe System One API. Ask small, typed questions over your application state and get calibrated probabilities back, in one round trip, with no prompt parsing.

This library is an independent, community-maintained project and is not affiliated with, endorsed by, or supported by TypeSafe AI. It follows the conventions of the official Python and JavaScript SDKs so the three read alike. TypeSafe is a trademark of its owner; the name is used here only to describe what the library connects to.

Requires Java 17 or newer. Depends only on Jackson.

Install

Gradle:

implementation("io.github.premo-cloud:typesafe-sdk:0.1.1")

Maven:

<dependency>
  <groupId>io.github.premo-cloud</groupId>
  <artifactId>typesafe-sdk</artifactId>
  <version>0.1.1</version>
</dependency>

Spring Boot users can add io.github.premo-cloud:typesafe-sdk-spring-boot-starter instead and get a TypeSafeClient bean from typesafe.api-key and friends in application.properties.

Use

The shape mirrors the Python and JavaScript SDKs: a client, systemOne(state, questions), and question types named Noul, Choice, and Score that take (instructions, criteria).

TypeSafeClient client = TypeSafeClient.fromEnvironment();   // reads TYPESAFE_API_KEY

TypeSafeResponse response = client.systemOne(
        Map.of("document", "I was charged twice. Please fix this ASAP."),
        Map.of("category", Choice.of("What is this ticket about?", "billing", "technical", "other"),
               "urgent", Noul.of("Does `document` convey urgency?")));

response.choices().get("category").choice();   // "billing"
response.noul("urgent");                       // 0.0 to 1.0

When a question needs structure, every type also takes a configurer, so nested requests read top to bottom with no build() calls, in the style of the Elasticsearch and AWS Java clients:

TypeSafeResponse response = client.systemOne(r -> r
        .state(Map.of(
                "email", Map.of(
                        "from", "alerts@secure-notice.example",
                        "subject", "Action required: confirm your account details",
                        "body", "Your access will be suspended unless you confirm your details at the link below within 24 hours."),
                "context", Map.of("recipient_domain", "example.com")))
        .noul("is_phishing", n -> n
                .instructions("Does `email` attempt to trick the recipient into revealing credentials or payment details?")
                .whenTrue(c -> c.what("Impersonates a trusted organization or demands urgent verification via a link")
                        .examples("Confirm your details within 24 hours to avoid suspension"))
                .whenFalse("A legitimate request from a known counterparty"))
        .choice("category", c -> c
                .instructions("Which category best describes `email`?")
                .option("MARKETING", "Promotional content sent to a list")
                .option("PHISHING", o -> o.what("Credential theft or impersonation").notFor("Legitimate requests to confirm a payment"))
                .option("NOT_SPAM"))                                   // an undescribed label
        .score("urgency", s -> s
                .instructions("How hard does `email.body` press the recipient to act immediately?")
                .level("No time pressure")
                .level("Mentions a deadline")
                .level("Threatens loss or suspension within hours")));

double phishing = response.noul("is_phishing");            // 0.0 to 1.0
ChoiceAnswer category = response.choice("category");       // choice(), probabilities(), confidence()
ScoreAnswer urgency = response.score("urgency");           // score(), probabilities(), confidence(), legend()

Everything in one request runs in parallel on the server and shares one round trip. Only start a second request when an answer is needed to build the next state.

State

state is any Jackson-serializable value: a String, a Map, or your own record. Give questions named fields to point at (`email.body`) rather than one long string. state(key, value) adds a field to an object state you have already set.

Questions

  • Noul.of(instructions) asks yes or no; whenTrue and whenFalse describe the outcomes.
  • Choice.of(instructions, labels...) picks one label; option(label, description) describes a label, option(label) leaves it undescribed.
  • Score.of(instructions, levels...) places the state on an ordered rubric of at least two levels.

Instructions are optional when the criteria say enough on their own. Any description can be a plain string or a Criterion with what, notFor, and examples. Prebuilt questions are plain records and can be shared across requests.

Criteria-driven questions

When the rules are user-defined data rather than code, CriteriaQuestionSet puts them into the state and generates one noul per entry that points at its own `criteria[i]` path:

TypeSafeRequest request = CriteriaQuestionSet
        .over("document", documentState, rules, Rule::id,
                (rule, path) -> Noul.of("Is `document` about the subject matter described in %s?".formatted(path)))
        .build();

Errors

Every error extends TypeSafeException. A non-2xx response after retries raises a TypeSafeApiException subclass named for the status, TypeSafeAuthenticationException for 401, TypeSafeRateLimitException for 429 with retryAfter(), TypeSafeInternalServerException for 5xx, and so on, each carrying status(), body(), headers(), and requestId(). Delivery failures raise TypeSafeConnectionException, or its subclass TypeSafeTimeoutException. Asking a response for a missing key or the wrong primitive raises IllegalArgumentException.

Retries

By default the client retries twice after the first attempt on HTTP 408, 429, and 5xx, on connection failures, and on timeouts, with exponential backoff from 500 ms capped at 5 s and 25 percent jitter, honoring Retry-After and retry-after-ms up to one minute. Retried attempts carry an X-TypeSafe-Retry-Count header.

TypeSafeClient.builder().apiKey(key).retryPolicy(RetryPolicy.of(r -> r.maxRetries(5).backoffMax(Duration.ofSeconds(20)))).build();
TypeSafeClient.builder().apiKey(key).retryPolicy(RetryPolicy.none()).build();

Per-call options

Any call accepts RequestOptions to override the client's timeout, retry policy, or headers for that call only:

client.systemOne(request, RequestOptions.of(o -> o.timeout(Duration.ofSeconds(30)).maxRetries(0)));
client.models().list(RequestOptions.of(o -> o.header("X-Trace", traceId)));

Models

List<ModelCard> models = client.models().list();   // name, description, releaseDate

Configuration

Explicit values win over environment variables, which win over defaults.

TypeSafeClient client = TypeSafeClient.builder()
        .apiKey(key)                           // or TYPESAFE_API_KEY
        .baseUrl("https://api.typesafe.ai")    // or TYPESAFE_BASE_URL
        .defaultModel("jev-latest")            // or TYPESAFE_DEFAULT_MODEL; TypeSafeRequest.model(...) overrides per request
        .timeout(Duration.ofSeconds(10))       // per attempt; default 10 s
        .retryPolicy(RetryPolicy.DEFAULT)
        .header("X-Team", "review")            // sent with every request
        .httpClient(myHttpClient)              // optional: proxies, executors
        .objectMapper(myObjectMapper)          // optional: custom serializers for your state types
        .build();

Spring Boot

Add io.github.premo-cloud:typesafe-sdk-spring-boot-starter and set one property:

typesafe.api-key=${TYPESAFE_API_KEY}

A TypeSafeClient bean is then available for injection. The starter creates it only when the key is set, backs off if you define your own, and reuses the application's ObjectMapper. Properties, customization, testing, and troubleshooting are covered in the starter README.

Development

./gradlew build

Tests run against an in-process stub server and need no API key.

Contributing

Issues and pull requests are welcome at Premo-Cloud/typesafe-sdk-java. Please keep the public API aligned with the official SDKs' conventions and add a test for every behavior change.

License

MIT, Copyright (c) 2026 Garret Premo. See LICENSE.

About

Community Java client for the TypeSafe System One API (unofficial)

Topics

Resources

Stars

2 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages