feat(coding-agent): removed Kagi summarizer integration from fetch tool
- Removed Kagi Universal Summarizer integration from fetch tool and YouTube scraper. - Removed `fetch.useKagiSummarizer` configuration setting from settings schema. - Simplified renderHtmlToText() and renderUrl() functions by removing Kagi summarization fallback logic. - Fixed indentation inconsistencies in test files from tabs to spaces.
This commit is contained in:
@@ -1,14 +1,21 @@
|
||||
# Changelog
|
||||
|
||||
## [Unreleased]
|
||||
### Fixed
|
||||
|
||||
- Canonicalized bash executor working directories before handing them to brush so `pwd` stays aligned with canonical Git worktree paths in symlinked workspaces
|
||||
|
||||
### Added
|
||||
|
||||
- Added `#`-triggered prompt action suggestions in the editor, with keybinding hints for line navigation and prompt copy actions
|
||||
|
||||
### Removed
|
||||
|
||||
- Removed Kagi Universal Summarizer integration from fetch tool—HTML rendering now uses jina, trafilatura, and lynx only
|
||||
- Removed `fetch.useKagiSummarizer` setting
|
||||
- Removed Kagi summarization from YouTube video handling
|
||||
|
||||
### Fixed
|
||||
|
||||
- Canonicalized bash executor working directories before handing them to brush so `pwd` stays aligned with canonical Git worktree paths in symlinked workspaces
|
||||
|
||||
## [13.9.10] - 2026-03-08
|
||||
### Added
|
||||
|
||||
@@ -5792,4 +5799,4 @@ Initial public release.
|
||||
- Git branch display in footer
|
||||
- Message queueing during streaming responses
|
||||
- OAuth integration for Gmail and Google Calendar access
|
||||
- HTML export with syntax highlighting and collapsible sections
|
||||
- HTML export with syntax highlighting and collapsible sections
|
||||
@@ -541,15 +541,6 @@ export const SETTINGS_SCHEMA = {
|
||||
default: true,
|
||||
ui: { tab: "tools", label: "Enable Fetch", description: "Enable the fetch tool for URL fetching" },
|
||||
},
|
||||
"fetch.useKagiSummarizer": {
|
||||
type: "boolean",
|
||||
default: true,
|
||||
ui: {
|
||||
tab: "tools",
|
||||
label: "Use Kagi in Fetch",
|
||||
description: "Use Kagi Universal Summarizer when rendering HTML in fetch",
|
||||
},
|
||||
},
|
||||
"web_search.enabled": {
|
||||
type: "boolean",
|
||||
default: true,
|
||||
|
||||
@@ -3884,18 +3884,11 @@ export class AgentSession {
|
||||
let attempt = 0;
|
||||
while (true) {
|
||||
try {
|
||||
compactResult = await compact(
|
||||
preparation,
|
||||
candidate,
|
||||
apiKey,
|
||||
undefined,
|
||||
autoCompactionSignal,
|
||||
{
|
||||
promptOverride: hookPrompt,
|
||||
extraContext: hookContext,
|
||||
remoteInstructions: this.#baseSystemPrompt,
|
||||
},
|
||||
);
|
||||
compactResult = await compact(preparation, candidate, apiKey, undefined, autoCompactionSignal, {
|
||||
promptOverride: hookPrompt,
|
||||
extraContext: hookContext,
|
||||
remoteInstructions: this.#baseSystemPrompt,
|
||||
});
|
||||
break;
|
||||
} catch (error) {
|
||||
if (autoCompactionSignal.aborted) {
|
||||
|
||||
@@ -15,7 +15,6 @@ import { renderStatusLine } from "../tui";
|
||||
import { CachedOutputBlock } from "../tui/output-block";
|
||||
import { formatDimensionNote, resizeImage } from "../utils/image-resize";
|
||||
import { ensureTool } from "../utils/tools-manager";
|
||||
import { summarizeUrlWithKagi } from "../web/kagi";
|
||||
import { specialHandlers } from "../web/scrapers";
|
||||
import type { RenderResult } from "../web/scrapers/types";
|
||||
import { finalizeOutput, loadPage, MAX_OUTPUT_CHARS } from "../web/scrapers/types";
|
||||
@@ -466,13 +465,12 @@ function parseFeedToMarkdown(content: string, maxItems = 10): string {
|
||||
}
|
||||
|
||||
/**
|
||||
* Render HTML to markdown using kagi, jina, trafilatura, lynx (in order of preference)
|
||||
* Render HTML to markdown using jina, trafilatura, lynx (in order of preference)
|
||||
*/
|
||||
async function renderHtmlToText(
|
||||
url: string,
|
||||
html: string,
|
||||
timeout: number,
|
||||
useKagiSummarizer: boolean,
|
||||
userSignal?: AbortSignal,
|
||||
): Promise<{ content: string; ok: boolean; method: string }> {
|
||||
const signal = ptree.combineSignals(userSignal, timeout * 1000);
|
||||
@@ -484,20 +482,7 @@ async function renderHtmlToText(
|
||||
signal,
|
||||
};
|
||||
|
||||
// Try Kagi Universal Summarizer first (if enabled and KAGI_API_KEY is configured)
|
||||
if (useKagiSummarizer) {
|
||||
try {
|
||||
const kagiSummary = await summarizeUrlWithKagi(url, { signal });
|
||||
if (kagiSummary && kagiSummary.length > 100 && !isLowQualityOutput(kagiSummary)) {
|
||||
return { content: kagiSummary, ok: true, method: "kagi" };
|
||||
}
|
||||
} catch {
|
||||
// Kagi failed, continue to next method
|
||||
signal?.throwIfAborted();
|
||||
}
|
||||
}
|
||||
|
||||
// Try jina next (reader API)
|
||||
// Try jina first (reader API)
|
||||
try {
|
||||
const jinaUrl = `https://r.jina.ai/${url}`;
|
||||
const response = await fetch(jinaUrl, {
|
||||
@@ -623,13 +608,7 @@ async function handleSpecialUrls(
|
||||
/**
|
||||
* Main render function implementing the full pipeline
|
||||
*/
|
||||
async function renderUrl(
|
||||
url: string,
|
||||
timeout: number,
|
||||
raw: boolean,
|
||||
useKagiSummarizer: boolean,
|
||||
signal?: AbortSignal,
|
||||
): Promise<FetchRenderResult> {
|
||||
async function renderUrl(url: string, timeout: number, raw: boolean, signal?: AbortSignal): Promise<FetchRenderResult> {
|
||||
const notes: string[] = [];
|
||||
const fetchedAt = new Date().toISOString();
|
||||
if (signal?.aborted) {
|
||||
@@ -973,7 +952,7 @@ async function renderUrl(
|
||||
}
|
||||
|
||||
// 5E: Render HTML with lynx or html2text
|
||||
const htmlResult = await renderHtmlToText(finalUrl, rawContent, timeout, useKagiSummarizer, signal);
|
||||
const htmlResult = await renderHtmlToText(finalUrl, rawContent, timeout, signal);
|
||||
if (!htmlResult.ok) {
|
||||
notes.push("html rendering failed (lynx/html2text unavailable)");
|
||||
const output = finalizeOutput(rawContent);
|
||||
@@ -1113,8 +1092,7 @@ export class FetchTool implements AgentTool<typeof fetchSchema, FetchToolDetails
|
||||
throw new ToolAbortError();
|
||||
}
|
||||
|
||||
const useKagiSummarizer = this.session.settings.get("fetch.useKagiSummarizer");
|
||||
const result = await renderUrl(url, effectiveTimeout, raw, useKagiSummarizer, signal);
|
||||
const result = await renderUrl(url, effectiveTimeout, raw, signal);
|
||||
const truncation = truncateHead(result.content, {
|
||||
maxBytes: DEFAULT_MAX_BYTES,
|
||||
maxLines: FETCH_DEFAULT_MAX_LINES,
|
||||
|
||||
@@ -1,18 +1,8 @@
|
||||
import { getEnvApiKey } from "@oh-my-pi/pi-ai";
|
||||
import { findCredential } from "./search/providers/utils";
|
||||
|
||||
const KAGI_SUMMARIZE_URL = "https://kagi.com/api/v0/summarize";
|
||||
const KAGI_SEARCH_URL = "https://kagi.com/api/v0/search";
|
||||
|
||||
interface KagiSummarizeResponse {
|
||||
data?: {
|
||||
output?: string;
|
||||
};
|
||||
error?: Array<{
|
||||
msg?: string;
|
||||
}>;
|
||||
}
|
||||
|
||||
interface KagiSearchResultObject {
|
||||
t: 0;
|
||||
url: string;
|
||||
@@ -105,14 +95,6 @@ function parseKagiErrorResponse(statusCode: number, responseText: string): KagiA
|
||||
}
|
||||
}
|
||||
|
||||
export interface KagiSummarizeOptions {
|
||||
engine?: string;
|
||||
summaryType?: string;
|
||||
targetLanguage?: string;
|
||||
cache?: boolean;
|
||||
signal?: AbortSignal;
|
||||
}
|
||||
|
||||
export interface KagiSearchOptions {
|
||||
limit?: number;
|
||||
signal?: AbortSignal;
|
||||
@@ -142,30 +124,6 @@ function getAuthHeaders(apiKey: string): Record<string, string> {
|
||||
};
|
||||
}
|
||||
|
||||
export async function summarizeUrlWithKagi(url: string, options: KagiSummarizeOptions = {}): Promise<string | null> {
|
||||
const apiKey = await findKagiApiKey();
|
||||
if (!apiKey) return null;
|
||||
|
||||
const requestUrl = new URL(KAGI_SUMMARIZE_URL);
|
||||
requestUrl.searchParams.set("url", url);
|
||||
requestUrl.searchParams.set("summary_type", options.summaryType ?? "summary");
|
||||
if (options.engine) requestUrl.searchParams.set("engine", options.engine);
|
||||
if (options.targetLanguage) requestUrl.searchParams.set("target_language", options.targetLanguage);
|
||||
if (options.cache !== undefined) requestUrl.searchParams.set("cache", String(options.cache));
|
||||
|
||||
const response = await fetch(requestUrl, {
|
||||
headers: getAuthHeaders(apiKey),
|
||||
signal: options.signal,
|
||||
});
|
||||
if (!response.ok) return null;
|
||||
|
||||
const payload = (await response.json()) as KagiSummarizeResponse;
|
||||
if (payload.error && payload.error.length > 0) return null;
|
||||
|
||||
const output = payload.data?.output?.trim();
|
||||
return output && output.length > 0 ? output : null;
|
||||
}
|
||||
|
||||
export async function searchWithKagi(query: string, options: KagiSearchOptions = {}): Promise<KagiSearchResult> {
|
||||
const apiKey = await findKagiApiKey();
|
||||
if (!apiKey) {
|
||||
|
||||
@@ -4,7 +4,6 @@ import * as path from "node:path";
|
||||
import { ptree, Snowflake } from "@oh-my-pi/pi-utils";
|
||||
import { throwIfAborted } from "../../tools/tool-errors";
|
||||
import { ensureTool } from "../../utils/tools-manager";
|
||||
import { summarizeUrlWithKagi } from "../kagi";
|
||||
import type { RenderResult, SpecialHandler } from "./types";
|
||||
import { buildResult, formatMediaDuration, formatNumber } from "./types";
|
||||
|
||||
@@ -110,22 +109,6 @@ export const handleYouTube: SpecialHandler = async (
|
||||
const notes: string[] = [];
|
||||
const videoUrl = `https://www.youtube.com/watch?v=${yt.videoId}`;
|
||||
|
||||
// Prefer Kagi Universal Summarizer when credentials are available
|
||||
try {
|
||||
const kagiSummary = await summarizeUrlWithKagi(videoUrl, { signal });
|
||||
if (kagiSummary && kagiSummary.length > 100) {
|
||||
return buildResult(kagiSummary, {
|
||||
url,
|
||||
finalUrl: videoUrl,
|
||||
method: "kagi",
|
||||
fetchedAt,
|
||||
notes: ["Used Kagi Universal Summarizer for YouTube"],
|
||||
});
|
||||
}
|
||||
} catch {
|
||||
throwIfAborted(signal);
|
||||
}
|
||||
|
||||
// Ensure yt-dlp is available (auto-download if missing)
|
||||
const ytdlp = await ensureTool("yt-dlp", { signal, silent: true });
|
||||
if (!ytdlp) {
|
||||
|
||||
@@ -7,10 +7,10 @@
|
||||
* them into a combined `answer` string on the SearchResponse.
|
||||
*/
|
||||
import { getEnvApiKey } from "@oh-my-pi/pi-ai";
|
||||
import { settings } from "../../../config/settings";
|
||||
import { callExaTool, findApiKey, isSearchResponse } from "../../../exa/mcp-client";
|
||||
import type { SearchResponse, SearchSource } from "../../../web/search/types";
|
||||
import { SearchProviderError } from "../../../web/search/types";
|
||||
import { settings } from "../../../config/settings";
|
||||
import { dateToAgeSeconds } from "../utils";
|
||||
import type { SearchParams } from "./base";
|
||||
import { SearchProvider } from "./base";
|
||||
|
||||
@@ -25,8 +25,8 @@ function getRuntimeSignals(): string[] {
|
||||
}
|
||||
|
||||
/**
|
||||
* Regression test: auto-compaction completion should resume the agent loop when
|
||||
* there are queued agent-level messages (follow-up/steering/custom).
|
||||
* Regression test: auto-compaction completion should resume the agent loop when
|
||||
* there are queued agent-level messages (follow-up/steering/custom).
|
||||
*/
|
||||
describe("AgentSession auto-compaction queue resume", () => {
|
||||
let tempDir: TempDir;
|
||||
@@ -60,17 +60,17 @@ describe("AgentSession auto-compaction queue resume", () => {
|
||||
"\t\t};",
|
||||
"\t});",
|
||||
'\tpi.on("auto_compaction_start", async (event) => {',
|
||||
`\t\tconst signals = globalThis.${runtimeSignalStoreKey} ?? (globalThis.${runtimeSignalStoreKey} = []);`,
|
||||
'\t\tsignals.push("compaction:start:" + event.reason);',
|
||||
"\t});",
|
||||
`\t\tconst signals = globalThis.${runtimeSignalStoreKey} ?? (globalThis.${runtimeSignalStoreKey} = []);`,
|
||||
'\t\tsignals.push("compaction:start:" + event.reason);',
|
||||
"\t});",
|
||||
'\tpi.on("auto_compaction_end", async (event) => {',
|
||||
`\t\tconst signals = globalThis.${runtimeSignalStoreKey} ?? (globalThis.${runtimeSignalStoreKey} = []);`,
|
||||
'\t\tsignals.push("compaction:end:" + (event.aborted ? "aborted" : "ok"));',
|
||||
"\t});",
|
||||
`\t\tconst signals = globalThis.${runtimeSignalStoreKey} ?? (globalThis.${runtimeSignalStoreKey} = []);`,
|
||||
'\t\tsignals.push("compaction:end:" + (event.aborted ? "aborted" : "ok"));',
|
||||
"\t});",
|
||||
'\tpi.on("todo_reminder", async (event) => {',
|
||||
`\t\tconst signals = globalThis.${runtimeSignalStoreKey} ?? (globalThis.${runtimeSignalStoreKey} = []);`,
|
||||
'\t\tsignals.push("todo:" + event.attempt + "/" + event.maxAttempts);',
|
||||
"\t});",
|
||||
`\t\tconst signals = globalThis.${runtimeSignalStoreKey} ?? (globalThis.${runtimeSignalStoreKey} = []);`,
|
||||
'\t\tsignals.push("todo:" + event.attempt + "/" + event.maxAttempts);',
|
||||
"\t});",
|
||||
"}",
|
||||
].join("\n"),
|
||||
);
|
||||
@@ -239,5 +239,4 @@ describe("AgentSession auto-compaction queue resume", () => {
|
||||
expect(getRuntimeSignals()).toContain("todo:1/3");
|
||||
expect(continueSpy).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
@@ -7,14 +7,12 @@ import type { ToolSession } from "@oh-my-pi/pi-coding-agent/tools";
|
||||
import { FetchTool } from "@oh-my-pi/pi-coding-agent/tools/fetch";
|
||||
import * as imageResize from "@oh-my-pi/pi-coding-agent/utils/image-resize";
|
||||
import * as toolsManager from "@oh-my-pi/pi-coding-agent/utils/tools-manager";
|
||||
import * as kagi from "@oh-my-pi/pi-coding-agent/web/kagi";
|
||||
import type { LoadPageResult } from "@oh-my-pi/pi-coding-agent/web/scrapers/types";
|
||||
import * as scrapers from "@oh-my-pi/pi-coding-agent/web/scrapers/types";
|
||||
import * as scraperUtils from "@oh-my-pi/pi-coding-agent/web/scrapers/utils";
|
||||
import * as natives from "@oh-my-pi/pi-natives";
|
||||
import { hookFetch, ptree, Snowflake } from "@oh-my-pi/pi-utils";
|
||||
|
||||
describe("fetch tool Kagi summarization toggle", () => {
|
||||
describe("fetch tool", () => {
|
||||
let testDir: string;
|
||||
|
||||
beforeEach(() => {
|
||||
@@ -41,64 +39,8 @@ describe("fetch tool Kagi summarization toggle", () => {
|
||||
};
|
||||
};
|
||||
|
||||
const mockLoadPage = () => {
|
||||
const pageResponse = (url: string): LoadPageResult => {
|
||||
if (url === "https://example.com") {
|
||||
return {
|
||||
ok: true,
|
||||
status: 200,
|
||||
contentType: "text/html",
|
||||
finalUrl: "https://example.com",
|
||||
content: "<html><body><h1>Example Domain</h1><p>Short sample content.</p></body></html>",
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
ok: false,
|
||||
status: 404,
|
||||
contentType: "",
|
||||
finalUrl: url,
|
||||
content: "",
|
||||
};
|
||||
};
|
||||
|
||||
return vi
|
||||
.spyOn(scrapers, "loadPage")
|
||||
.mockImplementation(async (url: string, _options?: unknown) => pageResponse(url));
|
||||
};
|
||||
|
||||
it("uses Kagi summarizer when enabled", async () => {
|
||||
const session = createSession({ "fetch.useKagiSummarizer": true });
|
||||
const tool = new FetchTool(session);
|
||||
const loadPageSpy = mockLoadPage();
|
||||
const summarizeSpy = vi.spyOn(kagi, "summarizeUrlWithKagi").mockResolvedValue("x".repeat(150));
|
||||
vi.spyOn(toolsManager, "ensureTool").mockResolvedValue(undefined);
|
||||
using _hook = hookFetch(() => new Response("blocked", { status: 500, statusText: "Blocked" }));
|
||||
|
||||
const result = await tool.execute("fetch-1", { url: "https://example.com" });
|
||||
|
||||
expect(loadPageSpy).toHaveBeenCalled();
|
||||
expect(summarizeSpy).toHaveBeenCalledTimes(1);
|
||||
expect(result.details?.method).toBe("kagi");
|
||||
});
|
||||
|
||||
it("skips Kagi summarizer when disabled", async () => {
|
||||
const session = createSession({ "fetch.useKagiSummarizer": false });
|
||||
const tool = new FetchTool(session);
|
||||
const loadPageSpy = mockLoadPage();
|
||||
const summarizeSpy = vi.spyOn(kagi, "summarizeUrlWithKagi").mockResolvedValue("x".repeat(150));
|
||||
vi.spyOn(toolsManager, "ensureTool").mockResolvedValue(undefined);
|
||||
using _hook = hookFetch(() => new Response("blocked", { status: 500, statusText: "Blocked" }));
|
||||
|
||||
const result = await tool.execute("fetch-2", { url: "https://example.com" });
|
||||
|
||||
expect(loadPageSpy).toHaveBeenCalled();
|
||||
expect(summarizeSpy).not.toHaveBeenCalled();
|
||||
expect(result.details?.method).not.toBe("kagi");
|
||||
});
|
||||
|
||||
it("returns an image content block when fetching image URLs", async () => {
|
||||
const session = createSession({ "fetch.useKagiSummarizer": false });
|
||||
const session = createSession();
|
||||
const tool = new FetchTool(session);
|
||||
const imageBytes = new Uint8Array([137, 80, 78, 71]);
|
||||
vi.spyOn(scrapers, "loadPage").mockResolvedValue({
|
||||
@@ -142,7 +84,7 @@ describe("fetch tool Kagi summarization toggle", () => {
|
||||
});
|
||||
|
||||
it("resizes fetched images before emitting image content blocks", async () => {
|
||||
const session = createSession({ "fetch.useKagiSummarizer": false });
|
||||
const session = createSession();
|
||||
const tool = new FetchTool(session);
|
||||
const resizeSpy = vi.spyOn(imageResize, "resizeImage").mockResolvedValue({
|
||||
buffer: new Uint8Array([1, 2, 3]),
|
||||
@@ -188,7 +130,7 @@ describe("fetch tool Kagi summarization toggle", () => {
|
||||
});
|
||||
|
||||
it("keeps markitdown extracted text for image responses", async () => {
|
||||
const session = createSession({ "fetch.useKagiSummarizer": false });
|
||||
const session = createSession();
|
||||
const tool = new FetchTool(session);
|
||||
const extractedText = "Converted image text content that is definitely longer than fifty characters.";
|
||||
vi.spyOn(imageResize, "resizeImage").mockResolvedValue({
|
||||
@@ -232,7 +174,7 @@ describe("fetch tool Kagi summarization toggle", () => {
|
||||
expect(imageBlock?.data).toBe("aW1hZ2U=");
|
||||
});
|
||||
it("falls back to text-only output for unsupported image MIME types", async () => {
|
||||
const session = createSession({ "fetch.useKagiSummarizer": false });
|
||||
const session = createSession();
|
||||
const tool = new FetchTool(session);
|
||||
const fetchBinarySpy = vi.spyOn(scraperUtils, "fetchBinary");
|
||||
vi.spyOn(scrapers, "loadPage").mockResolvedValue({
|
||||
@@ -255,7 +197,7 @@ describe("fetch tool Kagi summarization toggle", () => {
|
||||
});
|
||||
|
||||
it("uses binary conversion fallback for unsupported image MIME when extension is convertible", async () => {
|
||||
const session = createSession({ "fetch.useKagiSummarizer": false });
|
||||
const session = createSession();
|
||||
const tool = new FetchTool(session);
|
||||
const convertedText = "Converted image text from markitdown fallback with sufficient length to pass threshold.";
|
||||
const fetchBinarySpy = vi.spyOn(scraperUtils, "fetchBinary").mockResolvedValue({
|
||||
@@ -288,7 +230,7 @@ describe("fetch tool Kagi summarization toggle", () => {
|
||||
});
|
||||
|
||||
it("does not treat text/html at .png paths as inline images", async () => {
|
||||
const session = createSession({ "fetch.useKagiSummarizer": false });
|
||||
const session = createSession();
|
||||
const tool = new FetchTool(session);
|
||||
vi.spyOn(scraperUtils, "fetchBinary").mockResolvedValue({ ok: false, error: "not an image" });
|
||||
vi.spyOn(scrapers, "loadPage").mockResolvedValue({
|
||||
@@ -310,7 +252,7 @@ describe("fetch tool Kagi summarization toggle", () => {
|
||||
});
|
||||
|
||||
it("falls back to textual output when inline image refetch fails", async () => {
|
||||
const session = createSession({ "fetch.useKagiSummarizer": false });
|
||||
const session = createSession();
|
||||
const tool = new FetchTool(session);
|
||||
const convertSpy = vi.spyOn(scraperUtils, "convertWithMarkitdown");
|
||||
vi.spyOn(scrapers, "loadPage").mockResolvedValue({
|
||||
@@ -336,7 +278,7 @@ describe("fetch tool Kagi summarization toggle", () => {
|
||||
expect(fetchBinarySpy).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
it("falls back to text-only output when image payload bytes are invalid", async () => {
|
||||
const session = createSession({ "fetch.useKagiSummarizer": false });
|
||||
const session = createSession();
|
||||
const tool = new FetchTool(session);
|
||||
vi.spyOn(scrapers, "loadPage").mockResolvedValue({
|
||||
ok: true,
|
||||
@@ -377,7 +319,7 @@ describe("fetch tool Kagi summarization toggle", () => {
|
||||
expect(textBlock?.text).toContain("<html><body>gateway error</body></html>");
|
||||
});
|
||||
it("prefers rendered page content over site-wide llms.txt for deep pages", async () => {
|
||||
const session = createSession({ "fetch.useKagiSummarizer": false });
|
||||
const session = createSession();
|
||||
const tool = new FetchTool(session);
|
||||
const pageUrl = "https://bun.com/reference/bun/UnixSocketOptions";
|
||||
const pageHtml = "<html><body><main><h1>UnixSocketOptions</h1><p>Page-specific docs.</p></main></body></html>";
|
||||
@@ -445,7 +387,7 @@ describe("fetch tool Kagi summarization toggle", () => {
|
||||
});
|
||||
|
||||
it("uses section-scoped llms.txt fallback without requesting the site-wide file", async () => {
|
||||
const session = createSession({ "fetch.useKagiSummarizer": false });
|
||||
const session = createSession();
|
||||
const tool = new FetchTool(session);
|
||||
const pageUrl = "https://example.com/docs/reference/widget";
|
||||
const pageHtml = "<html><body><nav>Docs</nav><main><h1>Widget</h1></main></body></html>";
|
||||
|
||||
Reference in New Issue
Block a user