Every TypeScript program is built on a quiet act of faith. When you write const ticket: Ticket = JSON.parse(body), you are telling the compiler something it has no capacity to verify. The compiler takes you at your word. It will happily let you write ticket.customer.email for the next thousand lines, and it will never once ask whether customer actually exists on the object that came out of that parse.
This is not a flaw in TypeScript. It is a structural consequence of what a type system is. Types are a static approximation of runtime behavior, and that approximation is only sound inside the region of the program the compiler can see. A type checker is a proof assistant for a bounded universe. Inside that universe—a single process, a single compilation unit, a graph of modules the compiler has resolved—it can prove an astonishing amount. What it cannot prove is anything about a value that enters the program from outside: a byte stream from a socket, a line from a file, or the return of fetch().
That region—the place where the compiler's sight ends—is the boundary. And boundaries are where production systems fail.
The concepts and code demonstrated here are drawn directly from my ebook Jev: The Definitive Guide to System One AI here. Check also the 9 volumes bundle: TypeScript AI & Agentic Engineer Masterclass
The Physical Architecture of Software
Consider the physical architecture analogy. A blueprint is a type system. It specifies that a load-bearing wall carries a certain load, that a beam spans a certain distance, and that a pipe runs from point A to point B. The blueprint is checked, reviewed, and stamped. But the blueprint does not know what the concrete actually does on the day it is poured. The blueprint is a claim; the building is the reality; and the gap between them is where engineering discipline lives.
Software has exactly the same gap. Every application that talks to a network is, at that boundary, an application that has stopped being type-safe and started being hopeful.
This is why a client SDK is not a convenience library. It is not "a nicer way to call fetch." A well-designed SDK is a structural apparatus for making one specific boundary behave as though the type system's guarantees extended across it. Everything the SDK does—constructing a client, resolving configuration, serializing a request, retrying a failure, deserializing a response, mapping an HTTP status to an exception class—is in service of a single goal: to move the boundary outward, so that the region where types are true is as large as possible.
The Seam: Where Determinism Meets Probability
In the architecture of AI-powered software, System One models are not agents. They do not plan, they do not loop, they do not choose their own next action. They answer typed questions about a state, in parallel, and return typed answers with calibrated probabilities. The control flow—the if statements, the routing, the side effects, the persistence—stays in code, where it is testable, deterministic, and inspectable.
That architecture has a seam. The seam is the call.
Everything to the left of the seam is ordinary software: state assembly, question construction, thresholds, weights, routing. Everything to the right of the seam is a remote inference: a state is serialized, a request travels, a model evaluates every question independently and in parallel, and a set of probability distributions comes back.
The client SDK is the seam. It is the physically embodied boundary between two regimes: a regime of proof and a regime of probability. And because it is a seam between two regimes, every design decision inside it is a trade-off between two pressures: the pressure to be typed enough that the deterministic side never has to guess, and the pressure to be fast enough that the statistical side is usable in the 100-millisecond budget that makes it valuable at all.
Below about 100 milliseconds, a response feels causally coupled to the action that triggered it—the system feels like an extension of the user's intent. Jev's published latency lands precisely at this threshold. But the model's inference time is only one term in the budget. The total latency of a decision path involves DNS, TCP handshakes, TLS handshakes, serialization, network transit, queueing, inference, deserialization, and retry overhead. The SDK's engineering determines the size and stability of everything except the model inference.
The Wire Carries No Types: Runtime Validation as an Airlock
Compile-time TypeScript checks are stripped away during compilation. The tsc compiler emits JavaScript in which every interface, every type alias, and every generic parameter has vanished. At runtime, there is no Ticket type; there is an object with some properties, and the question of whether those properties are the right ones is a runtime question with a runtime answer.
Serialization is an entropic act. When you take a richly structured in-memory value and convert it into bytes, you lose information—methods, identity, type discriminators. Deserialization on the far side is a negentropic act: it must supply the missing structure from knowledge rather than from the data.
This is precisely the relationship between an airlock and a spacecraft. The interior has a stable atmosphere; the exterior is a vacuum. You cannot simply open a door; you must pass through a chamber that reconciles the two regimes. The SDK is that chamber. Its validation logic is the pressure equalization.
Generics as Type-Level Functions: The SDK's Central Contract
Consider what the SDK must accomplish. You pass a map of questions containing heterogeneous types: ChoiceQuestion, ScoreQuestion, and NoulQuestion. The answers that come back must be typed per question, such that response.answers["department"] is a ChoiceAnswer over exactly the options you declared, and response.answers["is_urgent"] is a NoulAnswer.
No single answer type can express this. What is needed is a type-level function: a mapping from the shape of the input map to the shape of the output map. This is where TypeScript's generics earn their keep.
import type {
ChoiceQuestion,
ChoiceResponse,
NoulQuestion,
NoulResponse,
ScoreQuestion,
ScoreResponse,
} from "@typesafe-ai/sdk";
// The input half of the contract: a question is one of three shapes.
type Question = NoulQuestion | ScoreQuestion | ChoiceQuestion;
// The output half: a type-level dispatcher from question shape to answer shape.
type ResultFor<T> = T extends NoulQuestion
? NoulResponse
: T extends ScoreQuestion<infer S>
? ScoreResponse<S>
: T extends ChoiceQuestion<infer E>
? ChoiceResponse<E>
: never;
This is a pattern match performed at build time. The SDK reads your question map as a program and computes the type of your answer map from it. Nothing at the call site needs an annotation. There is no casting, no as, and no interface you must maintain in parallel with your questions. The request is the source of truth; the types are its shadow.
Installing and Setting Up the Client
To bring these guarantees into your project, install the Typesafe AI SDK:
npm install @typesafe-ai/sdk
# or
pnpm add @typesafe-ai/sdk
The client object acts as a long-lived amortization device. It exists to pay certain costs once and reuse the payment, such as resolving configuration, maintaining connection pools for TCP reuse, and centralizing instrumentation.
Here is how you set up a production-ready edge route handler using @typesafe-ai/sdk:
// app/api/triage/route.ts
import {
TypeSafeClient,
choice,
noul,
score,
} from "@typesafe-ai/sdk";
/**
* A single, module-scope client. The SDK reads TYPESAFE_API_KEY from the
* environment when `apiKey` is omitted.
*/
const client = new TypeSafeClient({
apiKey: process.env.TYPESAFE_API_KEY,
timeout: 8_000,
});
/** Run on the Edge Runtime so the handler is co-located with the user. */
export const runtime = "edge";
interface TriageRequest {
message: string;
}
export async function POST(request: Request): Promise<Response> {
const body = (await request.json()) as TriageRequest;
if (typeof body?.message !== "string" || body.message.length === 0) {
return Response.json({ error: "message is required" }, { status: 400 });
}
// Ask Jev three independent questions about the same state in a single round trip.
const response = await client.systemOne({
state: { ticket: { text: body.message } },
questions: {
department: choice("Which team should handle this ticket?", {
billing: "Payments, invoices, refunds, subscriptions.",
technical: "Bugs, integrations, outages, account access.",
sales: "Pricing, upgrades, plan changes, trials.",
}),
is_urgent: noul("Does this message convey urgency or time-sensitivity?"),
frustration: score("How frustrated is the customer?", [
"Calm; just stating facts.",
"Frustrated but civil.",
"Very angry, strong language, or threats to churn.",
]),
},
});
const department = response.answers.department;
const isUrgent = response.answers.is_urgent;
const frustration = response.answers.frustration;
const lowConfidence = department.confidence < 0.75;
const shouldEscalate =
lowConfidence || isUrgent.noul > 0.8 || frustration.score > 1.5;
return Response.json({
department: department.choice,
isUrgent: isUrgent.noul > 0.5,
frustration: frustration.score,
route: shouldEscalate ? "human" : "auto",
});
}
Control Theory: Timeouts, Retries, and Backoff
The retry system is where the SDK's seam engineering becomes most visible. A request might fail because the network dropped, because the server returned a 5xx, or because the whole exchange took longer than the timeout allowed.
The SDK's RetryPolicy is a typed object designed to manage these failure modes without causing cascading outages:
-
maxRetries: Limits the retry budget to prevent overwhelming an already struggling service. -
httpStatuses: Explicitly targets transient errors like 408, 429, and 500-599 while ignoring client-side configuration errors like 401 or 422. -
backoffJitter: Randomizes backoff delays to prevent the thundering herd problem where thousands of clients retry simultaneously.
const robustClient = new TypeSafeClient({
apiKey: process.env.TYPESAFE_API_KEY,
timeout: 5_000,
retry: {
maxRetries: 3,
backoffInitialMs: 200,
backoffMaxMs: 4000,
backoffJitter: 0.25,
respectRetryAfter: true,
},
});
Conclusion
The Jev TypeScript SDK is far more than a simple API wrapper. By bridging the gap between compile-time static types and runtime validation, it establishes a reliable airlock at the boundary of your system. Through type-level functions, strict configuration cascades, and intelligent retry controls, it transforms the unpredictable nature of network communication into a predictable, robust engineering discipline.
When you treat the seam as your primary engineering artifact, sub-millisecond reliability stops being a hopeful wish and becomes a structural guarantee.
Top comments (0)