Vercel AI SDK
Use XField from the Vercel AI SDK through the OpenAI-compatible provider: generateText, streamText, tool calls and reasoning.
The AI SDK talks to XField through its OpenAI-compatible provider. You create the provider once with our base URL and your key, then pass model ids to generateText, streamText and the rest of the SDK as usual.
Install
npm install ai @ai-sdk/openai-compatible zodCreate the provider
import { createOpenAICompatible } from "@ai-sdk/openai-compatible";
export const field = createOpenAICompatible({
name: "field",
baseURL: "https://xfield.cloud.brutal.ai/api/v1",
apiKey: process.env.XFIELD_API_KEY,
includeUsage: true,
});name is a label used in errors and as the key for provider options. includeUsage asks for token counts on streamed responses. The provider calls https://xfield.cloud.brutal.ai/api/v1/chat/completions.
Generate text
import { generateText } from "ai";
import { field } from "./lib/field";
const { text, usage } = await generateText({
model: field("deepseek-v4.1-flash-1017-uncensored"),
system: "You are a terse security reviewer.",
prompt: "List three ways a JWT verifier can be misconfigured.",
});
console.log(text, usage);Stream text
import { streamText } from "ai";
import { field } from "./lib/field";
const result = streamText({
model: field("deepseek-v4.1-flash-1017-uncensored"),
prompt: "Write a short incident summary template.",
});
for await (const chunk of result.textStream) {
process.stdout.write(chunk);
}In a route handler, return result.toUIMessageStreamResponse() to feed useChat on the client.
Tool calls
Define tools with tool() and a Zod schema, and let the SDK run the loop with stopWhen.
import { generateText, tool, stepCountIs } from "ai";
import { z } from "zod";
import { field } from "./lib/field";
const { text, steps } = await generateText({
model: field("deepseek-v4.1-flash-1017-uncensored"),
tools: {
lookupHost: tool({
description: "Look up a host in the lab inventory.",
inputSchema: z.object({ hostname: z.string() }),
execute: async ({ hostname }) => ({ hostname, os: "Ubuntu 24.04", owner: "red-team-lab" }),
}),
},
stopWhen: stepCountIs(5),
prompt: "Which OS is lab-web-01 running, and who owns it?",
});
console.log(text, steps.length);stepCountIs caps the number of model calls in the loop. Tool calls work on every model.
Reasoning
The OpenAI-compatible provider reads the reasoning field that XField returns and turns it into reasoning parts. You do not need extractReasoningMiddleware; that is for models that wrap their thinking in tags inside the text.
const { text, reasoningText } = await generateText({
model: field("glm-5.3-uncensored"),
prompt: "Is this regex vulnerable to catastrophic backtracking: ^(a+)+$ ?",
providerOptions: {
field: { reasoningEffort: "high" },
},
});
console.log(reasoningText);
console.log(text);When streaming, iterate result.fullStream and handle parts of type reasoning-delta next to text-delta. Provider options go under the provider's name in camelCase; reasoningEffort becomes reasoning_effort on the request, and "none" turns thinking off.
Reasoning parts depend on your SDK version. If you do not see them, the reasoning text is still in the raw stream as delta.reasoning, and the answer itself is unaffected.
Tips
- Model ids are exact, including
-uncensored. See models. - Prompt caching is automatic. Keep the system prompt and tool definitions identical across calls so the prefix stays cached.
- Rate limits return 429. The SDK retries twice by default; raise
maxRetrieson the call for batch jobs. - Only
qwen-3.8-27b-uncensoredaccepts image parts.