@oh-my-pi/pi-coding-agent 15.0.1 → 15.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +38 -0
- package/package.json +8 -8
- package/src/commands/commit.ts +10 -0
- package/src/config/model-registry.ts +31 -1
- package/src/config/settings-schema.ts +11 -0
- package/src/discovery/claude-plugins.ts +19 -7
- package/src/eval/py/runner.py +42 -11
- package/src/eval/py/runtime.ts +1 -0
- package/src/extensibility/extensions/get-commands-handler.ts +77 -0
- package/src/extensibility/plugins/legacy-pi-compat.ts +48 -31
- package/src/hashline/input.ts +2 -1
- package/src/hashline/parser.ts +27 -3
- package/src/internal-urls/docs-index.generated.ts +8 -8
- package/src/internal-urls/router.ts +8 -0
- package/src/internal-urls/types.ts +21 -0
- package/src/lsp/config.ts +15 -6
- package/src/lsp/defaults.json +6 -2
- package/src/modes/acp/acp-agent.ts +248 -50
- package/src/modes/components/status-line/segments.ts +38 -4
- package/src/modes/controllers/extension-ui-controller.ts +3 -2
- package/src/modes/rpc/host-uris.ts +235 -0
- package/src/modes/rpc/rpc-mode.ts +27 -1
- package/src/modes/rpc/rpc-types.ts +57 -0
- package/src/modes/runtime-init.ts +2 -1
- package/src/modes/theme/defaults/dark-poimandres.json +1 -0
- package/src/modes/theme/defaults/light-poimandres.json +1 -0
- package/src/modes/theme/theme.ts +6 -0
- package/src/prompts/tools/github.md +4 -4
- package/src/prompts/tools/hashline.md +22 -26
- package/src/prompts/tools/read.md +55 -37
- package/src/task/discovery.ts +5 -2
- package/src/task/executor.ts +2 -1
- package/src/tools/bash-command-fixup.ts +47 -0
- package/src/tools/bash.ts +39 -15
- package/src/tools/browser/render.ts +2 -2
- package/src/tools/eval.ts +10 -2
- package/src/tools/gh.ts +37 -4
- package/src/tools/job.ts +16 -7
- package/src/tools/output-meta.ts +26 -0
- package/src/tools/read.ts +32 -4
- package/src/tools/ssh.ts +3 -2
- package/src/tools/write.ts +20 -0
- package/src/web/search/providers/anthropic.ts +5 -0
- package/src/web/search/providers/exa.ts +3 -0
- package/src/web/search/providers/gemini.ts +5 -0
- package/src/web/search/providers/jina.ts +5 -2
- package/src/web/search/providers/zai.ts +5 -2
package/src/tools/read.ts
CHANGED
|
@@ -60,6 +60,7 @@ import {
|
|
|
60
60
|
formatStyledTruncationWarning,
|
|
61
61
|
type OutputMeta,
|
|
62
62
|
resolveOutputMaxColumns,
|
|
63
|
+
stripOutputNotice,
|
|
63
64
|
} from "./output-meta";
|
|
64
65
|
import { expandPath, formatPathRelativeToCwd, resolveReadPath, splitPathAndSel } from "./path-utils";
|
|
65
66
|
import { formatBytes, replaceTabs, shortenPath, wrapBrackets } from "./render-utils";
|
|
@@ -172,6 +173,19 @@ function countTextLines(text: string): number {
|
|
|
172
173
|
if (text.length === 0) return 0;
|
|
173
174
|
return text.split("\n").length;
|
|
174
175
|
}
|
|
176
|
+
|
|
177
|
+
/**
|
|
178
|
+
* Footer appended to summarized reads telling the model how to recover the
|
|
179
|
+
* elided body. Without this hint, agents either ignore the `...`/`{ .. }`
|
|
180
|
+
* markers or burn a turn guessing the right selector (see issue #1046).
|
|
181
|
+
*/
|
|
182
|
+
function formatSummaryElisionFooter(readPath: string, elidedSpans: number, elidedLines: number): string {
|
|
183
|
+
if (elidedSpans <= 0) return "";
|
|
184
|
+
const spanWord = elidedSpans === 1 ? "region" : "regions";
|
|
185
|
+
const lineWord = elidedLines === 1 ? "line" : "lines";
|
|
186
|
+
const linePart = elidedLines > 0 ? `${elidedLines} ${lineWord} across ` : "";
|
|
187
|
+
return `[${linePart}${elidedSpans} elided ${spanWord}; read ${readPath}:raw or a line range like ${readPath}:1-9999 for verbatim content]`;
|
|
188
|
+
}
|
|
175
189
|
const READ_CHUNK_SIZE = 8 * 1024;
|
|
176
190
|
|
|
177
191
|
/**
|
|
@@ -484,7 +498,7 @@ export interface ReadToolDetails {
|
|
|
484
498
|
* Mirrors the same lines the model receives but without hashline/line-number prefixes,
|
|
485
499
|
* so the TUI can render the file content with its own gutter without re-parsing the formatted text. */
|
|
486
500
|
displayContent?: { text: string; startLine: number };
|
|
487
|
-
summary?: { lines: number; elidedSpans: number };
|
|
501
|
+
summary?: { lines: number; elidedSpans: number; elidedLines: number };
|
|
488
502
|
/** Number of unresolved git conflicts surfaced by this read (TUI uses for inline `⚠ N` badge). */
|
|
489
503
|
conflictCount?: number;
|
|
490
504
|
}
|
|
@@ -1317,6 +1331,7 @@ export class ReadTool implements AgentTool<typeof readSchema, ReadToolDetails> {
|
|
|
1317
1331
|
text: string;
|
|
1318
1332
|
displayText: string;
|
|
1319
1333
|
elidedSpans: number;
|
|
1334
|
+
elidedLines: number;
|
|
1320
1335
|
} {
|
|
1321
1336
|
const displayMode = resolveFileDisplayMode(this.session);
|
|
1322
1337
|
const shouldAddHashLines = displayMode.hashLines;
|
|
@@ -1377,11 +1392,13 @@ export class ReadTool implements AgentTool<typeof readSchema, ReadToolDetails> {
|
|
|
1377
1392
|
const modelParts: string[] = [];
|
|
1378
1393
|
const displayParts: string[] = [];
|
|
1379
1394
|
let elidedSpans = 0;
|
|
1395
|
+
let elidedLines = 0;
|
|
1380
1396
|
for (const unit of units) {
|
|
1381
1397
|
if (unit.kind === "elided") {
|
|
1382
1398
|
modelParts.push("...");
|
|
1383
1399
|
displayParts.push("...");
|
|
1384
1400
|
elidedSpans++;
|
|
1401
|
+
elidedLines += unit.endLine - unit.startLine + 1;
|
|
1385
1402
|
continue;
|
|
1386
1403
|
}
|
|
1387
1404
|
if (unit.kind === "merged") {
|
|
@@ -1396,13 +1413,15 @@ export class ReadTool implements AgentTool<typeof readSchema, ReadToolDetails> {
|
|
|
1396
1413
|
modelParts.push(formatted.model);
|
|
1397
1414
|
displayParts.push(formatted.display);
|
|
1398
1415
|
elidedSpans++;
|
|
1416
|
+
// Merged brace pair encloses (start+1)..(end-1) as elided.
|
|
1417
|
+
elidedLines += Math.max(0, unit.endLine - unit.startLine - 1);
|
|
1399
1418
|
continue;
|
|
1400
1419
|
}
|
|
1401
1420
|
modelParts.push(formatSingleLine(unit.line, unit.text, shouldAddHashLines, shouldAddLineNumbers));
|
|
1402
1421
|
displayParts.push(unit.text);
|
|
1403
1422
|
}
|
|
1404
1423
|
|
|
1405
|
-
return { text: modelParts.join("\n"), displayText: displayParts.join("\n"), elidedSpans };
|
|
1424
|
+
return { text: modelParts.join("\n"), displayText: displayParts.join("\n"), elidedSpans, elidedLines };
|
|
1406
1425
|
}
|
|
1407
1426
|
|
|
1408
1427
|
async execute(
|
|
@@ -1646,16 +1665,23 @@ export class ReadTool implements AgentTool<typeof readSchema, ReadToolDetails> {
|
|
|
1646
1665
|
const summary = await this.#trySummarize(absolutePath, fileSize, signal);
|
|
1647
1666
|
if (summary?.parsed && summary.elided) {
|
|
1648
1667
|
const renderedSummary = this.#renderSummary(summary);
|
|
1668
|
+
const footer = formatSummaryElisionFooter(
|
|
1669
|
+
localReadPath,
|
|
1670
|
+
renderedSummary.elidedSpans,
|
|
1671
|
+
renderedSummary.elidedLines,
|
|
1672
|
+
);
|
|
1673
|
+
const modelText = footer ? `${renderedSummary.text}\n\n${footer}` : renderedSummary.text;
|
|
1649
1674
|
details = {
|
|
1650
1675
|
displayContent: { text: renderedSummary.displayText, startLine: 1 },
|
|
1651
1676
|
summary: {
|
|
1652
1677
|
lines: countTextLines(renderedSummary.text),
|
|
1653
1678
|
elidedSpans: renderedSummary.elidedSpans,
|
|
1679
|
+
elidedLines: renderedSummary.elidedLines,
|
|
1654
1680
|
},
|
|
1655
1681
|
};
|
|
1656
1682
|
|
|
1657
1683
|
sourcePath = absolutePath;
|
|
1658
|
-
content = [{ type: "text", text:
|
|
1684
|
+
content = [{ type: "text", text: modelText }];
|
|
1659
1685
|
}
|
|
1660
1686
|
}
|
|
1661
1687
|
|
|
@@ -2169,7 +2195,9 @@ export const readToolRenderer = {
|
|
|
2169
2195
|
const rawText = result.content?.find(c => c.type === "text")?.text ?? "";
|
|
2170
2196
|
// Prefer structured `displayContent` from details when available so the TUI
|
|
2171
2197
|
// shows clean file content (no model-only hashline anchors) without parsing the formatted text.
|
|
2172
|
-
|
|
2198
|
+
// Fall back to the raw text, but strip the LLM-facing notice so it doesn't
|
|
2199
|
+
// echo next to the styled warning line below.
|
|
2200
|
+
const contentText = details?.displayContent?.text ?? stripOutputNotice(rawText, details?.meta);
|
|
2173
2201
|
const imageContent = result.content?.find(c => c.type === "image");
|
|
2174
2202
|
const rawPath = args?.file_path || args?.path || "";
|
|
2175
2203
|
const filePath = shortenPath(rawPath);
|
package/src/tools/ssh.ts
CHANGED
|
@@ -16,7 +16,7 @@ import { executeSSH } from "../ssh/ssh-executor";
|
|
|
16
16
|
import { renderStatusLine } from "../tui";
|
|
17
17
|
import { CachedOutputBlock } from "../tui/output-block";
|
|
18
18
|
import type { ToolSession } from ".";
|
|
19
|
-
import { formatStyledTruncationWarning, type OutputMeta } from "./output-meta";
|
|
19
|
+
import { formatStyledTruncationWarning, type OutputMeta, stripOutputNotice } from "./output-meta";
|
|
20
20
|
import { ToolError } from "./tool-errors";
|
|
21
21
|
import { toolResult } from "./tool-result";
|
|
22
22
|
import { clampTimeout } from "./tool-timeouts";
|
|
@@ -253,7 +253,8 @@ export const sshToolRenderer = {
|
|
|
253
253
|
render: (width: number): string[] => {
|
|
254
254
|
// REACTIVE: read mutable options at render time
|
|
255
255
|
const { expanded, renderContext } = options;
|
|
256
|
-
|
|
256
|
+
// Strip LLM-facing notice so we don't echo it next to the styled warning.
|
|
257
|
+
const output = stripOutputNotice(textContent, details?.meta).trimEnd();
|
|
257
258
|
const outputLines: string[] = [];
|
|
258
259
|
|
|
259
260
|
if (output) {
|
package/src/tools/write.ts
CHANGED
|
@@ -8,6 +8,8 @@ import { isEnoent, isRecord, prompt, untilAborted } from "@oh-my-pi/pi-utils";
|
|
|
8
8
|
import { type Static, Type } from "@sinclair/typebox";
|
|
9
9
|
import { stripHashlinePrefixes } from "../edit";
|
|
10
10
|
import type { RenderResultOptions } from "../extensibility/custom-tools/types";
|
|
11
|
+
import { InternalUrlRouter } from "../internal-urls";
|
|
12
|
+
import { parseInternalUrl } from "../internal-urls/parse";
|
|
11
13
|
import { createLspWritethrough, type FileDiagnosticsResult, type WritethroughCallback, writethroughNoop } from "../lsp";
|
|
12
14
|
import { getLanguageFromPath, highlightCode, type Theme } from "../modes/theme/theme";
|
|
13
15
|
import writeDescription from "../prompts/tools/write.md" with { type: "text" };
|
|
@@ -658,6 +660,24 @@ export class WriteTool implements AgentTool<typeof writeSchema, WriteToolDetails
|
|
|
658
660
|
return untilAborted(signal, async () => {
|
|
659
661
|
// Strip hashline display prefixes (LINE+ID|) if the model copied them from read output
|
|
660
662
|
const { text: cleanContent, stripped } = stripWriteContent(this.session, content);
|
|
663
|
+
const internalRouter = InternalUrlRouter.instance();
|
|
664
|
+
if (internalRouter.canHandle(path)) {
|
|
665
|
+
const parsed = parseInternalUrl(path);
|
|
666
|
+
const scheme = parsed.protocol.replace(/:$/, "").toLowerCase();
|
|
667
|
+
const handler = internalRouter.getHandler(scheme);
|
|
668
|
+
if (handler?.write) {
|
|
669
|
+
await handler.write(parsed, cleanContent, { cwd: this.session.cwd, signal });
|
|
670
|
+
let resultText = `Successfully wrote ${cleanContent.length} bytes to ${path}`;
|
|
671
|
+
if (stripped) {
|
|
672
|
+
resultText += `\nNote: auto-stripped hashline display prefixes from content before writing.`;
|
|
673
|
+
}
|
|
674
|
+
return { content: [{ type: "text", text: resultText }], details: {} };
|
|
675
|
+
}
|
|
676
|
+
// Schemes without a `write` hook fall through to existing logic
|
|
677
|
+
// (local:// resolves to a backing file via plan-mode-guard) or are
|
|
678
|
+
// rejected downstream when no backing file exists.
|
|
679
|
+
}
|
|
680
|
+
|
|
661
681
|
const conflictUri = parseConflictUri(path);
|
|
662
682
|
if (conflictUri) {
|
|
663
683
|
if (conflictUri.scope) {
|
|
@@ -38,6 +38,7 @@ export interface AnthropicSearchParams {
|
|
|
38
38
|
max_tokens?: number;
|
|
39
39
|
/** Sampling temperature (0–1). Lower = more focused/factual. */
|
|
40
40
|
temperature?: number;
|
|
41
|
+
signal?: AbortSignal;
|
|
41
42
|
}
|
|
42
43
|
|
|
43
44
|
/**
|
|
@@ -86,6 +87,7 @@ async function callSearch(
|
|
|
86
87
|
systemPrompt?: string,
|
|
87
88
|
maxTokens?: number,
|
|
88
89
|
temperature?: number,
|
|
90
|
+
signal?: AbortSignal,
|
|
89
91
|
): Promise<AnthropicApiResponse> {
|
|
90
92
|
const url = buildAnthropicUrl(auth);
|
|
91
93
|
const headers = buildAnthropicSearchHeaders(auth);
|
|
@@ -116,6 +118,7 @@ async function callSearch(
|
|
|
116
118
|
method: "POST",
|
|
117
119
|
headers,
|
|
118
120
|
body: JSON.stringify(body),
|
|
121
|
+
signal,
|
|
119
122
|
});
|
|
120
123
|
|
|
121
124
|
if (!response.ok) {
|
|
@@ -253,6 +256,7 @@ export async function searchAnthropic(params: AnthropicSearchParams): Promise<Se
|
|
|
253
256
|
params.system_prompt,
|
|
254
257
|
params.max_tokens,
|
|
255
258
|
params.temperature,
|
|
259
|
+
params.signal,
|
|
256
260
|
);
|
|
257
261
|
|
|
258
262
|
const result = parseResponse(response);
|
|
@@ -281,6 +285,7 @@ export class AnthropicProvider extends SearchProvider {
|
|
|
281
285
|
num_results: params.numSearchResults ?? params.limit,
|
|
282
286
|
max_tokens: params.maxOutputTokens,
|
|
283
287
|
temperature: params.temperature,
|
|
288
|
+
signal: params.signal,
|
|
284
289
|
});
|
|
285
290
|
}
|
|
286
291
|
}
|
|
@@ -29,6 +29,7 @@ export interface ExaSearchParams {
|
|
|
29
29
|
exclude_domains?: string[];
|
|
30
30
|
start_published_date?: string;
|
|
31
31
|
end_published_date?: string;
|
|
32
|
+
signal?: AbortSignal;
|
|
32
33
|
}
|
|
33
34
|
|
|
34
35
|
interface ExaSearchResult {
|
|
@@ -179,6 +180,7 @@ async function callExaSearch(apiKey: string, params: ExaSearchParams): Promise<E
|
|
|
179
180
|
"x-api-key": apiKey,
|
|
180
181
|
},
|
|
181
182
|
body: JSON.stringify(body),
|
|
183
|
+
signal: params.signal,
|
|
182
184
|
});
|
|
183
185
|
|
|
184
186
|
if (!response.ok) {
|
|
@@ -259,6 +261,7 @@ export class ExaProvider extends SearchProvider {
|
|
|
259
261
|
return searchExa({
|
|
260
262
|
query: params.query,
|
|
261
263
|
num_results: params.numSearchResults ?? params.limit,
|
|
264
|
+
signal: params.signal,
|
|
262
265
|
});
|
|
263
266
|
}
|
|
264
267
|
}
|
|
@@ -39,6 +39,7 @@ export interface GeminiSearchParams extends GeminiToolParams {
|
|
|
39
39
|
max_output_tokens?: number;
|
|
40
40
|
/** Sampling temperature (0–1). Lower = more focused/factual. */
|
|
41
41
|
temperature?: number;
|
|
42
|
+
signal?: AbortSignal;
|
|
42
43
|
}
|
|
43
44
|
|
|
44
45
|
export function buildGeminiRequestTools(params: GeminiToolParams): Array<Record<string, Record<string, unknown>>> {
|
|
@@ -235,6 +236,7 @@ async function callGeminiSearch(
|
|
|
235
236
|
maxOutputTokens?: number,
|
|
236
237
|
temperature?: number,
|
|
237
238
|
toolParams: GeminiToolParams = {},
|
|
239
|
+
signal?: AbortSignal,
|
|
238
240
|
): Promise<{
|
|
239
241
|
answer: string;
|
|
240
242
|
sources: SearchSource[];
|
|
@@ -308,6 +310,7 @@ async function callGeminiSearch(
|
|
|
308
310
|
...headers,
|
|
309
311
|
},
|
|
310
312
|
body: JSON.stringify(requestBody),
|
|
313
|
+
signal,
|
|
311
314
|
});
|
|
312
315
|
const urlFor = (attempt: number) =>
|
|
313
316
|
`${endpoints[Math.min(attempt, endpoints.length - 1)]}/v1internal:streamGenerateContent?alt=sse`;
|
|
@@ -500,6 +503,7 @@ export async function searchGemini(params: GeminiSearchParams): Promise<SearchRe
|
|
|
500
503
|
code_execution: params.code_execution,
|
|
501
504
|
url_context: params.url_context,
|
|
502
505
|
},
|
|
506
|
+
params.signal,
|
|
503
507
|
);
|
|
504
508
|
|
|
505
509
|
let sources = result.sources;
|
|
@@ -539,6 +543,7 @@ export class GeminiProvider extends SearchProvider {
|
|
|
539
543
|
google_search: params.googleSearch,
|
|
540
544
|
code_execution: params.codeExecution,
|
|
541
545
|
url_context: params.urlContext,
|
|
546
|
+
signal: params.signal,
|
|
542
547
|
});
|
|
543
548
|
}
|
|
544
549
|
}
|
|
@@ -17,6 +17,7 @@ const JINA_SEARCH_URL = "https://s.jina.ai";
|
|
|
17
17
|
export interface JinaSearchParams {
|
|
18
18
|
query: string;
|
|
19
19
|
num_results?: number;
|
|
20
|
+
signal?: AbortSignal;
|
|
20
21
|
}
|
|
21
22
|
|
|
22
23
|
interface JinaSearchResult {
|
|
@@ -33,13 +34,14 @@ export function findApiKey(): string | null {
|
|
|
33
34
|
}
|
|
34
35
|
|
|
35
36
|
/** Call Jina Reader search API. */
|
|
36
|
-
async function callJinaSearch(apiKey: string, query: string): Promise<JinaSearchResponse> {
|
|
37
|
+
async function callJinaSearch(apiKey: string, query: string, signal?: AbortSignal): Promise<JinaSearchResponse> {
|
|
37
38
|
const requestUrl = `${JINA_SEARCH_URL}/${encodeURIComponent(query)}`;
|
|
38
39
|
const response = await fetch(requestUrl, {
|
|
39
40
|
headers: {
|
|
40
41
|
Accept: "application/json",
|
|
41
42
|
Authorization: `Bearer ${apiKey}`,
|
|
42
43
|
},
|
|
44
|
+
signal,
|
|
43
45
|
});
|
|
44
46
|
|
|
45
47
|
if (!response.ok) {
|
|
@@ -58,7 +60,7 @@ export async function searchJina(params: JinaSearchParams): Promise<SearchRespon
|
|
|
58
60
|
throw new Error("JINA_API_KEY not found. Set it in environment or .env file.");
|
|
59
61
|
}
|
|
60
62
|
|
|
61
|
-
const response = await callJinaSearch(apiKey, params.query);
|
|
63
|
+
const response = await callJinaSearch(apiKey, params.query, params.signal);
|
|
62
64
|
const sources: SearchSource[] = [];
|
|
63
65
|
|
|
64
66
|
for (const result of response) {
|
|
@@ -91,6 +93,7 @@ export class JinaProvider extends SearchProvider {
|
|
|
91
93
|
return searchJina({
|
|
92
94
|
query: params.query,
|
|
93
95
|
num_results: params.numSearchResults ?? params.limit,
|
|
96
|
+
signal: params.signal,
|
|
94
97
|
});
|
|
95
98
|
}
|
|
96
99
|
}
|
|
@@ -20,6 +20,7 @@ const DEFAULT_NUM_RESULTS = 10;
|
|
|
20
20
|
export interface ZaiSearchParams {
|
|
21
21
|
query: string;
|
|
22
22
|
num_results?: number;
|
|
23
|
+
signal?: AbortSignal;
|
|
23
24
|
}
|
|
24
25
|
|
|
25
26
|
interface ZaiSearchResult {
|
|
@@ -55,7 +56,7 @@ export async function findApiKey(): Promise<string | null> {
|
|
|
55
56
|
return findCredential(getEnvApiKey("zai"), "zai");
|
|
56
57
|
}
|
|
57
58
|
|
|
58
|
-
async function callZaiTool(apiKey: string, args: Record<string, unknown
|
|
59
|
+
async function callZaiTool(apiKey: string, args: Record<string, unknown>, signal?: AbortSignal): Promise<unknown> {
|
|
59
60
|
const response = await fetch(ZAI_MCP_URL, {
|
|
60
61
|
method: "POST",
|
|
61
62
|
headers: {
|
|
@@ -72,6 +73,7 @@ async function callZaiTool(apiKey: string, args: Record<string, unknown>): Promi
|
|
|
72
73
|
arguments: args,
|
|
73
74
|
},
|
|
74
75
|
}),
|
|
76
|
+
signal,
|
|
75
77
|
});
|
|
76
78
|
|
|
77
79
|
if (!response.ok) {
|
|
@@ -157,7 +159,7 @@ async function callZaiSearch(apiKey: string, params: ZaiSearchParams): Promise<u
|
|
|
157
159
|
let lastError: unknown;
|
|
158
160
|
for (let i = 0; i < attempts.length; i++) {
|
|
159
161
|
try {
|
|
160
|
-
return await callZaiTool(apiKey, attempts[i]);
|
|
162
|
+
return await callZaiTool(apiKey, attempts[i], params.signal);
|
|
161
163
|
} catch (error) {
|
|
162
164
|
lastError = error;
|
|
163
165
|
const isLastAttempt = i === attempts.length - 1;
|
|
@@ -302,6 +304,7 @@ export class ZaiProvider extends SearchProvider {
|
|
|
302
304
|
return searchZai({
|
|
303
305
|
query: params.query,
|
|
304
306
|
num_results: params.numSearchResults ?? params.limit,
|
|
307
|
+
signal: params.signal,
|
|
305
308
|
});
|
|
306
309
|
}
|
|
307
310
|
}
|