drex-sdk
v0.1.0
Published
TypeScript SDK for the Drex API: calibrated decisions and document jobs
Maintainers
Readme
drex-sdk (TypeScript)
The official TypeScript client for the Drex API: calibrated answers to typed questions (POST /v1/systemone) and document jobs — parse, split, classify, extract and ground.
The client is built on the TypeSafe AI SDK (MIT; see THIRD_PARTY_NOTICES.md): systemOne, noul, choice, score, the inferred answer types, RetryPolicy and the error classes match @typesafe-ai/sdk, so TypeSafeClient code runs on DrexClient after renaming the import.
npm install drex-sdk
export DREX_API_KEY=nace_sk_...Node 20 or later. Create a key on the API keys page and keep it on a server: it spends your account's credit.
Decisions
import { choice, DrexClient, noul, score } from "drex-sdk";
const client = new DrexClient(); // reads DREX_API_KEY, DREX_BASE_URL, DREX_DEFAULT_MODEL, DREX_LOG_LEVEL
const { answers } = await client.systemOne({
state: "I was charged twice for my March invoice.",
questions: {
wants_refund: noul("Is the customer asking for a refund?"),
topic: choice("Which topic is it?", { billing: null, shipping: null, other: null }),
urgency: score("How urgent is it?", ["low", "medium", "high"]),
},
});
answers.wants_refund.noul; // number
answers.topic.choice; // "billing" | "shipping" | "other"Documents
import { readFile } from "node:fs/promises";
import { workspaceFile } from "drex-sdk";
// An https:// URL whose last path segment names the file: its extension picks the parser.
const parsed = await client.documents.parse({
source: "https://example.com/invoice.pdf",
output: { formats: ["markdown"] },
});
const job = await client.jobs.wait(parsed.job_id);
// A URL that does not end in a file name needs a file_name.
await client.documents.parse({
source: { type: "url", url: "https://arxiv.org/pdf/1706.03762", file_name: "attention.pdf" },
});
const file = await client.documents.upload(await readFile("invoice.pdf"), {
file_name: "invoice.pdf",
on_conflict: "new_version", // replace the file at that path instead of failing with path_conflict
});
const extracted = await client.documents.extract({
source: workspaceFile(file),
schema: { type: "object", properties: { total: { type: "number" } } },
wait_seconds: 60,
});
// Or save the schema once and name it.
const saved = await client.extractionSchemas.create({
name: "Invoice",
schema: { type: "object", properties: { total: { type: "number" } } },
});
await client.documents.extract({ source: workspaceFile(file), schema_id: saved.schema_id });Uploads go straight to the document service with a one-use token, never with your API key or the SDK's headers; files of 32 MiB and more go up in parts. A different file already at the path fails with path_conflict unless you pass on_conflict: "new_version": a ConflictError (409) for a file sent whole, a DrexError naming the failed upload job for one sent in parts. One sent in parts that is still assembling after ten minutes throws a DrexError: upload the same file to the same path again later to get it. To run a resumable upload yourself, use createUploadSession, uploadPart, completeUploadSession, getUploadSession (which parts landed) and abortUploadSession; createUploadGrant mints a token for another client to upload with. Every document job and upload session create carries an Idempotency-Key, so retries never start a second job or session. The /v1 routes send no Access-Control-Allow-Origin header, so a browser can't call them: keep the client on your server, let a browser upload with a grant your server mints, and hand it signed links from jobs.fileLink.
client.jobs has get, list, iter, wait, delete, events, getRequest (the request a job ran under), rows (a parsed sheet's rows, a page at a time), fileLink (a signed link to a stored file), and fetchFile and download for any job file, including full Markdown that is still being prepared. client.extractionSchemas has list, iter, get, create, listVersions and iterVersions (oldest first), getVersion and createVersion; creates are not retried unless you pass retry, since a retry could save a second schema.
Errors and retries
Every failure throws a DrexError; HTTP failures are APIError subclasses (BadRequestError, AuthenticationError, InsufficientCreditError, PermissionDeniedError, NotFoundError, ConflictError, UnprocessableEntityError, RateLimitError, InternalServerError, OverloadedError) with status, body, requestId, type, code, issues, detail and serverRetryable. jobs.wait throws JobFailedError or JobTimeoutError. The client retries 408, 429, 5xx, dropped connections and timeouts (2 retries within a 30 s budget, 500 ms doubling to 5 s, honoring retry-after-ms), except an error the server marks "retryable": false. The budget counts from the first attempt, so with the defaults an attempt that fails after 30 s, such as one that hit the 60 s timeout, is not retried: raise retry.timeoutMs above your attempts and waits, or pass null. jobs.events is not retried, and saved-schema creates aren't unless you pass retry.
// Up to 5 attempts of up to 90 s each, and the waits between them.
const client = new DrexClient({ timeout: 90_000, retry: { maxRetries: 4, timeoutMs: 500_000 } });logLevel: "debug" (or DREX_LOG_LEVEL=debug) logs headers and bodies, with the API key and upload tokens redacted.
Full guides: decisions and documents.
License
Apache-2.0
