import { load } from "cheerio"; import { Type } from "typebox"; import { HttpError } from "../shared/http"; import { serializeResponseWithMeta } from "../shared/serialize"; import { BaseProvider, type HttpClientFactory, type ProviderMetadata, type ProviderResult, type ToolDefinition, } from "./base"; function countOccurrences(text: string, needle: string): number { if (!needle) { return 0; } let count = 0; let index = 0; while (true) { const found = text.indexOf(needle, index); if (found === -1) { break; } count += 1; index = found + needle.length; } return count; } export class ZigProvider extends BaseProvider { public constructor(httpClientFactory: HttpClientFactory) { super(httpClientFactory); } getMetadata(): ProviderMetadata { return { name: "zig", description: "Zig programming language documentation", exposeAsTool: true, toolNames: ["zig_docs"], supportsLibrarySearch: false, requiredEnvVars: [], optionalEnvVars: [], toolTiers: { zig_docs: { tier: 5, deferRecommended: true, category: "fetch", }, }, }; } async searchLibrary(_library: string, _limit = 5): Promise { return { success: false, error: "Zig provider does not support library search", providerName: "zig", }; } protected _extractDocSections(soup: ReturnType): Array<{ title: string; summary: string; level: string }> { const sections: Array<{ title: string; summary: string; level: string }> = []; for (const heading of soup("h1,h2,h3").toArray()) { const headingEl = soup(heading); const title = headingEl.text().trim(); if (!title) { continue; } const summaryParts: string[] = []; let current = headingEl.next(); for (let i = 0; i < 2; i += 1) { if (!current.length) { break; } const tagName = String((current[0] as { tagName?: string } | undefined)?.tagName ?? "").toLowerCase(); if (tagName === "h1" || tagName === "h2" || tagName === "h3") { break; } if (tagName === "p" || tagName === "pre" || tagName === "code") { const text = current.text().trim().slice(0, 200); if (text) { summaryParts.push(text); } } current = current.next(); } sections.push({ title, summary: summaryParts.join(" "), level: String((heading as { tagName?: string }).tagName ?? "").toLowerCase(), }); } return sections; } protected _searchSections(sections: Array<{ title: string; summary: string; level: string }>, query: string): Array<{ title: string; summary: string; relevance_score: number }> { const matches: Array<{ title: string; summary: string; relevance_score: number }> = []; const queryWords = query.toLowerCase().split(/\s+/).filter(Boolean); for (const section of sections) { const titleLower = section.title.toLowerCase(); const summaryLower = section.summary.toLowerCase(); let totalScore = 0; for (const word of queryWords) { totalScore += countOccurrences(titleLower, word) * 2 + countOccurrences(summaryLower, word); } if (totalScore > 0) { matches.push({ title: section.title, summary: section.summary, relevance_score: totalScore, }); } } matches.sort((a, b) => b.relevance_score - a.relevance_score); return matches; } protected async _searchZigDocs(query: string): Promise> { const source = "https://ziglang.org/documentation/master/"; try { const client = await this.httpClient(); const response = (await client.get(source, { headers: { "User-Agent": "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/118.0 Safari/537.36" } })) as { text: string; raiseForStatus(): void; }; response.raiseForStatus(); const soup = load(response.text); const sections = this._extractDocSections(soup); const matches = this._searchSections(sections, query); return { query, source, matches: matches.slice(0, 5), total_matches: matches.length, }; } catch (error) { if (error instanceof HttpError) { return { query, error: `Failed to fetch Zig documentation: ${error}`, source, }; } return { query, error: `Error searching Zig documentation: ${error instanceof Error ? error.message : String(error)}`, source, }; } } getTools(): Record { const zigDocs: ToolDefinition = { name: "zig_docs", label: "Zig Docs", description: "Search official Zig documentation for language features, stdlib, concepts. When: Learning Zig language or finding documentation Good for: comptime, async, optionals, ArrayList, allocators, defer, error handling, build.zig Not for: Third-party Zig packages (use GitHub) Args: query=\"comptime\" Ex: zig_docs(\"defer\") → sections about defer mechanism", promptSnippet: "Search official Zig documentation for language features, stdlib, and concepts.", parameters: Type.Object({ query: Type.String({ description: "Search query" }), }), execute: async (_id: string, { query }: { query: string }) => { const result = await this._searchZigDocs(query); return serializeResponseWithMeta(result); }, }; return { zig_docs: zigDocs }; } }