Skip to content

OpenAI-compatible native search

Use OpenAISDK with a vendor base URL and an explicit provider label. Protocol compatibility does not make native search tools interchangeable. These shapes were checked against vendor documentation on 2026-10-18; model, region and account availability still follow the vendor’s matrix.

Alibaba’s official web-search guide shows a top-level enable_search request flag on the OpenAI-compatible API. Use provider: "qwen" and providerOptions.qwen.enableSearch, or the equivalent "dashscope" label and options key. This is a request flag, not a provider tool.

import { OpenAISDK, type QwenCallOptions } from "@warlock.js/ai-openai";
const qwen = new OpenAISDK({
apiKey: process.env.DASHSCOPE_API_KEY,
baseURL: process.env.DASHSCOPE_COMPATIBLE_BASE_URL,
provider: "qwen",
});
const searchOptions: QwenCallOptions = { enableSearch: true };
const model = qwen.model({ name: "qwen-plus" });
await model.complete([{ role: "user", content: "Find recent news" }], {
providerOptions: { qwen: searchOptions },
});

The flag is sent in both completion and streaming requests; false is also forwarded. Other provider labels ignore these keys. Alibaba documents that search-source responses are unavailable over this compatible route, and newer multimodal models may require its native multimodal API or Responses API. Those routes are unsupported by this adapter. No search request count or fee is invented from enableSearch or answer text.

Kimi’s legacy $web_search builtin retires on 20 October 2026 and is unsupported. The adapter rejects that declaration before making a request. Use the standalone REST tools recommended in the official migration notice. Kimi models use OpenAISDK with provider: "moonshot" (or "kimi").

import { ai } from "@warlock.js/ai";
import { OpenAISDK, kimiSearchTool, kimiFetchTool } from "@warlock.js/ai-openai";
const config = {
apiKey: process.env.MOONSHOT_API_KEY ?? "",
baseURL: "https://api.moonshot.ai/v1",
};
const moonshot = new OpenAISDK({ ...config, provider: "moonshot" });
const assistant = ai.agent({
model: moonshot.model({ name: "kimi-k3" }),
maxTrips: 4,
tools: [
kimiSearchTool({ ...config, mode: "pro", limit: 5, sites: ["moonshot.ai"] }),
kimiFetchTool({ ...config, maxContentChars: 12_000 }),
],
});
const result = await assistant.execute("Find recent research on prompt caching");
console.log(result.usage.serverToolUse, result.usage.cost?.serverTools);

These are ordinary client function tools: pass them directly in tools, without ai.providerTool(). They can also serve agents using other model providers. The configured key stays on the server; Authorization: Bearer authenticates the REST requests. Helpers default to https://api.moonshot.ai/v1; pass a baseURL explicitly for a proxy. fetch, timeout (transport milliseconds) and defaultHeaders follow the upstream OpenAI client options. Automatic transport retries are disabled to avoid repeating a potentially billed call.

FactoryModel arguments / tool nameREST requestModel result
kimiSearchTool({ apiKey }){ query } / kimi_searchPOST /v1/tools/search: { text_query, limit, timeout_seconds, include_content }{ results: [{ title, url, snippet, siteName, date, authority, text? }], truncated }
kimiSearchTool({ apiKey, mode: "pro" }){ query } / kimi_search_proPOST /v1/tools/search_pro: { text_query, limit, timeout_seconds, sites?, time_window? }Same envelope; each result has chunks: [{ text, score }] instead of text
kimiFetchTool({ apiKey }){ url } / kimi_fetchPOST /v1/tools/fetch: { url }{ url, title, markdown, truncated }

The vendor search response is { search_results: [...] }; fetch returns { url, title, markdown }. The helpers keep source information, omit search icons/MIME fields, and cap snippet/page/chunk text across the response to maxContentChars (default 12,000). truncated reports trimming; URL/title and source metadata stay available. Basic omits full page text unless includeContent: true; Pro retains ranked chunks and scores.

Configure limit from 1–20 (default 5), timeoutSeconds from 1–60 (default 30). Basic alone accepts includeContent; Pro alone accepts sites (at most five, OR semantics, no whitespace or parentheses) and timeWindow: { start?, end? }. Kimi validates dates as YYYY, YYYY-MM or YYYY-MM-DD, normalizes missing month/day to the first day of that period, and requires start ≤ end. Fetch accepts only HTTP/HTTPS URLs. The service applies its own URL risk controls; a 403 security_risk or 404 markdown_not_found fails the tool. Rate and concurrency limits surface as 429; no fixed account-independent QPS is assumed. See the official Basic, Pro and Fetch contracts.

Cancellation flows from the agent signal or createToolContext({ signal }). invoke() returns a ToolExecutionError with the provider error as cause: ProviderAuthError for authentication/account failures, ProviderRateLimitError for 429, ProviderTimeoutError for timeouts, InvalidRequestError for rejected requests, and ProviderError for other failures. Caller aborts use AgentCancelledError as the cause. The low-level execute() throws the mapped error and returns data alone; use invoke() to receive usage and reports.

Official pricing, verified 2026-10-18, lists USD $0.002 per Basic search, $0.003 per Pro search and $0.002 per fetch, excluding taxes. Search bills only HTTP 200 with nonempty search_results; fetch bills only HTTP 200 with nonblank markdown. Empty responses record zero billable units. Failed/aborted requests leave usage unreported when the response cannot establish billing.

invoke() records usage.serverToolUse.kimiSearchRequests, kimiSearchProRequests or kimiFetchRequests, and also the common webSearchRequests / webFetchRequests counter. Costs use only the namespaced keys in usage.cost.serverTools, preventing duplicate fees. These counts come from actual REST responses, never from the model’s function-call count. Agent and tool reports aggregate them in both completion and streaming runs; model tokens for the returned content are billed separately in later chat calls.

KIMI_SERVER_TOOL_PRICING exports the verified per-call rates. Helpers apply those rates by default; pricing: {} leaves calls unpriced, and pricing: { kimiSearchProRequests: rate } overrides a contract rate. The keys are compatible with ModelPricing.serverTools; do not price both the generic and namespaced counters when recomputing costs yourself.

xAI’s documented native search uses POST /v1/responses with { type: "web_search" }. XaiSDK currently wraps Chat Completions: native web_search / x_search definitions throw a clear InvalidRequestError before transport. Use the vendor’s Responses SDK for that feature. Setting api: "responses" on a compatible OpenAISDK is also unsupported; the framework’s Responses adapter remains direct-OpenAI-only.