Search
The API surface for leadtype/search and its subpaths. For tasks, start with
Add search, Stream AI answers,
and Agent search tools.
Vocabulary
- Chunk — a heading-aware slice of a page; each is a separate document, so results jump to the right section.
- Index (
search-index.json) — compact term postings + document/chunk refs. Loaded on every search. - Content store (
search-content.json) — chunk text, separate from the index so search stays cheap and content loads lazily. - BM25 — the ranking function. Weights: title 4×, headings 2×, body 1×, code 0.35×.
Build time — leadtype/search/node
| Name | Type | Default | Description |
|---|---|---|---|
outDir | string | - | Output root. Reads markdown under `<outDir>/docs/`. |
baseUrl | string | - | Base URL for absolute result links. |
mounts | DocsPathMount[] | - | Mounted URL prefixes — match the LLM/Agent Readability generators. |
i18n | DocsI18nConfig | - | Localization config; emits per-locale indexes. |
locale | string | - | Active locale when i18n is set. |
indexOptions | object | - | BM25 weight + tokenizer tuning. |
transformers | DocsTransformer[] | - | beforeSearchIndex / beforeSearchChunk hooks. |
Runtime query — leadtype/search
DocsSearchResult carries title, urlPath, urlWithHash, headingPath,
score, and (with content) excerpt. The runtime is edge-safe — no Node APIs.
Content readers
Use readDocsContentFile for a whole page, readDocsContentChunk when a result
already named the heading.
Framework hooks
| Import | Export | Returns |
|---|---|---|
leadtype/search/client | createSearchClient(collection, opts?) | { search(q), preload() } |
leadtype/search/react | useLeadtypeSearch(collection, opts?) | { query, search, results, status, error } |
leadtype/next/client | useLeadtypeSearch(collection, opts?) | same (Next App Router) |
leadtype/search/vue | useLeadtypeSearch(collection, opts?) | refs: { query, results, status, error, search, preload } |
leadtype/search/svelte | createLeadtypeSearch(collection, opts?) | stores: { query, results, status, error, search, preload } |
status is "idle" | "loading" | "ready" | "error". Options: debounceMs
(default 120), plus indexUrl / contentUrl / limit / fetch overrides.
Collection "docs" resolves to /docs/search-index.json + /docs/search-content.json.
Answer context & streaming
The system message constrains the model to the retrieved context and [1]-style
citations. streamDocsAnswer wraps this per runtime and returns
{ response: Response, sources: DocsAnswerSource[] } (plain-text stream + separate citations):
| Import | Model argument | Other options |
|---|---|---|
leadtype/search/vercel | model: LanguageModel | string | maxOutputTokens, timeout, providerOptions, tools |
leadtype/search/tanstack | adapter: AnyTextAdapter | maxTokens, modelOptions, tools |
leadtype/search/cloudflare | adapter from createCloudflareDocsAdapter(...) | maxTokens |
createCloudflareDocsAdapter({ provider, model, options }) — provider is one
of anthropic | gemini | grok | openai | openrouter | workers-ai.
Bash tools — leadtype/search/bash
Read-only virtual /docs with ls, cat, find, grep, rg. No network, no
execution, no writes.
Endpoint guards — leadtype/search
| Helper | Purpose |
|---|---|
validateDocsQuery(input, { maxChars, fieldName? }) | Trim and cap query text. |
readJsonWithLimit(request, { maxBytes }) | Reject oversized JSON bodies before parse. |
getClientIdentifier(request, { fallback? }) | Read proxy IP headers for a limiter key. |
createMemoryRateLimiter({ limit, windowMs }) | In-memory RateLimiter (demos). |
Adapt RateLimiter to a shared store (Redis, Vercel KV, Cloudflare KV, Durable
Objects) in production. Defaults live in docsSearchDefaults:
| Key | Default |
|---|---|
maxQueryChars | 400 |
askMaxQueryChars | 600 |
maxContextChars | 12000 |
maxSources | 6 |
searchLimit | 8 |
maxBodyBytes | 16 KB |
When to add embeddings
Start with the local index — static, cheap, edge-safe, and precise for API names, config keys, error messages, and paths. Add embeddings only when users search with vocabulary the docs don't use, or the corpus grows past tens of thousands of chunks. Even then, keep the lexical index for exact matches and layer embeddings on top.