@oh-my-pi/pi-coding-agent 15.3.2 → 15.4.1
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 +104 -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/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/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 +17 -23
- 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
|
@@ -1,27 +1,175 @@
|
|
|
1
|
+
import { type AuthStorage, getEnvApiKey } from "@oh-my-pi/pi-ai";
|
|
1
2
|
import type { SearchResponse } from "../../../web/search/types";
|
|
2
3
|
import { SearchProviderError } from "../../../web/search/types";
|
|
3
|
-
import {
|
|
4
|
+
import { ParallelApiError, type ParallelSearchResult, type ParallelSearchSource } from "../../parallel";
|
|
4
5
|
import { clampNumResults } from "../utils";
|
|
5
6
|
import type { SearchParams } from "./base";
|
|
6
7
|
import { SearchProvider } from "./base";
|
|
7
|
-
import { classifyProviderHttpError, toSearchSources } from "./utils";
|
|
8
|
+
import { classifyProviderHttpError, toSearchSources, withHardTimeout } from "./utils";
|
|
8
9
|
|
|
9
10
|
const DEFAULT_NUM_RESULTS = 10;
|
|
10
11
|
const MAX_NUM_RESULTS = 40;
|
|
12
|
+
const PARALLEL_SEARCH_URL = "https://api.parallel.ai/v1beta/search";
|
|
13
|
+
const PARALLEL_BETA_HEADER = "search-extract-2025-10-10";
|
|
11
14
|
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
15
|
+
function isObject(value: unknown): value is object {
|
|
16
|
+
return typeof value === "object" && value !== null;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function getOwnValue(value: object, key: string): unknown {
|
|
20
|
+
return Object.getOwnPropertyDescriptor(value, key)?.value;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function getString(value: object, key: string): string | undefined {
|
|
24
|
+
const field = getOwnValue(value, key);
|
|
25
|
+
return typeof field === "string" ? field : undefined;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function getObjectArray(value: object, key: string): object[] {
|
|
29
|
+
const field = getOwnValue(value, key);
|
|
30
|
+
return Array.isArray(field) ? field.filter(isObject) : [];
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function getStringArray(value: object, key: string): string[] {
|
|
34
|
+
const field = getOwnValue(value, key);
|
|
35
|
+
return Array.isArray(field) ? field.filter((item): item is string => typeof item === "string") : [];
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function extractParallelErrorMessage(payload: unknown): string | null {
|
|
39
|
+
if (!isObject(payload)) return null;
|
|
40
|
+
|
|
41
|
+
const directMessage = getString(payload, "message") ?? getString(payload, "detail") ?? getString(payload, "error");
|
|
42
|
+
if (directMessage && directMessage.trim().length > 0) {
|
|
43
|
+
return directMessage.trim();
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const errorObject = getOwnValue(payload, "error");
|
|
47
|
+
if (isObject(errorObject)) {
|
|
48
|
+
const nestedMessage = getString(errorObject, "message") ?? getString(errorObject, "detail");
|
|
49
|
+
if (nestedMessage && nestedMessage.trim().length > 0) {
|
|
50
|
+
return nestedMessage.trim();
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
return null;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function createParallelApiError(statusCode: number, detail?: string): ParallelApiError {
|
|
58
|
+
return new ParallelApiError(
|
|
59
|
+
detail ? `Parallel API error (${statusCode}): ${detail}` : `Parallel API error (${statusCode})`,
|
|
60
|
+
statusCode,
|
|
61
|
+
);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function parseParallelErrorResponse(statusCode: number, responseText: string): ParallelApiError {
|
|
65
|
+
const trimmedResponseText = responseText.trim();
|
|
66
|
+
if (trimmedResponseText.length === 0) {
|
|
67
|
+
return createParallelApiError(statusCode);
|
|
68
|
+
}
|
|
18
69
|
|
|
19
70
|
try {
|
|
20
|
-
const
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
71
|
+
const payload: unknown = JSON.parse(trimmedResponseText);
|
|
72
|
+
return createParallelApiError(statusCode, extractParallelErrorMessage(payload) ?? trimmedResponseText);
|
|
73
|
+
} catch {
|
|
74
|
+
return createParallelApiError(statusCode, trimmedResponseText);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function parseSearchPayload(payload: unknown): ParallelSearchResult {
|
|
79
|
+
if (!isObject(payload)) {
|
|
80
|
+
throw new ParallelApiError("Parallel search returned an invalid response payload.");
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
const requestId = getString(payload, "search_id") ?? "";
|
|
84
|
+
const rawResults = getObjectArray(payload, "results");
|
|
85
|
+
const sources: ParallelSearchSource[] = [];
|
|
86
|
+
|
|
87
|
+
for (const item of rawResults) {
|
|
88
|
+
const url = getString(item, "url");
|
|
89
|
+
if (!url) continue;
|
|
90
|
+
|
|
91
|
+
const excerpts = getStringArray(item, "excerpts");
|
|
92
|
+
const snippet = excerpts.length > 0 ? excerpts.join("\n\n") : undefined;
|
|
93
|
+
sources.push({
|
|
94
|
+
title: getString(item, "title") ?? url,
|
|
95
|
+
url,
|
|
96
|
+
snippet,
|
|
97
|
+
publishedDate: getString(item, "publish_date"),
|
|
98
|
+
excerpts,
|
|
24
99
|
});
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
return {
|
|
103
|
+
requestId,
|
|
104
|
+
sources,
|
|
105
|
+
warnings: [],
|
|
106
|
+
usage: [],
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
async function searchWithAuthStorage(
|
|
111
|
+
objective: string,
|
|
112
|
+
queries: string[],
|
|
113
|
+
params: {
|
|
114
|
+
signal?: AbortSignal;
|
|
115
|
+
},
|
|
116
|
+
authStorage: AuthStorage,
|
|
117
|
+
sessionId?: string,
|
|
118
|
+
): Promise<ParallelSearchResult> {
|
|
119
|
+
const apiKey = await authStorage.getApiKey("parallel", sessionId, { signal: params.signal });
|
|
120
|
+
if (!apiKey) {
|
|
121
|
+
throw new ParallelApiError(
|
|
122
|
+
"Parallel credentials not found. Set PARALLEL_API_KEY or login with 'omp /login parallel'.",
|
|
123
|
+
);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
const response = await fetch(PARALLEL_SEARCH_URL, {
|
|
127
|
+
method: "POST",
|
|
128
|
+
headers: {
|
|
129
|
+
Accept: "application/json",
|
|
130
|
+
"Content-Type": "application/json",
|
|
131
|
+
"x-api-key": apiKey,
|
|
132
|
+
"parallel-beta": PARALLEL_BETA_HEADER,
|
|
133
|
+
},
|
|
134
|
+
body: JSON.stringify({
|
|
135
|
+
objective,
|
|
136
|
+
search_queries: queries,
|
|
137
|
+
mode: "fast",
|
|
138
|
+
excerpts: {
|
|
139
|
+
max_chars_per_result: 10_000,
|
|
140
|
+
},
|
|
141
|
+
}),
|
|
142
|
+
signal: withHardTimeout(params.signal),
|
|
143
|
+
});
|
|
144
|
+
if (!response.ok) {
|
|
145
|
+
throw parseParallelErrorResponse(response.status, await response.text());
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
const payload: unknown = await response.json();
|
|
149
|
+
return parseSearchPayload(payload);
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
export async function searchParallel(
|
|
153
|
+
params: {
|
|
154
|
+
query: string;
|
|
155
|
+
num_results?: number;
|
|
156
|
+
signal?: AbortSignal;
|
|
157
|
+
},
|
|
158
|
+
authStorage: AuthStorage,
|
|
159
|
+
sessionId?: string,
|
|
160
|
+
): Promise<SearchResponse> {
|
|
161
|
+
const numResults = clampNumResults(params.num_results, DEFAULT_NUM_RESULTS, MAX_NUM_RESULTS);
|
|
162
|
+
|
|
163
|
+
try {
|
|
164
|
+
const result = await searchWithAuthStorage(
|
|
165
|
+
params.query,
|
|
166
|
+
[params.query],
|
|
167
|
+
{
|
|
168
|
+
signal: params.signal,
|
|
169
|
+
},
|
|
170
|
+
authStorage,
|
|
171
|
+
sessionId,
|
|
172
|
+
);
|
|
25
173
|
|
|
26
174
|
return {
|
|
27
175
|
provider: "parallel",
|
|
@@ -44,19 +192,19 @@ export class ParallelProvider extends SearchProvider {
|
|
|
44
192
|
readonly id = "parallel";
|
|
45
193
|
readonly label = "Parallel";
|
|
46
194
|
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
return !!(await findParallelApiKey());
|
|
50
|
-
} catch {
|
|
51
|
-
return false;
|
|
52
|
-
}
|
|
195
|
+
isAvailable(authStorage: AuthStorage) {
|
|
196
|
+
return !!getEnvApiKey("parallel") || authStorage.hasAuth("parallel");
|
|
53
197
|
}
|
|
54
198
|
|
|
55
199
|
search(params: SearchParams): Promise<SearchResponse> {
|
|
56
|
-
return searchParallel(
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
200
|
+
return searchParallel(
|
|
201
|
+
{
|
|
202
|
+
query: params.query,
|
|
203
|
+
num_results: params.numSearchResults ?? params.limit,
|
|
204
|
+
signal: params.signal,
|
|
205
|
+
},
|
|
206
|
+
params.authStorage,
|
|
207
|
+
params.sessionId,
|
|
208
|
+
);
|
|
61
209
|
}
|
|
62
210
|
}
|
|
@@ -3,13 +3,12 @@
|
|
|
3
3
|
*
|
|
4
4
|
* Supports three auth modes:
|
|
5
5
|
* - Cookies (`PERPLEXITY_COOKIES`) via `www.perplexity.ai/rest/sse/perplexity_ask`
|
|
6
|
-
* - OAuth
|
|
6
|
+
* - OAuth/session bearer via `AuthStorage` and `www.perplexity.ai/rest/sse/perplexity_ask`
|
|
7
7
|
* - API key (`PERPLEXITY_API_KEY`) via `api.perplexity.ai/chat/completions`
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
|
-
import { getEnvApiKey } from "@oh-my-pi/pi-ai";
|
|
11
|
-
import { $env,
|
|
12
|
-
import { AgentStorage } from "../../../session/agent-storage";
|
|
10
|
+
import { type AuthStorage, getEnvApiKey } from "@oh-my-pi/pi-ai";
|
|
11
|
+
import { $env, readSseJson } from "@oh-my-pi/pi-utils";
|
|
13
12
|
import type {
|
|
14
13
|
PerplexityMessageOutput,
|
|
15
14
|
PerplexityRequest,
|
|
@@ -34,12 +33,6 @@ const OAUTH_EXPIRY_BUFFER_MS = 5 * 60 * 1000;
|
|
|
34
33
|
const OAUTH_API_VERSION = "2.18";
|
|
35
34
|
const OAUTH_USER_AGENT = "Perplexity/641 CFNetwork/1568 Darwin/25.2.0";
|
|
36
35
|
|
|
37
|
-
interface PerplexityOAuthCredential {
|
|
38
|
-
type: "oauth";
|
|
39
|
-
access: string;
|
|
40
|
-
expires: number;
|
|
41
|
-
}
|
|
42
|
-
|
|
43
36
|
type PerplexityAuth =
|
|
44
37
|
| {
|
|
45
38
|
type: "api_key";
|
|
@@ -168,6 +161,8 @@ export interface PerplexitySearchParams {
|
|
|
168
161
|
temperature?: number;
|
|
169
162
|
/** Number of search results to retrieve. Defaults to 10. */
|
|
170
163
|
num_search_results?: number;
|
|
164
|
+
authStorage: AuthStorage;
|
|
165
|
+
sessionId?: string;
|
|
171
166
|
}
|
|
172
167
|
|
|
173
168
|
/** Find PERPLEXITY_API_KEY from environment or .env files (also checks PPLX_API_KEY) */
|
|
@@ -194,41 +189,49 @@ function jwtExpiryMs(token: string): number | undefined {
|
|
|
194
189
|
}
|
|
195
190
|
}
|
|
196
191
|
|
|
197
|
-
async function findOAuthToken(
|
|
198
|
-
|
|
192
|
+
async function findOAuthToken(
|
|
193
|
+
authStorage: AuthStorage,
|
|
194
|
+
sessionId: string | undefined,
|
|
195
|
+
signal: AbortSignal | undefined,
|
|
196
|
+
): Promise<string | null> {
|
|
199
197
|
try {
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
}
|
|
198
|
+
// `getOAuthAccess` returns the raw OAuth bearer only — runtime/config
|
|
199
|
+
// api_key overrides and stored api_key credentials are intentionally
|
|
200
|
+
// suppressed so we don't POST an `api.perplexity.ai` key to the
|
|
201
|
+
// `www.perplexity.ai` session/SSE endpoint.
|
|
202
|
+
const access = await authStorage.getOAuthAccess("perplexity", sessionId, { signal });
|
|
203
|
+
const token = access?.accessToken;
|
|
204
|
+
if (!token) return null;
|
|
205
|
+
// Trust the JWT's own `exp` claim if it has one; otherwise treat as
|
|
206
|
+
// non-expiring. Perplexity session JWTs commonly omit `exp`.
|
|
207
|
+
const jwtExpiry = jwtExpiryMs(token);
|
|
208
|
+
if (jwtExpiry !== undefined && jwtExpiry <= Date.now() + OAUTH_EXPIRY_BUFFER_MS) return null;
|
|
209
|
+
return token;
|
|
213
210
|
} catch {
|
|
214
211
|
return null;
|
|
215
212
|
}
|
|
216
|
-
return null;
|
|
217
213
|
}
|
|
218
214
|
|
|
219
|
-
async function findPerplexityAuth(
|
|
215
|
+
async function findPerplexityAuth(
|
|
216
|
+
authStorage: AuthStorage,
|
|
217
|
+
sessionId: string | undefined,
|
|
218
|
+
signal: AbortSignal | undefined,
|
|
219
|
+
): Promise<PerplexityAuth | null> {
|
|
220
220
|
// 1. PERPLEXITY_COOKIES env var
|
|
221
221
|
const cookies = $env.PERPLEXITY_COOKIES?.trim();
|
|
222
222
|
if (cookies) {
|
|
223
223
|
return { type: "cookies", cookies };
|
|
224
224
|
}
|
|
225
|
-
|
|
226
|
-
const
|
|
225
|
+
|
|
226
|
+
const apiKey = findApiKey();
|
|
227
|
+
|
|
228
|
+
// 2. OAuth/session bearer from AuthStorage.
|
|
229
|
+
const oauthToken = await findOAuthToken(authStorage, sessionId, signal);
|
|
227
230
|
if (oauthToken) {
|
|
228
231
|
return { type: "oauth", token: oauthToken };
|
|
229
232
|
}
|
|
233
|
+
|
|
230
234
|
// 3. PERPLEXITY_API_KEY env var
|
|
231
|
-
const apiKey = findApiKey();
|
|
232
235
|
if (apiKey) {
|
|
233
236
|
return { type: "api_key", token: apiKey };
|
|
234
237
|
}
|
|
@@ -493,7 +496,7 @@ function applySourceLimit(result: SearchResponse, limit?: number): SearchRespons
|
|
|
493
496
|
|
|
494
497
|
/** Execute Perplexity web search */
|
|
495
498
|
export async function searchPerplexity(params: PerplexitySearchParams): Promise<SearchResponse> {
|
|
496
|
-
const auth = await findPerplexityAuth();
|
|
499
|
+
const auth = await findPerplexityAuth(params.authStorage, params.sessionId, params.signal);
|
|
497
500
|
if (!auth) {
|
|
498
501
|
throw new Error("Perplexity auth not found. Set PERPLEXITY_COOKIES, PERPLEXITY_API_KEY, or login via OAuth.");
|
|
499
502
|
}
|
|
@@ -551,12 +554,8 @@ export class PerplexityProvider extends SearchProvider {
|
|
|
551
554
|
readonly id = "perplexity";
|
|
552
555
|
readonly label = "Perplexity";
|
|
553
556
|
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
return !!(await findPerplexityAuth());
|
|
557
|
-
} catch {
|
|
558
|
-
return false;
|
|
559
|
-
}
|
|
557
|
+
isAvailable(authStorage: AuthStorage): boolean {
|
|
558
|
+
return !!$env.PERPLEXITY_COOKIES?.trim() || authStorage.hasAuth("perplexity") || !!findApiKey();
|
|
560
559
|
}
|
|
561
560
|
|
|
562
561
|
search(params: SearchParams): Promise<SearchResponse> {
|
|
@@ -569,6 +568,8 @@ export class PerplexityProvider extends SearchProvider {
|
|
|
569
568
|
system_prompt: params.systemPrompt,
|
|
570
569
|
search_recency_filter: params.recency,
|
|
571
570
|
num_results: params.limit,
|
|
571
|
+
authStorage: params.authStorage,
|
|
572
|
+
sessionId: params.sessionId,
|
|
572
573
|
});
|
|
573
574
|
}
|
|
574
575
|
}
|
|
@@ -25,6 +25,8 @@
|
|
|
25
25
|
* Reference: https://docs.searxng.org/dev/search_api.html
|
|
26
26
|
*/
|
|
27
27
|
|
|
28
|
+
import type { AuthStorage } from "@oh-my-pi/pi-ai";
|
|
29
|
+
|
|
28
30
|
import { settings } from "../../../config/settings";
|
|
29
31
|
import type { SearchResponse, SearchSource } from "../../../web/search/types";
|
|
30
32
|
import { SearchProviderError } from "../../../web/search/types";
|
|
@@ -288,7 +290,7 @@ export class SearXNGProvider extends SearchProvider {
|
|
|
288
290
|
readonly id = "searxng";
|
|
289
291
|
readonly label = "SearXNG";
|
|
290
292
|
|
|
291
|
-
isAvailable() {
|
|
293
|
+
isAvailable(_authStorage: AuthStorage): boolean {
|
|
292
294
|
try {
|
|
293
295
|
return !!findEndpoint();
|
|
294
296
|
} catch {
|
|
@@ -5,12 +5,12 @@
|
|
|
5
5
|
* Endpoint: POST https://api.synthetic.new/v2/search
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
|
-
import { getEnvApiKey } from "@oh-my-pi/pi-ai";
|
|
8
|
+
import { type AuthStorage, getEnvApiKey } from "@oh-my-pi/pi-ai";
|
|
9
9
|
import type { SearchResponse, SearchSource } from "../../../web/search/types";
|
|
10
10
|
import { SearchProviderError } from "../../../web/search/types";
|
|
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 SYNTHETIC_SEARCH_URL = "https://api.synthetic.new/v2/search";
|
|
16
16
|
|
|
@@ -25,9 +25,13 @@ interface SyntheticSearchResponse {
|
|
|
25
25
|
results: SyntheticSearchResult[];
|
|
26
26
|
}
|
|
27
27
|
|
|
28
|
-
/**
|
|
29
|
-
export
|
|
30
|
-
|
|
28
|
+
/** Resolve Synthetic API key through the shared auth storage pipeline. */
|
|
29
|
+
export function findApiKey(
|
|
30
|
+
authStorage: AuthStorage,
|
|
31
|
+
sessionId?: string,
|
|
32
|
+
signal?: AbortSignal,
|
|
33
|
+
): Promise<string | undefined> {
|
|
34
|
+
return authStorage.getApiKey("synthetic", sessionId, { signal });
|
|
31
35
|
}
|
|
32
36
|
|
|
33
37
|
/** Call Synthetic search API. */
|
|
@@ -61,12 +65,8 @@ async function callSyntheticSearch(
|
|
|
61
65
|
}
|
|
62
66
|
|
|
63
67
|
/** Execute Synthetic web search. */
|
|
64
|
-
export async function searchSynthetic(params: {
|
|
65
|
-
|
|
66
|
-
num_results?: number;
|
|
67
|
-
signal?: AbortSignal;
|
|
68
|
-
}): Promise<SearchResponse> {
|
|
69
|
-
const apiKey = await findApiKey();
|
|
68
|
+
export async function searchSynthetic(params: SearchParams): Promise<SearchResponse> {
|
|
69
|
+
const apiKey = await findApiKey(params.authStorage, params.sessionId, params.signal);
|
|
70
70
|
if (!apiKey) {
|
|
71
71
|
throw new Error("Synthetic credentials not found. Set SYNTHETIC_API_KEY or login with 'omp /login synthetic'.");
|
|
72
72
|
}
|
|
@@ -84,7 +84,8 @@ export async function searchSynthetic(params: {
|
|
|
84
84
|
});
|
|
85
85
|
}
|
|
86
86
|
|
|
87
|
-
const
|
|
87
|
+
const numResults = params.numSearchResults ?? params.limit;
|
|
88
|
+
const limitedSources = numResults ? sources.slice(0, numResults) : sources;
|
|
88
89
|
|
|
89
90
|
return {
|
|
90
91
|
provider: "synthetic",
|
|
@@ -97,15 +98,11 @@ export class SyntheticProvider extends SearchProvider {
|
|
|
97
98
|
readonly id = "synthetic";
|
|
98
99
|
readonly label = "Synthetic";
|
|
99
100
|
|
|
100
|
-
isAvailable():
|
|
101
|
-
return
|
|
101
|
+
isAvailable(authStorage: AuthStorage): boolean {
|
|
102
|
+
return authStorage.hasAuth("synthetic") || !!getEnvApiKey("synthetic");
|
|
102
103
|
}
|
|
103
104
|
|
|
104
105
|
search(params: SearchParams): Promise<SearchResponse> {
|
|
105
|
-
return searchSynthetic(
|
|
106
|
-
query: params.query,
|
|
107
|
-
num_results: params.numSearchResults ?? params.limit,
|
|
108
|
-
signal: params.signal,
|
|
109
|
-
});
|
|
106
|
+
return searchSynthetic(params);
|
|
110
107
|
}
|
|
111
108
|
}
|
|
@@ -4,13 +4,13 @@
|
|
|
4
4
|
* Uses Tavily's agent-focused search API to return structured results with an
|
|
5
5
|
* optional synthesized answer.
|
|
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 TAVILY_SEARCH_URL = "https://api.tavily.com/search";
|
|
16
16
|
const DEFAULT_NUM_RESULTS = 5;
|
|
@@ -58,9 +58,13 @@ function getErrorMessage(value: unknown): string | null {
|
|
|
58
58
|
return null;
|
|
59
59
|
}
|
|
60
60
|
|
|
61
|
-
/** Find Tavily API key
|
|
62
|
-
export async function findApiKey(
|
|
63
|
-
|
|
61
|
+
/** Find Tavily API key through AuthStorage's unified refresh pipeline. */
|
|
62
|
+
export async function findApiKey(
|
|
63
|
+
authStorage: AuthStorage,
|
|
64
|
+
sessionId: string | undefined,
|
|
65
|
+
signal: AbortSignal | undefined,
|
|
66
|
+
): Promise<string | null> {
|
|
67
|
+
return (await authStorage.getApiKey("tavily", sessionId, { signal })) ?? null;
|
|
64
68
|
}
|
|
65
69
|
|
|
66
70
|
/** Exported for testing. Builds the Tavily request body from unified params. */
|
|
@@ -116,16 +120,22 @@ async function callTavilySearch(apiKey: string, params: TavilySearchParams): Pro
|
|
|
116
120
|
}
|
|
117
121
|
|
|
118
122
|
/** Execute Tavily web search. */
|
|
119
|
-
export async function searchTavily(params:
|
|
120
|
-
const
|
|
123
|
+
export async function searchTavily(params: SearchParams): Promise<SearchResponse> {
|
|
124
|
+
const tavilyParams: TavilySearchParams = {
|
|
125
|
+
query: params.query,
|
|
126
|
+
num_results: params.numSearchResults ?? params.limit,
|
|
127
|
+
recency: params.recency,
|
|
128
|
+
signal: params.signal,
|
|
129
|
+
};
|
|
130
|
+
const apiKey = await findApiKey(params.authStorage, params.sessionId, params.signal);
|
|
121
131
|
if (!apiKey) {
|
|
122
132
|
throw new Error(
|
|
123
|
-
'Tavily credentials not found. Set TAVILY_API_KEY or
|
|
133
|
+
'Tavily credentials not found. Set TAVILY_API_KEY or configure an API key for provider "tavily".',
|
|
124
134
|
);
|
|
125
135
|
}
|
|
126
136
|
|
|
127
|
-
const numResults = clampNumResults(
|
|
128
|
-
const response = await callTavilySearch(apiKey,
|
|
137
|
+
const numResults = clampNumResults(tavilyParams.num_results, DEFAULT_NUM_RESULTS, MAX_NUM_RESULTS);
|
|
138
|
+
const response = await callTavilySearch(apiKey, tavilyParams);
|
|
129
139
|
const sources: SearchSource[] = [];
|
|
130
140
|
|
|
131
141
|
for (const result of response.results ?? []) {
|
|
@@ -153,16 +163,11 @@ export class TavilyProvider extends SearchProvider {
|
|
|
153
163
|
readonly id = "tavily";
|
|
154
164
|
readonly label = "Tavily";
|
|
155
165
|
|
|
156
|
-
isAvailable():
|
|
157
|
-
return
|
|
166
|
+
isAvailable(authStorage: AuthStorage): boolean {
|
|
167
|
+
return authStorage.hasAuth("tavily") || !!getEnvApiKey("tavily");
|
|
158
168
|
}
|
|
159
169
|
|
|
160
170
|
search(params: SearchParams): Promise<SearchResponse> {
|
|
161
|
-
return searchTavily(
|
|
162
|
-
query: params.query,
|
|
163
|
-
num_results: params.numSearchResults ?? params.limit,
|
|
164
|
-
recency: params.recency,
|
|
165
|
-
signal: params.signal,
|
|
166
|
-
});
|
|
171
|
+
return searchTavily(params);
|
|
167
172
|
}
|
|
168
173
|
}
|
|
@@ -1,5 +1,4 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { AgentStorage } from "../../../session/agent-storage";
|
|
1
|
+
import type { AgentStorage } from "../../../session/agent-storage";
|
|
3
2
|
import { SearchProviderError, type SearchProviderId, type SearchSource } from "../../../web/search/types";
|
|
4
3
|
import { dateToAgeSeconds } from "../utils";
|
|
5
4
|
|
|
@@ -7,17 +6,24 @@ import { dateToAgeSeconds } from "../utils";
|
|
|
7
6
|
* Search for an API credential by checking an env-derived key first,
|
|
8
7
|
* then falling back to agent.db stored credentials for the given providers.
|
|
9
8
|
*
|
|
9
|
+
* The caller MUST supply an open {@link AgentStorage} handle so the helper
|
|
10
|
+
* never reaches out to global filesystem state; both the unified web_search
|
|
11
|
+
* chain and one-shot CLI calls open storage exactly once and thread it
|
|
12
|
+
* through every provider.
|
|
13
|
+
*
|
|
14
|
+
* @param storage - Open agent storage handle
|
|
10
15
|
* @param envKey - Pre-resolved environment variable value (or null)
|
|
11
16
|
* @param storageProviders - Provider names to look up in AgentStorage
|
|
12
17
|
*/
|
|
13
|
-
export
|
|
18
|
+
export function findCredential(
|
|
19
|
+
storage: AgentStorage | null | undefined,
|
|
14
20
|
envKey: string | null | undefined,
|
|
15
21
|
...storageProviders: string[]
|
|
16
|
-
):
|
|
22
|
+
): string | null {
|
|
17
23
|
if (envKey) return envKey;
|
|
24
|
+
if (!storage) return null;
|
|
18
25
|
|
|
19
26
|
try {
|
|
20
|
-
const storage = await AgentStorage.open(getAgentDbPath());
|
|
21
27
|
for (const provider of storageProviders) {
|
|
22
28
|
const records = storage.listAuthCredentials(provider);
|
|
23
29
|
for (const record of records) {
|
|
@@ -37,18 +43,6 @@ export async function findCredential(
|
|
|
37
43
|
return null;
|
|
38
44
|
}
|
|
39
45
|
|
|
40
|
-
/**
|
|
41
|
-
* Probe whether a provider's API key lookup resolves to a truthy value.
|
|
42
|
-
* Swallows lookup errors and reports unavailability.
|
|
43
|
-
*/
|
|
44
|
-
export async function isApiKeyAvailable(findApiKey: () => string | null | Promise<string | null>) {
|
|
45
|
-
try {
|
|
46
|
-
return !!(await findApiKey());
|
|
47
|
-
} catch {
|
|
48
|
-
return false;
|
|
49
|
-
}
|
|
50
|
-
}
|
|
51
|
-
|
|
52
46
|
/**
|
|
53
47
|
* Default hard ceiling for a single web-search round-trip. 60s tolerates
|
|
54
48
|
* legitimate slow LLM-mediated responses (anthropic web_search_20250305,
|
|
@@ -4,14 +4,14 @@
|
|
|
4
4
|
* Calls Z.AI's remote MCP server (`webSearchPrime`) and adapts results into
|
|
5
5
|
* the unified 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 { asRecord, asString } from "../../../web/scrapers/utils";
|
|
9
9
|
import type { SearchResponse, SearchSource } from "../../../web/search/types";
|
|
10
10
|
import { SearchProviderError } from "../../../web/search/types";
|
|
11
11
|
import { dateToAgeSeconds } from "../utils";
|
|
12
12
|
import type { SearchParams } from "./base";
|
|
13
13
|
import { SearchProvider } from "./base";
|
|
14
|
-
import { classifyProviderHttpError,
|
|
14
|
+
import { classifyProviderHttpError, withHardTimeout } from "./utils";
|
|
15
15
|
|
|
16
16
|
const ZAI_MCP_URL = "https://api.z.ai/api/mcp/web_search_prime/mcp";
|
|
17
17
|
const ZAI_TOOL_NAME = "web_search_prime";
|
|
@@ -21,6 +21,8 @@ export interface ZaiSearchParams {
|
|
|
21
21
|
query: string;
|
|
22
22
|
num_results?: number;
|
|
23
23
|
signal?: AbortSignal;
|
|
24
|
+
authStorage: AuthStorage;
|
|
25
|
+
sessionId?: string;
|
|
24
26
|
}
|
|
25
27
|
|
|
26
28
|
interface ZaiSearchResult {
|
|
@@ -51,9 +53,13 @@ interface JsonRpcPayload {
|
|
|
51
53
|
error?: JsonRpcError;
|
|
52
54
|
}
|
|
53
55
|
|
|
54
|
-
/**
|
|
55
|
-
export async function findApiKey(
|
|
56
|
-
|
|
56
|
+
/** Resolve Z.AI API credentials through the unified auth storage pipeline. */
|
|
57
|
+
export async function findApiKey(
|
|
58
|
+
authStorage: AuthStorage,
|
|
59
|
+
sessionId?: string,
|
|
60
|
+
signal?: AbortSignal,
|
|
61
|
+
): Promise<string | null> {
|
|
62
|
+
return (await authStorage.getApiKey("zai", sessionId, { signal })) ?? null;
|
|
57
63
|
}
|
|
58
64
|
|
|
59
65
|
async function callZaiTool(apiKey: string, args: Record<string, unknown>, signal?: AbortSignal): Promise<unknown> {
|
|
@@ -272,7 +278,7 @@ function toSources(results: ZaiSearchResult[]): SearchSource[] {
|
|
|
272
278
|
|
|
273
279
|
/** Execute Z.AI web search via remote MCP endpoint. */
|
|
274
280
|
export async function searchZai(params: ZaiSearchParams): Promise<SearchResponse> {
|
|
275
|
-
const apiKey = await findApiKey();
|
|
281
|
+
const apiKey = await findApiKey(params.authStorage, params.sessionId, params.signal);
|
|
276
282
|
if (!apiKey) {
|
|
277
283
|
throw new Error("Z.AI credentials not found. Set ZAI_API_KEY or login with 'omp /login zai'.");
|
|
278
284
|
}
|
|
@@ -298,8 +304,8 @@ export class ZaiProvider extends SearchProvider {
|
|
|
298
304
|
readonly id = "zai";
|
|
299
305
|
readonly label = "Z.AI";
|
|
300
306
|
|
|
301
|
-
isAvailable(): Promise<boolean> {
|
|
302
|
-
return
|
|
307
|
+
isAvailable(authStorage: AuthStorage): Promise<boolean> | boolean {
|
|
308
|
+
return authStorage.hasAuth("zai") || !!getEnvApiKey("zai");
|
|
303
309
|
}
|
|
304
310
|
|
|
305
311
|
search(params: SearchParams): Promise<SearchResponse> {
|
|
@@ -307,6 +313,8 @@ export class ZaiProvider extends SearchProvider {
|
|
|
307
313
|
query: params.query,
|
|
308
314
|
num_results: params.numSearchResults ?? params.limit,
|
|
309
315
|
signal: params.signal,
|
|
316
|
+
authStorage: params.authStorage,
|
|
317
|
+
sessionId: params.sessionId,
|
|
310
318
|
});
|
|
311
319
|
}
|
|
312
320
|
}
|
|
@@ -1,7 +0,0 @@
|
|
|
1
|
-
import type { HashlineCursor, HashlineEdit } from "./types";
|
|
2
|
-
export declare function cloneCursor(cursor: HashlineCursor): HashlineCursor;
|
|
3
|
-
export declare function parseHashline(diff: string): HashlineEdit[];
|
|
4
|
-
export declare function parseHashlineWithWarnings(diff: string): {
|
|
5
|
-
edits: HashlineEdit[];
|
|
6
|
-
warnings: string[];
|
|
7
|
-
};
|
|
@@ -1,7 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Back-compat re-export layer.
|
|
3
|
-
* All types and functions have moved to src/tool-discovery/tool-index.ts.
|
|
4
|
-
* This file exists solely so existing imports continue to compile without changes.
|
|
5
|
-
*/
|
|
6
|
-
export type { DiscoverableMCPSearchDocument, DiscoverableMCPSearchIndex, DiscoverableMCPSearchResult, DiscoverableMCPTool, DiscoverableMCPToolServerSummary, DiscoverableMCPToolSummary, } from "../tool-discovery/tool-index";
|
|
7
|
-
export { buildDiscoverableMCPSearchIndex, collectDiscoverableMCPTools, formatDiscoverableMCPToolServerSummary, getDiscoverableMCPTool, isMCPToolName, searchDiscoverableMCPTools, selectDiscoverableMCPToolNamesByServer, summarizeDiscoverableMCPTools, } from "../tool-discovery/tool-index";
|