@herbertgao/pi-extensions 2026.9.9 → 2026.9.11
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/README.md +1 -0
- package/THIRD_PARTY_NOTICES.md +26 -0
- package/node_modules/@herbertgao/pi-bark/package.json +2 -2
- package/node_modules/@herbertgao/pi-cc-extensions/README.en.md +2 -2
- package/node_modules/@herbertgao/pi-cc-extensions/README.md +2 -2
- package/node_modules/@herbertgao/pi-cc-extensions/package.json +3 -2
- package/node_modules/@herbertgao/pi-subagents/CHANGELOG.md +12 -0
- package/node_modules/@herbertgao/pi-subagents/README.md +427 -120
- package/node_modules/@herbertgao/pi-subagents/docs/rpc.md +184 -0
- package/node_modules/@herbertgao/pi-subagents/docs/workflows.md +466 -0
- package/node_modules/@herbertgao/pi-subagents/examples/agent-tool-description.md +6 -6
- package/node_modules/@herbertgao/pi-subagents/examples/workflows/compose.js +52 -0
- package/node_modules/@herbertgao/pi-subagents/examples/workflows/fan-out-audit.js +56 -0
- package/node_modules/@herbertgao/pi-subagents/examples/workflows/gated-fix.js +60 -0
- package/node_modules/@herbertgao/pi-subagents/examples/workflows/lib/count-child.js +30 -0
- package/node_modules/@herbertgao/pi-subagents/examples/workflows/review-panel.js +68 -0
- package/node_modules/@herbertgao/pi-subagents/examples/workflows/structured-findings.js +81 -0
- package/node_modules/@herbertgao/pi-subagents/package.json +12 -10
- package/node_modules/@herbertgao/pi-subagents/src/agent-file-toggle.ts +52 -12
- package/node_modules/@herbertgao/pi-subagents/src/agent-manager.ts +837 -146
- package/node_modules/@herbertgao/pi-subagents/src/agent-runner.ts +213 -39
- package/node_modules/@herbertgao/pi-subagents/src/cross-extension-rpc.ts +73 -14
- package/node_modules/@herbertgao/pi-subagents/src/custom-agents.ts +101 -47
- package/node_modules/@herbertgao/pi-subagents/src/index.ts +2249 -914
- package/node_modules/@herbertgao/pi-subagents/src/invocation-config.ts +13 -0
- package/node_modules/@herbertgao/pi-subagents/src/mention-clone.ts +215 -0
- package/node_modules/@herbertgao/pi-subagents/src/mention.ts +147 -0
- package/node_modules/@herbertgao/pi-subagents/src/model-resolver.ts +9 -1
- package/node_modules/@herbertgao/pi-subagents/src/nested-tools.ts +40 -26
- package/node_modules/@herbertgao/pi-subagents/src/output-file.ts +18 -8
- package/node_modules/@herbertgao/pi-subagents/src/prompts.ts +46 -9
- package/node_modules/@herbertgao/pi-subagents/src/schedule.ts +21 -16
- package/node_modules/@herbertgao/pi-subagents/src/settings.ts +137 -7
- package/node_modules/@herbertgao/pi-subagents/src/structured-output.ts +136 -0
- package/node_modules/@herbertgao/pi-subagents/src/types.ts +126 -8
- package/node_modules/@herbertgao/pi-subagents/src/ui/agent-mention.ts +274 -0
- package/node_modules/@herbertgao/pi-subagents/src/ui/agent-widget.ts +20 -5
- package/node_modules/@herbertgao/pi-subagents/src/ui/conversation-viewer.ts +10 -4
- package/node_modules/@herbertgao/pi-subagents/src/ui/fleet-list.ts +167 -22
- package/node_modules/@herbertgao/pi-subagents/src/ui/workflow-card.ts +555 -0
- package/node_modules/@herbertgao/pi-subagents/src/ui/workflow-dialog.ts +1304 -0
- package/node_modules/@herbertgao/pi-subagents/src/ui/workflow-menu.ts +226 -0
- package/node_modules/@herbertgao/pi-subagents/src/workflow/collisions.ts +122 -0
- package/node_modules/@herbertgao/pi-subagents/src/workflow/entry.ts +47 -0
- package/node_modules/@herbertgao/pi-subagents/src/workflow/host.ts +463 -0
- package/node_modules/@herbertgao/pi-subagents/src/workflow/journal.ts +164 -0
- package/node_modules/@herbertgao/pi-subagents/src/workflow/json-schema.ts +142 -0
- package/node_modules/@herbertgao/pi-subagents/src/workflow/meta.ts +401 -0
- package/node_modules/@herbertgao/pi-subagents/src/workflow/progress.ts +622 -0
- package/node_modules/@herbertgao/pi-subagents/src/workflow/runtime.ts +1399 -0
- package/node_modules/@herbertgao/pi-subagents/src/workflow/saved.ts +230 -0
- package/node_modules/@herbertgao/pi-subagents/src/workflow/task.ts +333 -0
- package/node_modules/@herbertgao/pi-subagents/src/workflow/tool-description.ts +200 -0
- package/node_modules/@herbertgao/pi-subagents/src/workflow/worker-source.ts +781 -0
- package/node_modules/@herbertgao/pi-subagents/src/worktree.ts +97 -95
- package/node_modules/@herbertgao/pi-subagents/src/xml.ts +13 -0
- package/node_modules/@herbertgao/resume-from/package.json +1 -1
- package/node_modules/@herbertgao/sol-pi/README.md +3 -3
- package/node_modules/@herbertgao/sol-pi/THIRD_PARTY_NOTICES.md +4 -4
- package/node_modules/@herbertgao/sol-pi/agents-install.md +4 -4
- package/node_modules/@herbertgao/sol-pi/docs/compatibility.md +6 -6
- package/node_modules/@herbertgao/sol-pi/package.json +2 -2
- package/node_modules/@narumitw/pi-btw/dist/index.ts +39 -89
- package/node_modules/@narumitw/pi-btw/dist/index.ts.map +3 -3
- package/node_modules/@narumitw/pi-btw/package.json +4 -4
- package/node_modules/@narumitw/pi-btw/src/btw.ts +28 -87
- package/node_modules/@narumitw/pi-btw/src/main-tree-picker.ts +8 -0
- package/node_modules/@narumitw/pi-btw/src/side-thread.ts +40 -37
- package/node_modules/@narumitw/pi-caffeinate/README.md +21 -66
- package/node_modules/@narumitw/pi-caffeinate/dist/index.ts +10 -41
- package/node_modules/@narumitw/pi-caffeinate/dist/index.ts.map +2 -2
- package/node_modules/@narumitw/pi-caffeinate/package.json +50 -51
- package/node_modules/@narumitw/pi-caffeinate/src/caffeinate.ts +637 -663
- package/node_modules/@narumitw/pi-caffeinate/src/dbus-inhibit.ts +114 -120
- package/node_modules/@narumitw/pi-caffeinate/src/inhibitor-process.ts +29 -29
- package/node_modules/@narumitw/pi-caffeinate/src/inhibitors.ts +108 -126
- package/node_modules/@narumitw/pi-caffeinate/src/settings.ts +124 -128
- package/node_modules/pi-multi-account/CHANGELOG.md +1209 -0
- package/node_modules/pi-multi-account/CONTRIBUTING.md +61 -0
- package/node_modules/pi-multi-account/LICENSE +21 -0
- package/node_modules/pi-multi-account/README.md +197 -0
- package/node_modules/pi-multi-account/SECURITY.md +27 -0
- package/node_modules/pi-multi-account/auth-file-transaction.ts +56 -0
- package/node_modules/pi-multi-account/child-usability.ts +233 -0
- package/node_modules/pi-multi-account/compaction-summary.ts +32 -0
- package/node_modules/pi-multi-account/completion-route-planner.ts +224 -0
- package/node_modules/pi-multi-account/context-guard.ts +420 -0
- package/node_modules/pi-multi-account/cursor/LICENSE +21 -0
- package/node_modules/pi-multi-account/cursor/NOTICE +2 -0
- package/node_modules/pi-multi-account/cursor/auth.ts +165 -0
- package/node_modules/pi-multi-account/cursor/bridge-handle.ts +155 -0
- package/node_modules/pi-multi-account/cursor/conversation-registry.ts +104 -0
- package/node_modules/pi-multi-account/cursor/cursor-models-raw.json +611 -0
- package/node_modules/pi-multi-account/cursor/cursor-shared.ts +192 -0
- package/node_modules/pi-multi-account/cursor/h2-bridge.mjs +175 -0
- package/node_modules/pi-multi-account/cursor/index.ts +572 -0
- package/node_modules/pi-multi-account/cursor/message-parsing.ts +323 -0
- package/node_modules/pi-multi-account/cursor/prompt-usage.ts +53 -0
- package/node_modules/pi-multi-account/cursor/proto/agent_pb.ts +15294 -0
- package/node_modules/pi-multi-account/cursor/proxy.ts +2510 -0
- package/node_modules/pi-multi-account/cursor/session-lifecycle.ts +40 -0
- package/node_modules/pi-multi-account/cursor/sse-keepalive.ts +24 -0
- package/node_modules/pi-multi-account/cursor/stream-lifecycle.ts +193 -0
- package/node_modules/pi-multi-account/cursor/upstream-watchdog.ts +88 -0
- package/node_modules/pi-multi-account/cursor-bridge.ts +240 -0
- package/node_modules/pi-multi-account/cursor-model-name.ts +12 -0
- package/node_modules/pi-multi-account/index.ts +11825 -0
- package/node_modules/pi-multi-account/model-catalog.ts +354 -0
- package/node_modules/pi-multi-account/package.json +101 -0
- package/node_modules/pi-multi-account/pi-contract.ts +281 -0
- package/node_modules/pi-multi-account/provider-payload-stream.ts +44 -0
- package/node_modules/pi-multi-account/provider-priority.ts +189 -0
- package/node_modules/pi-multi-account/slot-proxy-auth.ts +167 -0
- package/node_modules/pi-multi-account/slot-proxy.ts +344 -0
- package/node_modules/pi-multi-account/state-file-transaction.ts +67 -0
- package/node_modules/pi-multi-account/usage.ts +1099 -0
- package/node_modules/pi-typesafe/README.md +6 -2
- package/node_modules/pi-typesafe/dist/client.d.ts +11 -0
- package/node_modules/pi-typesafe/dist/client.js +45 -10
- package/node_modules/pi-typesafe/dist/index.d.ts +2 -2
- package/node_modules/pi-typesafe/dist/index.js +1 -1
- package/node_modules/pi-typesafe/package.json +2 -2
- package/node_modules/pi-web-access/CHANGELOG.md +36 -0
- package/node_modules/pi-web-access/README.md +75 -18
- package/node_modules/pi-web-access/anysearch.ts +4 -15
- package/node_modules/pi-web-access/bocha.ts +3 -22
- package/node_modules/pi-web-access/brave.ts +3 -21
- package/node_modules/pi-web-access/brightdata.ts +5 -32
- package/node_modules/pi-web-access/content-find.ts +168 -53
- package/node_modules/pi-web-access/curator-page.ts +4 -1
- package/node_modules/pi-web-access/curator-run.ts +2 -1
- package/node_modules/pi-web-access/curator-server.ts +1 -0
- package/node_modules/pi-web-access/dist/index.js +24620 -0
- package/node_modules/pi-web-access/domain-filter-normalization.ts +14 -0
- package/node_modules/pi-web-access/duckduckgo.ts +3 -21
- package/node_modules/pi-web-access/extract.ts +3 -1
- package/node_modules/pi-web-access/firecrawl.ts +5 -29
- package/node_modules/pi-web-access/gemini-search.ts +81 -32
- package/node_modules/pi-web-access/index.ts +149 -148
- package/node_modules/pi-web-access/jina-search.ts +4 -15
- package/node_modules/pi-web-access/kagi.ts +4 -13
- package/node_modules/pi-web-access/kimi-search.ts +5 -30
- package/node_modules/pi-web-access/mistral-search.ts +1 -15
- package/node_modules/pi-web-access/ollama.ts +2 -7
- package/node_modules/pi-web-access/openai-search.ts +174 -36
- package/node_modules/pi-web-access/opencode-session-headers.ts +24 -0
- package/node_modules/pi-web-access/package.json +10 -4
- package/node_modules/pi-web-access/page-query.ts +10 -2
- package/node_modules/pi-web-access/parallel.ts +1 -15
- package/node_modules/pi-web-access/pdf-extract.ts +3 -0
- package/node_modules/pi-web-access/querit.ts +5 -29
- package/node_modules/pi-web-access/search-answer-formatting.ts +11 -0
- package/node_modules/pi-web-access/search-result-count-normalization.ts +4 -0
- package/node_modules/pi-web-access/search1api.ts +5 -29
- package/node_modules/pi-web-access/searchinfinity.ts +5 -29
- package/node_modules/pi-web-access/searxng.ts +3 -21
- package/node_modules/pi-web-access/serpapi.ts +5 -28
- package/node_modules/pi-web-access/serpbase.ts +3 -22
- package/node_modules/pi-web-access/serpdive.ts +3 -21
- package/node_modules/pi-web-access/serper.ts +5 -28
- package/node_modules/pi-web-access/serply.ts +197 -0
- package/node_modules/pi-web-access/source-check.ts +11 -47
- package/node_modules/pi-web-access/summary-review.ts +7 -3
- package/node_modules/pi-web-access/tavily.ts +3 -21
- package/node_modules/pi-web-access/tinyfish.ts +5 -29
- package/node_modules/pi-web-access/utils.ts +9 -1
- package/node_modules/pi-web-access/valyu.ts +5 -28
- package/node_modules/pi-web-access/xai-search.ts +1 -15
- package/node_modules/pi-web-access/xcrawl.ts +5 -32
- package/package.json +17 -11
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import { existsSync, readFileSync } from "node:fs";
|
|
2
2
|
import { activityMonitor } from "./activity.ts";
|
|
3
|
+
import { normalizeDomain } from "./domain-filter-normalization.ts";
|
|
4
|
+
import { formatSearchResultsAsAnswer } from "./search-answer-formatting.ts";
|
|
3
5
|
import type { SearchOptions, SearchResponse } from "./perplexity.ts";
|
|
4
6
|
import { hasCredentialSource, redactCredential, resolveCredential } from "./credential-source.ts";
|
|
5
7
|
import { getWebSearchConfigPath } from "./utils.ts";
|
|
@@ -77,21 +79,6 @@ interface DomainFilters {
|
|
|
77
79
|
exclude: string[];
|
|
78
80
|
}
|
|
79
81
|
|
|
80
|
-
function normalizeDomain(value: string): string | null {
|
|
81
|
-
let input = value.trim().toLowerCase();
|
|
82
|
-
if (!input) return null;
|
|
83
|
-
if (input.startsWith("-")) input = input.slice(1).trim();
|
|
84
|
-
if (!input) return null;
|
|
85
|
-
try {
|
|
86
|
-
const parsed = input.includes("://") ? new URL(input) : new URL(`https://${input}`);
|
|
87
|
-
input = parsed.hostname;
|
|
88
|
-
} catch {
|
|
89
|
-
input = input.split("/")[0]?.split(":")[0] ?? "";
|
|
90
|
-
}
|
|
91
|
-
input = input.replace(/^\.+|\.+$/g, "");
|
|
92
|
-
return /^[a-z0-9][a-z0-9.-]*\.[a-z]{2,}$/i.test(input) ? input : null;
|
|
93
|
-
}
|
|
94
|
-
|
|
95
82
|
function parseDomainFilter(domainFilter: string[] | undefined): DomainFilters {
|
|
96
83
|
const filters: DomainFilters = { include: [], exclude: [] };
|
|
97
84
|
for (const raw of domainFilter ?? []) {
|
|
@@ -158,12 +145,6 @@ function parseSearchResponse(value: unknown): { results: SearchResponse["results
|
|
|
158
145
|
return { results };
|
|
159
146
|
}
|
|
160
147
|
|
|
161
|
-
function buildAnswer(results: SearchResponse["results"]): string {
|
|
162
|
-
return results.map((result) => result.snippet
|
|
163
|
-
? `${result.snippet}\nSource: ${result.title} (${result.url})`
|
|
164
|
-
: `Source: ${result.title} (${result.url})`).join("\n\n");
|
|
165
|
-
}
|
|
166
|
-
|
|
167
148
|
export function isBochaAvailable(): boolean {
|
|
168
149
|
return hasCredentialSource({ provider: "Bocha", configuredValue: loadConfig().bochaApiKey, environmentValue: process.env.BOCHA_API_KEY });
|
|
169
150
|
}
|
|
@@ -217,5 +198,5 @@ export async function searchWithBocha(query: string, options: SearchOptions = {}
|
|
|
217
198
|
}
|
|
218
199
|
activityMonitor.logComplete(activityId, response.status);
|
|
219
200
|
const results = parsed.results.filter((result) => passesDomainFilters(result.url, filters)).slice(0, numResults);
|
|
220
|
-
return { answer:
|
|
201
|
+
return { answer: formatSearchResultsAsAnswer(results), results };
|
|
221
202
|
}
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import { existsSync, readFileSync } from "node:fs";
|
|
2
2
|
import { activityMonitor } from "./activity.ts";
|
|
3
|
+
import { normalizeDomain } from "./domain-filter-normalization.ts";
|
|
4
|
+
import { normalizeSearchResultCount } from "./search-result-count-normalization.ts";
|
|
3
5
|
import type { SearchOptions, SearchResult, SearchResponse } from "./perplexity.ts";
|
|
4
6
|
import { hasCredentialSource, redactCredential, resolveCredential } from "./credential-source.ts";
|
|
5
7
|
import { fetchWithCredentialRedirects, getWebSearchConfigPath, resolveApiBaseUrl } from "./utils.ts";
|
|
@@ -56,26 +58,6 @@ function getApiUrl(): string {
|
|
|
56
58
|
})}/web/search`;
|
|
57
59
|
}
|
|
58
60
|
|
|
59
|
-
function normalizeCount(value: number | undefined): number {
|
|
60
|
-
if (typeof value !== "number" || !Number.isFinite(value)) return 5;
|
|
61
|
-
return Math.max(1, Math.min(Math.floor(value), 20));
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
function normalizeDomain(value: string): string | null {
|
|
65
|
-
let input = value.trim().toLowerCase();
|
|
66
|
-
if (!input) return null;
|
|
67
|
-
if (input.startsWith("-")) input = input.slice(1).trim();
|
|
68
|
-
if (!input) return null;
|
|
69
|
-
try {
|
|
70
|
-
const parsed = input.includes("://") ? new URL(input) : new URL(`https://${input}`);
|
|
71
|
-
input = parsed.hostname;
|
|
72
|
-
} catch {
|
|
73
|
-
input = input.split("/")[0]?.split(":")[0] ?? "";
|
|
74
|
-
}
|
|
75
|
-
input = input.replace(/^\.+|\.+$/g, "");
|
|
76
|
-
return /^[a-z0-9][a-z0-9.-]*\.[a-z]{2,}$/i.test(input) ? input : null;
|
|
77
|
-
}
|
|
78
|
-
|
|
79
61
|
function normalizeDomainFilters(domainFilter: string[] | undefined): NormalizedDomainFilters {
|
|
80
62
|
const filters: NormalizedDomainFilters = { allowed: [], blocked: [] };
|
|
81
63
|
if (!domainFilter?.length) return filters;
|
|
@@ -151,7 +133,7 @@ export async function searchWithBrave(
|
|
|
151
133
|
);
|
|
152
134
|
}
|
|
153
135
|
|
|
154
|
-
const numResults =
|
|
136
|
+
const numResults = normalizeSearchResultCount(options.numResults);
|
|
155
137
|
const domainFilters = normalizeDomainFilters(options.domainFilter);
|
|
156
138
|
const searchQuery = buildBraveQuery(query, options.domainFilter);
|
|
157
139
|
const activityId = activityMonitor.logStart({ type: "api", query: searchQuery });
|
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
import { existsSync, readFileSync } from "node:fs";
|
|
2
2
|
import { activityMonitor } from "./activity.ts";
|
|
3
|
+
import { normalizeDomain } from "./domain-filter-normalization.ts";
|
|
4
|
+
import { formatSearchResultsAsAnswer } from "./search-answer-formatting.ts";
|
|
5
|
+
import { normalizeSearchResultCount } from "./search-result-count-normalization.ts";
|
|
3
6
|
import type { SearchOptions, SearchResponse } from "./perplexity.ts";
|
|
4
7
|
import { hasCredentialSource, redactCredential, resolveCredential } from "./credential-source.ts";
|
|
5
8
|
import { getWebSearchConfigPath } from "./utils.ts";
|
|
@@ -182,26 +185,6 @@ function requireSerpZone(): string {
|
|
|
182
185
|
);
|
|
183
186
|
}
|
|
184
187
|
|
|
185
|
-
function normalizeCount(value: number | undefined): number {
|
|
186
|
-
if (typeof value !== "number" || !Number.isFinite(value)) return 5;
|
|
187
|
-
return Math.max(1, Math.min(Math.floor(value), 20));
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
function normalizeDomain(value: string): string | null {
|
|
191
|
-
let input = value.trim().toLowerCase();
|
|
192
|
-
if (!input) return null;
|
|
193
|
-
if (input.startsWith("-")) input = input.slice(1).trim();
|
|
194
|
-
if (!input) return null;
|
|
195
|
-
try {
|
|
196
|
-
const parsed = input.includes("://") ? new URL(input) : new URL(`https://${input}`);
|
|
197
|
-
input = parsed.hostname;
|
|
198
|
-
} catch {
|
|
199
|
-
input = input.split("/")[0]?.split(":")[0] ?? "";
|
|
200
|
-
}
|
|
201
|
-
input = input.replace(/^\.+|\.+$/g, "");
|
|
202
|
-
return /^[a-z0-9][a-z0-9.-]*\.[a-z]{2,}$/i.test(input) ? input : null;
|
|
203
|
-
}
|
|
204
|
-
|
|
205
188
|
interface DomainFilters {
|
|
206
189
|
include: string[];
|
|
207
190
|
exclude: string[];
|
|
@@ -436,16 +419,6 @@ function mapResults(
|
|
|
436
419
|
return mapped;
|
|
437
420
|
}
|
|
438
421
|
|
|
439
|
-
// A SERP zone returns ranked links, never a synthesized answer, so one is
|
|
440
|
-
// assembled from the sources — the same shape brave.ts and searxng.ts produce.
|
|
441
|
-
function buildAnswer(results: SearchResponse["results"]): string {
|
|
442
|
-
return results
|
|
443
|
-
.map((result) => result.snippet
|
|
444
|
-
? `${result.snippet}\nSource: ${result.title} (${result.url})`
|
|
445
|
-
: `Source: ${result.title} (${result.url})`)
|
|
446
|
-
.join("\n\n");
|
|
447
|
-
}
|
|
448
|
-
|
|
449
422
|
// Both halves are required: a token with no zone cannot make a request, and a
|
|
450
423
|
// zone with no token cannot either. Availability therefore checks the config this
|
|
451
424
|
// surface actually needs rather than merely a key, the way firecrawl.ts's
|
|
@@ -477,7 +450,7 @@ export async function searchWithBrightData(query: string, options: BrightDataSea
|
|
|
477
450
|
// resolver, and must not reach a billable endpoint.
|
|
478
451
|
const zone = requireSerpZone();
|
|
479
452
|
const apiKey = await requireApiKey(options.signal);
|
|
480
|
-
const numResults =
|
|
453
|
+
const numResults = normalizeSearchResultCount(options.numResults);
|
|
481
454
|
const filters = parseDomainFilter(options.domainFilter);
|
|
482
455
|
const searchQuery = buildSearchQuery(query, filters);
|
|
483
456
|
const body: Record<string, unknown> = {
|
|
@@ -560,5 +533,5 @@ export async function searchWithBrightData(query: string, options: BrightDataSea
|
|
|
560
533
|
|
|
561
534
|
activityMonitor.logComplete(activityId, response.status);
|
|
562
535
|
const results = mapResults(data.organic, numResults, filters);
|
|
563
|
-
return { answer:
|
|
536
|
+
return { answer: formatSearchResultsAsAnswer(results), results };
|
|
564
537
|
}
|
|
@@ -12,7 +12,6 @@ interface Match {
|
|
|
12
12
|
interface Range {
|
|
13
13
|
start: number;
|
|
14
14
|
end: number;
|
|
15
|
-
matches: Match[];
|
|
16
15
|
}
|
|
17
16
|
|
|
18
17
|
function normalize(value: string): string {
|
|
@@ -27,9 +26,9 @@ function editDistanceWithin(left: string, right: string, maximum: number): boole
|
|
|
27
26
|
let rowMinimum = i;
|
|
28
27
|
for (let j = 1; j <= right.length; j++) {
|
|
29
28
|
const value = Math.min(
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
29
|
+
previous[j] + 1,
|
|
30
|
+
current[j - 1] + 1,
|
|
31
|
+
previous[j - 1] + (left[i - 1] === right[j - 1] ? 0 : 1),
|
|
33
32
|
);
|
|
34
33
|
current[j] = value;
|
|
35
34
|
rowMinimum = Math.min(rowMinimum, value);
|
|
@@ -37,7 +36,7 @@ function editDistanceWithin(left: string, right: string, maximum: number): boole
|
|
|
37
36
|
if (rowMinimum > maximum) return false;
|
|
38
37
|
previous = current;
|
|
39
38
|
}
|
|
40
|
-
return
|
|
39
|
+
return previous[right.length] <= maximum;
|
|
41
40
|
}
|
|
42
41
|
|
|
43
42
|
function literalMatches(text: string, query: string, caseInsensitive: boolean): Match[] {
|
|
@@ -69,27 +68,54 @@ function fuzzyMatches(text: string, query: string): Match[] {
|
|
|
69
68
|
const first = tokens.find(token => matched.some(queryToken => {
|
|
70
69
|
const maximum = queryToken.length >= 9 ? 2 : queryToken.length >= 5 ? 1 : 0;
|
|
71
70
|
return editDistanceWithin(queryToken, normalize(token[0]), maximum);
|
|
72
|
-
}))
|
|
73
|
-
const start = paragraph.index +
|
|
74
|
-
matches.push({ query, start, end: start +
|
|
71
|
+
}))!;
|
|
72
|
+
const start = paragraph.index + first.index;
|
|
73
|
+
matches.push({ query, start, end: start + first[0].length });
|
|
75
74
|
}
|
|
76
75
|
return matches;
|
|
77
76
|
}
|
|
78
77
|
|
|
79
|
-
function mergeRanges(
|
|
78
|
+
function mergeRanges(ranges: Range[]): Range[] {
|
|
79
|
+
const merged: Range[] = [];
|
|
80
|
+
for (const range of [...ranges].sort((left, right) => left.start - right.start || left.end - right.end)) {
|
|
81
|
+
const previous = merged.at(-1);
|
|
82
|
+
if (previous && range.start <= previous.end) previous.end = Math.max(previous.end, range.end);
|
|
83
|
+
else merged.push({ ...range });
|
|
84
|
+
}
|
|
85
|
+
return merged;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
function contextRanges(textLength: number, matches: Match[]): Range[] {
|
|
80
89
|
const ranges: Range[] = [];
|
|
81
|
-
for (const match of
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
previous.end = Math.max(previous.end, end);
|
|
87
|
-
previous.matches.push(match);
|
|
88
|
-
} else {
|
|
89
|
-
ranges.push({ start, end, matches: [match] });
|
|
90
|
-
}
|
|
90
|
+
for (const match of matches) {
|
|
91
|
+
ranges.push({
|
|
92
|
+
start: Math.max(0, match.start - CONTEXT_CHARS),
|
|
93
|
+
end: Math.min(textLength, match.end + CONTEXT_CHARS),
|
|
94
|
+
});
|
|
91
95
|
}
|
|
92
|
-
return ranges;
|
|
96
|
+
return mergeRanges(ranges);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
function lowerBound(values: number[], target: number): number {
|
|
100
|
+
let low = 0;
|
|
101
|
+
let high = values.length;
|
|
102
|
+
while (low < high) {
|
|
103
|
+
const middle = (low + high) >>> 1;
|
|
104
|
+
if (values[middle] < target) low = middle + 1;
|
|
105
|
+
else high = middle;
|
|
106
|
+
}
|
|
107
|
+
return low;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
function upperBound(values: number[], target: number): number {
|
|
111
|
+
let low = 0;
|
|
112
|
+
let high = values.length;
|
|
113
|
+
while (low < high) {
|
|
114
|
+
const middle = (low + high) >>> 1;
|
|
115
|
+
if (values[middle] <= target) low = middle + 1;
|
|
116
|
+
else high = middle;
|
|
117
|
+
}
|
|
118
|
+
return low;
|
|
93
119
|
}
|
|
94
120
|
|
|
95
121
|
export function findContent(
|
|
@@ -98,42 +124,131 @@ export function findContent(
|
|
|
98
124
|
mode: FindMode,
|
|
99
125
|
): { text: string; matchCount: number; returnedMatches: number; queryResults: Array<{ query: string; matchCount: number }> } {
|
|
100
126
|
const normalizedQueries = [...new Set(queries.map(query => query.trim()).filter(Boolean))];
|
|
101
|
-
const
|
|
102
|
-
? fuzzyMatches(text, query)
|
|
103
|
-
:
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
}));
|
|
127
|
+
const occurrences = normalizedQueries.map(query => {
|
|
128
|
+
const matches = mode === "fuzzy" ? fuzzyMatches(text, query) : literalMatches(text, query, mode === "case-insensitive");
|
|
129
|
+
return { query, matches, starts: matches.map(match => match.start), ends: matches.map(match => match.end) };
|
|
130
|
+
});
|
|
131
|
+
const matches = occurrences.flatMap(result => result.matches);
|
|
132
|
+
const queryResults = occurrences.map(result => ({ query: result.query, matchCount: result.matches.length }));
|
|
108
133
|
|
|
109
134
|
const heading = matches.length > 0 ? `Text matches (${mode})` : `Text matches (${mode}): no matches`;
|
|
110
|
-
const sections = [heading];
|
|
111
|
-
let formattedLength = heading.length;
|
|
112
|
-
let returnedMatches = 0;
|
|
113
|
-
for (const range of mergeRanges(text.length, matches)) {
|
|
114
|
-
const prefix = range.start > 0 ? "…" : "";
|
|
115
|
-
const suffix = range.end < text.length ? "…" : "";
|
|
116
|
-
const snippet = `${prefix}${text.slice(range.start, range.end).replace(/\s+/g, " ").trim()}${suffix}`;
|
|
117
|
-
const counts = [...new Set(range.matches.map(match => match.query))]
|
|
118
|
-
.map(query => `\"${query}\" ×${range.matches.filter(match => match.query === query).length}`)
|
|
119
|
-
.join(", ");
|
|
120
|
-
const section = `${sections.length}. ${counts}\n${snippet}`;
|
|
121
|
-
if (formattedLength + 2 + section.length > MAX_OUTPUT_CHARS) break;
|
|
122
|
-
sections.push(section);
|
|
123
|
-
formattedLength += 2 + section.length;
|
|
124
|
-
returnedMatches += range.matches.length;
|
|
125
|
-
}
|
|
126
|
-
|
|
127
135
|
const missing = queryResults.filter(result => result.matchCount === 0).map(result => `\"${result.query}\"`);
|
|
128
|
-
const
|
|
129
|
-
...
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
+
const matchingQueries = occurrences.filter(result => result.matches.length > 0).map((result, index) => ({
|
|
137
|
+
...result,
|
|
138
|
+
id: `Q${index + 1}`,
|
|
139
|
+
order: index,
|
|
140
|
+
}));
|
|
141
|
+
const whitespaceRuns = [...text.matchAll(/\s+/g)].map(run => ({ start: run.index, end: run.index + run[0].length }));
|
|
142
|
+
const whitespaceStarts = whitespaceRuns.map(run => run.start);
|
|
143
|
+
const whitespaceEnds = whitespaceRuns.map(run => run.end);
|
|
144
|
+
const whitespaceSavings = [0];
|
|
145
|
+
for (const run of whitespaceRuns) whitespaceSavings.push(whitespaceSavings.at(-1)! + run.end - run.start - 1);
|
|
146
|
+
function normalizedLength(start: number, end: number): number {
|
|
147
|
+
const firstRun = upperBound(whitespaceStarts, start) - 1;
|
|
148
|
+
if (firstRun >= 0 && whitespaceEnds[firstRun] > start) start = whitespaceEnds[firstRun];
|
|
149
|
+
if (start >= end) return 0;
|
|
150
|
+
const lastRun = upperBound(whitespaceStarts, end - 1) - 1;
|
|
151
|
+
if (lastRun >= 0 && whitespaceEnds[lastRun] >= end) end = whitespaceStarts[lastRun];
|
|
152
|
+
if (start >= end) return 0;
|
|
153
|
+
const first = lowerBound(whitespaceStarts, start);
|
|
154
|
+
const last = upperBound(whitespaceEnds, end);
|
|
155
|
+
return end - start - (whitespaceSavings[last] - whitespaceSavings[first]);
|
|
156
|
+
}
|
|
157
|
+
function rangeCounts(range: Range) {
|
|
158
|
+
// Literal occurrences have fixed widths; fuzzy occurrences come from successive disjoint paragraphs.
|
|
159
|
+
return matchingQueries.flatMap(result => {
|
|
160
|
+
const first = lowerBound(result.starts, range.start);
|
|
161
|
+
const last = upperBound(result.ends, range.end);
|
|
162
|
+
return last > first ? [{ ...result, count: last - first, firstStart: result.starts[first] }] : [];
|
|
163
|
+
}).sort((left, right) => left.firstStart - right.firstStart || left.order - right.order);
|
|
164
|
+
}
|
|
165
|
+
const legend = matchingQueries.length > 0
|
|
166
|
+
? `Queries: ${matchingQueries.map(result => `${result.id} = \"${result.query}\"`).join(", ")}`
|
|
167
|
+
: "";
|
|
168
|
+
const missingNotice = missing.length > 0 ? `No matches: ${missing.join(", ")}` : "";
|
|
169
|
+
function measure(ranges: Range[], overflow = false, omitted: string[] = []) {
|
|
170
|
+
let length = heading.length;
|
|
171
|
+
let returnedMatches = 0;
|
|
172
|
+
if (overflow && legend) length += 2 + legend.length;
|
|
173
|
+
for (const [index, range] of ranges.entries()) {
|
|
174
|
+
const counts = rangeCounts(range);
|
|
175
|
+
if (counts.length === 0) continue;
|
|
176
|
+
const labelsLength = counts.reduce((total, result) => total
|
|
177
|
+
+ (overflow ? result.id.length : result.query.length + 2)
|
|
178
|
+
+ 2 + String(result.count).length, 2 * (counts.length - 1));
|
|
179
|
+
const snippetLength = normalizedLength(range.start, range.end)
|
|
180
|
+
+ (range.start > 0 ? 1 : 0) + (range.end < text.length ? 1 : 0);
|
|
181
|
+
length += 2 + String(index + 1).length + 2 + labelsLength + 1 + snippetLength;
|
|
182
|
+
returnedMatches += counts.reduce((total, result) => total + result.count, 0);
|
|
183
|
+
}
|
|
184
|
+
if (missingNotice) length += 2 + missingNotice.length;
|
|
185
|
+
if (omitted.length > 0) length += 2 + `No representative excerpt: ${omitted.join(", ")}.`.length;
|
|
186
|
+
if (returnedMatches < matches.length) length += 2 + `Showing ${returnedMatches} of ${matches.length} matches.`.length;
|
|
187
|
+
return { length, returnedMatches };
|
|
188
|
+
}
|
|
189
|
+
function formatRanges(ranges: Range[], overflow = false, omitted: string[] = []) {
|
|
190
|
+
const sections = [heading];
|
|
191
|
+
let returnedMatches = 0;
|
|
192
|
+
if (overflow && legend) sections.push(legend);
|
|
193
|
+
for (const [index, range] of ranges.entries()) {
|
|
194
|
+
const contained = rangeCounts(range);
|
|
195
|
+
if (contained.length === 0) continue;
|
|
196
|
+
const prefix = range.start > 0 ? "…" : "";
|
|
197
|
+
const suffix = range.end < text.length ? "…" : "";
|
|
198
|
+
const snippet = `${prefix}${text.slice(range.start, range.end).replace(/\s+/g, " ").trim()}${suffix}`;
|
|
199
|
+
const counts = contained
|
|
200
|
+
.map(result => `${overflow ? result.id : `\"${result.query}\"`} ×${result.count}`)
|
|
201
|
+
.join(", ");
|
|
202
|
+
sections.push(`${index + 1}. ${counts}\n${snippet}`);
|
|
203
|
+
returnedMatches += contained.reduce((total, result) => total + result.count, 0);
|
|
204
|
+
}
|
|
205
|
+
if (missingNotice) sections.push(missingNotice);
|
|
206
|
+
if (omitted.length > 0) sections.push(`No representative excerpt: ${omitted.join(", ")}.`);
|
|
207
|
+
if (returnedMatches < matches.length) sections.push(`Showing ${returnedMatches} of ${matches.length} matches.`);
|
|
208
|
+
return { text: sections.join("\n\n"), matchCount: matches.length, returnedMatches, queryResults };
|
|
136
209
|
}
|
|
137
210
|
|
|
138
|
-
|
|
211
|
+
const fullRanges = contextRanges(text.length, matches);
|
|
212
|
+
const full = measure(fullRanges);
|
|
213
|
+
if (full.returnedMatches === matches.length && full.length <= MAX_OUTPUT_CHARS) return formatRanges(fullRanges);
|
|
214
|
+
|
|
215
|
+
let ranges: Range[] = [];
|
|
216
|
+
let omitted = matchingQueries.map(result => result.id);
|
|
217
|
+
if (measure(ranges, true, omitted).length > MAX_OUTPUT_CHARS) {
|
|
218
|
+
const text = `${heading}\n\nUnable to format bounded excerpts: query metadata exceeds ${MAX_OUTPUT_CHARS} characters.\n\nShowing 0 of ${matches.length} matches.`;
|
|
219
|
+
return { text, matchCount: matches.length, returnedMatches: 0, queryResults };
|
|
220
|
+
}
|
|
221
|
+
const witnesses: Match[] = [];
|
|
222
|
+
for (const { id, matches: queryMatches } of matchingQueries) {
|
|
223
|
+
const proposedOmitted = omitted.filter(queryId => queryId !== id);
|
|
224
|
+
const currentLength = measure(ranges, true, omitted).length;
|
|
225
|
+
let selected: { witness: Match; ranges: Range[]; cost: number } | undefined;
|
|
226
|
+
for (const witness of queryMatches) {
|
|
227
|
+
const proposedRanges = mergeRanges([...ranges, { start: witness.start, end: witness.end }]);
|
|
228
|
+
const summary = measure(proposedRanges, true, proposedOmitted);
|
|
229
|
+
const candidate = {
|
|
230
|
+
witness,
|
|
231
|
+
ranges: proposedRanges,
|
|
232
|
+
cost: summary.length - currentLength,
|
|
233
|
+
};
|
|
234
|
+
if (summary.length <= MAX_OUTPUT_CHARS && (!selected || candidate.cost < selected.cost
|
|
235
|
+
|| candidate.cost === selected.cost && (witness.start < selected.witness.start
|
|
236
|
+
|| witness.start === selected.witness.start && witness.end < selected.witness.end))) selected = candidate;
|
|
237
|
+
}
|
|
238
|
+
if (selected) {
|
|
239
|
+
ranges = selected.ranges;
|
|
240
|
+
omitted = proposedOmitted;
|
|
241
|
+
witnesses.push(selected.witness);
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
for (const witness of witnesses) {
|
|
245
|
+
const proposed = mergeRanges([...ranges, {
|
|
246
|
+
start: Math.max(0, witness.start - CONTEXT_CHARS),
|
|
247
|
+
end: Math.min(text.length, witness.end + CONTEXT_CHARS),
|
|
248
|
+
}]);
|
|
249
|
+
if (measure(proposed, true, omitted).length <= MAX_OUTPUT_CHARS) ranges = proposed;
|
|
250
|
+
}
|
|
251
|
+
const allOccurrences = mergeRanges([...ranges, ...matches.map(match => ({ start: match.start, end: match.end }))]);
|
|
252
|
+
if (measure(allOccurrences, true, omitted).length <= MAX_OUTPUT_CHARS) ranges = allOccurrences;
|
|
253
|
+
return formatRanges(ranges, true, omitted);
|
|
139
254
|
}
|
|
@@ -45,6 +45,7 @@ function buildProviderButtons(
|
|
|
45
45
|
{ value: "serpbase", label: "SerpBase", available: available.serpbase },
|
|
46
46
|
{ value: "serpapi", label: "SerpApi", available: available.serpapi },
|
|
47
47
|
{ value: "serper", label: "Serper", available: available.serper },
|
|
48
|
+
{ value: "serply", label: "Serply", available: available.serply },
|
|
48
49
|
{ value: "valyu", label: "Valyu", available: available.valyu },
|
|
49
50
|
];
|
|
50
51
|
|
|
@@ -1472,7 +1473,7 @@ const SCRIPT = `(function() {
|
|
|
1472
1473
|
var token = DATA.sessionToken;
|
|
1473
1474
|
var timeoutSec = DATA.timeout;
|
|
1474
1475
|
var queries = Array.isArray(DATA.queries) ? DATA.queries : [];
|
|
1475
|
-
var providers = ["all", "openai", "exa", "brave", "parallel", "parallel-mcp", "tinyfish", "search1api", "searchinfinity", "querit", "tavily", "firecrawl", "jina", "serpdive", "kagi", "bocha", "ollama", "searxng", "duckduckgo", "perplexity", "gemini", "kimi", "anysearch", "xcrawl", "xai", "mistral", "brightdata", "serpbase", "serpapi", "serper", "valyu"];
|
|
1476
|
+
var providers = ["auto", "all", "openai", "exa", "brave", "parallel", "parallel-mcp", "tinyfish", "search1api", "searchinfinity", "querit", "tavily", "firecrawl", "jina", "serpdive", "kagi", "bocha", "ollama", "searxng", "duckduckgo", "perplexity", "gemini", "kimi", "anysearch", "xcrawl", "xai", "mistral", "brightdata", "serpbase", "serpapi", "serper", "serply", "valyu"];
|
|
1476
1477
|
var availProviders = DATA.availableProviders && typeof DATA.availableProviders === "object" ? DATA.availableProviders : {};
|
|
1477
1478
|
var workflow = "summary-review";
|
|
1478
1479
|
var initialDefaultProvider = typeof DATA.defaultProvider === "string" ? DATA.defaultProvider : "exa";
|
|
@@ -1676,6 +1677,7 @@ const SCRIPT = `(function() {
|
|
|
1676
1677
|
}
|
|
1677
1678
|
|
|
1678
1679
|
function providerLabel(provider) {
|
|
1680
|
+
if (provider === "auto") return "Auto";
|
|
1679
1681
|
if (provider === "all") return "All";
|
|
1680
1682
|
if (provider === "openai") return "OpenAI";
|
|
1681
1683
|
if (provider === "brave") return "Brave";
|
|
@@ -1705,6 +1707,7 @@ const SCRIPT = `(function() {
|
|
|
1705
1707
|
if (provider === "serpbase") return "SerpBase";
|
|
1706
1708
|
if (provider === "serpapi") return "SerpApi";
|
|
1707
1709
|
if (provider === "serper") return "Serper";
|
|
1710
|
+
if (provider === "serply") return "Serply";
|
|
1708
1711
|
if (provider === "valyu") return "Valyu";
|
|
1709
1712
|
return "Unknown";
|
|
1710
1713
|
}
|
|
@@ -7,7 +7,8 @@ export function resolveWebSearchWorkflow(input: unknown, hasUI: boolean): WebSea
|
|
|
7
7
|
if (normalized === "auto-summary") return "auto-summary";
|
|
8
8
|
if (!hasUI) return "none";
|
|
9
9
|
if (normalized === "none") return "none";
|
|
10
|
-
return "summary-review";
|
|
10
|
+
if (normalized === "summary-review") return "summary-review";
|
|
11
|
+
return "none";
|
|
11
12
|
}
|
|
12
13
|
|
|
13
14
|
export class CuratorRunState {
|
|
@@ -304,6 +304,7 @@ export function startCuratorServer(
|
|
|
304
304
|
if (provider === "brightdata") return availableProviders.brightdata;
|
|
305
305
|
if (provider === "serpbase") return availableProviders.serpbase;
|
|
306
306
|
if (provider === "serper") return availableProviders.serper;
|
|
307
|
+
if (provider === "serply") return availableProviders.serply;
|
|
307
308
|
if (provider === "valyu") return availableProviders.valyu;
|
|
308
309
|
return false;
|
|
309
310
|
}
|