@oh-my-pi/pi-coding-agent 16.1.2 → 16.1.4
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 +44 -1
- package/dist/cli.js +2990 -2991
- package/dist/types/config/model-resolver.d.ts +3 -3
- package/dist/types/mnemopi/embed-client.d.ts +70 -0
- package/dist/types/mnemopi/embed-protocol.d.ts +52 -0
- package/dist/types/mnemopi/embed-worker.d.ts +12 -0
- package/dist/types/mnemopi/state.d.ts +9 -1
- package/dist/types/modes/components/cache-invalidation-marker.d.ts +23 -10
- package/dist/types/modes/components/status-line/component.d.ts +2 -3
- package/dist/types/sdk.d.ts +12 -0
- package/dist/types/session/agent-session.d.ts +2 -0
- package/dist/types/session/agent-storage.d.ts +2 -0
- package/dist/types/session/auth-broker-config.d.ts +3 -2
- package/dist/types/session/history-storage.d.ts +1 -1
- package/dist/types/session/tool-choice-queue.d.ts +2 -0
- package/dist/types/tools/image-gen.d.ts +2 -2
- package/dist/types/tools/index.d.ts +2 -0
- package/dist/types/tui/hyperlink.d.ts +3 -2
- package/dist/types/utils/image-loading.d.ts +1 -1
- package/dist/types/utils/ipc.d.ts +22 -0
- package/dist/types/web/search/providers/perplexity-auth.d.ts +37 -0
- package/package.json +12 -12
- package/src/cli/bench-cli.ts +33 -2
- package/src/cli/dry-balance-cli.ts +4 -2
- package/src/cli.ts +8 -0
- package/src/commands/token.ts +52 -33
- package/src/config/append-only-context-mode.ts +45 -0
- package/src/config/model-discovery.ts +3 -0
- package/src/config/model-registry.ts +21 -3
- package/src/config/model-resolver.ts +31 -8
- package/src/discovery/builtin-rules/ts-no-return-type.md +0 -1
- package/src/extensibility/plugins/manager.ts +82 -22
- package/src/lsp/client.ts +24 -0
- package/src/mnemopi/backend.ts +49 -3
- package/src/mnemopi/embed-client.ts +401 -0
- package/src/mnemopi/embed-protocol.ts +35 -0
- package/src/mnemopi/embed-worker.ts +113 -0
- package/src/mnemopi/state.ts +29 -1
- package/src/modes/components/cache-invalidation-marker.ts +31 -15
- package/src/modes/components/custom-editor.test.ts +4 -3
- package/src/modes/components/custom-editor.ts +1 -1
- package/src/modes/components/model-selector.ts +2 -2
- package/src/modes/components/status-line/component.ts +64 -18
- package/src/modes/components/welcome.ts +1 -1
- package/src/modes/controllers/event-controller.ts +8 -0
- package/src/modes/controllers/selector-controller.ts +2 -2
- package/src/modes/theme/theme.ts +69 -0
- package/src/sdk.ts +37 -0
- package/src/session/agent-session.ts +13 -0
- package/src/session/agent-storage.ts +14 -0
- package/src/session/auth-broker-config.ts +2 -1
- package/src/session/history-storage.ts +13 -1
- package/src/session/tool-choice-queue.ts +6 -0
- package/src/stt/asr-client.ts +2 -7
- package/src/tiny/title-client.ts +2 -7
- package/src/tools/image-gen.ts +4 -8
- package/src/tools/index.ts +2 -0
- package/src/tools/render-utils.ts +4 -1
- package/src/tools/resolve.ts +1 -0
- package/src/tts/tts-client.ts +2 -7
- package/src/tui/hyperlink.ts +6 -3
- package/src/utils/image-loading.ts +12 -2
- package/src/utils/ipc.ts +38 -0
- package/src/web/search/providers/perplexity-auth.ts +133 -0
- package/src/web/search/providers/perplexity.ts +2 -125
package/src/tui/hyperlink.ts
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
*/
|
|
8
8
|
import * as url from "node:url";
|
|
9
9
|
import { TERMINAL } from "@oh-my-pi/pi-tui";
|
|
10
|
-
import { settings } from "../config/settings";
|
|
10
|
+
import { isSettingsInitialized, settings } from "../config/settings";
|
|
11
11
|
import {
|
|
12
12
|
LocalProtocolHandler,
|
|
13
13
|
memoryRootsFromRegistry,
|
|
@@ -45,8 +45,10 @@ function buildFileUri(filePath: string, opts?: { line?: number; col?: number }):
|
|
|
45
45
|
* - `"off"`: never
|
|
46
46
|
* - `"auto"`: when `process.stdout.isTTY`, `NO_COLOR` is unset, and the detected terminal reports hyperlink support
|
|
47
47
|
* - `"always"`: unconditionally (useful for viewers that support OSC 8 without advertising it)
|
|
48
|
+
* Before settings initialization, returns false so early render paths stay plain text.
|
|
48
49
|
*/
|
|
49
50
|
export function isHyperlinkEnabled(): boolean {
|
|
51
|
+
if (!isSettingsInitialized()) return false;
|
|
50
52
|
const mode = settings.get("tui.hyperlinks");
|
|
51
53
|
if (mode === "off") return false;
|
|
52
54
|
if (mode === "always") return true;
|
|
@@ -104,10 +106,11 @@ export function urlHyperlink(url: string, displayText: string): string {
|
|
|
104
106
|
* Wrap `displayText` in an OSC 8 hyperlink pointing at an HTTP(S) URL,
|
|
105
107
|
* bypassing terminal capability auto-detection. Used for auth prompts where
|
|
106
108
|
* an inert "click" label blocks login on terminals whose capabilities are
|
|
107
|
-
* not advertised. Still returns plain text
|
|
108
|
-
* opted out via `tui.hyperlinks=off`.
|
|
109
|
+
* not advertised. Still returns plain text before settings initialization or
|
|
110
|
+
* when the user has explicitly opted out via `tui.hyperlinks=off`.
|
|
109
111
|
*/
|
|
110
112
|
export function urlHyperlinkAlways(url: string, displayText: string): string {
|
|
113
|
+
if (!isSettingsInitialized()) return displayText;
|
|
111
114
|
if (settings.get("tui.hyperlinks") === "off") return displayText;
|
|
112
115
|
const normalized = url.match(/^www\./i) ? `https://${url}` : url;
|
|
113
116
|
try {
|
|
@@ -13,9 +13,19 @@ export const SUPPORTED_INPUT_IMAGE_MIME_TYPES = SUPPORTED_IMAGE_MIME_TYPES;
|
|
|
13
13
|
* with an opaque HTTP 400. Detect those models so the resize pipeline encodes
|
|
14
14
|
* to PNG/JPEG instead — the automatic equivalent of `OMP_NO_WEBP=1`.
|
|
15
15
|
*/
|
|
16
|
-
export function modelLacksWebpSupport(
|
|
16
|
+
export function modelLacksWebpSupport(
|
|
17
|
+
model: Pick<Model, "provider" | "api" | "imageInputDecoder"> | undefined,
|
|
18
|
+
): boolean {
|
|
17
19
|
if (!model) return false;
|
|
18
|
-
return
|
|
20
|
+
return (
|
|
21
|
+
model.imageInputDecoder === "stb" ||
|
|
22
|
+
model.provider === "ollama" ||
|
|
23
|
+
model.provider === "ollama-cloud" ||
|
|
24
|
+
model.provider === "llama.cpp" ||
|
|
25
|
+
model.provider === "lm-studio" ||
|
|
26
|
+
model.provider === "local-server" ||
|
|
27
|
+
model.api === "ollama-chat"
|
|
28
|
+
);
|
|
19
29
|
}
|
|
20
30
|
|
|
21
31
|
/**
|
package/src/utils/ipc.ts
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { logger } from "@oh-my-pi/pi-utils";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Narrow a value to a thenable so a rejection handler can be attached.
|
|
5
|
+
*
|
|
6
|
+
* Mirrors the local helper in `mcp/transports/stdio.ts` (kept separate because
|
|
7
|
+
* that copy serves the FileSink stdin-write path and is battle-tested there).
|
|
8
|
+
* This shared copy is the home for the IPC `send()` sites.
|
|
9
|
+
*/
|
|
10
|
+
export function isThenable(value: unknown): value is PromiseLike<unknown> {
|
|
11
|
+
return (
|
|
12
|
+
value != null &&
|
|
13
|
+
(typeof value === "object" || typeof value === "function") &&
|
|
14
|
+
typeof (value as { then?: unknown }).then === "function"
|
|
15
|
+
);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Send a message to a Bun subprocess over IPC, neutralizing both the
|
|
20
|
+
* synchronous throw ("cannot be used after the process has exited") and any
|
|
21
|
+
* asynchronous rejection (EPIPE from a pipe that broke between exit being
|
|
22
|
+
* observed and the next `send()`). The dead worker is detected separately via
|
|
23
|
+
* `onExit`/`onError` and respawned or disabled by the owning client; an
|
|
24
|
+
* un-awaited EPIPE rejection must not escape as a fatal unhandled rejection
|
|
25
|
+
* that takes down the whole session. See issue #2997.
|
|
26
|
+
*
|
|
27
|
+
* `label` prefixes the debug log on synchronous failure (e.g. "tts").
|
|
28
|
+
*/
|
|
29
|
+
export function safeSend(proc: { send(message: unknown): unknown }, message: unknown, label: string): void {
|
|
30
|
+
try {
|
|
31
|
+
const result = proc.send(message);
|
|
32
|
+
if (isThenable(result)) result.then(undefined, () => {});
|
|
33
|
+
} catch (error) {
|
|
34
|
+
logger.debug(`${label}: send to subprocess failed`, {
|
|
35
|
+
error: error instanceof Error ? error.message : String(error),
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
}
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
import type { AuthStorage, OAuthAccess } from "@oh-my-pi/pi-ai";
|
|
2
|
+
import { $env } from "@oh-my-pi/pi-utils";
|
|
3
|
+
|
|
4
|
+
export const PERPLEXITY_CHAT_BASE_URL = "https://api.perplexity.ai";
|
|
5
|
+
export const PERPLEXITY_RESPONSES_BASE_URL = "https://api.perplexity.ai/v1";
|
|
6
|
+
export const OPENROUTER_BASE_URL = "https://openrouter.ai/api/v1";
|
|
7
|
+
export const OAUTH_EXPIRY_BUFFER_MS = 5 * 60 * 1000;
|
|
8
|
+
|
|
9
|
+
export interface ApiConfig {
|
|
10
|
+
type: "api_key";
|
|
11
|
+
apiKey: string;
|
|
12
|
+
provider: "perplexity" | "openrouter";
|
|
13
|
+
chatBaseUrl: string;
|
|
14
|
+
responsesBaseUrl: string;
|
|
15
|
+
modelPrefix: string;
|
|
16
|
+
useResponses: boolean;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export type PerplexityAuth =
|
|
20
|
+
| ApiConfig
|
|
21
|
+
| {
|
|
22
|
+
type: "oauth";
|
|
23
|
+
access: OAuthAccess;
|
|
24
|
+
}
|
|
25
|
+
| {
|
|
26
|
+
type: "cookies";
|
|
27
|
+
cookies: string;
|
|
28
|
+
}
|
|
29
|
+
| {
|
|
30
|
+
type: "anonymous";
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
export interface PerplexityAuthOptions {
|
|
34
|
+
signal?: AbortSignal;
|
|
35
|
+
forceRefresh?: boolean;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/** Detect API-key endpoints to try in priority order (Perplexity direct, then OpenRouter). */
|
|
39
|
+
export async function getApiConfigs(
|
|
40
|
+
authStorage: AuthStorage,
|
|
41
|
+
sessionId: string | undefined,
|
|
42
|
+
options?: PerplexityAuthOptions,
|
|
43
|
+
): Promise<ApiConfig[]> {
|
|
44
|
+
const useResponses = $env.PI_PERPLEXITY_RESPONSES === "1";
|
|
45
|
+
const configs: ApiConfig[] = [];
|
|
46
|
+
|
|
47
|
+
const perplexityKey = await authStorage.getApiKey("perplexity", sessionId, options);
|
|
48
|
+
if (perplexityKey) {
|
|
49
|
+
configs.push({
|
|
50
|
+
type: "api_key",
|
|
51
|
+
apiKey: perplexityKey,
|
|
52
|
+
provider: "perplexity",
|
|
53
|
+
chatBaseUrl: PERPLEXITY_CHAT_BASE_URL,
|
|
54
|
+
responsesBaseUrl: PERPLEXITY_RESPONSES_BASE_URL,
|
|
55
|
+
modelPrefix: "",
|
|
56
|
+
useResponses,
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
const openrouterKey = await authStorage.getApiKey("openrouter", sessionId, options);
|
|
61
|
+
if (openrouterKey) {
|
|
62
|
+
configs.push({
|
|
63
|
+
type: "api_key",
|
|
64
|
+
apiKey: openrouterKey,
|
|
65
|
+
provider: "openrouter",
|
|
66
|
+
chatBaseUrl: OPENROUTER_BASE_URL,
|
|
67
|
+
responsesBaseUrl: OPENROUTER_BASE_URL,
|
|
68
|
+
modelPrefix: "perplexity/",
|
|
69
|
+
useResponses,
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
return configs;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Decode a Perplexity JWT's `exp` claim, in ms. Returns `undefined` when the
|
|
78
|
+
* token has no `exp` (which is the common case — Perplexity sessions are
|
|
79
|
+
* server-side and effectively non-expiring from the client's POV).
|
|
80
|
+
*/
|
|
81
|
+
export function jwtExpiryMs(token: string): number | undefined {
|
|
82
|
+
const parts = token.split(".");
|
|
83
|
+
if (parts.length !== 3) return undefined;
|
|
84
|
+
const payload = parts[1];
|
|
85
|
+
if (!payload) return undefined;
|
|
86
|
+
try {
|
|
87
|
+
const decoded = JSON.parse(Buffer.from(payload, "base64url").toString("utf8")) as { exp?: unknown };
|
|
88
|
+
if (typeof decoded.exp !== "number" || !Number.isFinite(decoded.exp)) return undefined;
|
|
89
|
+
return decoded.exp * 1000;
|
|
90
|
+
} catch {
|
|
91
|
+
return undefined;
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/** Collect all available auth methods to try in priority order */
|
|
96
|
+
export async function getAvailableAuthMethods(
|
|
97
|
+
authStorage: AuthStorage,
|
|
98
|
+
sessionId: string | undefined,
|
|
99
|
+
options?: PerplexityAuthOptions,
|
|
100
|
+
): Promise<PerplexityAuth[]> {
|
|
101
|
+
const methods: PerplexityAuth[] = [];
|
|
102
|
+
|
|
103
|
+
// 1. Cookies take precedence over OAuth as noted in comments/docs
|
|
104
|
+
const cookies = $env.PERPLEXITY_COOKIES?.trim();
|
|
105
|
+
if (cookies) {
|
|
106
|
+
methods.push({ type: "cookies", cookies });
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// 2. Perplexity OAuth (session bearer)
|
|
110
|
+
try {
|
|
111
|
+
const access = await authStorage.getOAuthAccess("perplexity", sessionId, options);
|
|
112
|
+
const token = access?.accessToken;
|
|
113
|
+
if (access && token) {
|
|
114
|
+
const jwtExpiry = jwtExpiryMs(token);
|
|
115
|
+
if (jwtExpiry === undefined || jwtExpiry > Date.now() + OAUTH_EXPIRY_BUFFER_MS) {
|
|
116
|
+
methods.push({ type: "oauth", access });
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
} catch {
|
|
120
|
+
// ignored
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// 3. API key configs (direct, then openrouter)
|
|
124
|
+
const apiConfigs = await getApiConfigs(authStorage, sessionId, options);
|
|
125
|
+
methods.push(...apiConfigs);
|
|
126
|
+
|
|
127
|
+
// 4. Fallback to Perplexity free (anonymous)
|
|
128
|
+
if (methods.length === 0) {
|
|
129
|
+
methods.push({ type: "anonymous" });
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
return methods;
|
|
133
|
+
}
|
|
@@ -14,7 +14,6 @@ import {
|
|
|
14
14
|
type AuthStorage,
|
|
15
15
|
type Context,
|
|
16
16
|
type FetchImpl,
|
|
17
|
-
type OAuthAccess,
|
|
18
17
|
type Usage,
|
|
19
18
|
withOAuthAccess,
|
|
20
19
|
} from "@oh-my-pi/pi-ai";
|
|
@@ -34,36 +33,19 @@ import { SearchProviderError } from "../../../web/search/types";
|
|
|
34
33
|
import { dateToAgeSeconds } from "../utils";
|
|
35
34
|
import type { SearchParams } from "./base";
|
|
36
35
|
import { SearchProvider } from "./base";
|
|
36
|
+
import { type ApiConfig, getAvailableAuthMethods } from "./perplexity-auth";
|
|
37
37
|
import { classifyProviderHttpError, withHardTimeout } from "./utils";
|
|
38
38
|
|
|
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";
|
|
42
39
|
const PERPLEXITY_OAUTH_ASK_URL = "https://www.perplexity.ai/rest/sse/perplexity_ask";
|
|
43
40
|
|
|
44
41
|
const DEFAULT_MAX_TOKENS = 8192;
|
|
45
42
|
const DEFAULT_TEMPERATURE = 0.2;
|
|
46
43
|
const DEFAULT_NUM_SEARCH_RESULTS = 20;
|
|
47
|
-
const OAUTH_EXPIRY_BUFFER_MS = 5 * 60 * 1000;
|
|
48
44
|
const OAUTH_API_VERSION = "2.18";
|
|
49
45
|
const OAUTH_USER_AGENT = "Perplexity/641 CFNetwork/1568 Darwin/25.2.0";
|
|
50
46
|
const ANONYMOUS_USER_AGENT =
|
|
51
47
|
"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";
|
|
52
48
|
|
|
53
|
-
type PerplexityAuth =
|
|
54
|
-
| ApiConfig
|
|
55
|
-
| {
|
|
56
|
-
type: "oauth";
|
|
57
|
-
access: OAuthAccess;
|
|
58
|
-
}
|
|
59
|
-
| {
|
|
60
|
-
type: "cookies";
|
|
61
|
-
cookies: string;
|
|
62
|
-
}
|
|
63
|
-
| {
|
|
64
|
-
type: "anonymous";
|
|
65
|
-
};
|
|
66
|
-
|
|
67
49
|
interface PerplexityOAuthStreamMarkdownBlock {
|
|
68
50
|
answer?: string;
|
|
69
51
|
chunks?: string[];
|
|
@@ -289,111 +271,6 @@ export interface PerplexitySearchParams {
|
|
|
289
271
|
fetch?: FetchImpl;
|
|
290
272
|
}
|
|
291
273
|
|
|
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;
|
|
338
|
-
}
|
|
339
|
-
|
|
340
|
-
/**
|
|
341
|
-
* Decode a Perplexity JWT's `exp` claim, in ms. Returns `undefined` when the
|
|
342
|
-
* token has no `exp` (which is the common case — Perplexity sessions are
|
|
343
|
-
* server-side and effectively non-expiring from the client's POV).
|
|
344
|
-
*/
|
|
345
|
-
function jwtExpiryMs(token: string): number | undefined {
|
|
346
|
-
const parts = token.split(".");
|
|
347
|
-
if (parts.length !== 3) return undefined;
|
|
348
|
-
const payload = parts[1];
|
|
349
|
-
if (!payload) return undefined;
|
|
350
|
-
try {
|
|
351
|
-
const decoded = JSON.parse(Buffer.from(payload, "base64url").toString("utf8")) as { exp?: unknown };
|
|
352
|
-
if (typeof decoded.exp !== "number" || !Number.isFinite(decoded.exp)) return undefined;
|
|
353
|
-
return decoded.exp * 1000;
|
|
354
|
-
} catch {
|
|
355
|
-
return undefined;
|
|
356
|
-
}
|
|
357
|
-
}
|
|
358
|
-
|
|
359
|
-
/** Collect all available auth methods to try in priority order */
|
|
360
|
-
async function getAvailableAuthMethods(
|
|
361
|
-
authStorage: AuthStorage,
|
|
362
|
-
sessionId: string | undefined,
|
|
363
|
-
signal: AbortSignal | undefined,
|
|
364
|
-
): Promise<PerplexityAuth[]> {
|
|
365
|
-
const methods: PerplexityAuth[] = [];
|
|
366
|
-
|
|
367
|
-
// 1. Perplexity OAuth & Cookies (same priority - highest)
|
|
368
|
-
try {
|
|
369
|
-
const access = await authStorage.getOAuthAccess("perplexity", sessionId, { signal });
|
|
370
|
-
const token = access?.accessToken;
|
|
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
|
-
}
|
|
377
|
-
} catch {
|
|
378
|
-
// ignored
|
|
379
|
-
}
|
|
380
|
-
|
|
381
|
-
const cookies = $env.PERPLEXITY_COOKIES?.trim();
|
|
382
|
-
if (cookies) {
|
|
383
|
-
methods.push({ type: "cookies", cookies });
|
|
384
|
-
}
|
|
385
|
-
|
|
386
|
-
const apiConfigs = await getApiConfigs(authStorage, sessionId, signal);
|
|
387
|
-
methods.push(...apiConfigs);
|
|
388
|
-
|
|
389
|
-
// 5. Fallback to Perplexity free (anonymous)
|
|
390
|
-
if (methods.length === 0) {
|
|
391
|
-
methods.push({ type: "anonymous" });
|
|
392
|
-
}
|
|
393
|
-
|
|
394
|
-
return methods;
|
|
395
|
-
}
|
|
396
|
-
|
|
397
274
|
interface PerplexityApiStreamMetadata {
|
|
398
275
|
id?: string;
|
|
399
276
|
model?: string;
|
|
@@ -904,7 +781,7 @@ export async function searchPerplexity(params: PerplexitySearchParams): Promise<
|
|
|
904
781
|
request.search_recency_filter = params.search_recency_filter;
|
|
905
782
|
}
|
|
906
783
|
|
|
907
|
-
const authMethods = await getAvailableAuthMethods(params.authStorage, params.sessionId, params.signal);
|
|
784
|
+
const authMethods = await getAvailableAuthMethods(params.authStorage, params.sessionId, { signal: params.signal });
|
|
908
785
|
let lastError: unknown;
|
|
909
786
|
|
|
910
787
|
for (const auth of authMethods) {
|