JavaScript SDK interfaces and type aliases
TL;DR Everything under
export type * from "./types"in@typesafe-ai/sdk0.6.0, plusWithResponse,EnvVar,Models, and the two exported constantsENVandLOG_LEVELS. Question types carry aconstgeneric over their criteria, andResultFormaps each question type to its answer type — that is the whole reasonanswers.x.choiceis a literal union rather thanstring.
Type map
| Type | Kind | Generic | Purpose |
|---|---|---|---|
JsonValue |
alias | — | Any JSON-compatible value |
EntryType |
alias | — | The shape accepted for state, instructions, criteria entries |
Description |
alias | — | A criterion description (= EntryType) |
NoulQuestion |
interface | — | Yes/no question |
ChoiceQuestion<T> |
interface | T extends ChoiceCriteria = ChoiceCriteria |
Named-alternative question |
ScoreQuestion<T> |
interface | T extends ScoreCriteria = ScoreCriteria |
Ordered-rubric question |
ChoiceCriteria |
alias | — | Label → description map |
ScoreCriteria |
alias | — | Ordered rubric tuple, length ≥ 2 |
Question |
alias | — | Union of the three question interfaces |
Questions |
interface | — | { [name: string]: Question } |
NoulResponse |
interface | — | Yes/no answer |
ChoiceResponse<T> |
interface | T extends ChoiceCriteria = ChoiceCriteria |
Selected label + probabilities |
ScoreResponse<T> |
interface | T extends ScoreCriteria = ScoreCriteria |
Expected score + legend + probabilities |
ScoreOf<T> |
alias | T extends ScoreCriteria |
Score keys inferred from the rubric |
ScoreLegend<T> |
alias | T extends ScoreCriteria |
Rubric descriptions keyed by score |
ResultFor<T> |
alias | T extends Question |
Question type → answer type |
Usage |
interface | — | Token usage |
SystemOneRequest<Q> |
interface | Q extends Questions = Questions |
What you pass to systemOne() |
SystemOneRequestPayload |
interface | — | The wire body, model resolved |
SystemOneResult<Q> |
interface | Q extends Questions |
What systemOne() resolves to |
ModelCard |
interface | — | One model in models.list() |
Models |
interface (docs) / class (source) | — | The client.models resource |
WithResponse<T> |
interface | T |
.withResponse() return shape |
TypeSafeClientConfig |
interface | — | Constructor options |
RetryPolicy |
interface | — | Retry configuration → JavaScript SDK error classes, RetryPolicy, RequestOptions |
RequestOptions |
interface | — | Per-call options → JavaScript SDK error classes, RetryPolicy, RequestOptions |
Fetch |
alias | — | Pluggable fetch |
Logger |
interface | — | Log sink |
LogLevel |
alias | — | Verbosity union |
EnvVar |
alias | — | Union of the ENV values |
ENV |
const | — | Config key → env var name |
LOG_LEVELS |
const | — | Ordered level list |
RetryPolicy and RequestOptions also live in src/types.ts, but are documented on JavaScript SDK error classes, RetryPolicy, RequestOptions alongside the failure modes they govern.
Primitive value types
JsonValue
type JsonValue =
| string
| number
| boolean
| null
| JsonValue[]
| { [key: string]: JsonValue };
"A JSON-compatible value." Recursive; used only as the element type inside EntryType.
EntryType
type EntryType = string | { [key: string]: JsonValue } | JsonValue[] | null;
"Text, a JSON object or array, or null for state, instructions, and criteria." Note it is not JsonValue: a bare number or boolean is not an EntryType.
Description
type Description = EntryType;
"A criterion description; null leaves the label undescribed." A readability alias over EntryType, used in ChoiceCriteria.
Questions
NoulQuestion
| Property | Type | Required | Default | Description |
|---|---|---|---|---|
type |
"noul" |
yes | — | Discriminant. |
instructions |
EntryType |
no | null when built with noul() |
The question as text, a JSON object, or an array; optional or null. |
criteria |
{ true?: EntryType; false?: EntryType } | null |
no | undefined |
Optional descriptions of the yes and no outcomes. |
Nested criteria members:
| Member | Type | Required | Description |
|---|---|---|---|
true |
EntryType |
no | Description of the yes outcome. |
false |
EntryType |
no | Description of the no outcome. |
ChoiceQuestion<T extends ChoiceCriteria = ChoiceCriteria>
| Property | Type | Required | Default | Description |
|---|---|---|---|---|
type |
"choice" |
yes | — | Discriminant. |
instructions |
EntryType |
no | — | The question as text, a JSON object, or an array; optional or null. |
criteria |
T |
yes | — | Descriptions of the available outcomes. |
ScoreQuestion<T extends ScoreCriteria = ScoreCriteria>
| Property | Type | Required | Default | Description |
|---|---|---|---|---|
type |
"score" |
yes | — | Discriminant. |
instructions |
EntryType |
no | — | The question as text, a JSON object, or an array; optional or null. |
criteria |
T |
yes | — | Descriptions of the available outcomes (ordered rubric, length ≥ 2). |
ChoiceCriteria
type ChoiceCriteria = { [label: string]: Description };
"Labels mapped to descriptions, or null for undescribed labels." The published docs render the index signature as [label: string]: EntryType; since Description = EntryType, the two spellings are identical.
ScoreCriteria
type ScoreCriteria = readonly [EntryType, EntryType, ...EntryType[]];
"At least two descriptions indexed by score from zero; null leaves a score undescribed." The tuple prefix is what enforces the two-entry minimum at compile time; validateQuestions re-checks it at runtime.
Question
type Question = NoulQuestion | ScoreQuestion | ChoiceQuestion;
"A question identified by its type field." The union members use their default generic arguments, so a Question-typed value has widened criteria.
Questions
interface Questions {
[name: string]: Question;
}
"Questions keyed by the names used to identify their answers." Must be nonempty at call time — systemOne() throws TypeSafeError "At least one question is required." for {}.
Responses
NoulResponse
| Property | Type | Readonly | Description |
|---|---|---|---|
type |
"noul" |
yes | Discriminant. |
noul |
number |
yes | Probability of a yes answer, from zero to one. |
ChoiceResponse<T extends ChoiceCriteria = ChoiceCriteria>
| Property | Type | Readonly | Description |
|---|---|---|---|
type |
"choice" |
yes | Discriminant. |
choice |
keyof T & string |
yes | The selected label. |
confidence |
number |
yes | Reported confidence in the selected label. |
probabilities |
{ readonly [label in keyof T]: number } |
yes | Probabilities keyed by label. |
(The generated docs print probabilities as { readonly [label in string | number | symbol]: number } — that is typedoc expanding keyof T for the default generic argument, not a different type.)
ScoreResponse<T extends ScoreCriteria = ScoreCriteria>
| Property | Type | Readonly | Description |
|---|---|---|---|
type |
"score" |
yes | Discriminant. |
score |
number |
yes | Expected score, which may fall between integer rubric levels. |
confidence |
number |
yes | Reported confidence in the score. |
legend |
ScoreLegend<T> |
yes | Rubric descriptions keyed by score. |
probabilities |
{ readonly [score in ScoreOf<T>]: number } |
yes | Probabilities keyed by score. |
(Docs print probabilities as { readonly [score in number | \${number}`]: number }` — again the expansion for the default generic argument.)
Because score is an expectation, it is generally not an integer: a 0–3 rubric can return 2.41. See Score questions and Confidence vs probability.
ScoreOf<T extends ScoreCriteria>
type ScoreOf<T> = number extends T["length"] ? number : Extract<keyof T, `${number}`>;
"Score keys inferred from the rubric; a fixed-length tuple yields its indices, otherwise number." With score("…", ["a", "b", "c"] as const) you get "0" | "1" | "2"; with a readonly EntryType[] of unknown length you get number.
ScoreLegend<T extends ScoreCriteria>
type ScoreLegend<T> = { readonly [score in ScoreOf<T>]: T[score] };
"Rubric descriptions keyed by score." This is the server echoing back your rubric keyed by index, so legend["2"] is the literal description you supplied.
ResultFor<T extends Question>
type ResultFor<T> =
T extends NoulQuestion ? NoulResponse
: T extends ScoreQuestion<infer S> ? ScoreResponse<S>
: T extends ChoiceQuestion<infer E> ? ChoiceResponse<E>
: never;
"The answer type for a question, preserving its criteria keys." Order matters: NoulQuestion is tested first, then ScoreQuestion, then ChoiceQuestion; anything else resolves to never.
Usage
| Property | Type | Readonly | Description |
|---|---|---|---|
input_tokens |
number |
yes | Number of input tokens used. |
output_tokens |
number |
yes | Number of output tokens used. |
Snake_case, matching the wire format in HTTP API: POST /v1/systemone and GET /v1/models.
Requests and results
SystemOneRequest<Q extends Questions = Questions>
"State and named questions for systemOne. Additional properties on a request variable are forwarded, including null values."
| Property | Type | Required | Default | Description |
|---|---|---|---|---|
state |
EntryType |
yes | — | Text, a JSON object or array, or null to evaluate. |
questions |
Q |
yes | — | Nonempty questions keyed by the names used to identify their answers. |
model |
string |
no | client defaultModel (jev-latest unless overridden) |
Model override. |
The "additional properties are forwarded" note is a consequence of TypeScript's excess-property check applying only to object literals: pass a pre-declared variable with extra keys and the SDK spreads them into the request body. Treat that as a way to send fields the SDK does not model yet, and as a footgun otherwise.
SystemOneRequestPayload
extends SystemOneRequest (with the default Questions), overriding model:
| Property | Type | Required | Description |
|---|---|---|---|
state |
EntryType |
yes | Inherited. |
questions |
Questions |
yes | Inherited. |
model |
string |
yes | Model resolved by the client; no longer optional. |
"Request body for POST /v1/systemone, with the model resolved." You rarely construct one — systemOne() builds it as {...request, model: request.model ?? this.defaultModel} — but it is the exact JSON shape that goes on the wire.
SystemOneResult<Q extends Questions>
| Property | Type | Readonly | Description |
|---|---|---|---|
model |
string |
yes | The model used to answer the request (resolved alias, e.g. jev-1.13.0). |
answers |
{ readonly [K in keyof Q]: ResultFor<Q[K]> } |
yes | Answers with types inferred from the supplied questions. |
usage |
Usage |
yes | Token usage for the request. |
Note that Q has no default here, unlike SystemOneRequest<Q> — you always write SystemOneResult<typeof questions> or let inference supply it.
ModelCard
| Property | Type | Readonly | Description |
|---|---|---|---|
name |
string |
yes | (undocumented in the source docstring) Model name. |
description |
string |
yes | (undocumented in the source docstring) |
release_date |
string |
yes | (undocumented in the source docstring) ISO date (inferred). |
"Metadata for an available model." The three fields carry no per-field docstrings in either docs or source.
Models
| Method | Signature | Description |
|---|---|---|
list |
list(options?: RequestOptions): APIPromise<ModelCard[]> |
List the models available to the account. options defaults to {}. |
"Access to the Models API resource." Reached as client.models; exported as a type only.
WithResponse<T>
| Property | Type | Readonly | Description |
|---|---|---|---|
data |
T |
no | The parsed response body. |
response |
Response |
no | The HTTP response, with its body consumed by parsing. |
requestId |
string | undefined |
no | Request ID from x-typesafe-request-id, or undefined when absent. |
Returned by APIPromise#withResponse(); exported from src/api-promise.ts, not src/types.ts.
Configuration types
TypeSafeClientConfig
"Client options. Explicit values take precedence over environment variables, then SDK defaults." Every property is optional.
| Property | Type | Required | Default | Description |
|---|---|---|---|---|
apiKey |
string |
no | TYPESAFE_API_KEY |
Required API key; falls back to the env var. |
baseURL |
string |
no | TYPESAFE_BASE_URL, then https://api.typesafe.ai |
API root. |
defaultModel |
string |
no | TYPESAFE_DEFAULT_MODEL, then jev-latest |
Default model. |
logLevel |
LogLevel |
no | TYPESAFE_LOG_LEVEL, then warn |
info logs request summaries; debug adds headers and bodies. Known credential headers are redacted; bodies are not. |
logger |
Logger |
no | prefixed console |
Logger filtered to logLevel and above. |
retry |
Partial<RetryPolicy> |
no | RetryPolicy defaults |
Retry overrides. |
timeout |
number |
no | 10000 |
Timeout per attempt in milliseconds, without a total retry budget. |
defaultHeaders |
Record<string, string> |
no | {} |
Additional request headers; per-call headers take precedence. |
dangerouslyAllowBrowser |
boolean |
no | false |
Allow browser use, exposing the API key to page users. |
fetch |
Fetch |
no | global fetch |
Custom HTTP fetch implementation for transport configuration or tests. |
Fetch
type Fetch = (input: string, init?: RequestInit) => Promise<Response>;
"HTTP fetch implementation compatible with the global fetch." Narrower than the DOM fetch: input is a string, not RequestInfo | URL.
Logger
"Log methods accepting a message and structured values; compatible with console." All four methods are required.
| Method | Signature | Returns |
|---|---|---|
debug |
(message: string, ...args: unknown[]) => void |
void |
info |
(message: string, ...args: unknown[]) => void |
void |
warn |
(message: string, ...args: unknown[]) => void |
void |
error |
(message: string, ...args: unknown[]) => void |
void |
LogLevel
type LogLevel = "debug" | "info" | "warn" | "error" | "off";
"Log verbosity; off disables logging." Default warn.
EnvVar
type EnvVar = (typeof ENV)[keyof typeof ENV];
Resolves to "TYPESAFE_API_KEY" | "TYPESAFE_BASE_URL" | "TYPESAFE_DEFAULT_MODEL" | "TYPESAFE_LOG_LEVEL". The generated docs give it no description.
Exported variables
ENV
const ENV: {
readonly apiKey: "TYPESAFE_API_KEY";
readonly baseURL: "TYPESAFE_BASE_URL";
readonly defaultModel: "TYPESAFE_DEFAULT_MODEL";
readonly logLevel: "TYPESAFE_LOG_LEVEL";
};
"Environment variable names for client configuration. Explicit options take precedence."
| Key | Literal value | Documented effect |
|---|---|---|
apiKey |
"TYPESAFE_API_KEY" |
Required API key; used when apiKey is omitted. |
baseURL |
"TYPESAFE_BASE_URL" |
API root; defaults to https://api.typesafe.ai. |
defaultModel |
"TYPESAFE_DEFAULT_MODEL" |
Default model name; defaults to jev-latest. |
logLevel |
"TYPESAFE_LOG_LEVEL" |
Log level; defaults to warn. |
LOG_LEVELS
const LOG_LEVELS: readonly LogLevel[];
"Supported log levels, from most to least verbose." Source value: ["debug", "info", "warn", "error", "off"]. The published docs give the type but not the value; the value comes from src/logging.ts.
VERSION
const VERSION: "0.6.0" = "0.6.0";
A literal type, not a widened string. See JavaScript/TypeScript SDK: install, client, choice/score/noul.
Runnable example: generics end to end
ESM, TypeScript. Nothing here is annotated except to prove the inferred types:
import { choice, noul, score, TypeSafeClient } from "@typesafe-ai/sdk";
import type {
ChoiceResponse,
NoulResponse,
Question,
Questions,
ResultFor,
ScoreResponse,
SystemOneRequest,
SystemOneResult,
} from "@typesafe-ai/sdk";
const client = new TypeSafeClient();
const questions = {
isBilling: noul("Is this ticket about billing?"),
tone: choice("What is the customer's tone?", { calm: null, frustrated: null, angry: null }),
urgency: score("How urgent is this ticket?", ["can wait", "this week", "today", "right now"]),
} satisfies Questions;
const request: SystemOneRequest<typeof questions> = {
state: { subject: "Charged twice", body: "Two $49 charges in August." },
questions,
model: "jev-latest",
};
const result: SystemOneResult<typeof questions> = await client.systemOne(request);
// ResultFor picks the answer type per question:
const billing: NoulResponse = result.answers.isBilling;
const tone: ChoiceResponse<{ calm: null; frustrated: null; angry: null }> = result.answers.tone;
const urgency: ScoreResponse<readonly ["can wait", "this week", "today", "right now"]> =
result.answers.urgency;
console.log(billing.noul); // number, 0..1
console.log(tone.choice); // "calm" | "frustrated" | "angry"
console.log(tone.probabilities.angry); // number
console.log(urgency.score, urgency.legend["3"]); // number, "right now"
console.log(urgency.probabilities["0"]); // number
console.log(result.model, result.usage.input_tokens, result.usage.output_tokens);
// Writing a generic helper: keep the `const` parameter to preserve literals.
async function ask<const Q extends Questions>(state: unknown, qs: Q) {
const { answers } = await client.systemOne({ state: state as never, questions: qs });
return answers;
}
// `ResultFor` is usable directly for a single question:
type ToneAnswer = ResultFor<(typeof questions)["tone"]>; // ChoiceResponse<{...}>
// A widened `Question` loses the literal keys — this is why you should not annotate:
const widened: Question = questions.tone;
type Widened = ResultFor<typeof widened>; // ChoiceResponse<ChoiceCriteria>, `choice` is `string`
CommonJS gets the same runtime behaviour but no type parameters at the call site unless you use // @ts-check with JSDoc:
const { choice, TypeSafeClient } = require("@typesafe-ai/sdk");
/** @type {import("@typesafe-ai/sdk").Questions} */
const questions = { tone: choice("Tone?", { calm: null, angry: null }) };
Gotchas
- Annotating kills inference.
const q: Questions = {...}widens every criteria object;answers.x.choicebecomesstring. Usesatisfies Questionsor no annotation at all. ScoreOfdegrades tonumberfor arrays whose length TypeScript cannot see (e.g.string[]), which silently removes the literal keys fromlegendandprobabilities.ChoiceResponse.choiceiskeyof T & string, so a criteria map built from aRecord<string, null>yieldsstring.EntryTypeexcludes bare numbers and booleans.state: 42does not type-check; wrap it (state: { value: 42 }) or stringify it.Usageuses snake_case while everything else in the SDK is camelCase — it mirrors the HTTP response.
Version notes
- Types described for
@typesafe-ai/sdk0.6.0 (repo commit66880ccded6cb642dc1809620c2b108c33730214, 2026-09-15). ScoreCriteriabecame areadonlytuple in 0.6.0; in 0.5.7 score criteria were a dictionary keyed by integers. See JavaScript SDK changelog.- Doc-vs-source:
Modelsis published under "Interfaces" but declaredexport class Modelsinsrc/resources/models.ts(re-exported asexport type { Models }, so only the type is public). - Doc-vs-source (cosmetic): the docs render
ChoiceCriteria's index signature asEntryTypewhere source writesDescription, and expand the mapped-type keys ofChoiceResponse.probabilities/ScoreResponse.probabilitiestostring | number | symbolandnumber | \${number}`` respectively. These are typedoc expansions of the same types.
Related
- JavaScript/TypeScript SDK: install, client, choice/score/noul — client, builders, examples
- JavaScript SDK error classes, RetryPolicy, RequestOptions —
RetryPolicy,RequestOptions, error classes - OpenAPI component schemas — the same shapes on the wire
- HTTP API: POST /v1/systemone and GET /v1/models —
POST /v1/systemone,GET /v1/models - Primitives: Choice, Score, Noul — what Choice, Score and Noul mean
- Python SDK question types (Noul, Choice, Score) — Python counterparts
Sources
- raw/docs/sdk__javascript__api.md (https://docs.typesafe.ai/sdk/javascript/api)
- raw/docs/sdk__javascript__api__interfaces__*.md (https://docs.typesafe.ai/sdk/javascript/api/interfaces/*)
- raw/docs/sdk__javascript__api__type-aliases__*.md (https://docs.typesafe.ai/sdk/javascript/api/type-aliases/*)
- raw/docs/sdk__javascript__api__variables__ENV.md, __LOG_LEVELS.md, __VERSION.md
- raw/github/typesafe-sdk-js/src/types.ts, src/env.ts, src/logging.ts, src/api-promise.ts, src/resources/models.ts (commit 66880ccded6cb642dc1809620c2b108c33730214)