@oh-my-pi/pi-coding-agent 16.0.5 → 16.0.6
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 +53 -0
- package/dist/cli.js +1927 -1376
- package/dist/types/advisor/advise-tool.d.ts +22 -19
- package/dist/types/autoresearch/tools/init-experiment.d.ts +13 -17
- package/dist/types/autoresearch/tools/log-experiment.d.ts +17 -19
- package/dist/types/autoresearch/tools/run-experiment.d.ts +3 -4
- package/dist/types/autoresearch/tools/update-notes.d.ts +4 -5
- package/dist/types/cli/ttsr-cli.d.ts +39 -0
- package/dist/types/commands/ttsr.d.ts +57 -0
- package/dist/types/commit/agentic/tools/analyze-file.d.ts +4 -5
- package/dist/types/commit/agentic/tools/git-file-diff.d.ts +4 -5
- package/dist/types/commit/agentic/tools/git-hunk.d.ts +5 -6
- package/dist/types/commit/agentic/tools/git-overview.d.ts +4 -5
- package/dist/types/commit/agentic/tools/propose-changelog.d.ts +23 -24
- package/dist/types/commit/agentic/tools/propose-commit.d.ts +11 -32
- package/dist/types/commit/agentic/tools/recent-commits.d.ts +3 -4
- package/dist/types/commit/agentic/tools/schemas.d.ts +6 -27
- package/dist/types/commit/agentic/tools/split-commit.d.ts +28 -49
- package/dist/types/commit/changelog/generate.d.ts +12 -13
- package/dist/types/commit/shared-llm.d.ts +10 -37
- package/dist/types/config/config-file.d.ts +4 -4
- package/dist/types/config/keybindings.d.ts +5 -0
- package/dist/types/config/models-config-schema.d.ts +625 -990
- package/dist/types/config/models-config.d.ts +229 -217
- package/dist/types/config/settings-schema.d.ts +53 -23
- package/dist/types/edit/hashline/params.d.ts +7 -11
- package/dist/types/edit/index.d.ts +2 -1
- package/dist/types/edit/modes/apply-patch.d.ts +4 -5
- package/dist/types/edit/modes/patch.d.ts +15 -24
- package/dist/types/edit/modes/replace.d.ts +16 -17
- package/dist/types/eval/js/index.d.ts +1 -0
- package/dist/types/extensibility/custom-commands/types.d.ts +6 -3
- package/dist/types/extensibility/custom-tools/types.d.ts +8 -5
- package/dist/types/extensibility/extensions/types.d.ts +6 -3
- package/dist/types/extensibility/hooks/types.d.ts +7 -4
- package/dist/types/extensibility/legacy-pi-ai-shim.d.ts +13 -5
- package/dist/types/extensibility/legacy-pi-coding-agent-shim.d.ts +17 -0
- package/dist/types/extensibility/typebox.d.ts +80 -58
- package/dist/types/goals/tools/goal-tool.d.ts +11 -24
- package/dist/types/index.d.ts +2 -0
- package/dist/types/lsp/index.d.ts +11 -26
- package/dist/types/lsp/types.d.ts +12 -28
- package/dist/types/mcp/client.d.ts +8 -0
- package/dist/types/modes/components/btw-panel.d.ts +1 -0
- package/dist/types/modes/components/custom-editor.d.ts +3 -1
- package/dist/types/modes/controllers/btw-controller.d.ts +2 -0
- package/dist/types/modes/controllers/input-controller.d.ts +1 -0
- package/dist/types/modes/interactive-mode.d.ts +3 -0
- package/dist/types/modes/setup-wizard/index.d.ts +1 -0
- package/dist/types/modes/setup-wizard/startup-splash.d.ts +7 -0
- package/dist/types/modes/theme/theme.d.ts +1 -1
- package/dist/types/modes/types.d.ts +3 -0
- package/dist/types/sdk.d.ts +5 -0
- package/dist/types/session/agent-session.d.ts +4 -0
- package/dist/types/startup-splash.d.ts +12 -0
- package/dist/types/task/types.d.ts +47 -48
- package/dist/types/tools/ask.d.ts +26 -27
- package/dist/types/tools/ast-edit.d.ts +17 -17
- package/dist/types/tools/ast-grep.d.ts +12 -13
- package/dist/types/tools/bash.d.ts +20 -17
- package/dist/types/tools/browser.d.ts +46 -71
- package/dist/types/tools/checkpoint.d.ts +14 -15
- package/dist/types/tools/debug.d.ts +82 -145
- package/dist/types/tools/eval.d.ts +30 -40
- package/dist/types/tools/find.d.ts +17 -18
- package/dist/types/tools/gh.d.ts +49 -78
- package/dist/types/tools/image-gen.d.ts +20 -36
- package/dist/types/tools/inspect-image.d.ts +10 -11
- package/dist/types/tools/irc.d.ts +22 -33
- package/dist/types/tools/job.d.ts +11 -12
- package/dist/types/tools/learn.d.ts +21 -28
- package/dist/types/tools/manage-skill.d.ts +13 -22
- package/dist/types/tools/memory-edit.d.ts +15 -24
- package/dist/types/tools/memory-recall.d.ts +7 -8
- package/dist/types/tools/memory-reflect.d.ts +9 -10
- package/dist/types/tools/memory-retain.d.ts +13 -14
- package/dist/types/tools/read.d.ts +7 -8
- package/dist/types/tools/resolve.d.ts +11 -18
- package/dist/types/tools/review.d.ts +9 -15
- package/dist/types/tools/search-tool-bm25.d.ts +9 -10
- package/dist/types/tools/search.d.ts +16 -17
- package/dist/types/tools/ssh.d.ts +14 -15
- package/dist/types/tools/todo.d.ts +27 -43
- package/dist/types/tools/tts.d.ts +8 -9
- package/dist/types/tools/write.d.ts +9 -10
- package/dist/types/tui/index.d.ts +1 -0
- package/dist/types/tui/width-aware-text.d.ts +23 -0
- package/dist/types/utils/markit.d.ts +10 -1
- package/dist/types/web/search/index.d.ts +17 -28
- package/dist/types/web/search/providers/perplexity.d.ts +0 -2
- package/dist/types/web/search/types.d.ts +32 -26
- package/package.json +14 -13
- package/scripts/omp +1 -1
- package/src/advisor/__tests__/advisor.test.ts +44 -1
- package/src/advisor/advise-tool.ts +34 -11
- package/src/autoresearch/tools/init-experiment.ts +13 -16
- package/src/autoresearch/tools/log-experiment.ts +15 -18
- package/src/autoresearch/tools/run-experiment.ts +3 -3
- package/src/autoresearch/tools/update-notes.ts +4 -4
- package/src/cli/ttsr-cli.ts +995 -0
- package/src/cli-commands.ts +1 -0
- package/src/cli.ts +7 -1
- package/src/commands/ttsr.ts +125 -0
- package/src/commit/agentic/tools/analyze-file.ts +4 -4
- package/src/commit/agentic/tools/git-file-diff.ts +4 -4
- package/src/commit/agentic/tools/git-hunk.ts +7 -5
- package/src/commit/agentic/tools/git-overview.ts +4 -4
- package/src/commit/agentic/tools/propose-changelog.ts +18 -15
- package/src/commit/agentic/tools/propose-commit.ts +6 -6
- package/src/commit/agentic/tools/recent-commits.ts +3 -3
- package/src/commit/agentic/tools/schemas.ts +8 -20
- package/src/commit/agentic/tools/split-commit.ts +19 -23
- package/src/commit/analysis/summary.ts +7 -5
- package/src/commit/changelog/generate.ts +15 -11
- package/src/commit/shared-llm.ts +17 -24
- package/src/config/config-file.ts +13 -15
- package/src/config/keybindings.ts +6 -0
- package/src/config/models-config-schema.ts +206 -179
- package/src/config/settings-schema.ts +34 -0
- package/src/discovery/builtin-rules/index.ts +2 -0
- package/src/discovery/builtin-rules/ts-import-type.md +2 -2
- package/src/discovery/builtin-rules/ts-no-any.md +11 -2
- package/src/discovery/builtin-rules/ts-no-inline-cast-access.md +55 -0
- package/src/edit/hashline/params.ts +12 -11
- package/src/edit/index.ts +5 -4
- package/src/edit/modes/apply-patch.ts +4 -4
- package/src/edit/modes/patch.ts +15 -18
- package/src/edit/modes/replace.ts +13 -17
- package/src/edit/renderer.ts +0 -1
- package/src/eval/agent-bridge.ts +11 -13
- package/src/eval/completion-bridge.ts +25 -17
- package/src/eval/js/context-manager.ts +17 -2
- package/src/eval/js/index.ts +1 -1
- package/src/eval/py/executor.ts +2 -2
- package/src/extensibility/custom-commands/loader.ts +5 -3
- package/src/extensibility/custom-commands/types.ts +6 -3
- package/src/extensibility/custom-tools/loader.ts +4 -2
- package/src/extensibility/custom-tools/types.ts +8 -5
- package/src/extensibility/extensions/loader.ts +4 -2
- package/src/extensibility/extensions/types.ts +6 -3
- package/src/extensibility/hooks/loader.ts +5 -2
- package/src/extensibility/hooks/types.ts +7 -4
- package/src/extensibility/legacy-pi-ai-shim.ts +42 -5
- package/src/extensibility/legacy-pi-coding-agent-shim.ts +113 -0
- package/src/extensibility/plugins/legacy-pi-compat.ts +13 -13
- package/src/extensibility/tool-proxy.ts +4 -1
- package/src/extensibility/typebox.ts +778 -251
- package/src/goals/guided-setup.ts +12 -3
- package/src/goals/tools/goal-tool.ts +6 -6
- package/src/index.ts +2 -0
- package/src/internal-urls/docs-index.generated.ts +11 -9
- package/src/lsp/types.ts +13 -27
- package/src/main.ts +19 -18
- package/src/mcp/client.ts +38 -13
- package/src/mcp/render.ts +102 -89
- package/src/modes/components/agent-hub.ts +11 -4
- package/src/modes/components/btw-panel.ts +5 -1
- package/src/modes/components/custom-editor.ts +18 -0
- package/src/modes/components/status-line/component.ts +8 -1
- package/src/modes/components/tool-execution.ts +17 -10
- package/src/modes/controllers/btw-controller.ts +69 -1
- package/src/modes/controllers/input-controller.ts +29 -0
- package/src/modes/interactive-mode.ts +38 -8
- package/src/modes/setup-wizard/index.ts +1 -0
- package/src/modes/setup-wizard/scenes/sign-in.ts +77 -5
- package/src/modes/setup-wizard/startup-splash.ts +107 -0
- package/src/modes/theme/theme.ts +133 -143
- package/src/modes/types.ts +3 -0
- package/src/modes/utils/context-usage.ts +9 -5
- package/src/modes/utils/hotkeys-markdown.ts +1 -0
- package/src/prompts/system/system-prompt.md +1 -0
- package/src/sdk.ts +21 -4
- package/src/session/agent-session.ts +160 -33
- package/src/session/session-history-format.ts +11 -2
- package/src/session/snapcompact-inline.ts +1 -1
- package/src/slash-commands/builtin-registry.ts +4 -11
- package/src/startup-splash.ts +19 -0
- package/src/task/executor.ts +11 -6
- package/src/task/types.ts +44 -41
- package/src/tool-discovery/tool-index.ts +17 -4
- package/src/tools/ask.ts +14 -14
- package/src/tools/ast-edit.ts +17 -14
- package/src/tools/ast-grep.ts +10 -9
- package/src/tools/bash.ts +15 -10
- package/src/tools/browser/launch.ts +13 -0
- package/src/tools/browser.ts +26 -32
- package/src/tools/checkpoint.ts +7 -7
- package/src/tools/debug.ts +72 -69
- package/src/tools/eval.ts +18 -19
- package/src/tools/find.ts +20 -13
- package/src/tools/gh.ts +29 -49
- package/src/tools/image-gen.ts +27 -32
- package/src/tools/inspect-image.ts +8 -9
- package/src/tools/irc.ts +12 -12
- package/src/tools/job.ts +6 -6
- package/src/tools/learn.ts +11 -14
- package/src/tools/manage-skill.ts +19 -23
- package/src/tools/memory-edit.ts +8 -8
- package/src/tools/memory-recall.ts +4 -4
- package/src/tools/memory-reflect.ts +5 -5
- package/src/tools/memory-retain.ts +9 -11
- package/src/tools/puppeteer/02_stealth_hairline.txt +1 -1
- package/src/tools/puppeteer/04_stealth_iframe.txt +4 -4
- package/src/tools/puppeteer/05_stealth_webgl.txt +1 -1
- package/src/tools/puppeteer/10_stealth_plugins.txt +6 -4
- package/src/tools/puppeteer/12_stealth_codecs.txt +2 -2
- package/src/tools/puppeteer/13_stealth_worker.txt +1 -1
- package/src/tools/read.ts +169 -13
- package/src/tools/report-tool-issue.ts +6 -6
- package/src/tools/resolve.ts +6 -6
- package/src/tools/review.ts +10 -12
- package/src/tools/search-tool-bm25.ts +5 -5
- package/src/tools/search.ts +20 -29
- package/src/tools/ssh.ts +8 -8
- package/src/tools/todo.ts +16 -19
- package/src/tools/tts.ts +16 -15
- package/src/tools/write.ts +5 -5
- package/src/tui/index.ts +1 -0
- package/src/tui/width-aware-text.ts +58 -0
- package/src/utils/markit.ts +17 -2
- package/src/web/search/index.ts +9 -9
- package/src/web/search/providers/perplexity.ts +373 -126
- package/src/web/search/types.ts +28 -48
|
@@ -8,12 +8,24 @@
|
|
|
8
8
|
* - Anonymous via `www.perplexity.ai/rest/sse/perplexity_ask`
|
|
9
9
|
*/
|
|
10
10
|
|
|
11
|
-
import {
|
|
11
|
+
import {
|
|
12
|
+
type AssistantMessage,
|
|
13
|
+
type AssistantMessageEventStream,
|
|
14
|
+
type AuthStorage,
|
|
15
|
+
type Context,
|
|
16
|
+
type FetchImpl,
|
|
17
|
+
type OAuthAccess,
|
|
18
|
+
type Usage,
|
|
19
|
+
withOAuthAccess,
|
|
20
|
+
} from "@oh-my-pi/pi-ai";
|
|
21
|
+
import { streamOpenAICompletions } from "@oh-my-pi/pi-ai/providers/openai-completions";
|
|
22
|
+
import { streamOpenAIResponses } from "@oh-my-pi/pi-ai/providers/openai-responses";
|
|
23
|
+
import { buildModel } from "@oh-my-pi/pi-catalog/build";
|
|
24
|
+
import type { Model, ModelSpec } from "@oh-my-pi/pi-catalog/types";
|
|
12
25
|
import { $env, readSseJson } from "@oh-my-pi/pi-utils";
|
|
13
26
|
import type {
|
|
14
|
-
PerplexityMessageOutput,
|
|
15
27
|
PerplexityRequest,
|
|
16
|
-
|
|
28
|
+
PerplexitySearchResult,
|
|
17
29
|
SearchCitation,
|
|
18
30
|
SearchResponse,
|
|
19
31
|
SearchSource,
|
|
@@ -24,7 +36,9 @@ import type { SearchParams } from "./base";
|
|
|
24
36
|
import { SearchProvider } from "./base";
|
|
25
37
|
import { classifyProviderHttpError, withHardTimeout } from "./utils";
|
|
26
38
|
|
|
27
|
-
const
|
|
39
|
+
const PERPLEXITY_CHAT_BASE_URL = "https://api.perplexity.ai";
|
|
40
|
+
const PERPLEXITY_RESPONSES_BASE_URL = "https://api.perplexity.ai/v1";
|
|
41
|
+
const OPENROUTER_BASE_URL = "https://openrouter.ai/api/v1";
|
|
28
42
|
const PERPLEXITY_OAUTH_ASK_URL = "https://www.perplexity.ai/rest/sse/perplexity_ask";
|
|
29
43
|
|
|
30
44
|
const DEFAULT_MAX_TOKENS = 8192;
|
|
@@ -37,10 +51,7 @@ const ANONYMOUS_USER_AGENT =
|
|
|
37
51
|
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/149.0.0.0 Safari/537.36";
|
|
38
52
|
|
|
39
53
|
type PerplexityAuth =
|
|
40
|
-
|
|
|
41
|
-
type: "api_key";
|
|
42
|
-
token: string;
|
|
43
|
-
}
|
|
54
|
+
| ApiConfig
|
|
44
55
|
| {
|
|
45
56
|
type: "oauth";
|
|
46
57
|
access: OAuthAccess;
|
|
@@ -278,9 +289,52 @@ export interface PerplexitySearchParams {
|
|
|
278
289
|
fetch?: FetchImpl;
|
|
279
290
|
}
|
|
280
291
|
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
292
|
+
interface ApiConfig {
|
|
293
|
+
type: "api_key";
|
|
294
|
+
apiKey: string;
|
|
295
|
+
provider: "perplexity" | "openrouter";
|
|
296
|
+
chatBaseUrl: string;
|
|
297
|
+
responsesBaseUrl: string;
|
|
298
|
+
modelPrefix: string;
|
|
299
|
+
useResponses: boolean;
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
/** Detect API-key endpoints to try in priority order (Perplexity direct, then OpenRouter). */
|
|
303
|
+
async function getApiConfigs(
|
|
304
|
+
authStorage: AuthStorage,
|
|
305
|
+
sessionId: string | undefined,
|
|
306
|
+
signal: AbortSignal | undefined,
|
|
307
|
+
): Promise<ApiConfig[]> {
|
|
308
|
+
const useResponses = $env.PI_PERPLEXITY_RESPONSES === "1";
|
|
309
|
+
const configs: ApiConfig[] = [];
|
|
310
|
+
|
|
311
|
+
const perplexityKey = await authStorage.getApiKey("perplexity", sessionId, { signal });
|
|
312
|
+
if (perplexityKey) {
|
|
313
|
+
configs.push({
|
|
314
|
+
type: "api_key",
|
|
315
|
+
apiKey: perplexityKey,
|
|
316
|
+
provider: "perplexity",
|
|
317
|
+
chatBaseUrl: PERPLEXITY_CHAT_BASE_URL,
|
|
318
|
+
responsesBaseUrl: PERPLEXITY_RESPONSES_BASE_URL,
|
|
319
|
+
modelPrefix: "",
|
|
320
|
+
useResponses,
|
|
321
|
+
});
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
const openrouterKey = await authStorage.getApiKey("openrouter", sessionId, { signal });
|
|
325
|
+
if (openrouterKey) {
|
|
326
|
+
configs.push({
|
|
327
|
+
type: "api_key",
|
|
328
|
+
apiKey: openrouterKey,
|
|
329
|
+
provider: "openrouter",
|
|
330
|
+
chatBaseUrl: OPENROUTER_BASE_URL,
|
|
331
|
+
responsesBaseUrl: OPENROUTER_BASE_URL,
|
|
332
|
+
modelPrefix: "perplexity/",
|
|
333
|
+
useResponses,
|
|
334
|
+
});
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
return configs;
|
|
284
338
|
}
|
|
285
339
|
|
|
286
340
|
/**
|
|
@@ -302,86 +356,236 @@ function jwtExpiryMs(token: string): number | undefined {
|
|
|
302
356
|
}
|
|
303
357
|
}
|
|
304
358
|
|
|
305
|
-
|
|
359
|
+
/** Collect all available auth methods to try in priority order */
|
|
360
|
+
async function getAvailableAuthMethods(
|
|
306
361
|
authStorage: AuthStorage,
|
|
307
362
|
sessionId: string | undefined,
|
|
308
363
|
signal: AbortSignal | undefined,
|
|
309
|
-
): Promise<
|
|
364
|
+
): Promise<PerplexityAuth[]> {
|
|
365
|
+
const methods: PerplexityAuth[] = [];
|
|
366
|
+
|
|
367
|
+
// 1. Perplexity OAuth & Cookies (same priority - highest)
|
|
310
368
|
try {
|
|
311
|
-
// `getOAuthAccess` returns the raw OAuth bearer only — runtime/config
|
|
312
|
-
// api_key overrides and stored api_key credentials are intentionally
|
|
313
|
-
// suppressed so we don't POST an `api.perplexity.ai` key to the
|
|
314
|
-
// `www.perplexity.ai` session/SSE endpoint.
|
|
315
369
|
const access = await authStorage.getOAuthAccess("perplexity", sessionId, { signal });
|
|
316
370
|
const token = access?.accessToken;
|
|
317
|
-
if (
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
371
|
+
if (access && token) {
|
|
372
|
+
const jwtExpiry = jwtExpiryMs(token);
|
|
373
|
+
if (jwtExpiry === undefined || jwtExpiry > Date.now() + OAUTH_EXPIRY_BUFFER_MS) {
|
|
374
|
+
methods.push({ type: "oauth", access });
|
|
375
|
+
}
|
|
376
|
+
}
|
|
323
377
|
} catch {
|
|
324
|
-
|
|
378
|
+
// ignored
|
|
325
379
|
}
|
|
326
|
-
}
|
|
327
380
|
|
|
328
|
-
async function findPerplexityAuth(
|
|
329
|
-
authStorage: AuthStorage,
|
|
330
|
-
sessionId: string | undefined,
|
|
331
|
-
signal: AbortSignal | undefined,
|
|
332
|
-
): Promise<PerplexityAuth> {
|
|
333
|
-
// 1. PERPLEXITY_COOKIES env var
|
|
334
381
|
const cookies = $env.PERPLEXITY_COOKIES?.trim();
|
|
335
382
|
if (cookies) {
|
|
336
|
-
|
|
383
|
+
methods.push({ type: "cookies", cookies });
|
|
337
384
|
}
|
|
338
385
|
|
|
339
|
-
const
|
|
386
|
+
const apiConfigs = await getApiConfigs(authStorage, sessionId, signal);
|
|
387
|
+
methods.push(...apiConfigs);
|
|
340
388
|
|
|
341
|
-
//
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
return { type: "oauth", access: oauthAccess };
|
|
389
|
+
// 5. Fallback to Perplexity free (anonymous)
|
|
390
|
+
if (methods.length === 0) {
|
|
391
|
+
methods.push({ type: "anonymous" });
|
|
345
392
|
}
|
|
346
393
|
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
394
|
+
return methods;
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
interface PerplexityApiStreamMetadata {
|
|
398
|
+
id?: string;
|
|
399
|
+
model?: string;
|
|
400
|
+
citations?: unknown;
|
|
401
|
+
search_results?: unknown;
|
|
402
|
+
related_questions?: unknown;
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
function buildPerplexityCompletionsModel(config: ApiConfig, request: PerplexityRequest): Model<"openai-completions"> {
|
|
406
|
+
const model = config.modelPrefix ? `${config.modelPrefix}${request.model}` : request.model;
|
|
407
|
+
const spec: ModelSpec<"openai-completions"> = {
|
|
408
|
+
id: model,
|
|
409
|
+
name: model,
|
|
410
|
+
api: "openai-completions",
|
|
411
|
+
provider: config.provider,
|
|
412
|
+
baseUrl: config.chatBaseUrl,
|
|
413
|
+
reasoning: false,
|
|
414
|
+
input: ["text"],
|
|
415
|
+
supportsTools: false,
|
|
416
|
+
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
|
|
417
|
+
contextWindow: null,
|
|
418
|
+
maxTokens: null,
|
|
419
|
+
compat: {
|
|
420
|
+
supportsStore: false,
|
|
421
|
+
supportsMultipleSystemMessages: true,
|
|
422
|
+
supportsReasoningParams: false,
|
|
423
|
+
supportsUsageInStreaming: true,
|
|
424
|
+
maxTokensField: "max_tokens",
|
|
425
|
+
},
|
|
426
|
+
};
|
|
427
|
+
return buildModel(spec);
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
function buildPerplexityResponsesModel(config: ApiConfig, request: PerplexityRequest): Model<"openai-responses"> {
|
|
431
|
+
const model = config.modelPrefix ? `${config.modelPrefix}${request.model}` : request.model;
|
|
432
|
+
const spec: ModelSpec<"openai-responses"> = {
|
|
433
|
+
id: model,
|
|
434
|
+
name: model,
|
|
435
|
+
api: "openai-responses",
|
|
436
|
+
provider: config.provider,
|
|
437
|
+
baseUrl: config.responsesBaseUrl,
|
|
438
|
+
reasoning: false,
|
|
439
|
+
input: ["text"],
|
|
440
|
+
supportsTools: false,
|
|
441
|
+
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
|
|
442
|
+
contextWindow: null,
|
|
443
|
+
maxTokens: null,
|
|
444
|
+
compat: {
|
|
445
|
+
alwaysSendMaxTokens: true,
|
|
446
|
+
supportsReasoningParams: false,
|
|
447
|
+
},
|
|
448
|
+
};
|
|
449
|
+
return buildModel(spec);
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
function buildPerplexityContext(request: PerplexityRequest): Context {
|
|
453
|
+
const systemPrompt: string[] = [];
|
|
454
|
+
const messages: Context["messages"] = [];
|
|
455
|
+
for (const message of request.messages) {
|
|
456
|
+
if (typeof message.content !== "string" || message.content.length === 0) continue;
|
|
457
|
+
if (message.role === "system") {
|
|
458
|
+
systemPrompt.push(message.content);
|
|
459
|
+
continue;
|
|
460
|
+
}
|
|
461
|
+
if (message.role === "user") {
|
|
462
|
+
messages.push({ role: "user", content: message.content, timestamp: 0 });
|
|
463
|
+
}
|
|
464
|
+
}
|
|
465
|
+
return { systemPrompt: systemPrompt.length > 0 ? systemPrompt : undefined, messages };
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
function buildPerplexityExtraBody(request: PerplexityRequest): Record<string, unknown> {
|
|
469
|
+
return {
|
|
470
|
+
search_mode: request.search_mode,
|
|
471
|
+
num_search_results: request.num_search_results,
|
|
472
|
+
web_search_options: request.web_search_options,
|
|
473
|
+
enable_search_classifier: request.enable_search_classifier,
|
|
474
|
+
reasoning_effort: request.reasoning_effort,
|
|
475
|
+
language_preference: request.language_preference,
|
|
476
|
+
return_related_questions: request.return_related_questions,
|
|
477
|
+
search_recency_filter: request.search_recency_filter,
|
|
478
|
+
};
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
function applyPerplexityExtraBody(payload: unknown, request: PerplexityRequest): void {
|
|
482
|
+
const record = asRecord(payload);
|
|
483
|
+
if (!record) return;
|
|
484
|
+
Object.assign(record, buildPerplexityExtraBody(request));
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
function collectPerplexityOutputMetadata(metadata: PerplexityApiStreamMetadata, output: unknown): void {
|
|
488
|
+
if (!Array.isArray(output)) return;
|
|
489
|
+
for (const item of output) {
|
|
490
|
+
const record = asRecord(item);
|
|
491
|
+
if (!record) continue;
|
|
492
|
+
if (Array.isArray(record.search_results)) metadata.search_results = record.search_results;
|
|
493
|
+
if (Array.isArray(record.results)) metadata.search_results = record.results;
|
|
494
|
+
if (Array.isArray(record.citations)) metadata.citations = record.citations;
|
|
495
|
+
if (Array.isArray(record.related_questions)) metadata.related_questions = record.related_questions;
|
|
496
|
+
collectPerplexityOutputMetadata(metadata, record.content);
|
|
350
497
|
}
|
|
498
|
+
}
|
|
351
499
|
|
|
352
|
-
|
|
353
|
-
|
|
500
|
+
function collectPerplexityMetadataFromRecord(
|
|
501
|
+
metadata: PerplexityApiStreamMetadata,
|
|
502
|
+
record: Record<string, unknown>,
|
|
503
|
+
): void {
|
|
504
|
+
const id = record.id;
|
|
505
|
+
if (typeof id === "string" && id.length > 0) metadata.id = id;
|
|
506
|
+
const model = record.model;
|
|
507
|
+
if (typeof model === "string" && model.length > 0) metadata.model = model;
|
|
508
|
+
if (Array.isArray(record.citations)) metadata.citations = record.citations;
|
|
509
|
+
if (Array.isArray(record.search_results)) metadata.search_results = record.search_results;
|
|
510
|
+
if (Array.isArray(record.related_questions)) metadata.related_questions = record.related_questions;
|
|
511
|
+
if (Array.isArray(record.results)) metadata.search_results = record.results;
|
|
512
|
+
collectPerplexityOutputMetadata(metadata, record.output);
|
|
513
|
+
const response = asRecord(record.response);
|
|
514
|
+
if (response) {
|
|
515
|
+
collectPerplexityOutputMetadata(metadata, response.output);
|
|
516
|
+
collectPerplexityMetadataFromRecord(metadata, response);
|
|
517
|
+
}
|
|
354
518
|
}
|
|
355
519
|
|
|
356
|
-
|
|
520
|
+
function collectPerplexityMetadata(metadata: PerplexityApiStreamMetadata, data: string): void {
|
|
521
|
+
if (data === "[DONE]") return;
|
|
522
|
+
const record = asRecord(parseJson(data));
|
|
523
|
+
if (record) collectPerplexityMetadataFromRecord(metadata, record);
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
async function drainAssistantStream(stream: AssistantMessageEventStream): Promise<AssistantMessage> {
|
|
527
|
+
let finalMessage: AssistantMessage | undefined;
|
|
528
|
+
for await (const event of stream) {
|
|
529
|
+
if (event.type === "done") {
|
|
530
|
+
finalMessage = event.message;
|
|
531
|
+
} else if (event.type === "error") {
|
|
532
|
+
finalMessage = event.error;
|
|
533
|
+
}
|
|
534
|
+
}
|
|
535
|
+
return finalMessage ?? stream.result();
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
function throwPerplexityStreamError(message: AssistantMessage): never {
|
|
539
|
+
const status = message.errorStatus ?? 500;
|
|
540
|
+
const details = message.errorMessage ?? "Perplexity API stream failed";
|
|
541
|
+
const classified = classifyProviderHttpError("perplexity", status, details);
|
|
542
|
+
if (classified) throw classified;
|
|
543
|
+
throw new SearchProviderError("perplexity", `Perplexity API error (${status}): ${details}`, status);
|
|
544
|
+
}
|
|
545
|
+
|
|
546
|
+
/** Call Perplexity API-key endpoint (or OpenRouter) through the shared OpenAI streaming providers. */
|
|
357
547
|
async function callPerplexityApi(
|
|
358
|
-
|
|
548
|
+
config: ApiConfig,
|
|
359
549
|
request: PerplexityRequest,
|
|
360
550
|
fetchImpl: FetchImpl | undefined,
|
|
361
551
|
signal?: AbortSignal,
|
|
362
|
-
): Promise<
|
|
363
|
-
const
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
body: JSON.stringify(request),
|
|
370
|
-
signal: withHardTimeout(signal),
|
|
371
|
-
});
|
|
372
|
-
|
|
373
|
-
if (!response.ok) {
|
|
374
|
-
const errorText = await response.text();
|
|
375
|
-
const classified = classifyProviderHttpError("perplexity", response.status, errorText);
|
|
376
|
-
if (classified) throw classified;
|
|
377
|
-
throw new SearchProviderError(
|
|
378
|
-
"perplexity",
|
|
379
|
-
`Perplexity API error (${response.status}): ${errorText}`,
|
|
380
|
-
response.status,
|
|
381
|
-
);
|
|
382
|
-
}
|
|
552
|
+
): Promise<SearchResponse> {
|
|
553
|
+
const metadata: PerplexityApiStreamMetadata = {};
|
|
554
|
+
const context = buildPerplexityContext(request);
|
|
555
|
+
const requestSignal = withHardTimeout(signal);
|
|
556
|
+
const onSseEvent = (event: { data: string }): void => {
|
|
557
|
+
collectPerplexityMetadata(metadata, event.data);
|
|
558
|
+
};
|
|
383
559
|
|
|
384
|
-
|
|
560
|
+
const message = config.useResponses
|
|
561
|
+
? await drainAssistantStream(
|
|
562
|
+
streamOpenAIResponses(buildPerplexityResponsesModel(config, request), context, {
|
|
563
|
+
apiKey: config.apiKey,
|
|
564
|
+
maxTokens: request.max_tokens ?? undefined,
|
|
565
|
+
temperature: request.temperature ?? undefined,
|
|
566
|
+
signal: requestSignal,
|
|
567
|
+
fetch: fetchImpl,
|
|
568
|
+
extraBody: buildPerplexityExtraBody(request),
|
|
569
|
+
onSseEvent,
|
|
570
|
+
}),
|
|
571
|
+
)
|
|
572
|
+
: await drainAssistantStream(
|
|
573
|
+
streamOpenAICompletions(buildPerplexityCompletionsModel(config, request), context, {
|
|
574
|
+
apiKey: config.apiKey,
|
|
575
|
+
maxTokens: request.max_tokens ?? undefined,
|
|
576
|
+
temperature: request.temperature ?? undefined,
|
|
577
|
+
signal: requestSignal,
|
|
578
|
+
fetch: fetchImpl,
|
|
579
|
+
onPayload: payload => applyPerplexityExtraBody(payload, request),
|
|
580
|
+
onSseEvent,
|
|
581
|
+
}),
|
|
582
|
+
);
|
|
583
|
+
|
|
584
|
+
if (message.stopReason === "error" || message.stopReason === "aborted") {
|
|
585
|
+
throwPerplexityStreamError(message);
|
|
586
|
+
}
|
|
587
|
+
|
|
588
|
+
return parseStreamedApiResponse(message, metadata);
|
|
385
589
|
}
|
|
386
590
|
|
|
387
591
|
function buildOAuthSources(event: PerplexityOAuthStreamEvent): SearchSource[] {
|
|
@@ -570,26 +774,49 @@ async function callPerplexityAsk(
|
|
|
570
774
|
};
|
|
571
775
|
}
|
|
572
776
|
|
|
573
|
-
function
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
777
|
+
function assistantText(message: AssistantMessage): string {
|
|
778
|
+
let text = "";
|
|
779
|
+
for (const block of message.content) {
|
|
780
|
+
if (block.type === "text") text += block.text;
|
|
781
|
+
}
|
|
782
|
+
return text;
|
|
783
|
+
}
|
|
784
|
+
|
|
785
|
+
function isPerplexitySearchResult(value: unknown): value is PerplexitySearchResult {
|
|
786
|
+
const record = asRecord(value);
|
|
787
|
+
return typeof record?.url === "string" && record.url.length > 0;
|
|
788
|
+
}
|
|
789
|
+
|
|
790
|
+
function searchResultsFromMetadata(metadata: PerplexityApiStreamMetadata): PerplexitySearchResult[] {
|
|
791
|
+
return Array.isArray(metadata.search_results) ? metadata.search_results.filter(isPerplexitySearchResult) : [];
|
|
792
|
+
}
|
|
793
|
+
|
|
794
|
+
function citationUrlsFromMetadata(metadata: PerplexityApiStreamMetadata): string[] {
|
|
795
|
+
return Array.isArray(metadata.citations)
|
|
796
|
+
? metadata.citations.filter((url): url is string => typeof url === "string" && url.length > 0)
|
|
797
|
+
: [];
|
|
577
798
|
}
|
|
578
799
|
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
800
|
+
function relatedQuestionsFromMetadata(metadata: PerplexityApiStreamMetadata): string[] {
|
|
801
|
+
return Array.isArray(metadata.related_questions)
|
|
802
|
+
? metadata.related_questions.filter(
|
|
803
|
+
(question): question is string => typeof question === "string" && question.trim().length > 0,
|
|
804
|
+
)
|
|
805
|
+
: [];
|
|
806
|
+
}
|
|
583
807
|
|
|
808
|
+
function buildApiSources(metadata: PerplexityApiStreamMetadata): {
|
|
809
|
+
sources: SearchSource[];
|
|
810
|
+
citations: SearchCitation[];
|
|
811
|
+
} {
|
|
584
812
|
const sources: SearchSource[] = [];
|
|
585
813
|
const citations: SearchCitation[] = [];
|
|
586
|
-
|
|
587
|
-
const citationUrls =
|
|
588
|
-
const searchResults = response.search_results ?? [];
|
|
814
|
+
const searchResults = searchResultsFromMetadata(metadata);
|
|
815
|
+
const citationUrls = citationUrlsFromMetadata(metadata);
|
|
589
816
|
|
|
590
817
|
if (citationUrls.length > 0) {
|
|
591
818
|
for (const url of citationUrls) {
|
|
592
|
-
const searchResult = searchResults.find(
|
|
819
|
+
const searchResult = searchResults.find(result => result.url === url);
|
|
593
820
|
sources.push({
|
|
594
821
|
title: searchResult?.title ?? url,
|
|
595
822
|
url,
|
|
@@ -597,10 +824,7 @@ function parseResponse(response: PerplexityResponse): SearchResponse {
|
|
|
597
824
|
publishedDate: searchResult?.date ?? undefined,
|
|
598
825
|
ageSeconds: dateToAgeSeconds(searchResult?.date),
|
|
599
826
|
});
|
|
600
|
-
citations.push({
|
|
601
|
-
url,
|
|
602
|
-
title: searchResult?.title ?? url,
|
|
603
|
-
});
|
|
827
|
+
citations.push({ url, title: searchResult?.title ?? url });
|
|
604
828
|
}
|
|
605
829
|
} else {
|
|
606
830
|
for (const searchResult of searchResults) {
|
|
@@ -614,7 +838,22 @@ function parseResponse(response: PerplexityResponse): SearchResponse {
|
|
|
614
838
|
}
|
|
615
839
|
}
|
|
616
840
|
|
|
617
|
-
|
|
841
|
+
return { sources, citations };
|
|
842
|
+
}
|
|
843
|
+
|
|
844
|
+
function usageFromAssistant(usage: Usage): SearchResponse["usage"] | undefined {
|
|
845
|
+
if (usage.input === 0 && usage.output === 0 && usage.totalTokens === 0) return undefined;
|
|
846
|
+
return {
|
|
847
|
+
inputTokens: usage.input,
|
|
848
|
+
outputTokens: usage.output,
|
|
849
|
+
totalTokens: usage.totalTokens,
|
|
850
|
+
};
|
|
851
|
+
}
|
|
852
|
+
|
|
853
|
+
function parseStreamedApiResponse(message: AssistantMessage, metadata: PerplexityApiStreamMetadata): SearchResponse {
|
|
854
|
+
const { sources, citations } = buildApiSources(metadata);
|
|
855
|
+
const relatedQuestions = relatedQuestionsFromMetadata(metadata);
|
|
856
|
+
const answer = assistantText(message);
|
|
618
857
|
|
|
619
858
|
return {
|
|
620
859
|
provider: "perplexity",
|
|
@@ -622,15 +861,9 @@ function parseResponse(response: PerplexityResponse): SearchResponse {
|
|
|
622
861
|
sources,
|
|
623
862
|
citations: citations.length > 0 ? citations : undefined,
|
|
624
863
|
relatedQuestions: relatedQuestions.length > 0 ? relatedQuestions : undefined,
|
|
625
|
-
usage:
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
outputTokens: response.usage.completion_tokens,
|
|
629
|
-
totalTokens: response.usage.total_tokens,
|
|
630
|
-
}
|
|
631
|
-
: undefined,
|
|
632
|
-
model: response.model,
|
|
633
|
-
requestId: response.id,
|
|
864
|
+
usage: usageFromAssistant(message.usage),
|
|
865
|
+
model: metadata.model ?? message.model,
|
|
866
|
+
requestId: metadata.id ?? message.responseId,
|
|
634
867
|
};
|
|
635
868
|
}
|
|
636
869
|
|
|
@@ -643,35 +876,6 @@ function applySourceLimit(result: SearchResponse, limit?: number): SearchRespons
|
|
|
643
876
|
|
|
644
877
|
/** Execute Perplexity web search */
|
|
645
878
|
export async function searchPerplexity(params: PerplexitySearchParams): Promise<SearchResponse> {
|
|
646
|
-
const auth = await findPerplexityAuth(params.authStorage, params.sessionId, params.signal);
|
|
647
|
-
|
|
648
|
-
if (auth.type !== "api_key") {
|
|
649
|
-
// OAuth bearer mode routes the whole authenticated unit (the ask
|
|
650
|
-
// session/SSE request) through the central auth-retry policy so a 401 or
|
|
651
|
-
// usage-limit force-refreshes, then rotates to a sibling credential.
|
|
652
|
-
// Cookie/env/anonymous modes have no rotatable credential — untouched.
|
|
653
|
-
const askResult =
|
|
654
|
-
auth.type === "oauth"
|
|
655
|
-
? await withOAuthAccess(
|
|
656
|
-
params.authStorage,
|
|
657
|
-
"perplexity",
|
|
658
|
-
access => callPerplexityAsk({ type: "oauth", token: access.accessToken }, params),
|
|
659
|
-
{ sessionId: params.sessionId, signal: params.signal, seed: auth.access },
|
|
660
|
-
)
|
|
661
|
-
: await callPerplexityAsk(auth, params);
|
|
662
|
-
return applySourceLimit(
|
|
663
|
-
{
|
|
664
|
-
provider: "perplexity",
|
|
665
|
-
answer: askResult.answer || undefined,
|
|
666
|
-
sources: askResult.sources,
|
|
667
|
-
model: askResult.model,
|
|
668
|
-
requestId: askResult.requestId,
|
|
669
|
-
authMode: auth.type === "anonymous" ? "anonymous" : "oauth",
|
|
670
|
-
},
|
|
671
|
-
params.num_results,
|
|
672
|
-
);
|
|
673
|
-
}
|
|
674
|
-
|
|
675
879
|
const systemPrompt = params.system_prompt;
|
|
676
880
|
const messages: PerplexityRequest["messages"] = [];
|
|
677
881
|
if (systemPrompt) {
|
|
@@ -700,10 +904,51 @@ export async function searchPerplexity(params: PerplexitySearchParams): Promise<
|
|
|
700
904
|
request.search_recency_filter = params.search_recency_filter;
|
|
701
905
|
}
|
|
702
906
|
|
|
703
|
-
const
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
907
|
+
const authMethods = await getAvailableAuthMethods(params.authStorage, params.sessionId, params.signal);
|
|
908
|
+
let lastError: unknown;
|
|
909
|
+
|
|
910
|
+
for (const auth of authMethods) {
|
|
911
|
+
if (auth.type === "api_key") {
|
|
912
|
+
try {
|
|
913
|
+
const result = await callPerplexityApi(auth, request, params.fetch, params.signal);
|
|
914
|
+
result.authMode = "api_key";
|
|
915
|
+
return applySourceLimit(result, params.num_results);
|
|
916
|
+
} catch (error) {
|
|
917
|
+
if (params.signal?.aborted) throw error;
|
|
918
|
+
lastError = error;
|
|
919
|
+
}
|
|
920
|
+
} else {
|
|
921
|
+
// Use OAuth/cookies/anonymous path
|
|
922
|
+
try {
|
|
923
|
+
const askResult =
|
|
924
|
+
auth.type === "oauth"
|
|
925
|
+
? await withOAuthAccess(
|
|
926
|
+
params.authStorage,
|
|
927
|
+
"perplexity",
|
|
928
|
+
access => callPerplexityAsk({ type: "oauth", token: access.accessToken }, params),
|
|
929
|
+
{ sessionId: params.sessionId, signal: params.signal, seed: auth.access },
|
|
930
|
+
)
|
|
931
|
+
: await callPerplexityAsk(auth, params);
|
|
932
|
+
return applySourceLimit(
|
|
933
|
+
{
|
|
934
|
+
provider: "perplexity",
|
|
935
|
+
answer: askResult.answer || undefined,
|
|
936
|
+
sources: askResult.sources,
|
|
937
|
+
model: askResult.model,
|
|
938
|
+
requestId: askResult.requestId,
|
|
939
|
+
authMode: auth.type === "anonymous" ? "anonymous" : "oauth",
|
|
940
|
+
},
|
|
941
|
+
params.num_results,
|
|
942
|
+
);
|
|
943
|
+
} catch (error) {
|
|
944
|
+
if (params.signal?.aborted) throw error;
|
|
945
|
+
lastError = error;
|
|
946
|
+
}
|
|
947
|
+
}
|
|
948
|
+
}
|
|
949
|
+
|
|
950
|
+
if (lastError) throw lastError;
|
|
951
|
+
throw new SearchProviderError("perplexity", "No authentication method available.", 401);
|
|
707
952
|
}
|
|
708
953
|
|
|
709
954
|
/** Search provider for Perplexity. */
|
|
@@ -712,7 +957,9 @@ export class PerplexityProvider extends SearchProvider {
|
|
|
712
957
|
readonly label = "Perplexity";
|
|
713
958
|
|
|
714
959
|
isAvailable(authStorage: AuthStorage): boolean {
|
|
715
|
-
return
|
|
960
|
+
return (
|
|
961
|
+
!!$env.PERPLEXITY_COOKIES?.trim() || authStorage.hasAuth("perplexity") || authStorage.hasAuth("openrouter")
|
|
962
|
+
);
|
|
716
963
|
}
|
|
717
964
|
|
|
718
965
|
/**
|