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.
Qwen / DashScope
Section titled “Qwen / DashScope”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 / Moonshot
Section titled “Kimi / Moonshot”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.
| Factory | Model arguments / tool name | REST request | Model result |
|---|---|---|---|
kimiSearchTool({ apiKey }) | { query } / kimi_search | POST /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_pro | POST /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_fetch | POST /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.
Request usage and prices
Section titled “Request usage and prices”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.