@oh-my-pi/pi-coding-agent 15.10.7 → 15.10.9
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 +27 -0
- package/dist/types/config/model-registry.d.ts +4 -2
- package/dist/types/config/model-resolver.d.ts +2 -0
- package/dist/types/config/settings-schema.d.ts +9 -0
- package/dist/types/extensibility/custom-tools/loader.d.ts +22 -3
- package/dist/types/extensibility/custom-tools/types.d.ts +3 -1
- package/dist/types/extensibility/extensions/index.d.ts +1 -1
- package/dist/types/extensibility/extensions/loader.d.ts +17 -1
- package/dist/types/extensibility/plugins/legacy-pi-compat.d.ts +8 -0
- package/dist/types/mcp/oauth-discovery.d.ts +4 -1
- package/dist/types/mcp/oauth-flow.d.ts +6 -1
- package/dist/types/mcp/transports/stdio.d.ts +12 -0
- package/dist/types/modes/components/custom-editor.d.ts +3 -2
- package/dist/types/sdk.d.ts +42 -2
- package/dist/types/task/executor.d.ts +16 -0
- package/dist/types/tools/fetch.d.ts +2 -1
- package/dist/types/tools/index.d.ts +20 -1
- package/dist/types/tools/report-tool-issue.d.ts +5 -0
- package/dist/types/tui/hyperlink.d.ts +8 -0
- package/dist/types/web/kagi.d.ts +2 -1
- package/dist/types/web/parallel.d.ts +3 -0
- package/dist/types/web/search/providers/anthropic.d.ts +2 -1
- package/dist/types/web/search/providers/base.d.ts +2 -1
- package/dist/types/web/search/providers/brave.d.ts +2 -1
- package/dist/types/web/search/providers/codex.d.ts +2 -1
- package/dist/types/web/search/providers/exa.d.ts +2 -1
- package/dist/types/web/search/providers/gemini.d.ts +2 -1
- package/dist/types/web/search/providers/jina.d.ts +7 -2
- package/dist/types/web/search/providers/kagi.d.ts +7 -2
- package/dist/types/web/search/providers/kimi.d.ts +7 -2
- package/dist/types/web/search/providers/parallel.d.ts +2 -1
- package/dist/types/web/search/providers/perplexity.d.ts +2 -1
- package/dist/types/web/search/providers/searxng.d.ts +2 -1
- package/dist/types/web/search/providers/synthetic.d.ts +7 -3
- package/dist/types/web/search/providers/tavily.d.ts +2 -1
- package/dist/types/web/search/providers/zai.d.ts +2 -1
- package/package.json +9 -9
- package/src/config/model-registry.ts +13 -7
- package/src/config/model-resolver.ts +57 -2
- package/src/config/settings-schema.ts +6 -0
- package/src/extensibility/custom-tools/loader.ts +43 -19
- package/src/extensibility/custom-tools/types.ts +3 -1
- package/src/extensibility/extensions/index.ts +1 -0
- package/src/extensibility/extensions/loader.ts +29 -6
- package/src/extensibility/plugins/legacy-pi-compat.ts +30 -6
- package/src/internal-urls/docs-index.generated.ts +1 -1
- package/src/mcp/oauth-discovery.ts +8 -3
- package/src/mcp/oauth-flow.ts +12 -5
- package/src/mcp/transports/stdio.ts +139 -3
- package/src/modes/components/assistant-message.ts +28 -6
- package/src/modes/components/custom-editor.ts +69 -9
- package/src/modes/components/transcript-container.ts +77 -25
- package/src/modes/controllers/input-controller.ts +1 -1
- package/src/modes/controllers/mcp-command-controller.ts +2 -2
- package/src/sdk.ts +138 -56
- package/src/ssh/ssh-executor.ts +60 -4
- package/src/task/executor.ts +19 -0
- package/src/task/index.ts +4 -0
- package/src/tools/fetch.ts +22 -5
- package/src/tools/image-gen.ts +33 -11
- package/src/tools/index.ts +21 -2
- package/src/tools/report-tool-issue.ts +7 -1
- package/src/tui/hyperlink.ts +27 -3
- package/src/web/kagi.ts +5 -2
- package/src/web/parallel.ts +7 -3
- package/src/web/search/providers/anthropic.ts +5 -1
- package/src/web/search/providers/base.ts +2 -1
- package/src/web/search/providers/brave.ts +5 -2
- package/src/web/search/providers/codex.ts +6 -2
- package/src/web/search/providers/exa.ts +91 -8
- package/src/web/search/providers/gemini.ts +6 -0
- package/src/web/search/providers/jina.ts +15 -5
- package/src/web/search/providers/kagi.ts +9 -2
- package/src/web/search/providers/kimi.ts +18 -4
- package/src/web/search/providers/parallel.ts +6 -2
- package/src/web/search/providers/perplexity.ts +7 -4
- package/src/web/search/providers/searxng.ts +6 -2
- package/src/web/search/providers/synthetic.ts +9 -5
- package/src/web/search/providers/tavily.ts +4 -2
- package/src/web/search/providers/zai.ts +15 -4
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
* SQLite store, never POSTs the broker sentinel to an OpenAI token endpoint.
|
|
8
8
|
*/
|
|
9
9
|
import * as os from "node:os";
|
|
10
|
-
import { type AuthStorage, getBundledModels } from "@oh-my-pi/pi-ai";
|
|
10
|
+
import { type AuthStorage, type FetchImpl, getBundledModels } from "@oh-my-pi/pi-ai";
|
|
11
11
|
import { decodeJwt } from "@oh-my-pi/pi-ai/oauth/openai-codex";
|
|
12
12
|
import { $env, readSseJson } from "@oh-my-pi/pi-utils";
|
|
13
13
|
import packageJson from "../../../../package.json" with { type: "json" };
|
|
@@ -66,6 +66,7 @@ function shouldRetryWithNextDefaultModel(error: unknown): boolean {
|
|
|
66
66
|
|
|
67
67
|
export interface CodexSearchParams {
|
|
68
68
|
signal?: AbortSignal;
|
|
69
|
+
fetch?: FetchImpl;
|
|
69
70
|
query: string;
|
|
70
71
|
system_prompt?: string;
|
|
71
72
|
num_results?: number;
|
|
@@ -322,6 +323,7 @@ async function callCodexSearch(
|
|
|
322
323
|
systemPrompt?: string;
|
|
323
324
|
searchContextSize?: "low" | "medium" | "high";
|
|
324
325
|
modelId: string;
|
|
326
|
+
fetch?: FetchImpl;
|
|
325
327
|
},
|
|
326
328
|
): Promise<{
|
|
327
329
|
answer: string;
|
|
@@ -356,7 +358,8 @@ async function callCodexSearch(
|
|
|
356
358
|
instructions: options.systemPrompt ?? DEFAULT_INSTRUCTIONS,
|
|
357
359
|
};
|
|
358
360
|
|
|
359
|
-
const
|
|
361
|
+
const fetchImpl = options.fetch ?? fetch;
|
|
362
|
+
const response = await fetchImpl(url, {
|
|
360
363
|
method: "POST",
|
|
361
364
|
headers,
|
|
362
365
|
body: JSON.stringify(body),
|
|
@@ -522,6 +525,7 @@ export async function searchCodex(params: SearchParams): Promise<SearchResponse>
|
|
|
522
525
|
systemPrompt: params.systemPrompt,
|
|
523
526
|
searchContextSize: "high",
|
|
524
527
|
modelId,
|
|
528
|
+
fetch: params.fetch,
|
|
525
529
|
});
|
|
526
530
|
break;
|
|
527
531
|
} catch (error) {
|
|
@@ -6,10 +6,10 @@
|
|
|
6
6
|
* Requests per-result summaries via `contents.summary` and synthesizes
|
|
7
7
|
* them into a combined `answer` string on the SearchResponse.
|
|
8
8
|
*/
|
|
9
|
-
import { type ApiKey, type AuthStorage, getEnvApiKey, withAuth } from "@oh-my-pi/pi-ai";
|
|
9
|
+
import { type ApiKey, type AuthStorage, type FetchImpl, getEnvApiKey, withAuth } from "@oh-my-pi/pi-ai";
|
|
10
10
|
import { settings } from "../../../config/settings";
|
|
11
|
-
import {
|
|
12
|
-
|
|
11
|
+
import { findApiKey, isSearchResponse } from "../../../exa/mcp-client";
|
|
12
|
+
import { parseSSE } from "../../../mcp/json-rpc";
|
|
13
13
|
import type { SearchResponse, SearchSource } from "../../../web/search/types";
|
|
14
14
|
import { SearchProviderError } from "../../../web/search/types";
|
|
15
15
|
import { dateToAgeSeconds } from "../utils";
|
|
@@ -32,6 +32,7 @@ export interface ExaSearchParams {
|
|
|
32
32
|
start_published_date?: string;
|
|
33
33
|
end_published_date?: string;
|
|
34
34
|
signal?: AbortSignal;
|
|
35
|
+
fetch?: FetchImpl;
|
|
35
36
|
/**
|
|
36
37
|
* Credential source. Resolved before falling back to `EXA_API_KEY` so
|
|
37
38
|
* Exa works when the key is stored via the broker/auth pipeline.
|
|
@@ -62,6 +63,48 @@ function asRecord(value: unknown): Record<string, unknown> | null {
|
|
|
62
63
|
return value as Record<string, unknown>;
|
|
63
64
|
}
|
|
64
65
|
|
|
66
|
+
function parseJsonContent(text: string): unknown | null {
|
|
67
|
+
try {
|
|
68
|
+
return JSON.parse(text) as unknown;
|
|
69
|
+
} catch {
|
|
70
|
+
return null;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function normalizeExaMcpPayload(payload: unknown): unknown {
|
|
75
|
+
const candidates: unknown[] = [];
|
|
76
|
+
const root = asRecord(payload);
|
|
77
|
+
|
|
78
|
+
if (root) {
|
|
79
|
+
if (root.structuredContent !== undefined) candidates.push(root.structuredContent);
|
|
80
|
+
if (root.data !== undefined) candidates.push(root.data);
|
|
81
|
+
if (root.result !== undefined) candidates.push(root.result);
|
|
82
|
+
candidates.push(root);
|
|
83
|
+
|
|
84
|
+
const content = root.content;
|
|
85
|
+
if (Array.isArray(content)) {
|
|
86
|
+
for (const item of content) {
|
|
87
|
+
const part = asRecord(item);
|
|
88
|
+
if (!part) continue;
|
|
89
|
+
const text = part.text;
|
|
90
|
+
if (typeof text !== "string" || text.trim().length === 0) continue;
|
|
91
|
+
const parsed = parseJsonContent(text);
|
|
92
|
+
if (parsed !== null) candidates.push(parsed);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
} else {
|
|
96
|
+
candidates.push(payload);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
for (const candidate of candidates) {
|
|
100
|
+
if (isSearchResponse(candidate)) {
|
|
101
|
+
return candidate;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
return payload;
|
|
106
|
+
}
|
|
107
|
+
|
|
65
108
|
function parseOptionalField(section: string, label: string): string | null | undefined {
|
|
66
109
|
const regex = new RegExp(`(?:^|\\n)${label}:\\s*([^\\n]*)`);
|
|
67
110
|
const match = section.match(regex);
|
|
@@ -180,7 +223,8 @@ export function buildExaRequestBody(params: ExaSearchParams): Record<string, unk
|
|
|
180
223
|
async function callExaSearch(apiKey: string, params: ExaSearchParams): Promise<ExaSearchResponse> {
|
|
181
224
|
const body = buildExaRequestBody(params);
|
|
182
225
|
|
|
183
|
-
const
|
|
226
|
+
const fetchImpl = params.fetch ?? fetch;
|
|
227
|
+
const response = await fetchImpl(EXA_API_URL, {
|
|
184
228
|
method: "POST",
|
|
185
229
|
headers: {
|
|
186
230
|
"Content-Type": "application/json",
|
|
@@ -211,14 +255,52 @@ function buildExaMcpArgs(params: ExaSearchParams): Record<string, unknown> {
|
|
|
211
255
|
}
|
|
212
256
|
|
|
213
257
|
async function callExaMcpSearch(params: ExaSearchParams): Promise<ExaSearchResponse> {
|
|
214
|
-
const
|
|
258
|
+
const query = new URLSearchParams();
|
|
259
|
+
const apiKey = findApiKey();
|
|
260
|
+
if (apiKey) query.set("exaApiKey", apiKey);
|
|
261
|
+
query.set("tools", "web_search_exa");
|
|
262
|
+
const fetchImpl = params.fetch ?? fetch;
|
|
263
|
+
const response = await fetchImpl(`https://mcp.exa.ai/mcp?${query.toString()}`, {
|
|
264
|
+
method: "POST",
|
|
265
|
+
headers: {
|
|
266
|
+
"Content-Type": "application/json",
|
|
267
|
+
Accept: "application/json, text/event-stream",
|
|
268
|
+
},
|
|
269
|
+
body: JSON.stringify({
|
|
270
|
+
jsonrpc: "2.0",
|
|
271
|
+
id: Math.random().toString(36).slice(2),
|
|
272
|
+
method: "tools/call",
|
|
273
|
+
params: {
|
|
274
|
+
name: "web_search_exa",
|
|
275
|
+
arguments: buildExaMcpArgs(params),
|
|
276
|
+
},
|
|
277
|
+
}),
|
|
215
278
|
signal: withHardTimeout(params.signal),
|
|
216
279
|
});
|
|
217
|
-
if (
|
|
218
|
-
|
|
280
|
+
if (!response.ok) {
|
|
281
|
+
throw new Error(`MCP request failed: ${response.status} ${response.statusText}`);
|
|
282
|
+
}
|
|
283
|
+
const mcpResponse = parseSSE(await response.text()) as {
|
|
284
|
+
result?: {
|
|
285
|
+
content?: Array<{ type: string; text?: string }>;
|
|
286
|
+
};
|
|
287
|
+
error?: {
|
|
288
|
+
code: number;
|
|
289
|
+
message: string;
|
|
290
|
+
};
|
|
291
|
+
} | null;
|
|
292
|
+
if (!mcpResponse) {
|
|
293
|
+
throw new Error("Failed to parse MCP response");
|
|
294
|
+
}
|
|
295
|
+
if (mcpResponse.error) {
|
|
296
|
+
throw new Error(`MCP error: ${mcpResponse.error.message}`);
|
|
297
|
+
}
|
|
298
|
+
const responsePayload = normalizeExaMcpPayload(mcpResponse.result);
|
|
299
|
+
if (isSearchResponse(responsePayload)) {
|
|
300
|
+
return responsePayload as ExaSearchResponse;
|
|
219
301
|
}
|
|
220
302
|
|
|
221
|
-
const parsed = parseExaMcpTextPayload(
|
|
303
|
+
const parsed = parseExaMcpTextPayload(responsePayload);
|
|
222
304
|
if (parsed) {
|
|
223
305
|
return parsed;
|
|
224
306
|
}
|
|
@@ -312,6 +394,7 @@ export class ExaProvider extends SearchProvider {
|
|
|
312
394
|
signal: params.signal,
|
|
313
395
|
authStorage: params.authStorage,
|
|
314
396
|
sessionId: params.sessionId,
|
|
397
|
+
fetch: params.fetch,
|
|
315
398
|
});
|
|
316
399
|
}
|
|
317
400
|
}
|
|
@@ -11,6 +11,7 @@
|
|
|
11
11
|
import {
|
|
12
12
|
ANTIGRAVITY_SYSTEM_INSTRUCTION,
|
|
13
13
|
type AuthStorage,
|
|
14
|
+
type FetchImpl,
|
|
14
15
|
getAntigravityUserAgent,
|
|
15
16
|
getGeminiCliHeaders,
|
|
16
17
|
} from "@oh-my-pi/pi-ai";
|
|
@@ -51,6 +52,7 @@ export interface GeminiSearchParams extends GeminiToolParams {
|
|
|
51
52
|
signal?: AbortSignal;
|
|
52
53
|
authStorage: AuthStorage;
|
|
53
54
|
sessionId?: string;
|
|
55
|
+
fetch?: FetchImpl;
|
|
54
56
|
}
|
|
55
57
|
|
|
56
58
|
export function buildGeminiRequestTools(params: GeminiToolParams): Array<Record<string, Record<string, unknown>>> {
|
|
@@ -156,6 +158,7 @@ async function callGeminiSearch(
|
|
|
156
158
|
maxOutputTokens: number | undefined,
|
|
157
159
|
temperature: number | undefined,
|
|
158
160
|
toolParams: GeminiToolParams,
|
|
161
|
+
fetchImpl: FetchImpl | undefined,
|
|
159
162
|
signal: AbortSignal | undefined,
|
|
160
163
|
): Promise<{
|
|
161
164
|
answer: string;
|
|
@@ -237,6 +240,7 @@ async function callGeminiSearch(
|
|
|
237
240
|
|
|
238
241
|
const response = await fetchWithRetry(urlFor, {
|
|
239
242
|
...buildInit(),
|
|
243
|
+
fetch: fetchImpl,
|
|
240
244
|
maxAttempts: MAX_RETRIES + 1,
|
|
241
245
|
defaultDelayMs: attempt => BASE_DELAY_MS * 2 ** attempt,
|
|
242
246
|
maxDelayMs: RATE_LIMIT_BUDGET_MS,
|
|
@@ -405,6 +409,7 @@ export async function searchGemini(params: GeminiSearchParams): Promise<SearchRe
|
|
|
405
409
|
code_execution: params.code_execution,
|
|
406
410
|
url_context: params.url_context,
|
|
407
411
|
},
|
|
412
|
+
params.fetch,
|
|
408
413
|
params.signal,
|
|
409
414
|
);
|
|
410
415
|
|
|
@@ -450,6 +455,7 @@ export class GeminiProvider extends SearchProvider {
|
|
|
450
455
|
signal: params.signal,
|
|
451
456
|
authStorage: params.authStorage,
|
|
452
457
|
sessionId: params.sessionId,
|
|
458
|
+
fetch: params.fetch,
|
|
453
459
|
});
|
|
454
460
|
}
|
|
455
461
|
}
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
* cleaned content.
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
|
-
import { type AuthStorage, getEnvApiKey } from "@oh-my-pi/pi-ai";
|
|
8
|
+
import { type AuthStorage, type FetchImpl, 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";
|
|
@@ -13,11 +13,13 @@ import { SearchProvider } from "./base";
|
|
|
13
13
|
import { classifyProviderHttpError, withHardTimeout } from "./utils";
|
|
14
14
|
|
|
15
15
|
const JINA_SEARCH_URL = "https://s.jina.ai";
|
|
16
|
+
type SearchParamsWithFetch = SearchParams & { fetch?: FetchImpl };
|
|
16
17
|
|
|
17
18
|
export interface JinaSearchParams {
|
|
18
19
|
query: string;
|
|
19
20
|
num_results?: number;
|
|
20
21
|
signal?: AbortSignal;
|
|
22
|
+
fetch?: FetchImpl;
|
|
21
23
|
}
|
|
22
24
|
|
|
23
25
|
interface JinaSearchResult {
|
|
@@ -34,9 +36,14 @@ export function findApiKey(): string | null {
|
|
|
34
36
|
}
|
|
35
37
|
|
|
36
38
|
/** Call Jina Reader search API. */
|
|
37
|
-
async function callJinaSearch(
|
|
39
|
+
async function callJinaSearch(
|
|
40
|
+
apiKey: string,
|
|
41
|
+
query: string,
|
|
42
|
+
signal?: AbortSignal,
|
|
43
|
+
fetchImpl: FetchImpl = fetch,
|
|
44
|
+
): Promise<JinaSearchResponse> {
|
|
38
45
|
const requestUrl = `${JINA_SEARCH_URL}/${encodeURIComponent(query)}`;
|
|
39
|
-
const response = await
|
|
46
|
+
const response = await fetchImpl(requestUrl, {
|
|
40
47
|
headers: {
|
|
41
48
|
Accept: "application/json",
|
|
42
49
|
Authorization: `Bearer ${apiKey}`,
|
|
@@ -62,7 +69,7 @@ export async function searchJina(params: JinaSearchParams): Promise<SearchRespon
|
|
|
62
69
|
throw new Error("JINA_API_KEY not found. Set it in environment or .env file.");
|
|
63
70
|
}
|
|
64
71
|
|
|
65
|
-
const response = await callJinaSearch(apiKey, params.query, params.signal);
|
|
72
|
+
const response = await callJinaSearch(apiKey, params.query, params.signal, params.fetch);
|
|
66
73
|
const sources: SearchSource[] = [];
|
|
67
74
|
|
|
68
75
|
for (const result of response) {
|
|
@@ -91,11 +98,14 @@ export class JinaProvider extends SearchProvider {
|
|
|
91
98
|
return !!findApiKey();
|
|
92
99
|
}
|
|
93
100
|
|
|
94
|
-
search(params:
|
|
101
|
+
search(params: SearchParamsWithFetch): Promise<SearchResponse> {
|
|
102
|
+
const fetchImpl = params.fetch;
|
|
103
|
+
|
|
95
104
|
return searchJina({
|
|
96
105
|
query: params.query,
|
|
97
106
|
num_results: params.numSearchResults ?? params.limit,
|
|
98
107
|
signal: params.signal,
|
|
108
|
+
fetch: fetchImpl,
|
|
99
109
|
});
|
|
100
110
|
}
|
|
101
111
|
}
|
|
@@ -3,7 +3,7 @@
|
|
|
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
|
+
import type { AuthStorage, FetchImpl } from "@oh-my-pi/pi-ai";
|
|
7
7
|
import type { SearchResponse } from "../../../web/search/types";
|
|
8
8
|
import { SearchProviderError } from "../../../web/search/types";
|
|
9
9
|
import { KagiApiError, searchWithKagi } from "../../kagi";
|
|
@@ -12,6 +12,8 @@ import type { SearchParams } from "./base";
|
|
|
12
12
|
import { SearchProvider } from "./base";
|
|
13
13
|
import { classifyProviderHttpError, toSearchSources } from "./utils";
|
|
14
14
|
|
|
15
|
+
type SearchParamsWithFetch = SearchParams & { fetch?: FetchImpl };
|
|
16
|
+
|
|
15
17
|
const DEFAULT_NUM_RESULTS = 10;
|
|
16
18
|
const MAX_NUM_RESULTS = 40;
|
|
17
19
|
|
|
@@ -23,6 +25,7 @@ export async function searchKagi(params: {
|
|
|
23
25
|
signal?: AbortSignal;
|
|
24
26
|
authStorage: AuthStorage;
|
|
25
27
|
sessionId?: string;
|
|
28
|
+
fetch?: FetchImpl;
|
|
26
29
|
}): Promise<SearchResponse> {
|
|
27
30
|
const numResults = clampNumResults(params.num_results, DEFAULT_NUM_RESULTS, MAX_NUM_RESULTS);
|
|
28
31
|
|
|
@@ -34,6 +37,7 @@ export async function searchKagi(params: {
|
|
|
34
37
|
recency: params.recency,
|
|
35
38
|
sessionId: params.sessionId,
|
|
36
39
|
signal: params.signal,
|
|
40
|
+
fetch: params.fetch,
|
|
37
41
|
},
|
|
38
42
|
params.authStorage,
|
|
39
43
|
);
|
|
@@ -66,7 +70,9 @@ export class KagiProvider extends SearchProvider {
|
|
|
66
70
|
return authStorage.hasAuth("kagi");
|
|
67
71
|
}
|
|
68
72
|
|
|
69
|
-
search(params:
|
|
73
|
+
search(params: SearchParamsWithFetch): Promise<SearchResponse> {
|
|
74
|
+
const fetchImpl = params.fetch;
|
|
75
|
+
|
|
70
76
|
return searchKagi({
|
|
71
77
|
query: params.query,
|
|
72
78
|
num_results: params.numSearchResults ?? params.limit,
|
|
@@ -74,6 +80,7 @@ export class KagiProvider extends SearchProvider {
|
|
|
74
80
|
signal: params.signal,
|
|
75
81
|
authStorage: params.authStorage,
|
|
76
82
|
sessionId: params.sessionId,
|
|
83
|
+
fetch: fetchImpl,
|
|
77
84
|
});
|
|
78
85
|
}
|
|
79
86
|
}
|
|
@@ -4,7 +4,7 @@
|
|
|
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 { type ApiKey, type AuthStorage, withAuth } from "@oh-my-pi/pi-ai";
|
|
7
|
+
import { type ApiKey, type AuthStorage, type FetchImpl, withAuth } from "@oh-my-pi/pi-ai";
|
|
8
8
|
import { $env } from "@oh-my-pi/pi-utils";
|
|
9
9
|
|
|
10
10
|
import type { SearchResponse, SearchSource } from "../../../web/search/types";
|
|
@@ -14,6 +14,8 @@ import type { SearchParams } from "./base";
|
|
|
14
14
|
import { SearchProvider } from "./base";
|
|
15
15
|
import { classifyProviderHttpError, withHardTimeout } from "./utils";
|
|
16
16
|
|
|
17
|
+
type SearchParamsWithFetch = SearchParams & { fetch?: FetchImpl };
|
|
18
|
+
|
|
17
19
|
const KIMI_SEARCH_URL = "https://api.kimi.com/coding/v1/search";
|
|
18
20
|
|
|
19
21
|
const DEFAULT_NUM_RESULTS = 10;
|
|
@@ -27,6 +29,7 @@ export interface KimiSearchParams {
|
|
|
27
29
|
signal?: AbortSignal;
|
|
28
30
|
authStorage: AuthStorage;
|
|
29
31
|
sessionId?: string;
|
|
32
|
+
fetch?: FetchImpl;
|
|
30
33
|
}
|
|
31
34
|
|
|
32
35
|
interface KimiSearchResult {
|
|
@@ -78,9 +81,16 @@ async function resolveKey(
|
|
|
78
81
|
|
|
79
82
|
async function callKimiSearch(
|
|
80
83
|
apiKey: string,
|
|
81
|
-
params: {
|
|
84
|
+
params: {
|
|
85
|
+
query: string;
|
|
86
|
+
limit: number;
|
|
87
|
+
includeContent: boolean;
|
|
88
|
+
signal?: AbortSignal;
|
|
89
|
+
fetch?: FetchImpl;
|
|
90
|
+
},
|
|
82
91
|
): Promise<{ response: KimiSearchResponse; requestId?: string }> {
|
|
83
|
-
const
|
|
92
|
+
const fetchImpl = params.fetch ?? fetch;
|
|
93
|
+
const response = await fetchImpl(resolveBaseUrl(), {
|
|
84
94
|
method: "POST",
|
|
85
95
|
headers: {
|
|
86
96
|
Accept: "application/json",
|
|
@@ -130,6 +140,7 @@ export async function searchKimi(params: KimiSearchParams): Promise<SearchRespon
|
|
|
130
140
|
limit,
|
|
131
141
|
includeContent: params.include_content ?? false,
|
|
132
142
|
signal: params.signal,
|
|
143
|
+
fetch: params.fetch,
|
|
133
144
|
}),
|
|
134
145
|
{ signal: params.signal },
|
|
135
146
|
);
|
|
@@ -170,13 +181,16 @@ export class KimiProvider extends SearchProvider {
|
|
|
170
181
|
);
|
|
171
182
|
}
|
|
172
183
|
|
|
173
|
-
search(params:
|
|
184
|
+
search(params: SearchParamsWithFetch): Promise<SearchResponse> {
|
|
185
|
+
const fetchImpl = params.fetch;
|
|
186
|
+
|
|
174
187
|
return searchKimi({
|
|
175
188
|
query: params.query,
|
|
176
189
|
num_results: params.numSearchResults ?? params.limit,
|
|
177
190
|
signal: params.signal,
|
|
178
191
|
authStorage: params.authStorage,
|
|
179
192
|
sessionId: params.sessionId,
|
|
193
|
+
fetch: fetchImpl,
|
|
180
194
|
});
|
|
181
195
|
}
|
|
182
196
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { type ApiKey, type AuthStorage, getEnvApiKey, withAuth } from "@oh-my-pi/pi-ai";
|
|
1
|
+
import { type ApiKey, type AuthStorage, type FetchImpl, getEnvApiKey, withAuth } from "@oh-my-pi/pi-ai";
|
|
2
2
|
import type { SearchResponse } from "../../../web/search/types";
|
|
3
3
|
import { SearchProviderError } from "../../../web/search/types";
|
|
4
4
|
import { ParallelApiError, type ParallelSearchResult, type ParallelSearchSource } from "../../parallel";
|
|
@@ -112,6 +112,7 @@ async function searchWithAuthStorage(
|
|
|
112
112
|
queries: string[],
|
|
113
113
|
params: {
|
|
114
114
|
signal?: AbortSignal;
|
|
115
|
+
fetch?: FetchImpl;
|
|
115
116
|
},
|
|
116
117
|
authStorage: AuthStorage,
|
|
117
118
|
sessionId?: string,
|
|
@@ -131,7 +132,7 @@ async function searchWithAuthStorage(
|
|
|
131
132
|
return withAuth(
|
|
132
133
|
keyOrResolver,
|
|
133
134
|
async key => {
|
|
134
|
-
const response = await fetch(PARALLEL_SEARCH_URL, {
|
|
135
|
+
const response = await (params.fetch ?? fetch)(PARALLEL_SEARCH_URL, {
|
|
135
136
|
method: "POST",
|
|
136
137
|
headers: {
|
|
137
138
|
Accept: "application/json",
|
|
@@ -165,6 +166,7 @@ export async function searchParallel(
|
|
|
165
166
|
query: string;
|
|
166
167
|
num_results?: number;
|
|
167
168
|
signal?: AbortSignal;
|
|
169
|
+
fetch?: FetchImpl;
|
|
168
170
|
},
|
|
169
171
|
authStorage: AuthStorage,
|
|
170
172
|
sessionId?: string,
|
|
@@ -177,6 +179,7 @@ export async function searchParallel(
|
|
|
177
179
|
[params.query],
|
|
178
180
|
{
|
|
179
181
|
signal: params.signal,
|
|
182
|
+
fetch: params.fetch,
|
|
180
183
|
},
|
|
181
184
|
authStorage,
|
|
182
185
|
sessionId,
|
|
@@ -213,6 +216,7 @@ export class ParallelProvider extends SearchProvider {
|
|
|
213
216
|
query: params.query,
|
|
214
217
|
num_results: params.numSearchResults ?? params.limit,
|
|
215
218
|
signal: params.signal,
|
|
219
|
+
fetch: params.fetch,
|
|
216
220
|
},
|
|
217
221
|
params.authStorage,
|
|
218
222
|
params.sessionId,
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
* - Anonymous via `www.perplexity.ai/rest/sse/perplexity_ask`
|
|
9
9
|
*/
|
|
10
10
|
|
|
11
|
-
import { type AuthStorage, getEnvApiKey } from "@oh-my-pi/pi-ai";
|
|
11
|
+
import { type AuthStorage, type FetchImpl, getEnvApiKey } from "@oh-my-pi/pi-ai";
|
|
12
12
|
import { $env, readSseJson } from "@oh-my-pi/pi-utils";
|
|
13
13
|
import type {
|
|
14
14
|
PerplexityMessageOutput,
|
|
@@ -275,6 +275,7 @@ export interface PerplexitySearchParams {
|
|
|
275
275
|
num_search_results?: number;
|
|
276
276
|
authStorage: AuthStorage;
|
|
277
277
|
sessionId?: string;
|
|
278
|
+
fetch?: FetchImpl;
|
|
278
279
|
}
|
|
279
280
|
|
|
280
281
|
/** Find PERPLEXITY_API_KEY from environment or .env files (also checks PPLX_API_KEY) */
|
|
@@ -356,9 +357,10 @@ async function findPerplexityAuth(
|
|
|
356
357
|
async function callPerplexityApi(
|
|
357
358
|
apiKey: string,
|
|
358
359
|
request: PerplexityRequest,
|
|
360
|
+
fetchImpl: FetchImpl | undefined,
|
|
359
361
|
signal?: AbortSignal,
|
|
360
362
|
): Promise<PerplexityResponse> {
|
|
361
|
-
const response = await fetch(PERPLEXITY_API_URL, {
|
|
363
|
+
const response = await (fetchImpl ?? fetch)(PERPLEXITY_API_URL, {
|
|
362
364
|
method: "POST",
|
|
363
365
|
headers: {
|
|
364
366
|
Authorization: `Bearer ${apiKey}`,
|
|
@@ -505,7 +507,7 @@ async function callPerplexityAsk(
|
|
|
505
507
|
requestParams.source = "default";
|
|
506
508
|
}
|
|
507
509
|
|
|
508
|
-
const response = await fetch(PERPLEXITY_OAUTH_ASK_URL, {
|
|
510
|
+
const response = await (params.fetch ?? fetch)(PERPLEXITY_OAUTH_ASK_URL, {
|
|
509
511
|
method: "POST",
|
|
510
512
|
headers,
|
|
511
513
|
body: JSON.stringify({
|
|
@@ -686,7 +688,7 @@ export async function searchPerplexity(params: PerplexitySearchParams): Promise<
|
|
|
686
688
|
request.search_recency_filter = params.search_recency_filter;
|
|
687
689
|
}
|
|
688
690
|
|
|
689
|
-
const response = await callPerplexityApi(auth.token, request, params.signal);
|
|
691
|
+
const response = await callPerplexityApi(auth.token, request, params.fetch, params.signal);
|
|
690
692
|
const result = parseResponse(response);
|
|
691
693
|
result.authMode = "api_key";
|
|
692
694
|
return applySourceLimit(result, params.num_results);
|
|
@@ -722,6 +724,7 @@ export class PerplexityProvider extends SearchProvider {
|
|
|
722
724
|
num_results: params.limit,
|
|
723
725
|
authStorage: params.authStorage,
|
|
724
726
|
sessionId: params.sessionId,
|
|
727
|
+
fetch: params.fetch,
|
|
725
728
|
});
|
|
726
729
|
}
|
|
727
730
|
}
|
|
@@ -25,7 +25,7 @@
|
|
|
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";
|
|
28
|
+
import type { AuthStorage, FetchImpl } from "@oh-my-pi/pi-ai";
|
|
29
29
|
|
|
30
30
|
import { settings } from "../../../config/settings";
|
|
31
31
|
import type { SearchResponse, SearchSource } from "../../../web/search/types";
|
|
@@ -207,12 +207,13 @@ async function callSearXNGSearch(
|
|
|
207
207
|
categories?: string;
|
|
208
208
|
language?: string;
|
|
209
209
|
signal?: AbortSignal;
|
|
210
|
+
fetch?: FetchImpl;
|
|
210
211
|
},
|
|
211
212
|
auth: SearXNGAuth | null,
|
|
212
213
|
): Promise<SearXNGResponse> {
|
|
213
214
|
const { url, headers } = buildRequest(endpoint, params, auth);
|
|
214
215
|
|
|
215
|
-
const response = await fetch(url, {
|
|
216
|
+
const response = await (params.fetch ?? fetch)(url, {
|
|
216
217
|
headers,
|
|
217
218
|
signal: withHardTimeout(params.signal),
|
|
218
219
|
});
|
|
@@ -233,6 +234,7 @@ export async function searchSearXNG(params: {
|
|
|
233
234
|
num_results?: number;
|
|
234
235
|
recency?: "day" | "week" | "month" | "year";
|
|
235
236
|
signal?: AbortSignal;
|
|
237
|
+
fetch?: FetchImpl;
|
|
236
238
|
}): Promise<SearchResponse> {
|
|
237
239
|
const numResults = clampNumResults(params.num_results, DEFAULT_NUM_RESULTS, MAX_NUM_RESULTS);
|
|
238
240
|
|
|
@@ -260,6 +262,7 @@ export async function searchSearXNG(params: {
|
|
|
260
262
|
...params,
|
|
261
263
|
categories,
|
|
262
264
|
language,
|
|
265
|
+
fetch: params.fetch,
|
|
263
266
|
},
|
|
264
267
|
auth,
|
|
265
268
|
);
|
|
@@ -304,6 +307,7 @@ export class SearXNGProvider extends SearchProvider {
|
|
|
304
307
|
num_results: params.numSearchResults ?? params.limit,
|
|
305
308
|
recency: params.recency,
|
|
306
309
|
signal: params.signal,
|
|
310
|
+
fetch: params.fetch,
|
|
307
311
|
});
|
|
308
312
|
}
|
|
309
313
|
}
|
|
@@ -5,13 +5,15 @@
|
|
|
5
5
|
* Endpoint: POST https://api.synthetic.new/v2/search
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
|
-
import { type ApiKey, type AuthStorage, getEnvApiKey, withAuth } from "@oh-my-pi/pi-ai";
|
|
8
|
+
import { type ApiKey, type AuthStorage, type FetchImpl, getEnvApiKey, withAuth } 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
13
|
import { classifyProviderHttpError, withHardTimeout } from "./utils";
|
|
14
14
|
|
|
15
|
+
type SearchParamsWithFetch = SearchParams & { fetch?: FetchImpl };
|
|
16
|
+
|
|
15
17
|
const SYNTHETIC_SEARCH_URL = "https://api.synthetic.new/v2/search";
|
|
16
18
|
|
|
17
19
|
interface SyntheticSearchResult {
|
|
@@ -39,8 +41,9 @@ async function callSyntheticSearch(
|
|
|
39
41
|
apiKey: string,
|
|
40
42
|
query: string,
|
|
41
43
|
signal?: AbortSignal,
|
|
44
|
+
fetchImpl: FetchImpl = fetch,
|
|
42
45
|
): Promise<SyntheticSearchResponse> {
|
|
43
|
-
const response = await
|
|
46
|
+
const response = await fetchImpl(SYNTHETIC_SEARCH_URL, {
|
|
44
47
|
method: "POST",
|
|
45
48
|
headers: {
|
|
46
49
|
"Content-Type": "application/json",
|
|
@@ -65,12 +68,13 @@ async function callSyntheticSearch(
|
|
|
65
68
|
}
|
|
66
69
|
|
|
67
70
|
/** Execute Synthetic web search. */
|
|
68
|
-
export async function searchSynthetic(params:
|
|
71
|
+
export async function searchSynthetic(params: SearchParamsWithFetch): Promise<SearchResponse> {
|
|
69
72
|
const keyOrResolver: ApiKey = params.authStorage.resolver("synthetic", {
|
|
70
73
|
sessionId: params.sessionId,
|
|
71
74
|
});
|
|
72
75
|
|
|
73
|
-
const
|
|
76
|
+
const fetchImpl = params.fetch;
|
|
77
|
+
const data = await withAuth(keyOrResolver, key => callSyntheticSearch(key, params.query, params.signal, fetchImpl), {
|
|
74
78
|
signal: params.signal,
|
|
75
79
|
missingKeyMessage: "Synthetic credentials not found. Set SYNTHETIC_API_KEY or login with 'omp /login synthetic'.",
|
|
76
80
|
});
|
|
@@ -104,7 +108,7 @@ export class SyntheticProvider extends SearchProvider {
|
|
|
104
108
|
return authStorage.hasAuth("synthetic") || !!getEnvApiKey("synthetic");
|
|
105
109
|
}
|
|
106
110
|
|
|
107
|
-
search(params:
|
|
111
|
+
search(params: SearchParamsWithFetch): Promise<SearchResponse> {
|
|
108
112
|
return searchSynthetic(params);
|
|
109
113
|
}
|
|
110
114
|
}
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* Uses Tavily's agent-focused search API to return structured results with an
|
|
5
5
|
* optional synthesized answer.
|
|
6
6
|
*/
|
|
7
|
-
import { type ApiKey, type AuthStorage, getEnvApiKey, withAuth } from "@oh-my-pi/pi-ai";
|
|
7
|
+
import { type ApiKey, type AuthStorage, type FetchImpl, getEnvApiKey, withAuth } 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";
|
|
@@ -21,6 +21,7 @@ export interface TavilySearchParams {
|
|
|
21
21
|
num_results?: number;
|
|
22
22
|
recency?: "day" | "week" | "month" | "year";
|
|
23
23
|
signal?: AbortSignal;
|
|
24
|
+
fetch?: FetchImpl;
|
|
24
25
|
}
|
|
25
26
|
|
|
26
27
|
interface TavilySearchResult {
|
|
@@ -89,7 +90,7 @@ export function buildRequestBody(params: TavilySearchParams): Record<string, unk
|
|
|
89
90
|
}
|
|
90
91
|
|
|
91
92
|
async function callTavilySearch(apiKey: string, params: TavilySearchParams): Promise<TavilySearchResponse> {
|
|
92
|
-
const response = await fetch(TAVILY_SEARCH_URL, {
|
|
93
|
+
const response = await (params.fetch ?? fetch)(TAVILY_SEARCH_URL, {
|
|
93
94
|
method: "POST",
|
|
94
95
|
headers: {
|
|
95
96
|
"Content-Type": "application/json",
|
|
@@ -126,6 +127,7 @@ export async function searchTavily(params: SearchParams): Promise<SearchResponse
|
|
|
126
127
|
num_results: params.numSearchResults ?? params.limit,
|
|
127
128
|
recency: params.recency,
|
|
128
129
|
signal: params.signal,
|
|
130
|
+
fetch: params.fetch,
|
|
129
131
|
};
|
|
130
132
|
const keyOrResolver: ApiKey = params.authStorage.resolver("tavily", {
|
|
131
133
|
sessionId: params.sessionId,
|