@oh-my-pi/pi-coding-agent 15.3.2 → 15.4.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +110 -0
- package/dist/types/cli/file-processor.d.ts +1 -1
- package/dist/types/config/settings-schema.d.ts +45 -3
- package/dist/types/config/settings.d.ts +1 -1
- package/dist/types/debug/raw-sse.d.ts +2 -0
- package/dist/types/edit/file-read-cache.d.ts +15 -4
- package/dist/types/edit/index.d.ts +3 -8
- package/dist/types/edit/renderer.d.ts +1 -2
- package/dist/types/eval/__tests__/shared-executors.test.d.ts +1 -0
- package/dist/types/eval/js/shared/local-module-loader.d.ts +16 -0
- package/dist/types/eval/js/shared/rewrite-imports.d.ts +4 -0
- package/dist/types/eval/js/shared/runtime.d.ts +14 -8
- package/dist/types/eval/py/executor.d.ts +1 -2
- package/dist/types/eval/py/kernel.d.ts +6 -0
- package/dist/types/eval/py/tool-bridge.d.ts +1 -5
- package/dist/types/eval/session-id.d.ts +3 -0
- package/dist/types/extensibility/extensions/types.d.ts +1 -3
- package/dist/types/hashline/anchors.d.ts +15 -9
- package/dist/types/hashline/constants.d.ts +0 -2
- package/dist/types/hashline/diff.d.ts +1 -2
- package/dist/types/hashline/executor.d.ts +52 -0
- package/dist/types/hashline/hash.d.ts +44 -93
- package/dist/types/hashline/index.d.ts +2 -1
- package/dist/types/hashline/input.d.ts +2 -9
- package/dist/types/hashline/recovery.d.ts +3 -9
- package/dist/types/hashline/tokenizer.d.ts +91 -0
- package/dist/types/hashline/types.d.ts +5 -7
- package/dist/types/modes/components/extensions/types.d.ts +0 -4
- package/dist/types/modes/types.d.ts +1 -0
- package/dist/types/modes/utils/ui-helpers.d.ts +1 -0
- package/dist/types/sdk.d.ts +2 -0
- package/dist/types/session/agent-session.d.ts +11 -15
- package/dist/types/session/agent-storage.d.ts +11 -10
- package/dist/types/slash-commands/acp-builtins.d.ts +3 -3
- package/dist/types/slash-commands/types.d.ts +0 -5
- package/dist/types/task/executor.d.ts +2 -0
- package/dist/types/tool-discovery/tool-index.d.ts +0 -50
- package/dist/types/tools/index.d.ts +2 -8
- package/dist/types/tools/match-line-format.d.ts +4 -4
- package/dist/types/tools/output-schema-validator.d.ts +64 -0
- package/dist/types/tools/review.d.ts +13 -0
- package/dist/types/tools/search-tool-bm25.d.ts +1 -1
- package/dist/types/tools/search.d.ts +4 -3
- package/dist/types/utils/edit-mode.d.ts +1 -1
- package/dist/types/web/kagi.d.ts +4 -2
- package/dist/types/web/parallel.d.ts +4 -3
- package/dist/types/web/scrapers/types.d.ts +2 -1
- package/dist/types/web/search/index.d.ts +12 -4
- package/dist/types/web/search/provider.d.ts +2 -1
- package/dist/types/web/search/providers/anthropic.d.ts +9 -4
- package/dist/types/web/search/providers/base.d.ts +34 -2
- package/dist/types/web/search/providers/brave.d.ts +8 -1
- package/dist/types/web/search/providers/codex.d.ts +13 -9
- package/dist/types/web/search/providers/exa.d.ts +10 -1
- package/dist/types/web/search/providers/gemini.d.ts +20 -23
- package/dist/types/web/search/providers/jina.d.ts +2 -1
- package/dist/types/web/search/providers/kagi.d.ts +4 -1
- package/dist/types/web/search/providers/kimi.d.ts +10 -1
- package/dist/types/web/search/providers/parallel.d.ts +3 -2
- package/dist/types/web/search/providers/perplexity.d.ts +5 -2
- package/dist/types/web/search/providers/searxng.d.ts +2 -1
- package/dist/types/web/search/providers/synthetic.d.ts +5 -8
- package/dist/types/web/search/providers/tavily.d.ts +11 -4
- package/dist/types/web/search/providers/utils.d.ts +8 -6
- package/dist/types/web/search/providers/zai.d.ts +12 -3
- package/package.json +7 -7
- package/src/cli/file-processor.ts +12 -2
- package/src/cli.ts +0 -8
- package/src/commands/commit.ts +8 -8
- package/src/config/prompt-templates.ts +6 -6
- package/src/config/settings-schema.ts +47 -3
- package/src/config/settings.ts +5 -5
- package/src/debug/raw-sse.ts +68 -3
- package/src/edit/file-read-cache.ts +68 -25
- package/src/edit/index.ts +6 -37
- package/src/edit/renderer.ts +9 -47
- package/src/edit/streaming.ts +43 -56
- package/src/eval/__tests__/shared-executors.test.ts +520 -0
- package/src/eval/js/context-manager.ts +64 -53
- package/src/eval/js/shared/local-module-loader.ts +265 -0
- package/src/eval/js/shared/prelude.txt +4 -0
- package/src/eval/js/shared/rewrite-imports.ts +85 -0
- package/src/eval/js/shared/runtime.ts +129 -86
- package/src/eval/js/worker-core.ts +23 -38
- package/src/eval/py/executor.ts +155 -84
- package/src/eval/py/kernel.ts +10 -1
- package/src/eval/py/prelude.py +22 -24
- package/src/eval/py/runner.py +203 -85
- package/src/eval/py/tool-bridge.ts +17 -10
- package/src/eval/session-id.ts +8 -0
- package/src/exec/bash-executor.ts +27 -16
- package/src/extensibility/extensions/runner.ts +0 -1
- package/src/extensibility/extensions/types.ts +1 -3
- package/src/hashline/anchors.ts +56 -65
- package/src/hashline/apply.ts +29 -31
- package/src/hashline/constants.ts +0 -3
- package/src/hashline/diff-preview.ts +4 -5
- package/src/hashline/diff.ts +30 -4
- package/src/hashline/execute.ts +91 -26
- package/src/hashline/executor.ts +239 -0
- package/src/hashline/grammar.lark +12 -10
- package/src/hashline/hash.ts +69 -114
- package/src/hashline/index.ts +2 -1
- package/src/hashline/input.ts +48 -41
- package/src/hashline/prefixes.ts +21 -11
- package/src/hashline/recovery.ts +63 -71
- package/src/hashline/stream.ts +2 -2
- package/src/hashline/tokenizer.ts +467 -0
- package/src/hashline/types.ts +6 -8
- package/src/internal-urls/docs-index.generated.ts +7 -7
- package/src/modes/components/extensions/types.ts +0 -5
- package/src/modes/components/session-observer-overlay.ts +11 -2
- package/src/modes/components/settings-selector.ts +10 -1
- package/src/modes/components/tree-selector.ts +10 -2
- package/src/modes/controllers/command-controller.ts +1 -3
- package/src/modes/controllers/extension-ui-controller.ts +10 -11
- package/src/modes/controllers/selector-controller.ts +5 -5
- package/src/modes/theme/theme.ts +4 -2
- package/src/modes/types.ts +4 -1
- package/src/modes/utils/ui-helpers.ts +4 -0
- package/src/prompts/agents/explore.md +1 -1
- package/src/prompts/tools/ast-edit.md +1 -1
- package/src/prompts/tools/ast-grep.md +1 -1
- package/src/prompts/tools/eval.md +1 -1
- package/src/prompts/tools/hashline.md +73 -94
- package/src/prompts/tools/read.md +4 -4
- package/src/prompts/tools/search.md +3 -3
- package/src/sdk.ts +33 -26
- package/src/session/agent-session.ts +59 -66
- package/src/session/agent-storage.ts +13 -14
- package/src/slash-commands/acp-builtins.ts +3 -3
- package/src/slash-commands/types.ts +0 -6
- package/src/task/executor.ts +26 -57
- package/src/task/index.ts +8 -4
- package/src/tool-discovery/tool-index.ts +0 -134
- package/src/tools/ast-edit.ts +36 -13
- package/src/tools/ast-grep.ts +45 -4
- package/src/tools/browser/tab-worker.ts +3 -2
- package/src/tools/eval.ts +2 -1
- package/src/tools/fetch.ts +23 -14
- package/src/tools/index.ts +2 -8
- package/src/tools/irc.ts +59 -5
- package/src/tools/match-line-format.ts +5 -7
- package/src/tools/output-schema-validator.ts +132 -0
- package/src/tools/read.ts +142 -31
- package/src/tools/review.ts +23 -0
- package/src/tools/search-tool-bm25.ts +3 -30
- package/src/tools/search.ts +48 -16
- package/src/tools/write.ts +3 -3
- package/src/tools/yield.ts +32 -41
- package/src/utils/edit-mode.ts +1 -2
- package/src/utils/file-mentions.ts +2 -2
- package/src/web/kagi.ts +15 -6
- package/src/web/parallel.ts +9 -6
- package/src/web/scrapers/types.ts +7 -1
- package/src/web/scrapers/youtube.ts +13 -7
- package/src/web/search/index.ts +37 -11
- package/src/web/search/provider.ts +5 -3
- package/src/web/search/providers/anthropic.ts +30 -21
- package/src/web/search/providers/base.ts +35 -2
- package/src/web/search/providers/brave.ts +4 -4
- package/src/web/search/providers/codex.ts +118 -89
- package/src/web/search/providers/exa.ts +3 -2
- package/src/web/search/providers/gemini.ts +58 -155
- package/src/web/search/providers/jina.ts +4 -4
- package/src/web/search/providers/kagi.ts +17 -11
- package/src/web/search/providers/kimi.ts +29 -13
- package/src/web/search/providers/parallel.ts +171 -23
- package/src/web/search/providers/perplexity.ts +38 -37
- package/src/web/search/providers/searxng.ts +3 -1
- package/src/web/search/providers/synthetic.ts +16 -19
- package/src/web/search/providers/tavily.ts +23 -18
- package/src/web/search/providers/utils.ts +11 -17
- package/src/web/search/providers/zai.ts +16 -8
- package/dist/types/hashline/parser.d.ts +0 -7
- package/dist/types/mcp/discoverable-tool-metadata.d.ts +0 -7
- package/dist/types/tools/vim.d.ts +0 -58
- package/dist/types/vim/buffer.d.ts +0 -41
- package/dist/types/vim/commands.d.ts +0 -6
- package/dist/types/vim/engine.d.ts +0 -47
- package/dist/types/vim/parser.d.ts +0 -3
- package/dist/types/vim/render.d.ts +0 -25
- package/dist/types/vim/types.d.ts +0 -182
- package/src/hashline/parser.ts +0 -246
- package/src/mcp/discoverable-tool-metadata.ts +0 -24
- package/src/prompts/tools/vim.md +0 -98
- package/src/tools/vim.ts +0 -949
- package/src/vim/buffer.ts +0 -309
- package/src/vim/commands.ts +0 -382
- package/src/vim/engine.ts +0 -2409
- package/src/vim/parser.ts +0 -134
- package/src/vim/render.ts +0 -252
- package/src/vim/types.ts +0 -197
package/src/tools/yield.ts
CHANGED
|
@@ -8,15 +8,13 @@ import type { TSchema } from "@oh-my-pi/pi-ai/types";
|
|
|
8
8
|
import {
|
|
9
9
|
dereferenceJsonSchema,
|
|
10
10
|
isValidJsonSchema,
|
|
11
|
-
type JsonSchemaValidationIssue,
|
|
12
11
|
type JsonSchemaValidationResult,
|
|
13
12
|
sanitizeSchemaForStrictMode,
|
|
14
13
|
tryEnforceStrictSchema,
|
|
15
|
-
validateJsonSchemaValue,
|
|
16
14
|
} from "@oh-my-pi/pi-ai/utils/schema";
|
|
17
15
|
import { subprocessToolRegistry } from "../task/subprocess-tool-registry";
|
|
18
16
|
import type { ToolSession } from ".";
|
|
19
|
-
import {
|
|
17
|
+
import { buildOutputValidator, formatAllValidationIssues } from "./output-schema-validator";
|
|
20
18
|
|
|
21
19
|
export interface YieldDetails {
|
|
22
20
|
data: unknown;
|
|
@@ -34,16 +32,6 @@ function formatSchema(schema: unknown): string {
|
|
|
34
32
|
}
|
|
35
33
|
}
|
|
36
34
|
|
|
37
|
-
function formatJsonSchemaIssues(issues: ReadonlyArray<JsonSchemaValidationIssue> | undefined): string {
|
|
38
|
-
if (!issues || issues.length === 0) return "Unknown schema validation error.";
|
|
39
|
-
return issues
|
|
40
|
-
.map(issue => {
|
|
41
|
-
const path = issue.path.length === 0 ? "" : `${issue.path.map(seg => String(seg)).join("/")}: `;
|
|
42
|
-
return `${path}${issue.message}`;
|
|
43
|
-
})
|
|
44
|
-
.join("; ");
|
|
45
|
-
}
|
|
46
|
-
|
|
47
35
|
function looseRecordSchema(description: string): Record<string, unknown> {
|
|
48
36
|
return {
|
|
49
37
|
type: "object",
|
|
@@ -100,6 +88,15 @@ function wrapYieldParameters(dataSchema: Record<string, unknown>): Record<string
|
|
|
100
88
|
};
|
|
101
89
|
}
|
|
102
90
|
|
|
91
|
+
/**
|
|
92
|
+
* Max consecutive schema-validation failures before the yield tool overrides validation
|
|
93
|
+
* and lets non-conforming data through. The override is a safety net for schemas the
|
|
94
|
+
* JTD→JSON-Schema converter cannot fully express; it should not be reached during normal
|
|
95
|
+
* model retries. Three matches the existing "3 reminders" pattern elsewhere in the agent
|
|
96
|
+
* runtime.
|
|
97
|
+
*/
|
|
98
|
+
const MAX_SCHEMA_RETRIES = 3;
|
|
99
|
+
|
|
103
100
|
export class YieldTool implements AgentTool<TSchema, YieldDetails> {
|
|
104
101
|
readonly name = "yield";
|
|
105
102
|
readonly label = "Submit Result";
|
|
@@ -120,21 +117,14 @@ export class YieldTool implements AgentTool<TSchema, YieldDetails> {
|
|
|
120
117
|
let parameters: TSchema;
|
|
121
118
|
|
|
122
119
|
try {
|
|
123
|
-
const
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
if (normalizedSchema !== undefined && normalizedSchema !== false && !schemaError) {
|
|
133
|
-
if (!isValidJsonSchema(normalizedSchema)) {
|
|
134
|
-
schemaError = "invalid JSON schema";
|
|
135
|
-
} else {
|
|
136
|
-
validate = value => validateJsonSchemaValue(normalizedSchema, value);
|
|
137
|
-
}
|
|
120
|
+
const {
|
|
121
|
+
validator,
|
|
122
|
+
jsonSchema: normalizedSchema,
|
|
123
|
+
normalized,
|
|
124
|
+
error: schemaError,
|
|
125
|
+
} = buildOutputValidator(session.outputSchema);
|
|
126
|
+
if (validator) {
|
|
127
|
+
validate = value => validator.validate(value);
|
|
138
128
|
}
|
|
139
129
|
|
|
140
130
|
const schemaHint = formatSchema(normalizedSchema ?? session.outputSchema);
|
|
@@ -142,21 +132,15 @@ export class YieldTool implements AgentTool<TSchema, YieldDetails> {
|
|
|
142
132
|
? `Structured JSON output (output schema invalid; accepting unconstrained object): ${schemaError}`
|
|
143
133
|
: `Structured output matching the schema:\n${schemaHint}`;
|
|
144
134
|
let sanitizedSchema: Record<string, unknown> | undefined;
|
|
145
|
-
if (
|
|
146
|
-
|
|
147
|
-
normalizedSchema != null &&
|
|
148
|
-
typeof normalizedSchema === "object" &&
|
|
149
|
-
!Array.isArray(normalizedSchema)
|
|
150
|
-
) {
|
|
151
|
-
const normalizedRecord = normalizedSchema as Record<string, unknown>;
|
|
152
|
-
const strictProbe = tryEnforceStrictSchema(normalizedRecord);
|
|
135
|
+
if (!schemaError && normalizedSchema !== undefined) {
|
|
136
|
+
const strictProbe = tryEnforceStrictSchema(normalizedSchema);
|
|
153
137
|
if (strictProbe.strict) {
|
|
154
|
-
sanitizedSchema = sanitizeSchemaForStrictMode(
|
|
138
|
+
sanitizedSchema = sanitizeSchemaForStrictMode(normalizedSchema);
|
|
155
139
|
} else {
|
|
156
|
-
sanitizedSchema =
|
|
140
|
+
sanitizedSchema = normalizedSchema;
|
|
157
141
|
this.strict = false;
|
|
158
142
|
}
|
|
159
|
-
} else if (!schemaError &&
|
|
143
|
+
} else if (!schemaError && normalized === true) {
|
|
160
144
|
sanitizedSchema = {};
|
|
161
145
|
this.strict = false;
|
|
162
146
|
}
|
|
@@ -229,8 +213,15 @@ export class YieldTool implements AgentTool<TSchema, YieldDetails> {
|
|
|
229
213
|
const parsed = this.#validate(data);
|
|
230
214
|
if (!parsed.success) {
|
|
231
215
|
this.#schemaValidationFailures++;
|
|
232
|
-
if (this.#schemaValidationFailures <=
|
|
233
|
-
|
|
216
|
+
if (this.#schemaValidationFailures <= MAX_SCHEMA_RETRIES) {
|
|
217
|
+
const remaining = MAX_SCHEMA_RETRIES - this.#schemaValidationFailures;
|
|
218
|
+
const retryHint =
|
|
219
|
+
remaining > 0
|
|
220
|
+
? ` Call yield again with the corrected shape — ${remaining} retry attempt(s) remain before the schema constraint is dropped.`
|
|
221
|
+
: " Call yield again with the corrected shape — this is the final retry before the schema constraint is dropped.";
|
|
222
|
+
throw new Error(
|
|
223
|
+
`Output does not match schema: ${formatAllValidationIssues(parsed.issues)}.${retryHint}`,
|
|
224
|
+
);
|
|
234
225
|
}
|
|
235
226
|
schemaValidationOverridden = true;
|
|
236
227
|
}
|
package/src/utils/edit-mode.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { $env } from "@oh-my-pi/pi-utils";
|
|
2
2
|
|
|
3
|
-
export type EditMode = "replace" | "patch" | "hashline" | "
|
|
3
|
+
export type EditMode = "replace" | "patch" | "hashline" | "apply_patch";
|
|
4
4
|
|
|
5
5
|
export const DEFAULT_EDIT_MODE: EditMode = "hashline";
|
|
6
6
|
|
|
@@ -9,7 +9,6 @@ const EDIT_MODE_IDS = {
|
|
|
9
9
|
hashline: "hashline",
|
|
10
10
|
patch: "patch",
|
|
11
11
|
replace: "replace",
|
|
12
|
-
vim: "vim",
|
|
13
12
|
} as const satisfies Record<string, EditMode>;
|
|
14
13
|
|
|
15
14
|
export const EDIT_MODES = Object.keys(EDIT_MODE_IDS) as EditMode[];
|
|
@@ -12,7 +12,7 @@ import type { ImageContent } from "@oh-my-pi/pi-ai";
|
|
|
12
12
|
import { glob } from "@oh-my-pi/pi-natives";
|
|
13
13
|
import { fuzzyMatch } from "@oh-my-pi/pi-tui";
|
|
14
14
|
import { formatAge, formatBytes, readImageMetadata } from "@oh-my-pi/pi-utils";
|
|
15
|
-
import {
|
|
15
|
+
import { computeFileHash, formatHashlineHeader, formatNumberedLines } from "../hashline/hash";
|
|
16
16
|
import type { FileMentionMessage } from "../session/messages";
|
|
17
17
|
import {
|
|
18
18
|
DEFAULT_MAX_BYTES,
|
|
@@ -356,7 +356,7 @@ export async function generateFileMentionMessages(
|
|
|
356
356
|
const content = await Bun.file(absolutePath).text();
|
|
357
357
|
let { output, lineCount } = buildTextOutput(content);
|
|
358
358
|
if (options?.useHashLines) {
|
|
359
|
-
output =
|
|
359
|
+
output = `${formatHashlineHeader(resolvedPath, computeFileHash(content))}\n${formatNumberedLines(output)}`;
|
|
360
360
|
}
|
|
361
361
|
files.push({ path: resolvedPath, content: output, lineCount });
|
|
362
362
|
} catch {
|
package/src/web/kagi.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
1
|
+
import type { AuthStorage } from "@oh-my-pi/pi-ai";
|
|
2
|
+
import { withHardTimeout } from "./search/providers/utils";
|
|
3
3
|
|
|
4
4
|
const KAGI_SEARCH_URL = "https://kagi.com/api/v0/search";
|
|
5
5
|
|
|
@@ -97,6 +97,7 @@ function parseKagiErrorResponse(statusCode: number, responseText: string): KagiA
|
|
|
97
97
|
|
|
98
98
|
export interface KagiSearchOptions {
|
|
99
99
|
limit?: number;
|
|
100
|
+
sessionId?: string;
|
|
100
101
|
signal?: AbortSignal;
|
|
101
102
|
}
|
|
102
103
|
|
|
@@ -113,8 +114,12 @@ export interface KagiSearchResult {
|
|
|
113
114
|
relatedQuestions: string[];
|
|
114
115
|
}
|
|
115
116
|
|
|
116
|
-
export async function findKagiApiKey(
|
|
117
|
-
|
|
117
|
+
export async function findKagiApiKey(
|
|
118
|
+
authStorage: AuthStorage,
|
|
119
|
+
sessionId?: string,
|
|
120
|
+
signal?: AbortSignal,
|
|
121
|
+
): Promise<string | null> {
|
|
122
|
+
return (await authStorage.getApiKey("kagi", sessionId, { signal })) ?? null;
|
|
118
123
|
}
|
|
119
124
|
|
|
120
125
|
function getAuthHeaders(apiKey: string): Record<string, string> {
|
|
@@ -124,8 +129,12 @@ function getAuthHeaders(apiKey: string): Record<string, string> {
|
|
|
124
129
|
};
|
|
125
130
|
}
|
|
126
131
|
|
|
127
|
-
export async function searchWithKagi(
|
|
128
|
-
|
|
132
|
+
export async function searchWithKagi(
|
|
133
|
+
query: string,
|
|
134
|
+
options: KagiSearchOptions = {},
|
|
135
|
+
authStorage: AuthStorage,
|
|
136
|
+
): Promise<KagiSearchResult> {
|
|
137
|
+
const apiKey = await findKagiApiKey(authStorage, options.sessionId, options.signal);
|
|
129
138
|
if (!apiKey) {
|
|
130
139
|
throw new KagiApiError("Kagi credentials not found. Set KAGI_API_KEY or login with 'omp /login kagi'.");
|
|
131
140
|
}
|
package/src/web/parallel.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { getEnvApiKey } from "@oh-my-pi/pi-ai";
|
|
2
|
+
import type { AgentStorage } from "../session/agent-storage";
|
|
2
3
|
import { findCredential, withHardTimeout } from "./search/providers/utils";
|
|
3
4
|
|
|
4
5
|
const PARALLEL_API_URL = "https://api.parallel.ai";
|
|
@@ -73,8 +74,8 @@ export class ParallelApiError extends Error {
|
|
|
73
74
|
}
|
|
74
75
|
}
|
|
75
76
|
|
|
76
|
-
export
|
|
77
|
-
return findCredential(getEnvApiKey("parallel"), "parallel");
|
|
77
|
+
export function findParallelApiKey(storage: AgentStorage | null | undefined): string | null {
|
|
78
|
+
return findCredential(storage, getEnvApiKey("parallel"), "parallel");
|
|
78
79
|
}
|
|
79
80
|
|
|
80
81
|
export function getParallelExtractContent(document: ParallelExtractDocument): string {
|
|
@@ -284,9 +285,10 @@ function parseExtractPayload(payload: unknown): ParallelExtractResult {
|
|
|
284
285
|
export async function searchWithParallel(
|
|
285
286
|
objective: string,
|
|
286
287
|
queries: string[],
|
|
287
|
-
options: ParallelSearchOptions
|
|
288
|
+
options: ParallelSearchOptions,
|
|
289
|
+
storage: AgentStorage | null | undefined,
|
|
288
290
|
): Promise<ParallelSearchResult> {
|
|
289
|
-
const apiKey =
|
|
291
|
+
const apiKey = findParallelApiKey(storage);
|
|
290
292
|
if (!apiKey) {
|
|
291
293
|
throw new ParallelApiError(
|
|
292
294
|
"Parallel credentials not found. Set PARALLEL_API_KEY or login with 'omp /login parallel'.",
|
|
@@ -316,9 +318,10 @@ export async function searchWithParallel(
|
|
|
316
318
|
|
|
317
319
|
export async function extractWithParallel(
|
|
318
320
|
urls: string[],
|
|
319
|
-
options: ParallelExtractOptions
|
|
321
|
+
options: ParallelExtractOptions,
|
|
322
|
+
storage: AgentStorage | null | undefined,
|
|
320
323
|
): Promise<ParallelExtractResult> {
|
|
321
|
-
const apiKey =
|
|
324
|
+
const apiKey = findParallelApiKey(storage);
|
|
322
325
|
if (!apiKey) {
|
|
323
326
|
throw new ParallelApiError(
|
|
324
327
|
"Parallel credentials not found. Set PARALLEL_API_KEY or login with 'omp /login parallel'.",
|
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
import { ptree } from "@oh-my-pi/pi-utils";
|
|
5
5
|
import type TurndownService from "turndown";
|
|
6
6
|
|
|
7
|
+
import type { AgentStorage } from "../../session/agent-storage";
|
|
7
8
|
import { ToolAbortError } from "../../tools/tool-errors";
|
|
8
9
|
|
|
9
10
|
export { formatNumber } from "@oh-my-pi/pi-utils";
|
|
@@ -19,7 +20,12 @@ export interface RenderResult {
|
|
|
19
20
|
notes: string[];
|
|
20
21
|
}
|
|
21
22
|
|
|
22
|
-
export type SpecialHandler = (
|
|
23
|
+
export type SpecialHandler = (
|
|
24
|
+
url: string,
|
|
25
|
+
timeout: number,
|
|
26
|
+
signal?: AbortSignal,
|
|
27
|
+
storage?: AgentStorage | null,
|
|
28
|
+
) => Promise<RenderResult | null>;
|
|
23
29
|
|
|
24
30
|
export const MAX_OUTPUT_CHARS = 500_000;
|
|
25
31
|
export const MAX_BYTES = 50 * 1024 * 1024;
|
|
@@ -3,6 +3,7 @@ import * as os from "node:os";
|
|
|
3
3
|
import * as path from "node:path";
|
|
4
4
|
import { ptree, Snowflake } from "@oh-my-pi/pi-utils";
|
|
5
5
|
import { settings } from "../../config/settings";
|
|
6
|
+
import type { AgentStorage } from "../../session/agent-storage";
|
|
6
7
|
import { throwIfAborted } from "../../tools/tool-errors";
|
|
7
8
|
import { ensureTool } from "../../utils/tools-manager";
|
|
8
9
|
import { extractWithParallel, findParallelApiKey, getParallelExtractContent } from "../parallel";
|
|
@@ -101,6 +102,7 @@ export const handleYouTube: SpecialHandler = async (
|
|
|
101
102
|
url: string,
|
|
102
103
|
timeout: number,
|
|
103
104
|
userSignal?: AbortSignal,
|
|
105
|
+
storage?: AgentStorage | null,
|
|
104
106
|
): Promise<RenderResult | null> => {
|
|
105
107
|
throwIfAborted(userSignal);
|
|
106
108
|
const yt = parseYouTubeUrl(url);
|
|
@@ -112,14 +114,18 @@ export const handleYouTube: SpecialHandler = async (
|
|
|
112
114
|
const videoUrl = `https://www.youtube.com/watch?v=${yt.videoId}`;
|
|
113
115
|
|
|
114
116
|
// Prefer Parallel extract when credentials are available
|
|
115
|
-
if (settings.get("providers.parallelFetch") &&
|
|
117
|
+
if (settings.get("providers.parallelFetch") && findParallelApiKey(storage)) {
|
|
116
118
|
try {
|
|
117
|
-
const parallelResult = await extractWithParallel(
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
119
|
+
const parallelResult = await extractWithParallel(
|
|
120
|
+
[videoUrl],
|
|
121
|
+
{
|
|
122
|
+
objective: "Extract the main content of this YouTube video page",
|
|
123
|
+
excerpts: true,
|
|
124
|
+
fullContent: false,
|
|
125
|
+
signal,
|
|
126
|
+
},
|
|
127
|
+
storage,
|
|
128
|
+
);
|
|
123
129
|
const firstDocument = parallelResult.results[0];
|
|
124
130
|
if (firstDocument) {
|
|
125
131
|
const content = getParallelExtractContent(firstDocument);
|
package/src/web/search/index.ts
CHANGED
|
@@ -3,15 +3,16 @@
|
|
|
3
3
|
*
|
|
4
4
|
* Single tool supporting Anthropic, Perplexity, Exa, Brave, Jina, Kimi, Gemini, Codex, Tavily, Kagi, Z.AI, SearXNG, and Synthetic
|
|
5
5
|
* providers with provider-specific parameters exposed conditionally.
|
|
6
|
-
*
|
|
7
6
|
*/
|
|
8
7
|
import type { AgentTool, AgentToolContext, AgentToolResult, AgentToolUpdateCallback } from "@oh-my-pi/pi-agent-core";
|
|
8
|
+
import type { AuthStorage } from "@oh-my-pi/pi-ai";
|
|
9
9
|
import { prompt } from "@oh-my-pi/pi-utils";
|
|
10
10
|
import * as z from "zod/v4";
|
|
11
11
|
import type { CustomTool, CustomToolContext, RenderResultOptions } from "../../extensibility/custom-tools/types";
|
|
12
12
|
import type { Theme } from "../../modes/theme/theme";
|
|
13
13
|
import webSearchSystemPrompt from "../../prompts/system/web-search.md" with { type: "text" };
|
|
14
14
|
import webSearchDescription from "../../prompts/tools/web-search.md" with { type: "text" };
|
|
15
|
+
import { discoverAuthStorage } from "../../sdk";
|
|
15
16
|
import type { ToolSession } from "../../tools";
|
|
16
17
|
import { formatAge } from "../../tools/render-utils";
|
|
17
18
|
import { throwIfAborted } from "../../tools/tool-errors";
|
|
@@ -114,18 +115,25 @@ function formatForLLM(response: SearchResponse): string {
|
|
|
114
115
|
return parts.join("\n");
|
|
115
116
|
}
|
|
116
117
|
|
|
118
|
+
interface ExecuteSearchOptions {
|
|
119
|
+
authStorage: AuthStorage;
|
|
120
|
+
sessionId?: string;
|
|
121
|
+
signal?: AbortSignal;
|
|
122
|
+
}
|
|
123
|
+
|
|
117
124
|
/** Execute web search */
|
|
118
125
|
async function executeSearch(
|
|
119
126
|
_toolCallId: string,
|
|
120
127
|
params: SearchQueryParams,
|
|
121
|
-
|
|
128
|
+
options: ExecuteSearchOptions,
|
|
122
129
|
): Promise<{ content: Array<{ type: "text"; text: string }>; details: SearchRenderDetails }> {
|
|
130
|
+
const { authStorage, sessionId, signal } = options;
|
|
123
131
|
const providers =
|
|
124
132
|
params.provider && params.provider !== "auto"
|
|
125
|
-
? await getSearchProvider(params.provider).then(provider =>
|
|
126
|
-
provider.isAvailable() ? [provider] : resolveProviderChain("auto"),
|
|
133
|
+
? await getSearchProvider(params.provider).then(async provider =>
|
|
134
|
+
(await provider.isAvailable(authStorage)) ? [provider] : resolveProviderChain(authStorage, "auto"),
|
|
127
135
|
)
|
|
128
|
-
: await resolveProviderChain();
|
|
136
|
+
: await resolveProviderChain(authStorage);
|
|
129
137
|
if (providers.length === 0) {
|
|
130
138
|
const message = "No web search provider configured.";
|
|
131
139
|
return {
|
|
@@ -148,6 +156,8 @@ async function executeSearch(
|
|
|
148
156
|
numSearchResults: params.num_search_results,
|
|
149
157
|
temperature: params.temperature,
|
|
150
158
|
signal,
|
|
159
|
+
authStorage,
|
|
160
|
+
sessionId,
|
|
151
161
|
});
|
|
152
162
|
|
|
153
163
|
const text = formatForLLM(response);
|
|
@@ -190,18 +200,27 @@ async function executeSearch(
|
|
|
190
200
|
|
|
191
201
|
/**
|
|
192
202
|
* Execute a web search query for CLI/testing workflows.
|
|
203
|
+
*
|
|
204
|
+
* `authStorage` may be omitted; in that case we discover one via the standard
|
|
205
|
+
* factory (`discoverAuthStorage`), which honours `OMP_AUTH_BROKER_URL` and
|
|
206
|
+
* otherwise opens the local SQLite credential store.
|
|
193
207
|
*/
|
|
194
208
|
export async function runSearchQuery(
|
|
195
209
|
params: SearchQueryParams,
|
|
210
|
+
options: { authStorage?: AuthStorage; sessionId?: string; signal?: AbortSignal } = {},
|
|
196
211
|
): Promise<{ content: Array<{ type: "text"; text: string }>; details: SearchRenderDetails }> {
|
|
197
|
-
|
|
212
|
+
const authStorage = options.authStorage ?? (await discoverAuthStorage());
|
|
213
|
+
return executeSearch("cli-web-search", params, {
|
|
214
|
+
authStorage,
|
|
215
|
+
sessionId: options.sessionId,
|
|
216
|
+
signal: options.signal,
|
|
217
|
+
});
|
|
198
218
|
}
|
|
199
219
|
|
|
200
220
|
/**
|
|
201
221
|
* Web search tool implementation.
|
|
202
222
|
*
|
|
203
223
|
* Supports Anthropic, Perplexity, Exa, Brave, Jina, Kimi, Gemini, Codex, Z.AI, SearXNG, and Synthetic providers with automatic fallback.
|
|
204
|
-
* Session is accepted for interface consistency but not used.
|
|
205
224
|
*/
|
|
206
225
|
export class WebSearchTool implements AgentTool<typeof webSearchSchema, SearchRenderDetails> {
|
|
207
226
|
readonly name = "web_search";
|
|
@@ -212,7 +231,10 @@ export class WebSearchTool implements AgentTool<typeof webSearchSchema, SearchRe
|
|
|
212
231
|
readonly loadMode = "discoverable";
|
|
213
232
|
readonly summary = "Search the web for up-to-date information";
|
|
214
233
|
|
|
215
|
-
|
|
234
|
+
#session: ToolSession;
|
|
235
|
+
|
|
236
|
+
constructor(session: ToolSession) {
|
|
237
|
+
this.#session = session;
|
|
216
238
|
this.description = prompt.render(webSearchDescription);
|
|
217
239
|
}
|
|
218
240
|
|
|
@@ -223,7 +245,9 @@ export class WebSearchTool implements AgentTool<typeof webSearchSchema, SearchRe
|
|
|
223
245
|
_onUpdate?: AgentToolUpdateCallback<SearchRenderDetails>,
|
|
224
246
|
_context?: AgentToolContext,
|
|
225
247
|
): Promise<AgentToolResult<SearchRenderDetails>> {
|
|
226
|
-
|
|
248
|
+
const authStorage = this.#session.authStorage ?? (await discoverAuthStorage());
|
|
249
|
+
const sessionId = this.#session.getSessionId?.() ?? undefined;
|
|
250
|
+
return executeSearch(_toolCallId, params, { authStorage, sessionId, signal });
|
|
227
251
|
}
|
|
228
252
|
}
|
|
229
253
|
|
|
@@ -238,10 +262,12 @@ export const webSearchCustomTool: CustomTool<typeof webSearchSchema, SearchRende
|
|
|
238
262
|
toolCallId: string,
|
|
239
263
|
params: SearchToolParams,
|
|
240
264
|
_onUpdate,
|
|
241
|
-
|
|
265
|
+
ctx: CustomToolContext,
|
|
242
266
|
signal?: AbortSignal,
|
|
243
267
|
) {
|
|
244
|
-
|
|
268
|
+
const authStorage = ctx.modelRegistry?.authStorage ?? (await discoverAuthStorage());
|
|
269
|
+
const sessionId = ctx.sessionManager.getSessionId();
|
|
270
|
+
return executeSearch(toolCallId, params, { authStorage, sessionId, signal });
|
|
245
271
|
},
|
|
246
272
|
|
|
247
273
|
renderCall(args: SearchToolParams, options: RenderResultOptions, theme: Theme) {
|
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
// The `label`/`id` metadata is kept inline so callers needing a display name
|
|
9
9
|
// (error formatting, UI listings) do not force a load.
|
|
10
10
|
|
|
11
|
+
import type { AuthStorage } from "@oh-my-pi/pi-ai";
|
|
11
12
|
import type { SearchProvider } from "./providers/base";
|
|
12
13
|
import type { SearchProviderId } from "./types";
|
|
13
14
|
|
|
@@ -64,7 +65,7 @@ const PROVIDER_META: Record<SearchProviderId, ProviderMeta> = {
|
|
|
64
65
|
},
|
|
65
66
|
codex: {
|
|
66
67
|
id: "codex",
|
|
67
|
-
label: "
|
|
68
|
+
label: "OpenAI",
|
|
68
69
|
load: async () => new (await import("./providers/codex")).CodexProvider(),
|
|
69
70
|
},
|
|
70
71
|
tavily: {
|
|
@@ -148,13 +149,14 @@ export function setPreferredSearchProvider(provider: SearchProviderId | "auto"):
|
|
|
148
149
|
* is walked, so unconfigured providers never pay the load cost.
|
|
149
150
|
*/
|
|
150
151
|
export async function resolveProviderChain(
|
|
152
|
+
authStorage: AuthStorage,
|
|
151
153
|
preferredProvider: SearchProviderId | "auto" = preferredProvId,
|
|
152
154
|
): Promise<SearchProvider[]> {
|
|
153
155
|
const providers: SearchProvider[] = [];
|
|
154
156
|
|
|
155
157
|
if (preferredProvider !== "auto") {
|
|
156
158
|
const provider = await getSearchProvider(preferredProvider);
|
|
157
|
-
if (await provider.isAvailable()) {
|
|
159
|
+
if (await provider.isAvailable(authStorage)) {
|
|
158
160
|
providers.push(provider);
|
|
159
161
|
}
|
|
160
162
|
}
|
|
@@ -162,7 +164,7 @@ export async function resolveProviderChain(
|
|
|
162
164
|
for (const id of SEARCH_PROVIDER_ORDER) {
|
|
163
165
|
if (id === preferredProvider) continue;
|
|
164
166
|
const provider = await getSearchProvider(id);
|
|
165
|
-
if (await provider.isAvailable()) {
|
|
167
|
+
if (await provider.isAvailable(authStorage)) {
|
|
166
168
|
providers.push(provider);
|
|
167
169
|
}
|
|
168
170
|
}
|
|
@@ -7,10 +7,11 @@
|
|
|
7
7
|
import {
|
|
8
8
|
type AnthropicAuthConfig,
|
|
9
9
|
type AnthropicSystemBlock,
|
|
10
|
+
type AuthStorage,
|
|
11
|
+
buildAnthropicAuthConfig,
|
|
10
12
|
buildAnthropicSearchHeaders,
|
|
11
13
|
buildAnthropicSystemBlocks,
|
|
12
14
|
buildAnthropicUrl,
|
|
13
|
-
findAnthropicAuth,
|
|
14
15
|
stripClaudeToolPrefix,
|
|
15
16
|
} from "@oh-my-pi/pi-ai";
|
|
16
17
|
import { $env } from "@oh-my-pi/pi-utils";
|
|
@@ -34,9 +35,7 @@ export interface AnthropicSearchParams {
|
|
|
34
35
|
query: string;
|
|
35
36
|
system_prompt?: string;
|
|
36
37
|
num_results?: number;
|
|
37
|
-
/** Maximum output tokens. Defaults to 4096. */
|
|
38
38
|
max_tokens?: number;
|
|
39
|
-
/** Sampling temperature (0–1). Lower = more focused/factual. */
|
|
40
39
|
temperature?: number;
|
|
41
40
|
signal?: AbortSignal;
|
|
42
41
|
}
|
|
@@ -242,30 +241,47 @@ function parseResponse(response: AnthropicApiResponse): SearchResponse {
|
|
|
242
241
|
* @returns Search response with synthesized answer, sources, and citations
|
|
243
242
|
* @throws {Error} If no Anthropic credentials are configured
|
|
244
243
|
*/
|
|
245
|
-
export async function searchAnthropic(
|
|
246
|
-
|
|
244
|
+
export async function searchAnthropic(
|
|
245
|
+
params: SearchParams | AnthropicSearchParams,
|
|
246
|
+
_legacyStorage?: unknown,
|
|
247
|
+
): Promise<SearchResponse> {
|
|
248
|
+
const searchApiKey = $env.ANTHROPIC_SEARCH_API_KEY;
|
|
249
|
+
const searchBaseUrl = $env.ANTHROPIC_SEARCH_BASE_URL;
|
|
250
|
+
let auth: AnthropicAuthConfig | undefined;
|
|
251
|
+
|
|
252
|
+
if (searchApiKey) {
|
|
253
|
+
auth = buildAnthropicAuthConfig(searchApiKey, searchBaseUrl);
|
|
254
|
+
} else if ("authStorage" in params) {
|
|
255
|
+
const apiKey = await params.authStorage.getApiKey("anthropic", params.sessionId, {
|
|
256
|
+
signal: params.signal,
|
|
257
|
+
});
|
|
258
|
+
if (apiKey) auth = buildAnthropicAuthConfig(apiKey);
|
|
259
|
+
}
|
|
260
|
+
|
|
247
261
|
if (!auth) {
|
|
248
262
|
throw new Error(
|
|
249
|
-
"No Anthropic credentials found. Set ANTHROPIC_API_KEY or configure OAuth
|
|
263
|
+
"No Anthropic credentials found. Set ANTHROPIC_SEARCH_API_KEY or ANTHROPIC_API_KEY, or configure Anthropic OAuth.",
|
|
250
264
|
);
|
|
251
265
|
}
|
|
252
266
|
|
|
253
267
|
const model = getModel();
|
|
268
|
+
const systemPrompt = "authStorage" in params ? params.systemPrompt : params.system_prompt;
|
|
269
|
+
const maxTokens = "authStorage" in params ? params.maxOutputTokens : params.max_tokens;
|
|
254
270
|
const response = await callSearch(
|
|
255
271
|
auth,
|
|
256
272
|
model,
|
|
257
273
|
params.query,
|
|
258
|
-
|
|
259
|
-
|
|
274
|
+
systemPrompt,
|
|
275
|
+
maxTokens,
|
|
260
276
|
params.temperature,
|
|
261
277
|
params.signal,
|
|
262
278
|
);
|
|
263
279
|
|
|
264
280
|
const result = parseResponse(response);
|
|
265
281
|
|
|
266
|
-
|
|
267
|
-
if (
|
|
268
|
-
result.sources = result.sources.slice(0,
|
|
282
|
+
const numResults = "authStorage" in params ? (params.numSearchResults ?? params.limit) : params.num_results;
|
|
283
|
+
if (numResults && result.sources.length > numResults) {
|
|
284
|
+
result.sources = result.sources.slice(0, numResults);
|
|
269
285
|
}
|
|
270
286
|
|
|
271
287
|
return result;
|
|
@@ -276,18 +292,11 @@ export class AnthropicProvider extends SearchProvider {
|
|
|
276
292
|
readonly id = "anthropic";
|
|
277
293
|
readonly label = "Anthropic";
|
|
278
294
|
|
|
279
|
-
isAvailable() {
|
|
280
|
-
return
|
|
295
|
+
isAvailable(authStorage: AuthStorage): Promise<boolean> | boolean {
|
|
296
|
+
return Boolean($env.ANTHROPIC_SEARCH_API_KEY) || authStorage.hasAuth("anthropic");
|
|
281
297
|
}
|
|
282
298
|
|
|
283
299
|
search(params: SearchParams): Promise<SearchResponse> {
|
|
284
|
-
return searchAnthropic(
|
|
285
|
-
query: params.query,
|
|
286
|
-
system_prompt: params.systemPrompt,
|
|
287
|
-
num_results: params.numSearchResults ?? params.limit,
|
|
288
|
-
max_tokens: params.maxOutputTokens,
|
|
289
|
-
temperature: params.temperature,
|
|
290
|
-
signal: params.signal,
|
|
291
|
-
});
|
|
300
|
+
return searchAnthropic(params);
|
|
292
301
|
}
|
|
293
302
|
}
|
|
@@ -1,6 +1,16 @@
|
|
|
1
|
+
import type { AuthStorage } from "@oh-my-pi/pi-ai";
|
|
1
2
|
import type { SearchProviderId, SearchResponse } from "../types";
|
|
2
3
|
|
|
3
|
-
/**
|
|
4
|
+
/**
|
|
5
|
+
* Shared web search parameters passed to providers.
|
|
6
|
+
*
|
|
7
|
+
* `authStorage` is the **only** credential source providers may consult.
|
|
8
|
+
* Opening a sibling SQLite handle or calling provider-direct refresh helpers
|
|
9
|
+
* (e.g. `refreshOpenAICodexToken`, `refreshGoogleCloudToken`) is prohibited:
|
|
10
|
+
* it races the broker's per-credential refresh and POSTs the broker sentinel
|
|
11
|
+
* (`REMOTE_REFRESH_SENTINEL`) to the upstream token endpoint, which classifies
|
|
12
|
+
* as `invalid_grant` and disables the row.
|
|
13
|
+
*/
|
|
4
14
|
export interface SearchParams {
|
|
5
15
|
query: string;
|
|
6
16
|
limit?: number;
|
|
@@ -26,6 +36,20 @@ export interface SearchParams {
|
|
|
26
36
|
googleSearch?: Record<string, unknown>;
|
|
27
37
|
codeExecution?: Record<string, unknown>;
|
|
28
38
|
urlContext?: Record<string, unknown>;
|
|
39
|
+
/**
|
|
40
|
+
* The single source of truth for credentials. Providers MUST consult this
|
|
41
|
+
* handle exclusively (`getApiKey` for bearer-style auth, `getOAuthAccess`
|
|
42
|
+
* when identity metadata is required). Do not open `AgentStorage` or any
|
|
43
|
+
* `AuthCredentialStore` directly — that bypasses the broker pipeline and
|
|
44
|
+
* the per-credential single-flight refresh.
|
|
45
|
+
*/
|
|
46
|
+
authStorage: AuthStorage;
|
|
47
|
+
/**
|
|
48
|
+
* Optional session id used as the round-robin / sticky key when selecting
|
|
49
|
+
* among multiple credentials for the same provider. Pass through from the
|
|
50
|
+
* caller's agent session when available; otherwise omit.
|
|
51
|
+
*/
|
|
52
|
+
sessionId?: string;
|
|
29
53
|
}
|
|
30
54
|
|
|
31
55
|
/** Base class for web search providers. */
|
|
@@ -33,6 +57,15 @@ export abstract class SearchProvider {
|
|
|
33
57
|
abstract readonly id: SearchProviderId;
|
|
34
58
|
abstract readonly label: string;
|
|
35
59
|
|
|
36
|
-
|
|
60
|
+
/**
|
|
61
|
+
* Indicates whether this provider has the credentials/config it needs to
|
|
62
|
+
* service a request right now. Implementations consult the passed
|
|
63
|
+
* {@link AuthStorage} — never a sibling store.
|
|
64
|
+
*/
|
|
65
|
+
abstract isAvailable(authStorage: AuthStorage): Promise<boolean> | boolean;
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Execute a search. Credentials MUST be resolved through `params.authStorage`.
|
|
69
|
+
*/
|
|
37
70
|
abstract search(params: SearchParams): Promise<SearchResponse>;
|
|
38
71
|
}
|
|
@@ -4,13 +4,13 @@
|
|
|
4
4
|
* Calls Brave's web search REST API and maps results into the unified
|
|
5
5
|
* SearchResponse shape used by the web search tool.
|
|
6
6
|
*/
|
|
7
|
-
import { getEnvApiKey } from "@oh-my-pi/pi-ai";
|
|
7
|
+
import { type AuthStorage, getEnvApiKey } from "@oh-my-pi/pi-ai";
|
|
8
8
|
import type { SearchResponse, SearchSource } from "../../../web/search/types";
|
|
9
9
|
import { SearchProviderError } from "../../../web/search/types";
|
|
10
10
|
import { clampNumResults, dateToAgeSeconds } from "../utils";
|
|
11
11
|
import type { SearchParams } from "./base";
|
|
12
12
|
import { SearchProvider } from "./base";
|
|
13
|
-
import { classifyProviderHttpError,
|
|
13
|
+
import { classifyProviderHttpError, withHardTimeout } from "./utils";
|
|
14
14
|
|
|
15
15
|
const BRAVE_SEARCH_URL = "https://api.search.brave.com/res/v1/web/search";
|
|
16
16
|
const DEFAULT_NUM_RESULTS = 10;
|
|
@@ -134,8 +134,8 @@ export class BraveProvider extends SearchProvider {
|
|
|
134
134
|
readonly id = "brave";
|
|
135
135
|
readonly label = "Brave";
|
|
136
136
|
|
|
137
|
-
isAvailable() {
|
|
138
|
-
return
|
|
137
|
+
isAvailable(_authStorage: AuthStorage): boolean {
|
|
138
|
+
return !!findApiKey();
|
|
139
139
|
}
|
|
140
140
|
|
|
141
141
|
search(params: SearchParams): Promise<SearchResponse> {
|