Skip to content
NewDeepSeek V4.1 Flash 1017 Uncensored is live, with 1M tokens of context.Read the launch
SDKs and frameworks

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

Shell
npm install ai @ai-sdk/openai-compatible zod

Create the provider

lib/field.ts
TypeScript
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

TypeScript
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

TypeScript
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.

TypeScript
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.

TypeScript
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 maxRetries on the call for batch jobs.
  • Only qwen-3.8-27b-uncensored accepts image parts.