@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
|
# pi-typesafe
|
|
2
2
|
|
|
3
|
+
[](https://github.com/DevMortimer/pi-typesafe/actions/workflows/ci.yml)
|
|
4
|
+
|
|
3
5
|
[Jev](https://typesafe.ai) inside [Pi](https://pi.dev). Jev is TypeSafe's judgment model: send it some state and typed questions and it returns probabilities instead of prose, in well under a second, for a fraction of a cent. This package gives Pi three things built on it:
|
|
4
6
|
|
|
5
7
|
- **A tool for the agent.** `typesafe_evaluate` hands small structured judgments (classify, triage, compare, score) to Jev and returns calibrated numbers in one batched call.
|
|
@@ -141,13 +143,15 @@ Your extension owns its own user consent and budget; `/typesafe enable` applies
|
|
|
141
143
|
## Development
|
|
142
144
|
|
|
143
145
|
```bash
|
|
144
|
-
npm
|
|
145
|
-
npm run check # typecheck, offline tests
|
|
146
|
+
npm ci
|
|
147
|
+
npm run check # build, typecheck, offline tests
|
|
146
148
|
cp .env.example .env # add your key locally; .env is git-ignored
|
|
147
149
|
npm run test:live # one billable sample request
|
|
148
150
|
npm run dev:pi # start Pi with only this working tree as extension (.env optional)
|
|
149
151
|
```
|
|
150
152
|
|
|
153
|
+
Pull requests run key-free checks on Linux and macOS, supported Node versions, and the installed npm package. Version tags prepare a draft release; npm publishing stays manual. See [Contributing](CONTRIBUTING.md) and [CI and continuous delivery](docs/ci-cd.md).
|
|
154
|
+
|
|
151
155
|
## License
|
|
152
156
|
|
|
153
157
|
MIT
|
|
@@ -1,9 +1,20 @@
|
|
|
1
1
|
import type { Fetch, Questions, SystemOneRequest, SystemOneResult } from "@typesafe-ai/sdk";
|
|
2
2
|
import type { BatchEvaluation, BatchOptions } from "./batch.js";
|
|
3
3
|
import type { BlockedCap, SpendCaps, UsageLedger, UsageReport } from "./usage.js";
|
|
4
|
+
export type TypeSafeBackend = "typesafe" | "openrouter";
|
|
5
|
+
export interface BackendConfig {
|
|
6
|
+
host: string;
|
|
7
|
+
keyEnv?: string;
|
|
8
|
+
/** Request path, when the backend does not serve the SDK's own `/v1/systemone`. */
|
|
9
|
+
path?: string;
|
|
10
|
+
}
|
|
11
|
+
/** Registry of known judgment backends. Extendable by callers. */
|
|
12
|
+
export declare const DECISIONS_BACKENDS: Record<TypeSafeBackend, BackendConfig>;
|
|
4
13
|
export interface TypeSafeOptions {
|
|
5
14
|
/** Defaults to TYPESAFE_API_KEY, then the key saved by `/typesafe login`; never returned. */
|
|
6
15
|
apiKey?: string;
|
|
16
|
+
/** Judgment backend. When omitted, routes to the default TypeSafe host. */
|
|
17
|
+
backend?: TypeSafeBackend;
|
|
7
18
|
/** Defaults to jev-latest. No model is inferred from submitted content. */
|
|
8
19
|
model?: string;
|
|
9
20
|
/** Per request. Default: 15 seconds. No automatic retries. */
|
|
@@ -5,6 +5,17 @@ import { keySituation } from "./credentials.js";
|
|
|
5
5
|
import { TypeSafeIntegrationError, safeError } from "./errors.js";
|
|
6
6
|
import { DEFAULT_MAX_INPUT_BYTES, assertWithinByteLimit, prepareEvaluationRequest } from "./schema.js";
|
|
7
7
|
import { DEFAULT_USD_PER_MTOK, capsFromEnvironment, estimateUsd, mergeCaps, openUsageLedger } from "./usage.js";
|
|
8
|
+
/** The path the SDK appends to whatever base URL it is given. */
|
|
9
|
+
const SDK_PATH = "/v1/systemone";
|
|
10
|
+
/** Registry of known judgment backends. Extendable by callers. */
|
|
11
|
+
export const DECISIONS_BACKENDS = {
|
|
12
|
+
typesafe: { host: "https://api.typesafe.ai", keyEnv: "TYPESAFE_API_KEY" },
|
|
13
|
+
openrouter: { host: "https://openrouter.ai", keyEnv: "OPENROUTER_API_KEY", path: "/api/alpha/decisions" },
|
|
14
|
+
};
|
|
15
|
+
/** Send the SDK's fixed path to the backend's own, preserving any caller-supplied transport. */
|
|
16
|
+
function backendFetch(path, inner = fetch) {
|
|
17
|
+
return (input, init) => inner(String(input).replace(SDK_PATH, path), init);
|
|
18
|
+
}
|
|
8
19
|
/** Default attempts per client instance; the extension quotes the same number in its consent copy. */
|
|
9
20
|
export const DEFAULT_MAX_REQUESTS = 20;
|
|
10
21
|
function positiveInteger(value, label) {
|
|
@@ -69,15 +80,38 @@ function capsDescription(caps) {
|
|
|
69
80
|
/** A bounded, server-side TypeSafe client independent of Pi's runtime. */
|
|
70
81
|
export function createTypeSafe(options = {}) {
|
|
71
82
|
let apiKey = options.apiKey?.trim();
|
|
83
|
+
// Resolve backend and host.
|
|
84
|
+
const backendName = options.backend;
|
|
85
|
+
let baseURL = "https://api.typesafe.ai";
|
|
86
|
+
let keyEnv = "TYPESAFE_API_KEY";
|
|
87
|
+
let backendPath;
|
|
88
|
+
if (backendName !== undefined) {
|
|
89
|
+
const backend = DECISIONS_BACKENDS[backendName];
|
|
90
|
+
if (!backend)
|
|
91
|
+
throw new TypeSafeIntegrationError("configuration", `Unknown judgment backend "${backendName}". Valid backends: ${Object.keys(DECISIONS_BACKENDS).join(", ")}.`);
|
|
92
|
+
baseURL = backend.host;
|
|
93
|
+
keyEnv = backend.keyEnv ?? "TYPESAFE_API_KEY";
|
|
94
|
+
backendPath = backend.path;
|
|
95
|
+
}
|
|
96
|
+
if (!apiKey) {
|
|
97
|
+
// Backend-specific env var first (e.g. OPENROUTER_API_KEY), then fall back to
|
|
98
|
+
// the standard TYPESAFE_API_KEY / stored-key resolution.
|
|
99
|
+
if (backendName !== undefined && keyEnv !== "TYPESAFE_API_KEY") {
|
|
100
|
+
const fromEnv = process.env[keyEnv]?.trim();
|
|
101
|
+
if (fromEnv)
|
|
102
|
+
apiKey = fromEnv;
|
|
103
|
+
}
|
|
104
|
+
if (!apiKey) {
|
|
105
|
+
const situation = keySituation();
|
|
106
|
+
if (situation.kind === "unusable")
|
|
107
|
+
throw new TypeSafeIntegrationError("configuration", situation.reason);
|
|
108
|
+
if (situation.kind === "environment" || situation.kind === "stored")
|
|
109
|
+
apiKey = situation.key;
|
|
110
|
+
}
|
|
111
|
+
}
|
|
72
112
|
if (!apiKey) {
|
|
73
|
-
|
|
74
|
-
if (situation.kind === "unusable")
|
|
75
|
-
throw new TypeSafeIntegrationError("configuration", situation.reason);
|
|
76
|
-
if (situation.kind === "environment" || situation.kind === "stored")
|
|
77
|
-
apiKey = situation.key;
|
|
113
|
+
throw new TypeSafeIntegrationError("configuration", `No API key. Run /typesafe login in Pi, or set ${keyEnv} in the environment.`);
|
|
78
114
|
}
|
|
79
|
-
if (!apiKey)
|
|
80
|
-
throw new TypeSafeIntegrationError("configuration", "No TypeSafe API key. Run /typesafe login in Pi, or set TYPESAFE_API_KEY in the environment.");
|
|
81
115
|
const timeout = positiveInteger(options.timeoutMs ?? 15_000, "timeoutMs");
|
|
82
116
|
const maxInputBytes = positiveInteger(options.maxInputBytes ?? DEFAULT_MAX_INPUT_BYTES, "maxInputBytes");
|
|
83
117
|
const maxRequests = positiveInteger(options.maxRequests ?? DEFAULT_MAX_REQUESTS, "maxRequests");
|
|
@@ -88,18 +122,19 @@ export function createTypeSafe(options = {}) {
|
|
|
88
122
|
...(options.maxInputTokensPerDay === undefined ? {} : { maxInputTokensPerDay: positiveInteger(options.maxInputTokensPerDay, "maxInputTokensPerDay") }),
|
|
89
123
|
...(options.maxUsdPerDay === undefined ? {} : { maxUsdPerDay: positiveNumber(options.maxUsdPerDay, "maxUsdPerDay") }),
|
|
90
124
|
}, capsFromEnvironment());
|
|
91
|
-
const
|
|
125
|
+
const transport = backendPath ? backendFetch(backendPath, options.fetch) : options.fetch;
|
|
126
|
+
const model = options.model ?? (backendName === "openrouter" ? "typesafe/jev-1.13" : "jev-latest");
|
|
92
127
|
if (typeof model !== "string" || !model.trim() || model.length > 100)
|
|
93
128
|
throw new TypeSafeIntegrationError("configuration", "model must be a nonempty string of at most 100 characters.");
|
|
94
129
|
// Do not inherit SDK debug logging or alternate destinations from the environment.
|
|
95
130
|
const client = new TypeSafeClient({
|
|
96
131
|
apiKey,
|
|
97
132
|
defaultModel: model,
|
|
98
|
-
baseURL
|
|
133
|
+
baseURL,
|
|
99
134
|
timeout,
|
|
100
135
|
retry: { maxRetries: 0 },
|
|
101
136
|
logLevel: "off",
|
|
102
|
-
...(
|
|
137
|
+
...(transport ? { fetch: transport } : {}),
|
|
103
138
|
});
|
|
104
139
|
const ledger = options.ledger ?? openUsageLedger({ usdPerMTok });
|
|
105
140
|
const usage = { requestsStarted: 0, requestsSucceeded: 0, requestsFailed: 0, inputTokens: 0, outputTokens: 0 };
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
export { createTypeSafe, DEFAULT_MAX_REQUESTS } from "./client.js";
|
|
2
|
-
export type { TypeSafe, TypeSafeOptions, EvaluationOptions, Evaluation, UsageSnapshot, SpendReport, } from "./client.js";
|
|
1
|
+
export { createTypeSafe, DEFAULT_MAX_REQUESTS, DECISIONS_BACKENDS } from "./client.js";
|
|
2
|
+
export type { TypeSafe, TypeSafeOptions, EvaluationOptions, Evaluation, UsageSnapshot, SpendReport, TypeSafeBackend, BackendConfig, } from "./client.js";
|
|
3
3
|
export { ask, DEFAULT_ASK_TIMEOUT_MS } from "./ask.js";
|
|
4
4
|
export type { AskAnswer, AskOptions, Judge } from "./ask.js";
|
|
5
5
|
export { fanOut, DEFAULT_CONCURRENCY, evaluateMany, evaluateAll, chunkEvaluationRequest } from "./batch.js";
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export { createTypeSafe, DEFAULT_MAX_REQUESTS } from "./client.js";
|
|
1
|
+
export { createTypeSafe, DEFAULT_MAX_REQUESTS, DECISIONS_BACKENDS } from "./client.js";
|
|
2
2
|
export { ask, DEFAULT_ASK_TIMEOUT_MS } from "./ask.js";
|
|
3
3
|
export { fanOut, DEFAULT_CONCURRENCY, evaluateMany, evaluateAll, chunkEvaluationRequest } from "./batch.js";
|
|
4
4
|
export { authState, authStatePath, clearAuthState, describeAuth, recordAuthFailure, recordAuthVerified } from "./auth.js";
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pi-typesafe",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.6.1",
|
|
4
4
|
"description": "TypeSafe AI (Jev) decisions for Pi: batched Choice/Score/Noul evaluation tool, terminal playground, and a typed API other extensions build on.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -60,7 +60,7 @@
|
|
|
60
60
|
"test": "node --import tsx --test tests/*.test.ts",
|
|
61
61
|
"test:live": "node --env-file=.env scripts/live-smoke.mjs",
|
|
62
62
|
"dev:pi": "node scripts/dev-pi.mjs",
|
|
63
|
-
"check": "npm run
|
|
63
|
+
"check": "npm run build && npm run typecheck && npm test",
|
|
64
64
|
"prepack": "npm run build"
|
|
65
65
|
},
|
|
66
66
|
"dependencies": {
|
|
@@ -4,6 +4,42 @@ All notable changes to this project will be documented in this file.
|
|
|
4
4
|
|
|
5
5
|
## [Unreleased]
|
|
6
6
|
|
|
7
|
+
## [0.30.0] - 2026-09-19
|
|
8
|
+
|
|
9
|
+
### Highlights
|
|
10
|
+
|
|
11
|
+
- Start installed copies of Pi Web Access much faster with a precompiled bundle.
|
|
12
|
+
- Use standalone OpenAI search or reuse an existing Pi provider URL without duplicating gateway configuration.
|
|
13
|
+
- Control which search providers and fetch modes are available.
|
|
14
|
+
- Search Google through the new explicit Serply provider.
|
|
15
|
+
- Get more reliable proxy handling, OpenCode requests, PDF answers, source checks, and stored-content retrieval.
|
|
16
|
+
|
|
17
|
+
### Added
|
|
18
|
+
|
|
19
|
+
- Added opt-in `openaiUseProviderBaseUrl` to reuse a selected Pi provider's URL and credentials for OpenAI search. An explicit `openaiResponsesUrl` still takes precedence.
|
|
20
|
+
- Added opt-in `openaiUseAlphaSearch` for standalone OpenAI search, with source limits, recency filtering, allowed-domain filtering, and normal provider fallback. Existing Responses search remains the default. Thanks to [@ZacharyQin](https://github.com/ZacharyQin) for [PR #420](https://github.com/nicobailon/pi-web-access/pull/420).
|
|
21
|
+
- Added `fetch.defaultMode` and `fetch.allowedModes` for choosing the default `fetch_content` mode and disabling unwanted modes. Thanks to [@Slooz](https://github.com/Slooz) for #395.
|
|
22
|
+
- Added `webSearch.allowedProviders` to restrict providers consistently across search, source checks, routing, aggregation, schemas, and Curator. Thanks to [@Slooz](https://github.com/Slooz) for #396.
|
|
23
|
+
- Added an explicit-only Serply Google Search provider with domain filtering, recency filtering, routing, and Curator support. Configure it with `serplyApiKey` or `SERPLY_API_KEY`. Thanks to Serply vendor [@googio](https://github.com/googio) for PR #386.
|
|
24
|
+
|
|
25
|
+
### Changed
|
|
26
|
+
|
|
27
|
+
- Published packages now load a precompiled bundle for faster startup, while source checkouts continue loading TypeScript directly. Thanks to [@Yisus423](https://github.com/Yisus423) for [issue #418](https://github.com/nicobailon/pi-web-access/issues/418) and [PR #419](https://github.com/nicobailon/pi-web-access/pull/419).
|
|
28
|
+
- Fresh installs now use the silent `none` web search workflow by default. Explicit and configured Curator or summary workflows are unchanged. Thanks to [@ducaoya](https://github.com/ducaoya) for issue #416.
|
|
29
|
+
|
|
30
|
+
### Fixed
|
|
31
|
+
|
|
32
|
+
- Preserve legacy `~/.pi/web-search.json` configuration when `~/.pi/agent/web-search.json` is absent in default environments without `XDG_CONFIG_HOME`. Thanks to [@fancyboi999](https://github.com/fancyboi999) for issue #411.
|
|
33
|
+
- List Crawl4AI in the README provider summary and the package description, which both still omitted it after the provider shipped in 0.29.0. Thanks to [@bergheim](https://github.com/bergheim) for [PR #382](https://github.com/nicobailon/pi-web-access/pull/382).
|
|
34
|
+
- Removed the unconditional global `fetch` replacement during extension initialization; proxy transport is now installed lazily when a proxied web-tool operation runs. Thanks to [@AdrianJ20](https://github.com/AdrianJ20) for [issue #388](https://github.com/nicobailon/pi-web-access/issues/388).
|
|
35
|
+
- Send OpenCode session attribution headers when generating summaries, preventing configured `opencode` and `opencode-go` summary models from silently falling through to another candidate. Thanks to [@damozhang](https://github.com/damozhang) for issue #385.
|
|
36
|
+
- Send the `x-opencode-session` / `x-opencode-client` attribution headers when `fetch_content` answer mode uses an `opencode` or `opencode-go` model. The answer path dispatches through the model registry, which bypasses the attribution headers Pi merges in the main agent loop, so those requests were rejected with `400 MissingSessionID`. Thanks to [@MrSerious0](https://github.com/MrSerious0) for PR #381.
|
|
37
|
+
- Pass extracted PDF Markdown to `fetch_content` answer mode instead of the saved-file notice, while preserving readable-mode file output and stored-content retrieval. Thanks to [@MDGChamomile](https://github.com/MDGChamomile) for PR #390.
|
|
38
|
+
- Prevent malformed `fetch_content` auth, mode, or proxy parameters from breaking tool-call rendering while preserving strict execution validation. Thanks to [@tekumara](https://github.com/tekumara) for issue #387.
|
|
39
|
+
- Clarified that a negative `fetch_content` answer-mode result means the answer was not found in the extracted content, rather than asserting that it is absent from the whole page. Thanks to [@Slooz](https://github.com/Slooz) for issue #394.
|
|
40
|
+
- Stop inferring claim support or contradiction from unrelated lexical markers in `source_check`; retrieved passages now require manual semantic review. Thanks to [@wayenchan](https://github.com/wayenchan) for issue #383.
|
|
41
|
+
- Return bounded `get_search_content` excerpts instead of dropping oversized merged match ranges, using compact query IDs to reserve representative ranges when discovered spans fit the output budget. Thanks to [@MDGChamomile](https://github.com/MDGChamomile) for PR #391.
|
|
42
|
+
|
|
7
43
|
## [0.29.0] - 2026-09-10
|
|
8
44
|
|
|
9
45
|
### Highlights
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
# Pi Web Access
|
|
6
6
|
|
|
7
|
-
**Web search, content extraction, and video understanding for Pi agent. OpenAI/Codex search, zero-config Exa search, Brave, Parallel, TinyFish, Search1API, Searchinfinity, Querit, Tavily, Firecrawl, Jina, SERPdive, Kagi, Bocha, Ollama, AnySearch, XCrawl, Valyu, xAI/Grok, Mistral, Bright Data SERP, SerpBase, SerpApi, Serper, self-hosted SearXNG, keyless DuckDuckGo, optional browser-cookie Gemini Web, Kimi Code Plan search, or bring your own API keys.**
|
|
7
|
+
**Web search, content extraction, and video understanding for Pi agent. OpenAI/Codex search, zero-config Exa search, Brave, Parallel, TinyFish, Search1API, Searchinfinity, Querit, Tavily, Firecrawl, Jina, SERPdive, Kagi, Bocha, Ollama, AnySearch, XCrawl, Valyu, xAI/Grok, Mistral, Bright Data SERP, SerpBase, SerpApi, Serper, explicit-only Serply, self-hosted SearXNG, self-hosted Crawl4AI extraction, keyless DuckDuckGo, optional browser-cookie Gemini Web, Kimi Code Plan search, or bring your own API keys.**
|
|
8
8
|
|
|
9
9
|
[](https://www.npmjs.com/package/pi-web-access)
|
|
10
10
|
[](https://opensource.org/licenses/MIT)
|
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
|
|
15
15
|
## Why Pi Web Access
|
|
16
16
|
|
|
17
|
-
**Zero Config** — Works out of the box with Exa MCP (no API key needed). If you're signed into Pi with a Codex subscription, OpenAI web search can reuse that auth. An active Kimi Code Plan signed in through `/login kimi-coding` enables explicit Kimi search without a separate Open Platform key. Add API keys or endpoints for OpenAI, Brave, Parallel, TinyFish, Search1API, Searchinfinity, Querit, Tavily, Firecrawl, Jina, SERPdive, Kagi, Bocha, Ollama, Valyu, SerpBase, SerpApi, Serper, Exa, Perplexity, Gemini API, or Mistral for more control; configure a self-hosted SearXNG endpoint for private search; or opt into browser-cookie access for Gemini Web.
|
|
17
|
+
**Zero Config** — Works out of the box with Exa MCP (no API key needed). If you're signed into Pi with a Codex subscription, OpenAI web search can reuse that auth. An active Kimi Code Plan signed in through `/login kimi-coding` enables explicit Kimi search without a separate Open Platform key. Add API keys or endpoints for OpenAI, Brave, Parallel, TinyFish, Search1API, Searchinfinity, Querit, Tavily, Firecrawl, Jina, SERPdive, Kagi, Bocha, Ollama, Valyu, SerpBase, SerpApi, Serper, explicit-only Serply, Exa, Perplexity, Gemini API, or Mistral for more control; configure a self-hosted SearXNG endpoint for private search; or opt into browser-cookie access for Gemini Web.
|
|
18
18
|
|
|
19
19
|
**Video Understanding** — Point it at a YouTube video or local screen recording and ask questions about what's on screen. Full transcripts, visual descriptions, and frame extraction at exact timestamps.
|
|
20
20
|
|
|
@@ -49,7 +49,51 @@ Works immediately with no API keys — Exa MCP provides zero-config search. If P
|
|
|
49
49
|
|
|
50
50
|
In `auto` mode (default), `web_search` tries a configured SearXNG endpoint first for local/private search. When the active Pi model is `openai-codex`, it then tries Codex-backed OpenAI search. Otherwise it tries Exa (direct API if keyed, MCP if not) before OpenAI, then Brave, Parallel, TinyFish, Search1API, Searchinfinity, Querit, Tavily, Firecrawl, Jina, SERPdive, Perplexity, Gemini API, and Gemini Web when browser-cookie access is enabled. Exa handles search; curator summary drafts are generated separately by the configured Pi summary model, defaulting to Claude Haiku, Codex Luna, Codex Terra, Gemini 3.6 Flash, GPT-5 mini, then DeepSeek V4 Flash when available. Slow summary drafts fall back to a deterministic result summary after a bounded deadline.
|
|
51
51
|
|
|
52
|
-
For a third-party Responses-compatible gateway, set `openaiResponsesUrl` to its full Responses endpoint. Pi credentials with a custom `baseUrl`
|
|
52
|
+
For a third-party Responses-compatible gateway, set `openaiResponsesUrl` to its full Responses endpoint, or opt into reusing the selected Pi provider's base URL as described below. Without either opt-in, Pi credentials with a custom `baseUrl` are refused before sending a request, and availability checks mark OpenAI unavailable without blocking other providers. The auth-resolved base URL takes precedence over the model's; gateway Responses/`web_search` support is not inferred. An explicit endpoint overrides this guard, including an explicit official endpoint. Existing Codex subscription endpoint selection is preserved when provider URL reuse is not active.
|
|
53
|
+
|
|
54
|
+
### Reuse the selected Pi provider URL
|
|
55
|
+
|
|
56
|
+
Set `openaiUseProviderBaseUrl: true` to derive the search endpoint from the Pi provider whose credentials were selected. This avoids maintaining the same gateway address in both `models.json` and `web-search.json`. The default is `false`.
|
|
57
|
+
|
|
58
|
+
```json
|
|
59
|
+
{
|
|
60
|
+
"openaiSearchProviders": ["my-openai-provider"],
|
|
61
|
+
"openaiUseProviderBaseUrl": true
|
|
62
|
+
}
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
If `openaiResponsesUrl` is supplied, this switch is ignored and the existing explicit-endpoint/auth rules apply. Remove that field to use the provider address. Otherwise the switch must be a boolean. The selected provider's auth-resolved `baseUrl` takes precedence over its model's `baseUrl`; credentials and URL are resolved together for each search. Candidates with missing or invalid resolved base URLs are skipped in configured order, keeping each candidate's credentials bound to its own URL. Reload Pi after changing its provider configuration.
|
|
66
|
+
|
|
67
|
+
Only absolute HTTP(S) URLs are accepted. Preserve the origin, port, prefix, and query parameters, and complete the Responses path as follows:
|
|
68
|
+
|
|
69
|
+
| Provider base URL | Derived Responses endpoint |
|
|
70
|
+
| --- | --- |
|
|
71
|
+
| `https://gateway.example.com` | `https://gateway.example.com/v1/responses` |
|
|
72
|
+
| `https://gateway.example.com/v1` | `https://gateway.example.com/v1/responses` |
|
|
73
|
+
| `https://gateway.example.com/team/v1/` | `https://gateway.example.com/team/v1/responses` |
|
|
74
|
+
| `https://gateway.example.com/v1/responses` | `https://gateway.example.com/v1/responses` |
|
|
75
|
+
|
|
76
|
+
Codex auth with the official `https://chatgpt.com/backend-api` base uses `/backend-api/codex/responses`. When reusing a custom provider URL with Codex credentials, retain its destination and required account headers rather than redirecting to the official host. If `openaiUseAlphaSearch` is also true, replace the derived `/responses` suffix with `/alpha/search`.
|
|
77
|
+
|
|
78
|
+
If no candidate has usable Pi credentials and a valid provider base URL, this mode fails closed and marks OpenAI unavailable; it does not fall back to the official endpoint or a standalone API key. API-key-only configurations can use `openaiResponsesUrl` instead. This switch applies to independent OpenAI provider selection; `searchRouting.useCurrentModel` retains its existing official-endpoint selection and eligibility rules.
|
|
79
|
+
|
|
80
|
+
### Optional OpenAI standalone search
|
|
81
|
+
|
|
82
|
+
Set `openaiUseAlphaSearch: true` to use the independent Codex `alpha/search` protocol instead of Responses-hosted `web_search`. The default is `false`; omitting the flag keeps existing Responses behavior. Non-boolean values are rejected.
|
|
83
|
+
|
|
84
|
+
```json
|
|
85
|
+
{
|
|
86
|
+
"openaiUseAlphaSearch": true,
|
|
87
|
+
"openaiResponsesUrl": "https://gateway.example.com/v1/responses",
|
|
88
|
+
"openaiSearchProviders": ["my-openai-provider"]
|
|
89
|
+
}
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
The selected Responses endpoint must end in `/responses` (an optional trailing slash is accepted). Its path suffix becomes `/alpha/search`, preserving the host, port, prefix, and query parameters. For example, `/v1/responses` becomes `/v1/alpha/search`. By default, Codex subscription auth selects `https://chatgpt.com/backend-api/codex/alpha/search`; with provider URL reuse enabled, it follows the resolved provider endpoint instead. Existing credential selection, search-model overrides, custom-base-URL safeguards when URL reuse is disabled, and official current-model eligibility rules still apply. This flag does not make an arbitrary gateway support standalone search.
|
|
93
|
+
|
|
94
|
+
Standalone requests use `id`, `model`, and `commands.search_query`, not a Responses tool call. Plaintext `output` and structured `text_result` sources are returned without another model-generated summary. Encrypted-only responses are rejected. `numResults` defaults to 5 and caps the deduplicated source list at up to 20; recency maps to 1/7/30/365 days, and positive domain filters map to `domains`. Excluded domains (`-example.com`) are explicitly unsupported in this mode rather than silently ignored.
|
|
95
|
+
|
|
96
|
+
Missing endpoint responses (HTTP 404/405/501) and excluded-domain requests follow the existing `unsupported` fallback policy. Other HTTP errors, cancellation, proxy transport, and the 60-second request deadline retain their existing behavior. There is no automatic retry through Responses. To permit another provider, configure `searchRouting.fallbackOn` accordingly; explicit `provider: "openai"` remains strict. Reload Pi after changing configuration.
|
|
53
97
|
|
|
54
98
|
To route automatic searches through the active Pi model, configure an ordered route without a top-level `provider`:
|
|
55
99
|
|
|
@@ -63,7 +107,7 @@ To route automatic searches through the active Pi model, configure an ordered ro
|
|
|
63
107
|
}
|
|
64
108
|
```
|
|
65
109
|
|
|
66
|
-
With `useCurrentModel: true`, the automatic `openai` step uses Hosted `web_search` when the active model is a GPT model backed by an official OpenAI Responses endpoint: `openai`/`openai-responses` on HTTPS `api.openai.com`, or `openai-codex`/`openai-codex-responses` on the official ChatGPT Codex endpoint. Third-party gateways, Azure, and other models continue to the next route entry. A tool-level `provider` or top-level `provider` remains an explicit override; `provider: "openai"` keeps the existing independent OpenAI/Codex search-model behavior.
|
|
110
|
+
With `useCurrentModel: true`, the automatic `openai` step uses Hosted `web_search` (or standalone search when `openaiUseAlphaSearch` is enabled) when the active model is a GPT model backed by an official OpenAI Responses endpoint: `openai`/`openai-responses` on HTTPS `api.openai.com`, or `openai-codex`/`openai-codex-responses` on the official ChatGPT Codex endpoint. Third-party gateways, Azure, and other models continue to the next route entry. A tool-level `provider` or top-level `provider` remains an explicit override; `provider: "openai"` keeps the existing independent OpenAI/Codex search-model behavior.
|
|
67
111
|
|
|
68
112
|
For sandboxed networks that provide outbound proxy transport through environment variables, set `ssrf.trustEnvProxy` to `true` to skip local DNS preflight for proxied hostnames:
|
|
69
113
|
|
|
@@ -119,7 +163,7 @@ fetch_content({ url: "/path/to/recording.mp4", prompt: "What error appears on sc
|
|
|
119
163
|
|
|
120
164
|
### web_search
|
|
121
165
|
|
|
122
|
-
Search the web via OpenAI, Brave, Parallel, TinyFish, Search1API, Searchinfinity, Querit, Tavily, Firecrawl, Jina, SERPdive, Kagi, Bocha, Ollama, AnySearch, XCrawl, Valyu, xAI, Mistral, Bright Data SERP, SerpBase, SerpApi, Serper, self-hosted SearXNG, keyless DuckDuckGo, Exa, Perplexity AI, Gemini, or Kimi.
|
|
166
|
+
Search the web via OpenAI, Brave, Parallel, TinyFish, Search1API, Searchinfinity, Querit, Tavily, Firecrawl, Jina, SERPdive, Kagi, Bocha, Ollama, AnySearch, XCrawl, Valyu, xAI, Mistral, Bright Data SERP, SerpBase, SerpApi, Serper, Serply, self-hosted SearXNG, keyless DuckDuckGo, Exa, Perplexity AI, Gemini, or Kimi. By default, returns source-linked search results or provider answers.
|
|
123
167
|
|
|
124
168
|
```typescript
|
|
125
169
|
web_search({ query: "rust async programming" })
|
|
@@ -142,9 +186,9 @@ web_search({ queries: ["query 1", "query 2"], workflow: "auto-summary" })
|
|
|
142
186
|
| `numResults` | Results per query (default: 5, max: 20) |
|
|
143
187
|
| `recencyFilter` | `day`, `week`, `month`, or `year` |
|
|
144
188
|
| `domainFilter` | Limit to domains (prefix with `-` to exclude) |
|
|
145
|
-
| `provider` | Configured provider when omitted or set to `auto`; `all` searches every eligible provider except Parallel MCP, DuckDuckGo, Kimi, AnySearch, XCrawl, Valyu, xAI, Mistral, Bright Data, SerpBase, SerpApi, and
|
|
189
|
+
| `provider` | Configured provider when omitted or set to `auto`; `all` searches every eligible provider except Parallel MCP, DuckDuckGo, Kimi, AnySearch, XCrawl, Valyu, xAI, Mistral, Bright Data, SerpBase, SerpApi, Serper, and Serply simultaneously; otherwise `openai`, `brave`, `parallel`, `parallel-mcp`, `tinyfish`, `search1api`, `searchinfinity`, `querit`, `tavily`, `firecrawl`, `jina`, `serpdive`, `kagi`, `bocha`, `ollama`, `anysearch`, `xcrawl`, `valyu`, `xai`, `mistral`, `brightdata`, `serpbase`, `serpapi`, `serper`, `serply`, `searxng`, `duckduckgo`, `exa`, `perplexity`, `gemini`, or `kimi` (auto-selects when no provider or routing is configured; Parallel MCP, DuckDuckGo, Kimi, AnySearch, XCrawl, Valyu, xAI, Mistral, Bright Data, SerpBase, SerpApi, Serper, and Serply are explicit-only) |
|
|
146
190
|
| `includeContent` | Fetch full page content from sources in background |
|
|
147
|
-
| `workflow` | `none` (skip curator), `summary-review` (open curator and auto-generate a summary draft
|
|
191
|
+
| `workflow` | `none` (skip curator; fresh-install default), `summary-review` (open curator and auto-generate a summary draft), or `auto-summary` (generate a summary without opening the curator) |
|
|
148
192
|
|
|
149
193
|
Batch searches run up to three queries concurrently. Provider routing and fallback within each query remain sequential.
|
|
150
194
|
|
|
@@ -192,11 +236,11 @@ get_search_content({ responseId: "abc123", urlIndex: 0, findText: "installation"
|
|
|
192
236
|
get_search_content({ responseId: "abc123", urlIndex: 0, findText: ["timeout", "retry"], findMode: "fuzzy" })
|
|
193
237
|
```
|
|
194
238
|
|
|
195
|
-
`findMode` supports `exact`, `case-insensitive` (default), and `fuzzy`. Finder output is capped at 20,000 characters with match counts and nearby context. `findText` cannot be combined with `offset` or `limit`. The default `limit` and maximum permitted `limit` use `maxInlineContentChars`.
|
|
239
|
+
`findMode` supports `exact`, `case-insensitive` (default), and `fuzzy`. Finder output is capped at 20,000 characters with match counts and nearby context. Fitting responses retain their existing format and document order. Overflow responses identify queries as `Q1`, `Q2`, and so on, list each full query once, and reserve a representative excerpt for each query whose discovered match span fits the output budget. Queries with oversized spans are listed as having no representative excerpt. `findText` cannot be combined with `offset` or `limit`. The default `limit` and maximum permitted `limit` use `maxInlineContentChars`.
|
|
196
240
|
|
|
197
241
|
### source_check
|
|
198
242
|
|
|
199
|
-
|
|
243
|
+
Gather evidence for a claim and return a machine-readable artifact with exact passage citations for manual semantic review. Search results are deduplicated and capped at 20 sources; `fetchContent` fetches at most 5 pages, while stored and retrieved content remains subject to the configured `maxInlineContentChars` `offset`/`limit` bounds.
|
|
200
244
|
|
|
201
245
|
```typescript
|
|
202
246
|
source_check({ claim: "The API supports streaming responses" })
|
|
@@ -208,7 +252,7 @@ source_check({
|
|
|
208
252
|
})
|
|
209
253
|
```
|
|
210
254
|
|
|
211
|
-
The artifact
|
|
255
|
+
The artifact preserves the `supported`, `contradicted`, `unclear`, or `missing-evidence` claim status schema, source quality hints, SHA-256 content hashes, and passage IDs with exact source offsets. It does not infer semantic support or contradiction automatically: retrieved passages produce `unclear` for manual review, while no passages produce `missing-evidence`. Search and fetch errors remain in the artifact instead of being silently discarded. Artifacts are stored with the session and retrieved through `get_search_content` using the returned `responseId`; paged artifact responses are JSON slices, so request the next `offset` when needed.
|
|
212
256
|
|
|
213
257
|
## Capabilities
|
|
214
258
|
|
|
@@ -250,6 +294,8 @@ Requires `ffmpeg` (and `yt-dlp` for YouTube). Timestamps accept `H:MM:SS`, `MM:S
|
|
|
250
294
|
|
|
251
295
|
### PDFs
|
|
252
296
|
|
|
297
|
+
With `mode: "answer"`, the answer model receives the extracted PDF Markdown rather than the saved-file notice. The Markdown file is still saved, and the original extracted content remains available through `get_search_content`; ordinary readable-mode PDF fetches continue to return the file path.
|
|
298
|
+
|
|
253
299
|
PDF URLs are converted to Markdown and saved under the temporary `pi-web-pdf` directory by default so the agent can `read` specific sections without loading the full document into context. Three engines are available, selected with `pdf.provider` (`"auto"` is the default):
|
|
254
300
|
|
|
255
301
|
| Provider | Engine | Trade-offs |
|
|
@@ -371,7 +417,7 @@ Toggle with **Ctrl+Shift+W** to see live request/response activity:
|
|
|
371
417
|
|
|
372
418
|
## Configuration
|
|
373
419
|
|
|
374
|
-
Config defaults to `~/.pi/agent/web-search.json` when neither `PI_CODING_AGENT_DIR` nor `XDG_CONFIG_HOME` is set. `PI_CODING_AGENT_DIR` takes precedence when set; with `XDG_CONFIG_HOME`, an existing `XDG_CONFIG_HOME/pi/web-search.json` is preferred, an existing legacy `~/.pi/web-search.json` remains usable for compatibility, and the XDG path is used as the new-config target when neither file exists.
|
|
420
|
+
Config defaults to `~/.pi/agent/web-search.json` when neither `PI_CODING_AGENT_DIR` nor `XDG_CONFIG_HOME` is set. `PI_CODING_AGENT_DIR` takes precedence when set; with `XDG_CONFIG_HOME`, an existing `XDG_CONFIG_HOME/pi/web-search.json` is preferred, an existing legacy `~/.pi/web-search.json` remains usable for compatibility, and the XDG path is used as the new-config target when neither file exists. When neither environment variable is set, an existing `~/.pi/agent/web-search.json` is preferred, an existing legacy `~/.pi/web-search.json` remains usable for compatibility, and the agent directory is used as the new-config target when neither file exists. Every field is optional.
|
|
375
421
|
|
|
376
422
|
```json
|
|
377
423
|
{
|
|
@@ -395,6 +441,7 @@ Config defaults to `~/.pi/agent/web-search.json` when neither `PI_CODING_AGENT_D
|
|
|
395
441
|
"serpbaseApiKey": "$SERPBASE_API_KEY",
|
|
396
442
|
"serpapiApiKey": "$SERPAPI_KEY",
|
|
397
443
|
"serperApiKey": "$SERPER_API_KEY",
|
|
444
|
+
"serplyApiKey": "$SERPLY_API_KEY",
|
|
398
445
|
"brightdataApiKey": "$BRIGHTDATA_API_KEY",
|
|
399
446
|
"brightdataSerpZone": "pi_serp",
|
|
400
447
|
"xaiApiKey": "xai-...",
|
|
@@ -433,11 +480,14 @@ Config defaults to `~/.pi/agent/web-search.json` when neither `PI_CODING_AGENT_D
|
|
|
433
480
|
},
|
|
434
481
|
"fetch": {
|
|
435
482
|
"timeout": 30,
|
|
483
|
+
"defaultMode": "readable",
|
|
484
|
+
"allowedModes": ["readable", "raw", "answer"],
|
|
436
485
|
"answerProvider": "openai",
|
|
437
486
|
"answerModel": "gpt-5.6"
|
|
438
487
|
},
|
|
439
488
|
"webSearch": {
|
|
440
|
-
"enabled": true
|
|
489
|
+
"enabled": true,
|
|
490
|
+
"allowedProviders": ["openai", "brave", "exa"]
|
|
441
491
|
},
|
|
442
492
|
"tools": {
|
|
443
493
|
"webSearch": { "enabled": true },
|
|
@@ -510,9 +560,11 @@ Config defaults to `~/.pi/agent/web-search.json` when neither `PI_CODING_AGENT_D
|
|
|
510
560
|
}
|
|
511
561
|
```
|
|
512
562
|
|
|
563
|
+
`webSearch.allowedProviders` is an optional, non-empty search-provider allowlist with no duplicate entries. When omitted, every current search provider remains permitted. When present, explicit scalar and array requests for providers outside the list fail before any provider request, while `auto`, `all`, configured routing, `source_check`, and Curator use only listed providers. The allowlist does not make explicit-only or paid providers eligible for automatic fallback or `all`; they must still be named explicitly or included in `searchRouting.providers`. A configured `provider`/`searchProvider` or `searchRouting.providers` entry outside the allowlist is rejected as invalid configuration. This setting affects search only: it does not restrict `fetchRouting`, fetch answer models, or summary models.
|
|
564
|
+
|
|
513
565
|
`summaryModel` accepts an optional thinking-level suffix, such as `anthropic/claude-haiku-4-5:low`. Supported suffixes are `off`, `minimal`, `low`, `medium`, `high`, `xhigh`, and `max`.
|
|
514
566
|
|
|
515
|
-
All provider API-key fields (`openaiApiKey`, `braveApiKey`, `parallelApiKey`, `tinyfishApiKey`, `search1apiApiKey`, `searchinfinityApiKey`, `queritApiKey`, `tavilyApiKey`, `jinaApiKey`, `serpdiveApiKey`, `kagiApiKey`, `bochaApiKey`, `ollamaApiKey`, `valyuApiKey`, `serpbaseApiKey`, `serpapiApiKey`, `serperApiKey`, `anysearchApiKey`, `xcrawlApiKey`, `xaiApiKey`, `mistralApiKey`, `brightdataApiKey`, `firecrawlApiKey`, `crawl4aiApiToken`, `exaApiKey`, `perplexityApiKey`, `geminiApiKey`, `datalabApiKey`, and `cloudflareApiKey`) accept explicit credential sources. Use `$NAME` or `${NAME}` to read one named environment variable, or prefix a trusted local shell command with `!` to resolve one value at provider request time. Escape `$$` as a literal leading `$` and `$!` as a literal leading `!`:
|
|
567
|
+
All provider API-key fields (`openaiApiKey`, `braveApiKey`, `parallelApiKey`, `tinyfishApiKey`, `search1apiApiKey`, `searchinfinityApiKey`, `queritApiKey`, `tavilyApiKey`, `jinaApiKey`, `serpdiveApiKey`, `kagiApiKey`, `bochaApiKey`, `ollamaApiKey`, `valyuApiKey`, `serpbaseApiKey`, `serpapiApiKey`, `serperApiKey`, `serplyApiKey`, `anysearchApiKey`, `xcrawlApiKey`, `xaiApiKey`, `mistralApiKey`, `brightdataApiKey`, `firecrawlApiKey`, `crawl4aiApiToken`, `exaApiKey`, `perplexityApiKey`, `geminiApiKey`, `datalabApiKey`, and `cloudflareApiKey`) accept explicit credential sources. Use `$NAME` or `${NAME}` to read one named environment variable, or prefix a trusted local shell command with `!` to resolve one value at provider request time. Escape `$$` as a literal leading `$` and `$!` as a literal leading `!`:
|
|
516
568
|
|
|
517
569
|
```json
|
|
518
570
|
{
|
|
@@ -523,7 +575,7 @@ All provider API-key fields (`openaiApiKey`, `braveApiKey`, `parallelApiKey`, `t
|
|
|
523
575
|
}
|
|
524
576
|
```
|
|
525
577
|
|
|
526
|
-
This syntax applies to provider credentials only; other configuration fields are not interpolated. `firecrawlApiKey`, `crawl4aiApiToken`, `kagiApiKey`, `ollamaApiKey`, `valyuApiKey`, `serpbaseApiKey`, `serpapiApiKey`, `serperApiKey`, `mistralApiKey`, and `brightdataApiKey` use the same credential-source rules, while `braveBaseUrl`, `exaBaseUrl`, `tavilyBaseUrl`, `firecrawlBaseUrl`, `firecrawlApiVersion`, `firecrawlFreshScrape`, `crawl4aiBaseUrl`, `brightdataSerpZone`, and `brightdataUnlockerZone` are literal config values.
|
|
578
|
+
This syntax applies to provider credentials only; other configuration fields are not interpolated. `firecrawlApiKey`, `crawl4aiApiToken`, `kagiApiKey`, `ollamaApiKey`, `valyuApiKey`, `serpbaseApiKey`, `serpapiApiKey`, `serperApiKey`, `serplyApiKey`, `mistralApiKey`, and `brightdataApiKey` use the same credential-source rules, while `braveBaseUrl`, `exaBaseUrl`, `tavilyBaseUrl`, `firecrawlBaseUrl`, `firecrawlApiVersion`, `firecrawlFreshScrape`, `crawl4aiBaseUrl`, `brightdataSerpZone`, and `brightdataUnlockerZone` are literal config values.
|
|
527
579
|
|
|
528
580
|
A command source is not run while the extension loads or registers tools. Each selected provider request runs it again with a five-second timeout, a 16 KiB output limit, a minimized environment, and a one-line non-empty stdout requirement. Command text and stderr are omitted from errors. These commands are trusted local configuration, not a same-user process isolation boundary; use absolute executable paths and protect the config file. `OP_SESSION_*` and `OP_SERVICE_ACCOUNT_TOKEN`, when present in Pi's environment, are forwarded to trusted resolver commands so 1Password CLI sessions and service accounts can be reused without storing their credentials in config. For example, `"braveApiKey": "!/absolute/path/to/op read 'op://Automation/Brave/credential'"` resolves that item after you replace the executable placeholder with your trusted installation's absolute path, but the `op://` argument is not an authorization boundary: every configured resolver command that receives the token can exercise all vault and item permissions granted to its service account. Prefer a narrowly scoped service account. An explicit source overrides legacy provider environment variables and fails that provider locally rather than falling back with a stale credential. Direct Google Gemini API requests send the resolved key only in the `x-goog-api-key` header, never in the URL.
|
|
529
581
|
|
|
@@ -539,6 +591,8 @@ Set `braveBaseUrl`, `exaBaseUrl`, or `tavilyBaseUrl` to route those providers th
|
|
|
539
591
|
|
|
540
592
|
`fetch.timeout` is an optional positive finite number of seconds for direct HTTP fetches and the Jina Reader fallback. When omitted, both use a 30-second budget. Fractional values are supported and rounded up to at least 1 millisecond; values that cannot be converted to a finite safe integer delay from 1 through Node's 2,147,483,647 ms timer maximum are rejected. An invalid declared value fails closed with an error naming `web-search.json`. An internal/per-call `timeoutMs` override takes precedence over this setting. Other remote extraction fallbacks keep their own documented budgets.
|
|
541
593
|
|
|
594
|
+
`fetch.defaultMode` sets the mode used when `fetch_content` omits `mode`, and `fetch.allowedModes` controls which modes the tool exposes and accepts. Valid modes are `readable`, `raw`, and `answer`. By default, the default mode is `readable` and all three modes are allowed. The default must be included in the non-empty, duplicate-free allowed list. An explicitly requested disabled mode fails before fetching or invoking a model and is never replaced with another mode. Raw mode remains direct HTTP-only and never runs readability, specialized source handling, or hosted extraction fallbacks.
|
|
595
|
+
|
|
542
596
|
`fetch.answerProvider` and `fetch.answerModel` are an optional pair that selects the model used by `fetch_content` answer mode when no per-call `answerModel` is supplied. Both values must be non-empty strings and must identify an enabled text-capable model available in Pi's model registry; invalid or partial configuration fails closed. This is opt-in: answering with the configured provider/model can send fetched page text outside the current session and may incur that provider's costs. A per-call `answerModel` override is resolved first and remains usable even when these configured defaults are malformed.
|
|
543
597
|
|
|
544
598
|
Set `searxngBaseUrl` or `SEARXNG_BASE_URL` to use a self-hosted SearXNG JSON API. A configured endpoint is preferred first in `auto` mode for local/private search. Its base URL and redirects remain subject to the SSRF guard; add only the narrowest self-hosted range to `ssrf.allowRanges` when it resolves to a private or synthetic range. Optional `searxngHeaders` merges extra HTTP headers into each SearXNG request (string values only; invalid header names are ignored), which is useful for reverse-proxy or Zero Trust auth such as Cloudflare Access service tokens (`CF-Access-Client-Id` / `CF-Access-Client-Secret`). Configured headers override the default `Accept: application/json` when the same name is supplied. Thanks to Marcos A. Núñez (@marnunez) for PR #107 and Avinash Kanaujiya (@avinashkanaujiya) for issue #105.
|
|
@@ -567,9 +621,11 @@ Bright Data Web Unlocker is a paid `fetch_content` fallback after Parallel and b
|
|
|
567
621
|
|
|
568
622
|
**Serper.** Set `serperApiKey` or `SERPER_API_KEY` and select `provider: "serper"` to query Serper's Google Search API. Serper is explicit-only: it is never chosen by `auto` or `provider: "all"`, but it can be configured as the named provider or added to `searchRouting`.
|
|
569
623
|
|
|
624
|
+
**Serply.** Set `serplyApiKey` or `SERPLY_API_KEY` and select `provider: "serply"` to query [Serply](https://serply.io)'s Google Search API. Serply is explicit-only: it is never chosen by `auto` or `provider: "all"`, but it can be configured as the named provider or added to `searchRouting`. The key is sent in the `X-Api-Key` request header. Domain filters are sent as Google `site:` clauses and reapplied locally; recency maps to Google's `tbs` time filter. Each request consumes Serply search credits; see the [API documentation](https://serply.io/docs).
|
|
625
|
+
|
|
570
626
|
**Parallel MCP.** Select `provider: "parallel-mcp"` to use Parallel Search MCP without an API key, or add it to `searchRouting`. It is explicit-only and is never chosen by `auto` or `provider: "all"`; the existing `parallel` provider remains the key-required REST API. A configured `parallelApiKey` or `PARALLEL_API_KEY` is sent as an optional Bearer token for higher MCP limits. To use MCP `web_fetch`, add `parallel-mcp` to `fetchRouting.providers` and set `fetchRouting.allowRemoteHostedProviders` to `true`; it is not part of the default fetch route.
|
|
571
627
|
|
|
572
|
-
Without an explicit `$` or `!` source, `OPENAI_API_KEY`, `BRAVE_API_KEY`, `PARALLEL_API_KEY`, `TINYFISH_API_KEY`, `SEARCH1API_KEY`, `SEARCHINFINITY_API_KEY`, `QUERIT_API_KEY`, `TAVILY_API_KEY`, `JINA_API_KEY`, `SERPDIVE_API_KEY`, `KAGI_API_KEY`, `BOCHA_API_KEY`, `OLLAMA_API_KEY`, `SERPBASE_API_KEY`, `SERPAPI_KEY`, `SERPER_API_KEY`, `ANYSEARCH_API_KEY`, `XCRAWL_API_KEY`, `VALYU_API_KEY`, `XAI_API_KEY`, `MISTRAL_API_KEY`, `BRIGHTDATA_API_KEY`, `FIRECRAWL_API_KEY`, `CRAWL4AI_API_TOKEN`, `EXA_API_KEY`, `GEMINI_API_KEY`, `DATALAB_API_KEY`, `DATALAB_PROCESSING_LOCATION`, `DATALAB_MODE`, `DATALAB_API_BASE`, `PERPLEXITY_API_KEY`, `GOOGLE_GEMINI_BASE_URL`, and `CLOUDFLARE_API_KEY` env vars retain their existing precedence over literal config file values. `openaiResponsesUrl` can point OpenAI `web_search` and `source_check` at a third-party gateway that supports the OpenAI Responses API and web search tool; it is an explicit endpoint override, not derived from Pi model provider settings, and defaults to `https://api.openai.com/v1/responses`. `openaiSearchModel` pins the model id used for OpenAI `web_search`, bypassing automatic selection (newest terra-tier model); the id is sent verbatim with whichever OpenAI auth resolves, so gateway-only model ids work too. `xaiSearchModel` similarly pins the xAI search model. `openaiSearchProviders` sets which Pi model providers OpenAI `web_search` resolves login credentials from, in priority order; it defaults to `["openai-codex", "openai"]`, entries that are not registered or not signed in are skipped, and an empty array skips Pi credentials entirely so the `openaiApiKey` / `OPENAI_API_KEY` fallback applies. Useful for choosing between multiple Codex accounts (for example a second account registered by an extension) or forcing API-key billing while signed into Codex. Configured Exa API keys use Exa's own account limits directly; any legacy local `exa-usage.json` file is ignored. `GOOGLE_GEMINI_BASE_URL` overrides the Gemini API host for Gemini generate-content calls such as search, URL context, YouTube, and local video analysis. Set it to a bare host with no trailing slash and no version segment, for example `https://my-gateway.example.com/gemini`; `geminiBaseUrl` is the config-file equivalent. When the configured host contains `gateway.ai.cloudflare.com`, authentication uses `cf-aig-authorization: Bearer <token>` from `CLOUDFLARE_API_KEY` or `cloudflareApiKey`, and `GEMINI_API_KEY` is not required for generate-content calls. Alternatively, set `geminiAuth` to `"adc"` to authenticate Gemini generate-content calls with Google Application Default Credentials (ADC) instead of an API key; calls go to the Vertex AI endpoint (`aiplatform.googleapis.com`) with an OAuth bearer token minted from the ADC file (`GOOGLE_APPLICATION_CREDENTIALS` or `~/.config/gcloud/application_default_credentials.json`, i.e. `gcloud auth application-default login`). `geminiProject`/`geminiLocation` set the Vertex project and location and fall back to the `GOOGLE_CLOUD_PROJECT`/`GOOGLE_CLOUD_LOCATION` (or `GCLOUD_PROJECT`) env vars; project and location are required. ADC supports `authorized_user` (OAuth refresh token) and `service_account` (JWT assertion) credential files, and tokens are cached and refreshed from expiry. ADC mode covers search, URL context, and PDF/inline-data extraction; YouTube and local video analysis still go through the Gemini Files API, so they fall back to Gemini Web unless a `GEMINI_API_KEY` is also configured. The access token is treated as a credential and is redacted from errors. Local video file upload still uses Google's Files API directly, so gateway-only video extraction falls back to Gemini Web unless a `GEMINI_API_KEY` is also configured. `provider` or `searchProvider` sets the default search provider and is used when a tool call omits `provider` or sends `"auto"`: `"all"`, `"openai"`, `"brave"`, `"parallel"`, `"parallel-mcp"`, `"tinyfish"`, `"search1api"`, `"searchinfinity"`, `"querit"`, `"tavily"`, `"firecrawl"`, `"jina"`, `"serpdive"`, `"kagi"`, `"bocha"`, `"ollama"`, `"anysearch"`, `"xcrawl"`, `"valyu"`, `"xai"`, `"mistral"`, `"brightdata"`, `"serpbase"`, `"serpapi"`, `"serper"`, `"searxng"`, `"exa"`, `"perplexity"`, or `"gemini"`. Parallel MCP, AnySearch, XCrawl, Valyu, xAI, Mistral, Bright Data, SerpBase, SerpApi, and
|
|
628
|
+
Without an explicit `$` or `!` source, `OPENAI_API_KEY`, `BRAVE_API_KEY`, `PARALLEL_API_KEY`, `TINYFISH_API_KEY`, `SEARCH1API_KEY`, `SEARCHINFINITY_API_KEY`, `QUERIT_API_KEY`, `TAVILY_API_KEY`, `JINA_API_KEY`, `SERPDIVE_API_KEY`, `KAGI_API_KEY`, `BOCHA_API_KEY`, `OLLAMA_API_KEY`, `SERPBASE_API_KEY`, `SERPAPI_KEY`, `SERPER_API_KEY`, `SERPLY_API_KEY`, `ANYSEARCH_API_KEY`, `XCRAWL_API_KEY`, `VALYU_API_KEY`, `XAI_API_KEY`, `MISTRAL_API_KEY`, `BRIGHTDATA_API_KEY`, `FIRECRAWL_API_KEY`, `CRAWL4AI_API_TOKEN`, `EXA_API_KEY`, `GEMINI_API_KEY`, `DATALAB_API_KEY`, `DATALAB_PROCESSING_LOCATION`, `DATALAB_MODE`, `DATALAB_API_BASE`, `PERPLEXITY_API_KEY`, `GOOGLE_GEMINI_BASE_URL`, and `CLOUDFLARE_API_KEY` env vars retain their existing precedence over literal config file values. `openaiResponsesUrl` can point OpenAI `web_search` and `source_check` at a third-party gateway that supports the OpenAI Responses API and web search tool; it is an explicit endpoint override, not derived from Pi model provider settings, and defaults to `https://api.openai.com/v1/responses`. `openaiSearchModel` pins the model id used for OpenAI `web_search`, bypassing automatic selection (newest terra-tier model); the id is sent verbatim with whichever OpenAI auth resolves, so gateway-only model ids work too. `xaiSearchModel` similarly pins the xAI search model. `openaiSearchProviders` sets which Pi model providers OpenAI `web_search` resolves login credentials from, in priority order; it defaults to `["openai-codex", "openai"]`, entries that are not registered or not signed in are skipped, and an empty array skips Pi credentials entirely so the `openaiApiKey` / `OPENAI_API_KEY` fallback applies. Useful for choosing between multiple Codex accounts (for example a second account registered by an extension) or forcing API-key billing while signed into Codex. Configured Exa API keys use Exa's own account limits directly; any legacy local `exa-usage.json` file is ignored. `GOOGLE_GEMINI_BASE_URL` overrides the Gemini API host for Gemini generate-content calls such as search, URL context, YouTube, and local video analysis. Set it to a bare host with no trailing slash and no version segment, for example `https://my-gateway.example.com/gemini`; `geminiBaseUrl` is the config-file equivalent. When the configured host contains `gateway.ai.cloudflare.com`, authentication uses `cf-aig-authorization: Bearer <token>` from `CLOUDFLARE_API_KEY` or `cloudflareApiKey`, and `GEMINI_API_KEY` is not required for generate-content calls. Alternatively, set `geminiAuth` to `"adc"` to authenticate Gemini generate-content calls with Google Application Default Credentials (ADC) instead of an API key; calls go to the Vertex AI endpoint (`aiplatform.googleapis.com`) with an OAuth bearer token minted from the ADC file (`GOOGLE_APPLICATION_CREDENTIALS` or `~/.config/gcloud/application_default_credentials.json`, i.e. `gcloud auth application-default login`). `geminiProject`/`geminiLocation` set the Vertex project and location and fall back to the `GOOGLE_CLOUD_PROJECT`/`GOOGLE_CLOUD_LOCATION` (or `GCLOUD_PROJECT`) env vars; project and location are required. ADC supports `authorized_user` (OAuth refresh token) and `service_account` (JWT assertion) credential files, and tokens are cached and refreshed from expiry. ADC mode covers search, URL context, and PDF/inline-data extraction; YouTube and local video analysis still go through the Gemini Files API, so they fall back to Gemini Web unless a `GEMINI_API_KEY` is also configured. The access token is treated as a credential and is redacted from errors. Local video file upload still uses Google's Files API directly, so gateway-only video extraction falls back to Gemini Web unless a `GEMINI_API_KEY` is also configured. `provider` or `searchProvider` sets the default search provider and is used when a tool call omits `provider` or sends `"auto"`: `"all"`, `"openai"`, `"brave"`, `"parallel"`, `"parallel-mcp"`, `"tinyfish"`, `"search1api"`, `"searchinfinity"`, `"querit"`, `"tavily"`, `"firecrawl"`, `"jina"`, `"serpdive"`, `"kagi"`, `"bocha"`, `"ollama"`, `"anysearch"`, `"xcrawl"`, `"valyu"`, `"xai"`, `"mistral"`, `"brightdata"`, `"serpbase"`, `"serpapi"`, `"serper"`, `"serply"`, `"searxng"`, `"exa"`, `"perplexity"`, or `"gemini"`. Parallel MCP, AnySearch, XCrawl, Valyu, xAI, Mistral, Bright Data, SerpBase, SerpApi, Serper, and Serply are never selected by `auto`; choose them explicitly or place them in `searchRouting`. If either single-provider field is configured, it takes precedence over `searchRouting`. Otherwise, `searchRouting` can opt into an ordered `providers` list and an explicit `fallbackOn` list containing `"transient"`, `"quota"`, `"network"`, and/or `"invalid-response"`; only those typed failures continue to the next available candidate. `"all"` is not valid inside `searchRouting.providers`, because that list defines sequential fallback rather than multi-provider aggregation. Named providers remain strict, and exhausted routes return per-provider diagnostics. `provider` can also be a non-empty array of named providers such as `["brave", "exa"]`; those providers run concurrently using the same aggregation path as `"all"`, while `"auto"` and `"all"` are invalid inside arrays. Random, weighted, sticky, and cooldown routing are not enabled. This is also updated automatically when you change the provider in the curator UI. Set `webSearch.enabled` to `false` to unregister the configured search and source-check tools while leaving fetch/content tools available. `toolNames` can opt into alternate public tool names for environments where another extension or model reserves the defaults, without changing behavior: `webSearch`, `sourceCheck`, `fetchContent`, and `getSearchContent` default to `web_search`, `source_check`, `fetch_content`, and `get_search_content`. `workflow` sets the default search workflow: `"none"` (default; raw results, no curator), `"summary-review"` (opens curator with an auto-generated summary draft), or `"auto-summary"` (returns a model-generated summary without opening the browser curator). Overridden per-call via the `workflow` parameter on the configured search tool, or toggled at runtime with `/curator`. `browserCookies.profile` pins Gemini Web cookie lookup to a specific Chromium profile. When omitted, detected Chromium profiles are scanned in stable order and the first profile containing the required Gemini cookies is used. macOS discovery supports Helium, Chrome, Brave, and Arc; Linux discovery supports Chromium and Chrome. `allowBrowserCookies` enables Chromium cookie extraction for Gemini Web; it defaults to `false` to avoid browser data access and surprise macOS Keychain prompts. You can also set `PI_ALLOW_BROWSER_COOKIES=1`. Cookie databases are copied to a temporary read-only working copy; the reader uses `node:sqlite` when available and otherwise tries the `sqlite3` CLI or Python's standard-library SQLite module. `searchModel` overrides the Gemini API model used by the configured search tool without changing URL, YouTube, or video extraction defaults. Gemini API grounded search uses `gemini-3.6-flash` by default; set `searchModel` to choose another model. Gemini Web browser-cookie fallback uses its separate `gemini-3.1-pro` default because Gemini Web relies on private header values; explicitly configured unsupported Web models fail instead of silently falling back to 2.5 Flash. `summaryModel` sets the default model used for generating summary drafts in the curator UI and `auto-summary` mode (e.g. `"anthropic/claude-haiku-4-5"`, `"openai-codex/gpt-5.3-codex-spark"`, or `"openrouter/nvidia/nemotron-3-super-120b-a12b:free"`). Preferred summary and query-rewrite models also resolve through routed provider registrations such as OpenRouter when the native provider is unavailable. When Pi `enabledModels` is configured, summaries are limited to that allowlist; if no enabled summary model is available, the tool returns a deterministic summary instead of calling an unrelated model. `summaryGenerationDeadlineMs` sets the maximum time for one summary model attempt in the curator UI and `auto-summary` mode. It defaults to `30000`, must be a positive integer, and is capped at `600000`. `maxInlineContentChars` sets the direct `fetch_content` content slice and the default and maximum `get_search_content` slice. It defaults to `30000`, must be a positive integer, and is capped at `200000`; full fetched content remains stored for later retrieval. `curatorTimeoutSeconds` controls the initial curator idle timeout (default `20`, max `600`); users can still adjust the timer in the curator UI. `ssrf.allowRanges` lists CIDR ranges (e.g. `"198.18.0.0/15"`, `"fd00::/8"`) exempted from the SSRF guard that otherwise blocks private/reserved IP ranges. This unblocks `fetch_content`/`web_search` on hosts whose network proxy runs in TUN + fake-IP mode (Surge, Clash, Mihomo, Stash, ...), where public domains resolve into a synthetic reserved range. It is **off by default** — the guard stays fully enabled unless you list ranges here. Use the narrowest range that covers your proxy's fake-IP pool. All-address CIDRs such as `0.0.0.0/0` and `::/0` are rejected. `ssrf.trustEnvProxy` is a separate opt-in for sandboxed environments with valid HTTP(S) proxy env vars; it skips local DNS preflight only for proxied hostnames and still blocks localhost, literal private IP targets, and `NO_PROXY` matches. It does not configure proxy transport.
|
|
573
629
|
### Kimi Code Plan
|
|
574
630
|
|
|
575
631
|
Run `/login kimi-coding` in Pi and complete sign-in for an active Kimi Code Plan. Then select `provider: "kimi"`, include `"kimi"` in an explicit provider array, or add it to `searchRouting.providers`. The extension resolves a model with provider `kimi-coding` from Pi's model registry and reuses Pi's refreshed OAuth credential; no Moonshot Open Platform key is configured here.
|
|
@@ -580,7 +636,7 @@ Kimi is explicit-only: it is never chosen by `auto` and never participates in `p
|
|
|
580
636
|
|
|
581
637
|
### All providers
|
|
582
638
|
|
|
583
|
-
Set `provider: "all"` on `web_search` or `source_check`, or configure `"provider": "all"` as the default, to run the same query against every eligible search provider simultaneously. Parallel MCP, DuckDuckGo, Kimi, AnySearch, XCrawl, Valyu, xAI, Mistral, Bright Data, SerpBase, SerpApi, and
|
|
639
|
+
Set `provider: "all"` on `web_search` or `source_check`, or configure `"provider": "all"` as the default, to run the same query against every eligible search provider simultaneously. Parallel MCP, DuckDuckGo, Kimi, AnySearch, XCrawl, Valyu, xAI, Mistral, Bright Data, SerpBase, SerpApi, Serper, and Serply are always excluded because they are explicit-only; Bright Data, SerpBase, SerpApi, Serper, and Serply are paid Google SERP providers, while Kimi and Mistral may consume account quota or paid search-tool usage, so `all` never spends either resource without an explicit request. Exa remains eligible through its zero-config MCP path, OpenAI can use Pi auth, and other API-backed search providers participate when their API key, local endpoint, or gateway makes them available. Browser-cookie access alone does not opt Gemini into `all`; select Gemini explicitly or configure its API/gateway.
|
|
584
640
|
|
|
585
641
|
Successful provider answers are preserved separately while source URLs and inline content are deduplicated, and one provider failure does not discard the other results. If every participating provider fails, the tool returns per-provider diagnostics. Configured Firecrawl participates in `all` like other eligible providers. In the Curator, **All** can also be selected like the other provider buttons. Each participating provider gets its own result card, including a provider badge and independent selection checkbox; failed providers get their own disabled error card. The final summary is generated from the selected provider cards and is what Pi receives. Outside the Curator, the same provider answers remain available as labeled sections in one tool response.
|
|
586
642
|
|
|
@@ -909,7 +965,7 @@ Remote curator sessions print the URL instead of trying to open a browser by def
|
|
|
909
965
|
|
|
910
966
|
#### Disabling browser auto-open
|
|
911
967
|
|
|
912
|
-
`autoOpenBrowser` is also useful
|
|
968
|
+
`autoOpenBrowser` is also useful for requested local Curator sessions:
|
|
913
969
|
|
|
914
970
|
```json
|
|
915
971
|
{
|
|
@@ -917,7 +973,7 @@ Remote curator sessions print the URL instead of trying to open a browser by def
|
|
|
917
973
|
}
|
|
918
974
|
```
|
|
919
975
|
|
|
920
|
-
When `false`,
|
|
976
|
+
When `false`, a requested Curator session never tries to open a Glimpse window or a browser and always prints the URL for you to open manually. For requested local-only Curator sessions it defaults to `true`; remote curator sessions print the URL unless you set `autoOpenBrowser: true` explicitly. This is worth setting locally when you would rather paste the link into a specific browser than have one launched for you. It changes nothing about where the server binds; that is `curatorRemote`'s job alone.
|
|
921
977
|
|
|
922
978
|
### Shortcuts
|
|
923
979
|
|
|
@@ -976,6 +1032,7 @@ Rate limits: Perplexity is capped at 10 requests/minute (client-side). Jina Sear
|
|
|
976
1032
|
| `serpbase.ts` | Explicit-only SerpBase Google SERP provider |
|
|
977
1033
|
| `serpapi.ts` | Explicit-only SerpApi Google Search provider |
|
|
978
1034
|
| `serper.ts` | Explicit-only Serper Google SERP provider |
|
|
1035
|
+
| `serply.ts` | Explicit-only Serply Google SERP provider |
|
|
979
1036
|
| `anysearch.ts` | Explicit-only AnySearch search provider |
|
|
980
1037
|
| `xcrawl.ts` | Explicit-only XCrawl search provider |
|
|
981
1038
|
| `valyu.ts` | Explicit-only Valyu research search provider |
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import { existsSync, readFileSync } from "node:fs";
|
|
2
2
|
import { activityMonitor } from "./activity.ts";
|
|
3
|
+
import { formatSearchResultsAsAnswer } from "./search-answer-formatting.ts";
|
|
4
|
+
import { normalizeSearchResultCount } from "./search-result-count-normalization.ts";
|
|
3
5
|
import type { ExtractedContent } from "./extract.ts";
|
|
4
6
|
import type { SearchOptions, SearchResponse } from "./perplexity.ts";
|
|
5
7
|
import { redactCredential, resolveCredential } from "./credential-source.ts";
|
|
@@ -65,11 +67,6 @@ async function getApiKey(signal?: AbortSignal): Promise<string | null> {
|
|
|
65
67
|
});
|
|
66
68
|
}
|
|
67
69
|
|
|
68
|
-
function normalizeCount(value: number | undefined): number {
|
|
69
|
-
if (typeof value !== "number" || !Number.isFinite(value)) return 5;
|
|
70
|
-
return Math.max(1, Math.min(Math.floor(value), 20));
|
|
71
|
-
}
|
|
72
|
-
|
|
73
70
|
function errorMessage(err: unknown): string {
|
|
74
71
|
return err instanceof Error ? err.message : String(err);
|
|
75
72
|
}
|
|
@@ -113,21 +110,13 @@ function parseResponse(value: unknown): AnySearchResponse {
|
|
|
113
110
|
return { code: 0, data: { results, metadata: data.metadata as Record<string, unknown> } };
|
|
114
111
|
}
|
|
115
112
|
|
|
116
|
-
function buildAnswer(results: SearchResponse["results"]): string {
|
|
117
|
-
return results
|
|
118
|
-
.map((result) => result.snippet
|
|
119
|
-
? `${result.snippet}\nSource: ${result.title} (${result.url})`
|
|
120
|
-
: `Source: ${result.title} (${result.url})`)
|
|
121
|
-
.join("\n\n");
|
|
122
|
-
}
|
|
123
|
-
|
|
124
113
|
export function isAnySearchAvailable(): boolean {
|
|
125
114
|
return true;
|
|
126
115
|
}
|
|
127
116
|
|
|
128
117
|
export async function searchWithAnySearch(query: string, options: AnySearchSearchOptions = {}): Promise<SearchResponse> {
|
|
129
118
|
const apiKey = await getApiKey(options.signal);
|
|
130
|
-
const numResults =
|
|
119
|
+
const numResults = normalizeSearchResultCount(options.numResults);
|
|
131
120
|
const body = { query, max_results: numResults };
|
|
132
121
|
const activityId = activityMonitor.logStart({ type: "api", query });
|
|
133
122
|
let response: Response;
|
|
@@ -183,7 +172,7 @@ export async function searchWithAnySearch(query: string, options: AnySearchSearc
|
|
|
183
172
|
url: result.url,
|
|
184
173
|
snippet: result.snippet,
|
|
185
174
|
}));
|
|
186
|
-
const mapped: SearchResponse = { answer:
|
|
175
|
+
const mapped: SearchResponse = { answer: formatSearchResultsAsAnswer(results), results };
|
|
187
176
|
if (options.includeContent) {
|
|
188
177
|
const inlineContent: ExtractedContent[] = data.data.results.slice(0, numResults)
|
|
189
178
|
.filter(result => result.content.length > 0)
|