TypeScript SDK Errors
Catch typed budget, run-control, provider, configuration, coverage, and failover errors from the TypeScript SDK.
Every public Solwyn SDK error extends the built-in Error through SolwynError. Catch concrete
classes when your application has a recovery action, and use SolwynError as the final SDK-wide
boundary. Provider SDK errors remain provider errors.
Catch errors with instanceof
TypeScript catch values should be treated as unknown. Narrow by class rather than parsing an error
message:
import {
BudgetExceededError,
ProviderUnavailableError,
RunStoppedError,
Solwyn,
SolwynError,
} from "@solwyn/sdk";
import OpenAI from "openai";
const solwynApiKey = process.env.SOLWYN_API_KEY;
if (!solwynApiKey) throw new Error("SOLWYN_API_KEY is required");
const client = new Solwyn(new OpenAI(), { apiKey: solwynApiKey });
try {
await client.chat.completions.create({
model: "gpt-4o",
messages: [{ role: "user", content: "Process the next work item." }],
});
} catch (error: unknown) {
if (error instanceof RunStoppedError) {
console.error(`Run ${error.agentRunId} stopped by ${error.source}: ${error.reason}`);
} else if (error instanceof BudgetExceededError) {
console.error(`Budget ${error.currentUsage} / ${error.budgetLimit}`);
} else if (error instanceof ProviderUnavailableError) {
console.error("No provider could be dispatched", error.attempted);
} else if (error instanceof SolwynError) {
console.error(error.name, error.message);
} else {
throw error; // provider error or application error
}
} finally {
await client.close();
}RunStoppedError is deliberately not a BudgetExceededError; both are direct concrete branches
under SolwynError. Keep the run-stop handler separate so a generic budget retry cannot swallow an
operator or velocity stop.
Error hierarchy
Error
└─ SolwynError
├─ BudgetExceededError
├─ RunStoppedError
├─ ProviderUnavailableError
├─ ConfigurationError
├─ UnsupportedSurfaceError
├─ UntrackedSpendSurfaceError
├─ CoverageMismatchError
├─ SurfaceInspectionError
├─ UntranslatableRequestError
└─ UntranslatableModelErrorThe SDK can also throw SolwynError directly for a small number of invalid runtime invariants:
- malformed per-call or run tag values, or invalid run handles and names;
- an intercepted call made after
close()begins, which rejects with"client is closed"; - a LangChain or OpenAI Agents path that the attribution recipe does not admit. These refusals fail closed, and LangChain refusals are marked non-retryable for LangChain.
A final instanceof SolwynError branch covers those cases.
Concrete error reference
BudgetExceededError
Thrown before provider dispatch when an ordinary budget decision blocks the call:
- a live Cloud hard denial;
- an applicable hard denial retained through a Cloud outage; or
- local fail-closed enforcement when
failOpen: false, including the no-prior-limit case.
| Field | Type | Meaning |
|---|---|---|
projectId | string | null | Cloud project ID, or null when the outage path has no project snapshot. |
budgetLimit | number | Limit attached to the decision; 0 when unknown. |
currentUsage | number | Usage attached to the decision; 0 when unknown. |
estimatedCost | number | Diagnostic $0.00003 × estimated input tokens, not provider pricing. |
budgetPeriod | string | Cloud denied_by_period, or "unknown" for a local decision without one. |
mode | string | Mode attached to the live or local decision. |
See budgets for live-mode authority, retained denials, and outage order.
RunStoppedError
Thrown when the active exact run ID was stopped by a Cloud directive or deny-eligible local velocity
rule. It blocks later dispatches and aborts an active stream at the next raw provider-chunk boundary,
regardless of failOpen or budgetMode.
| Field | Type | Meaning |
|---|---|---|
agentRunId | string | Exact stopped run ID. |
reason | string | Bounded structural stop reason. |
source | "server" | "local_velocity" | Where the stop originated. |
ProviderUnavailableError
Thrown when no candidate can be dispatched—for example, every applicable circuit is open, candidate selection returns no usable target, or the failover window expires before another hop may start.
| Field | Type | Meaning |
|---|---|---|
provider | string | null | Provider on the legacy provider/state construction shape. |
circuitState | string | null | Circuit state on the legacy construction shape. |
attempted | string[] | null | Ordered provider-name list when the router can supply one. |
If provider attempts actually execute and the chain is exhausted, the final provider's original
exception is re-thrown instead of being replaced with ProviderUnavailableError. See
failover.
ConfigurationError
Thrown synchronously or before dispatch when the SDK cannot safely honor a configuration or request.
Its field: string | null identifies the rejected option or structural field when available.
Constructor and environment schema errors use the internal snake_case name, such as
budget_check_timeout.
Common cases include:
- an absent or malformed Solwyn project key;
- an unknown constructor option, a non-function
fetch, a partiallogger, or aselectionPolicywithoutorder(); - a
providerpin that conflicts with the provider client's family; - wrapping an already wrapped client;
- a malformed fallback tuple, tag map, timeout, or coverage acknowledgment;
- a request-options body/query override that could differ from the request admitted by the budget check;
- a blocked Bedrock
InvokeModel*command (includingInvokeModelWithBidirectionalStreamCommand),StartAsyncInvokeCommand, or a callback-style interceptedsendcall; - unsupported Responses shapes such as background creation or streaming
responses.parse; coverage(...)called on an object that is not a Solwyn wrapper (field: "client");- LangChain or OpenAI Agents attribution without
@solwyn/sdk/nodeloaded (field: null), a raw provider client passed instead of a Solwyn wrapper (field: "client"), an invalid LangChainnamePrefix, or an invalid AgentsapiorOpenAIProvideroption.
import { ConfigurationError, Solwyn } from "@solwyn/sdk";
import OpenAI from "openai";
try {
new Solwyn(new OpenAI({ apiKey: "test-provider-key" }), {
apiKey: "not-a-project-key",
});
} catch (error: unknown) {
if (!(error instanceof ConfigurationError)) throw error;
console.error("Invalid field:", error.field);
}UnsupportedSurfaceError
Thrown when the selected known provider adapter cannot safely dispatch a tracked surface. Fields:
surface: string and provider: string. One example is reaching a tracked embeddings resource on a
dialect whose adapter cannot serve embeddings.
UntrackedSpendSurfaceError
Thrown before provider I/O when an unknown or explicitly unmetered spend surface is reached with
onUnmetered: "raise".
| Field | Type |
|---|---|
surface | string |
token | string | null |
provider | string |
clientShape | Provider surface-shape label |
kind | Effective surface kind |
capabilityScope | Capability-scope label or null |
driftedFromRuleId | Reviewed rule ID or null |
Since 0.2.0, legacy raw-request and client escapes on native OpenAI, Azure OpenAI, Anthropic, and
Together clients, OpenAI Realtime session and client-secret creation and call acceptance, and
Google GenAI Live and ApiClient transports report kind: "unmetered_spend" with explicit
capability scopes instead of "unknown".
Use providers for the reviewed surface boundary and
configuration for onUnmetered and acknowledgeUntracked.
CoverageMismatchError
Thrown by coverage(client).expect(...) when the observed provider graph differs from a pinned
expectation. differences is a non-empty readonly string[] describing the structural drift.
SurfaceInspectionError
Thrown when coverage(...) cannot safely inspect part of a provider client. It carries
path: string, a closed structural stage label, and causeType: string; it does not retain the
original thrown value or its message.
UntranslatableRequestError
Thrown before target-provider I/O when a cross-dialect failover request falls outside the minimal
translation subset. It carries only source, target, and feature structural labels. Translation
errors abort the whole fallback chain.
UntranslatableModelError
Thrown before target-provider I/O when a cross-dialect fallback does not have a usable target model.
It carries model and provider. See failover.
Provider errors pass through
Solwyn classifies provider failures to decide breaker health and whether another hop is safe, but it
does not replace request-shaped or terminal provider errors. OpenAI RateLimitError, Anthropic
APIError, AWS service exceptions, Google errors, and other provider values can therefore reach your
existing handlers unchanged.
If you configure fallbacks, a failure may be consumed when policy permits another attempt. If the
router must stop, it rethrows the original provider value; treat non-Solwyn catches as unknown and
use the provider SDK's own type guards.
Control-plane and reporting failures
Not every operational failure becomes an exception on the model-call path:
- Budget-check outages follow
failOpen, last-known-limit, lease, and retained-authority rules. - Metadata and settlement delivery is queued and retried; a reporting outage does not replace a successful provider response.
- Per-event ingest rejections are logged rather than thrown from an already completed LLM call. Ordinary rejected events are terminal; eligible denial receipts are folded into bounded aggregates and can be replayed after recovery.
- Unmetered surfaces warn once by default; choose
onUnmetered: "raise"to make them fail closed.
Inject a logger to route those diagnostics into your application's logging system. Use
offline testing to exercise denial, outage, stop, and ingest-rejection
paths without real network traffic, and privacy for the structural-only
error and diagnostic boundary.