@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
|
@@ -3,9 +3,10 @@
|
|
|
3
3
|
*
|
|
4
4
|
* Thin wrapper that adapts shared Kagi API utilities to SearchResponse shape.
|
|
5
5
|
*/
|
|
6
|
+
import type { AuthStorage } from "@oh-my-pi/pi-ai";
|
|
6
7
|
import type { SearchResponse } from "../../../web/search/types";
|
|
7
8
|
import { SearchProviderError } from "../../../web/search/types";
|
|
8
|
-
import {
|
|
9
|
+
import { KagiApiError, searchWithKagi } from "../../kagi";
|
|
9
10
|
import { clampNumResults } from "../utils";
|
|
10
11
|
import type { SearchParams } from "./base";
|
|
11
12
|
import { SearchProvider } from "./base";
|
|
@@ -19,14 +20,21 @@ export async function searchKagi(params: {
|
|
|
19
20
|
query: string;
|
|
20
21
|
num_results?: number;
|
|
21
22
|
signal?: AbortSignal;
|
|
23
|
+
authStorage: AuthStorage;
|
|
24
|
+
sessionId?: string;
|
|
22
25
|
}): Promise<SearchResponse> {
|
|
23
26
|
const numResults = clampNumResults(params.num_results, DEFAULT_NUM_RESULTS, MAX_NUM_RESULTS);
|
|
24
27
|
|
|
25
28
|
try {
|
|
26
|
-
const result = await searchWithKagi(
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
29
|
+
const result = await searchWithKagi(
|
|
30
|
+
params.query,
|
|
31
|
+
{
|
|
32
|
+
limit: numResults,
|
|
33
|
+
sessionId: params.sessionId,
|
|
34
|
+
signal: params.signal,
|
|
35
|
+
},
|
|
36
|
+
params.authStorage,
|
|
37
|
+
);
|
|
30
38
|
|
|
31
39
|
return {
|
|
32
40
|
provider: "kagi",
|
|
@@ -51,12 +59,8 @@ export class KagiProvider extends SearchProvider {
|
|
|
51
59
|
readonly id = "kagi";
|
|
52
60
|
readonly label = "Kagi";
|
|
53
61
|
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
return !!(await findKagiApiKey());
|
|
57
|
-
} catch {
|
|
58
|
-
return false;
|
|
59
|
-
}
|
|
62
|
+
isAvailable(authStorage: AuthStorage): boolean {
|
|
63
|
+
return authStorage.hasAuth("kagi");
|
|
60
64
|
}
|
|
61
65
|
|
|
62
66
|
search(params: SearchParams): Promise<SearchResponse> {
|
|
@@ -64,6 +68,8 @@ export class KagiProvider extends SearchProvider {
|
|
|
64
68
|
query: params.query,
|
|
65
69
|
num_results: params.numSearchResults ?? params.limit,
|
|
66
70
|
signal: params.signal,
|
|
71
|
+
authStorage: params.authStorage,
|
|
72
|
+
sessionId: params.sessionId,
|
|
67
73
|
});
|
|
68
74
|
}
|
|
69
75
|
}
|
|
@@ -4,14 +4,15 @@
|
|
|
4
4
|
* Uses Moonshot Kimi Code search API to retrieve web results.
|
|
5
5
|
* Endpoint: POST https://api.kimi.com/coding/v1/search
|
|
6
6
|
*/
|
|
7
|
-
import {
|
|
7
|
+
import type { AuthStorage } from "@oh-my-pi/pi-ai";
|
|
8
8
|
import { $env } from "@oh-my-pi/pi-utils";
|
|
9
|
+
|
|
9
10
|
import type { SearchResponse, SearchSource } from "../../../web/search/types";
|
|
10
11
|
import { SearchProviderError } from "../../../web/search/types";
|
|
11
12
|
import { clampNumResults, dateToAgeSeconds } from "../utils";
|
|
12
13
|
import type { SearchParams } from "./base";
|
|
13
14
|
import { SearchProvider } from "./base";
|
|
14
|
-
import { classifyProviderHttpError,
|
|
15
|
+
import { classifyProviderHttpError, withHardTimeout } from "./utils";
|
|
15
16
|
|
|
16
17
|
const KIMI_SEARCH_URL = "https://api.kimi.com/coding/v1/search";
|
|
17
18
|
|
|
@@ -24,6 +25,8 @@ export interface KimiSearchParams {
|
|
|
24
25
|
num_results?: number;
|
|
25
26
|
include_content?: boolean;
|
|
26
27
|
signal?: AbortSignal;
|
|
28
|
+
authStorage: AuthStorage;
|
|
29
|
+
sessionId?: string;
|
|
27
30
|
}
|
|
28
31
|
|
|
29
32
|
interface KimiSearchResult {
|
|
@@ -51,14 +54,20 @@ function resolveBaseUrl(): string {
|
|
|
51
54
|
return asTrimmed($env.MOONSHOT_SEARCH_BASE_URL) ?? asTrimmed($env.KIMI_SEARCH_BASE_URL) ?? KIMI_SEARCH_URL;
|
|
52
55
|
}
|
|
53
56
|
|
|
54
|
-
/** Find Kimi search credentials from environment or
|
|
55
|
-
async function findApiKey(
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
57
|
+
/** Find Kimi search credentials from environment or AuthStorage. */
|
|
58
|
+
async function findApiKey(
|
|
59
|
+
authStorage: AuthStorage,
|
|
60
|
+
sessionId: string | undefined,
|
|
61
|
+
signal: AbortSignal | undefined,
|
|
62
|
+
): Promise<string | null> {
|
|
63
|
+
const envKey = asTrimmed($env.MOONSHOT_SEARCH_API_KEY) ?? asTrimmed($env.KIMI_SEARCH_API_KEY);
|
|
64
|
+
if (envKey) return envKey;
|
|
65
|
+
|
|
66
|
+
return (
|
|
67
|
+
(await authStorage.getApiKey("moonshot", sessionId, { signal })) ??
|
|
68
|
+
(await authStorage.getApiKey("kimi-code", sessionId, { signal })) ??
|
|
69
|
+
null
|
|
70
|
+
);
|
|
62
71
|
}
|
|
63
72
|
|
|
64
73
|
async function callKimiSearch(
|
|
@@ -99,7 +108,7 @@ async function callKimiSearch(
|
|
|
99
108
|
|
|
100
109
|
/** Execute Kimi web search. */
|
|
101
110
|
export async function searchKimi(params: KimiSearchParams): Promise<SearchResponse> {
|
|
102
|
-
const apiKey = await findApiKey();
|
|
111
|
+
const apiKey = await findApiKey(params.authStorage, params.sessionId, params.signal);
|
|
103
112
|
if (!apiKey) {
|
|
104
113
|
throw new Error(
|
|
105
114
|
"Kimi search credentials not found. Set MOONSHOT_SEARCH_API_KEY, KIMI_SEARCH_API_KEY, MOONSHOT_API_KEY, or login with 'omp /login moonshot'.",
|
|
@@ -141,8 +150,13 @@ export class KimiProvider extends SearchProvider {
|
|
|
141
150
|
readonly id = "kimi";
|
|
142
151
|
readonly label = "Kimi";
|
|
143
152
|
|
|
144
|
-
isAvailable():
|
|
145
|
-
return
|
|
153
|
+
isAvailable(authStorage: AuthStorage): boolean {
|
|
154
|
+
return (
|
|
155
|
+
!!asTrimmed($env.MOONSHOT_SEARCH_API_KEY) ||
|
|
156
|
+
!!asTrimmed($env.KIMI_SEARCH_API_KEY) ||
|
|
157
|
+
authStorage.hasAuth("moonshot") ||
|
|
158
|
+
authStorage.hasAuth("kimi-code")
|
|
159
|
+
);
|
|
146
160
|
}
|
|
147
161
|
|
|
148
162
|
search(params: SearchParams): Promise<SearchResponse> {
|
|
@@ -150,6 +164,8 @@ export class KimiProvider extends SearchProvider {
|
|
|
150
164
|
query: params.query,
|
|
151
165
|
num_results: params.numSearchResults ?? params.limit,
|
|
152
166
|
signal: params.signal,
|
|
167
|
+
authStorage: params.authStorage,
|
|
168
|
+
sessionId: params.sessionId,
|
|
153
169
|
});
|
|
154
170
|
}
|
|
155
171
|
}
|
|
@@ -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
|
}
|