TypeScript SDK AI SDK Middleware
Add Solwyn budget checks and usage reporting to Vercel AI SDK language models with the TypeScript SDK middleware.
@solwyn/sdk/ai-sdk provides a Vercel AI SDK language-model middleware for generateText() and
streamText(). It checks budget authority before the model call and settles usage afterward while
leaving the model result and stream parts unchanged.
import { openai } from "@ai-sdk/openai";
import { generateText, wrapLanguageModel } from "ai";
import { createSolwynMiddleware } from "@solwyn/sdk/ai-sdk";
const solwyn = createSolwynMiddleware({
apiKey: process.env.SOLWYN_API_KEY!,
});
const model = wrapLanguageModel({
model: openai("gpt-4o"),
middleware: solwyn.middleware,
});
try {
const { text } = await generateText({
model,
prompt: "Explain vector search in two sentences.",
});
console.log(text);
} finally {
await solwyn.close();
}The ai package (>=7.0.14 <8) is an optional peer dependency of @solwyn/sdk; install the AI
SDK and the model provider package your application uses. AI SDK 7 requires Node.js 22 or later.
What the middleware does
For each buffered or streaming language-model call, the middleware:
- Reads the wrapped model's
providerandmodelIdfor attribution. - Measures the normalized prompt locally and converts its length into an input-token estimate.
- Performs a budget check before
doGenerateordoStreamruns. - Returns a hard denial as the SDK's typed error without invoking the model.
- Uses provider-reported usage for settlement when it is available.
- Falls back to explicitly estimated token counts when usage is missing or incomplete.
- Queues the settlement and content-free metadata event for delivery.
For streams, parts pass through unchanged and backpressure is preserved. Terminal usage settles the successful call. Cancellation settles the usage observed so far, and a stream error is reported once without replacing the original error.
The handle owns its budget and reporting resources. Always call close() so queued settlements
and events can flush. close() is idempotent, and the handle also implements
Symbol.asyncDispose for await using. Once close() begins, new calls reject with
SolwynError("client is closed"); buffered calls already in flight drain within
reporterShutdownDeadline, and open streams settle once from the usage observed so far. Create a
new middleware handle for later work. An abandoned stream is settled from its observed usage when
it is garbage-collected, but finalization timing is runtime-dependent, so consume or cancel
streams explicitly.
Provider attribution
Common AI SDK provider identifiers are normalized to Solwyn's provider names. This includes
OpenAI, Azure OpenAI, Anthropic (including Vertex Anthropic), Amazon Bedrock, Google, xAI,
DeepSeek, Mistral, Z.ai, Groq, Together, Fireworks, Perplexity, OpenRouter, Ollama, LM Studio,
vLLM, generic OpenAI-compatible providers, and Alibaba (attributed as qwen). Unknown provider
identifiers use openai_compatible attribution.
Vercel AI Gateway models need one extra step. The AI SDK identifies a string model as gateway,
so Solwyn derives the vendor from the model ID's vendor/model prefix. Install
@ai-sdk/gateway when using this form:
import { gateway } from "@ai-sdk/gateway";
import { generateText, wrapLanguageModel } from "ai";
import { createSolwynMiddleware } from "@solwyn/sdk/ai-sdk";
const solwyn = createSolwynMiddleware({
apiKey: process.env.SOLWYN_API_KEY!,
});
const model = wrapLanguageModel({
model: gateway("anthropic/claude-sonnet-4.5"),
middleware: solwyn.middleware,
});
try {
await generateText({ model, prompt: "Hello" });
} finally {
await solwyn.close();
}This call is attributed to anthropic, rather than grouping all gateway traffic under a generic
provider.
Agent runs and leases
The middleware reads the active Node run context installed by @solwyn/sdk/node. Calls inside a
run() callback receive its run identity and explicit run tags. Untagged, run-scoped text calls
can use local token leases; tagged or unscoped calls use per-call checks.
import { openai } from "@ai-sdk/openai";
import { streamText, wrapLanguageModel } from "ai";
import { run } from "@solwyn/sdk/node";
import { createSolwynMiddleware } from "@solwyn/sdk/ai-sdk";
const solwyn = createSolwynMiddleware({
apiKey: process.env.SOLWYN_API_KEY!,
leaseEnabled: true,
leaseOutputBoundDefault: 2048,
});
const model = wrapLanguageModel({
model: openai("gpt-4o"),
middleware: solwyn.middleware,
});
try {
await run("support-agent", async () => {
const result = streamText({ model, prompt: "Draft a reply." });
for await (const chunk of result.textStream) {
process.stdout.write(chunk);
}
});
} finally {
await solwyn.close();
}A server stop returned by the middleware's pre-flight check surfaces as RunStoppedError before
the next AI SDK model dispatch. The middleware also refuses dispatch after a concurrent stop and
aborts an in-flight stream at its next chunk boundary. A stopped stream keeps its stop after
clearRunTermination(). Run stops and caller aborts settle the stream usage observed so far,
including the part pulled at the stop boundary. The call's abortSignal also cancels budget
preflight. Local velocity detection is not supported by the middleware. See TypeScript SDK Runs for exact-ID stop
and clear semantics.
Options
createSolwynMiddleware() accepts the following camelCase options:
| Group | Options |
|---|---|
| Connection | apiKey, apiUrl, fetch |
| Budget behavior | failOpen, budgetMode, budgetCheckCacheTtl, budgetCheckTimeout |
| Leases | leaseEnabled, leaseOutputBoundDefault |
| Control-plane health | controlPlaneFailureThreshold, controlPlaneRecoveryTimeout, breakerReportingEnabled |
| Reporting | reporterBatchSize, reporterFlushInterval, reporterMaxQueueSize, reporterMaxInFlight, reporterMaxSendAttempts, reporterRetryBackoffBase, reporterRetryBackoffCap, reporterShutdownDeadline, breakerReportHeartbeat |
| Diagnostics | logger, traceCorrelation |
Unknown option names fail synchronously with ConfigurationError. The general meanings and
defaults match the corresponding client options; see
TypeScript SDK Configuration.
Scope and limitations
The middleware wraps one AI SDK language model. It does not wrap a provider client, select a
fallback client, translate requests between provider dialects, or expose the drop-in wrapper's
provider routing options. If you need Solwyn-managed provider-client failover, use the Solwyn
wrapper described in TypeScript SDK Failover.
The middleware applies to the AI SDK's language-model generate and stream paths. Provider
client media methods, provider surface coverage controls, and provider-specific helpers belong to
the drop-in wrapper rather than this entry.
breakerReportingEnabled is accepted for configuration parity, but this middleware has no
provider-client breaker snapshot to report.
The middleware receives normalized prompt and result content because that is how the AI SDK middleware contract works. It inspects content locally only to derive length estimates. Prompt and response content is never sent to Solwyn or logged by the SDK. See TypeScript SDK Privacy.