JavaScript and TypeScript SDK for TypeSafe AI.
Install the SDK (Node.js 22 or newer):
npm install @typesafe-ai/sdkSet TYPESAFE_API_KEY in your environment, then create and use the client:
import { choice, TypeSafeClient } from "@typesafe-ai/sdk";
const client = new TypeSafeClient();
const response = await client.systemOne({
state: { document: "I was charged twice. Please fix this ASAP." },
questions: {
category: choice("What is this ticket about?", {
billing: null,
technical: null,
other: null,
}),
},
});
console.log(response.answers.category.choice);Answer types are inferred from your questions. The package includes ESM, CommonJS, and TypeScript declarations.
Create an image with image() from file bytes or a Blob such as a File, or with
Image.fromBase64() from base64 text. Images must be PNG or JPEG; the API detects the format
from the file bytes and rejects other formats, such as WebP and GIF, with a BadRequestError.
There are three ways to use an image in a request, and they can be combined.
Image support is experimental. Opt in by sending the TypeSafe-Danger-Zone-Images: true
header:
const client = new TypeSafeClient({
defaultHeaders: { "TypeSafe-Danger-Zone-Images": "true" },
});To send it with a single request instead, pass it in the call's headers option.
Put the image anywhere in state, question instructions, or criteria:
import { openAsBlob } from "node:fs";
import { image, noul, score, TypeSafeClient } from "@typesafe-ai/sdk";
const client = new TypeSafeClient();
const photo = image(await openAsBlob("returns/RMA-1042.jpg"));
const { answers } = await client.systemOne({
state: { claim: "The mug arrived with a chipped rim.", photo },
questions: {
damaged: noul("Is the product in the photo damaged?"),
severity: score("How severe is the damage?", ["none", "cosmetic", "unusable"]),
},
});
console.log(answers.damaged.noul, answers.severity.score);Tag a template literal with withAssets to reference images inside text. This route
handler checks a seller's upload against the catalog photo:
import { choice, image, noul, TypeSafeClient, withAssets } from "@typesafe-ai/sdk";
import { catalogPhotoUrl } from "./catalog";
const client = new TypeSafeClient();
export async function POST(request: Request): Promise<Response> {
const form = await request.formData();
const upload = image(form.get("photo") as File);
const catalog = image(await (await fetch(catalogPhotoUrl(String(form.get("sku"))))).blob());
const { answers } = await client.systemOne({
state: withAssets`A seller uploaded ${upload} for a listing whose catalog photo is ${catalog}.`,
questions: {
sameProduct: noul(withAssets`Does ${upload} show the same product as ${catalog}?`),
quality: choice(withAssets`Is ${upload} usable as a listing photo?`, {
usable: null,
blurry: null,
cropped: null,
notAProduct: null,
}),
},
});
return Response.json({ sameProduct: answers.sameProduct.noul, quality: answers.quality.choice });
}Each image is sent once, however many times it is used. An image in an untagged template
literal, such as `Compare ${upload}`, rejects the call with a TypeSafeError before the
request is sent.
To write a list of images, join it with withAssets.join, which works like Array.prototype.join:
const uploads = (form.getAll("photos") as File[]).map((file) => image(file));
const state = withAssets`The seller uploaded ${withAssets.join(uploads, ", ")}.`;Pass images in assets, keyed by a reference of your choice, to reference them from plain
strings, such as prompts stored in configuration:
import { readFile } from "node:fs/promises";
import { image, noul, TypeSafeClient } from "@typesafe-ai/sdk";
const client = new TypeSafeClient();
const { answers } = await client.systemOne({
state: "A customer returned <delivered>. The catalog photo is <catalog>.",
questions: { matches: noul("Is <delivered> the product shown in <catalog>?") },
assets: {
"<delivered>": image(await readFile("returns/RMA-1042.jpg")),
"<catalog>": image(await readFile("catalog/mug.png")),
},
});
console.log(answers.matches.noul);Use distinct delimiters, such as <delivered>, so references don't match ordinary text.
An image in assets that is also placed in content or in withAssets text is written there as
its name. Every name in assets must appear in the request; otherwise the call rejects with a
TypeSafeError before sending.
Ask a box question to locate what its instructions describe. The answer's box holds the
edges as fractions of the image's height (top, bottom) and width (left, right), and its
confidence is null when the API doesn't report one. Use the jev-preview model for box
questions:
import { openAsBlob } from "node:fs";
import { box, image, TypeSafeClient } from "@typesafe-ai/sdk";
const client = new TypeSafeClient();
const { answers } = await client.systemOne({
state: image(await openAsBlob("returns/RMA-1042.jpg")),
questions: { damage: box("The damaged area of the product") },
model: "jev-preview",
});
const { top, bottom, left, right } = answers.damage.box;Requests with images are sent as multipart/form-data, with each image's file bytes in its
own part.
The client compresses request bodies of 1 KiB or more with zstd, which reduces upload size and
time, especially for large states. Smaller bodies fit in a single TCP segment, so they are sent
uncompressed. zstd requires Node.js 22.15 or newer; on other runtimes, all bodies are sent
uncompressed. Pass compression: false to send all bodies uncompressed, for example to an API
that doesn't accept compressed requests:
const client = new TypeSafeClient({ compression: false });Learn what TypeSafe can do in the TypeSafe docs. See the SDK's client and types for API options and defaults.