TypeScript SDK
The complete TypeScript surface — every export, its signature, the two builds, and where each behaviour is documented.
On this page
The complete public surface of tokentriage for TypeScript, in one place. Behaviour is documented
on the topic pages; this is the list of what exists and what each call takes.
import * as tt from "tokentriage";
Requires Node 18 or newer. Local unreleased version 1.0.0-rc.2, contract version 1, zero runtime dependencies,
dual ESM and CommonJS.
Two builds, and which one you get
The package ships an edge-safe default build and a Node build, selected by export condition.
| Condition | Build | Async context |
|---|---|---|
node |
dist/node/ |
AsyncLocalStorage; scopes propagate |
| anything else | dist/ |
a no-op carrier; scopes run but do not propagate |
On edge runtimes, use the callback’s explicit handle:
await trace({}, async scope => {
const requestHeaders = scope.headers();
});
The handle supports nested step(), headers() and recordReceipt().
It preserves identity when passed through your application without ambient storage.
Existing callbacks accepting no argument remain valid.
Configured provider client
The unreleased TokenTriage from tokentriage/client accepts baseUrl, apiKey
and mode. await connection.openai() creates an official OpenAI client;
connection.openaiOptions() returns options for your own constructor and fetch.
metadata(response) and errorDetails(error) read response-bound information.
openai(), openaiOptions() and native() also take the saved-mode binding:
gatewayId (required), environmentId, baseUrl, credentialSource and
providerApiKey. Supplying gatewayId selects saved mode, where apiKey is sent
as the Authorization bearer and the gateway and environment selector headers are
stamped for you. Send the exported gatewayModel as the model. providerApiKey
needs credentialSource: "request_supplied"; without it, and for any apiKey
passed to a saved-mode call, the SDK throws ConfigurationError naming
provider_key_not_allowed before building a request.
See the first request and streaming examples.
Guardrail refusals
A gateway that refuses a request on a guardrail answers 403 for a policy block and 503 for a
guard whose detector could not run, with the typed refusal at the top level of the body and
x-tt-guardrail: blocked | unavailable on the response. tokentriage/client rewrites that body
into the shape the official client can read, so the thrown PermissionDeniedError or
InternalServerError already carries the written reason in its message.
guardrailRefusal(error: unknown): GuardrailRefusal | null
Reads the typed fields back off a thrown provider error. null means the failure was not a
guardrail refusal (a wrong key, a spent budget, a broken route) and is yours to handle as before.
try {
await client.chat.completions.create({ model: gatewayModel, messages });
} catch (thrown) {
const refusal = guardrailRefusal(thrown);
if (refusal === null) throw thrown;
console.log(refusal.type, refusal.binding, refusal.category, refusal.phase, refusal.message);
}
GuardrailRefusal fields, every one nullable because the wire makes it optional:
| Field | What it says |
|---|---|
type |
"guardrail_blocked" or "guardrail_unavailable"; null mid-stream |
binding |
the guardrail binding that acted |
category |
pii, secrets, injection, … when the refusal reached one |
phase |
input, output or stream |
ruleRef |
the policy rule that fired — a block only |
reason |
which detector was suspended — an unavailable refusal only |
status |
403 or 503; null mid-stream |
disclosure |
the x-tt-guardrail value, when the response carried it |
midStream |
the refusal was read from stream bytes, not from a response |
message |
the written sentence; it names what acted and never the matched content |
requestId |
x-tt-request-id, to quote |
ruleRef and reason are never both set, and neither is ever filled in for the other: a block
means “do not retry this content”, an unavailable guard means “retry”. isBlocked(refusal) and
isUnavailable(refusal) are the two predicates.
streamGuardrailRefusal(data: string | Uint8Array): GuardrailRefusal | null
A refusal that arrives mid-stream is not an error at all: the stream ends with a terminal delta
carrying finish_reason: "content_filter" (exported as STREAM_FINISH_REASON) and no
explanation, because a refusal that said why would be an oracle for the detector that fired. Pass
relayed SSE bytes, or any prefix of them, to read it; a reader that already parses events can
compare the finish reason itself. The full disclosure is on the request’s row, never on the wire.
Configuration
init(options?: InitOptions): void
Installs the process configuration. Resolution is argument, then environment, then default. The
one call that throws, with TokenTriageConfigError naming the field.
InitOptions carries baseUrl, adminUrl, log, disabled, emitTraceparent, tierHeader,
exporters, and the default dimensions agent, user, tenant, feature and tags.
check(options?: CheckOptions): Promise<CheckReport>
The first-run doctor. Admin plane only, not the data plane.
Scopes
trace<T>(options: TraceOptions, fn: (scope: Trace) => T): T and trace<T>(fn: (scope: Trace) => T): T
step<T>(options: StepOptions, fn: (scope: Step) => T): T and step<T>(fn: (scope: Step) => T): T
Callback-scoped; the callback’s return value, including a promise, passes through. There is no
decorator form and no observe — those are Python-only conveniences.
currentStep(): Step | null
The innermost open explicit step. currentTrace is not exported.
Trace exposes traceId, headers() and lastReceipt. Step adds stepId, parentId and
receipts.
Behaviour: traces, steps, and context.
Wire
headers(overrides?: CallOverrides): Record<string, string>
receipt(source: ReceiptSource, status?: number): Receipt
lastReceipt(): Receipt | null
receipt accepts a Response, a Headers, or a plain record. It never throws, and returns an
all-absent receipt rather than null.
Behaviour: from your call to the wire and reading the receipt.
Transport
fetch is a drop-in for globalThis.fetch that injects headers, opens an implicit
per-request step, and pairs the receipt.
wrapFetch(base?: FetchFn, options?: WrapFetchOptions): FetchFn builds an origin-scoped
wrapper around a fetch you supply. options.origins takes an explicit allowlist instead of
deriving one from baseUrl.
bridge() from tokentriage/adapters/otel adopts the active OpenTelemetry span’s ids. The
OpenTelemetry API is an optional peer.
Behaviour: adapters.
Ledger
import { ledger } from "tokentriage";
All async. All use the credential-containment protocol and read the token from
TOKENTRIAGE_ADMIN_TOKEN only.
ledger.fetch(receiptOrReqId, options?): Promise<RowView> — options { adminUrl?, waitS? }
ledger.fetchTrace(receiptOrTraceId, options?): Promise<TraceView>
ledger.feedback(receiptOrReqId, { ok, note?, adminUrl? }): Promise<FeedbackAck>
ledger.outcomes(receiptOrReqId, options: OutcomeOptions): Promise<OutcomeAck> — options
carry metrics, source, evaluator, evalVer, and optionally labels and adminUrl.
Errors: PlaneCollision, AdminAuthRequired, NotFound, LedgerError. feedback is the one
call that is not shielded and rejects on failure.
Behaviour: fetching a costed row and outcomes and feedback.
Types and helpers
The whole type surface is exported from the package root: Mode, StepClass, CacheStatus,
CacheControl, RefusalType, Cost, Rewrite, CacheInfo, RefusalKind, Refusal,
Receipt, ReceiptSource, InitOptions, Dims, TraceOptions, StepOptions, CallOverrides,
Trace, Step, WrapFetchOptions, StepRecord, StepStatus, Exporter, CheckFinding,
CheckReport and CheckOptions.
renderCost(cost): string produces the pinned money string for any cost state.
version: string is the package version.
Cost is a discriminated union: usd is a decimal string, reachable only after narrowing on
state === "priced". Several closed vocabularies are literal unions here that are plain strings
in Python; see parity.
Exporter
registerExporter(exporter) and DebugExporter, matching Python:
[tt] step trace=af1ee2db0e477e5b3511fefb3b291231 step=20bdde57899eb179 parent=null agent=null class=null status=error dur_us=1119.5 receipts=0
Testing and management
const { recordingFetch, assertTtHeaders } = tt.testing;
import { Client } from "tokentriage/management";
testing is built in, needing no extra. management is a separate generated client on its own
contract version. See testing instrumented code and
the management client.
Subpaths and packaging
Three import paths: tokentriage, tokentriage/adapters/otel and tokentriage/management.
The package is dual ESM and CommonJS with types nested per condition, so a CommonJS consumer gets CommonJS declarations rather than ESM declarations served under a CommonJS build. The type shape is identical across conditions; only the runtime differs.
sideEffects lists the Node entry alone, because installing the async-context carrier is an
import-time effect a bundler must not remove. The rest of the package is side-effect free and
tree-shakeable.