@oh-my-pi/pi-coding-agent 15.11.3 → 15.11.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 +54 -0
- package/dist/cli.js +353 -294
- package/dist/types/config/api-key-resolver.d.ts +9 -3
- package/dist/types/config/keybindings.d.ts +1 -1
- package/dist/types/config/model-discovery.d.ts +6 -4
- package/dist/types/config/model-registry.d.ts +7 -4
- package/dist/types/config/settings-schema.d.ts +458 -155
- package/dist/types/export/html/template.generated.d.ts +1 -1
- package/dist/types/mnemopi/config.d.ts +3 -1
- package/dist/types/modes/components/settings-defs.d.ts +9 -2
- package/dist/types/modes/components/settings-selector.d.ts +9 -4
- package/dist/types/modes/components/tool-execution.d.ts +12 -1
- package/dist/types/modes/components/transcript-container.d.ts +12 -0
- package/dist/types/modes/controllers/input-controller.d.ts +9 -1
- package/dist/types/modes/theme/theme.d.ts +23 -3
- package/dist/types/session/agent-session.d.ts +14 -7
- package/dist/types/session/auth-storage.d.ts +1 -1
- package/dist/types/session/snapcompact-inline.d.ts +28 -0
- package/dist/types/slash-commands/helpers/active-oauth-account.d.ts +14 -0
- package/dist/types/system-prompt.d.ts +3 -1
- package/dist/types/task/render.d.ts +16 -6
- package/dist/types/tools/gh.d.ts +3 -0
- package/dist/types/tools/render-utils.d.ts +8 -16
- package/dist/types/utils/session-color.d.ts +15 -3
- package/dist/types/web/kagi.d.ts +1 -2
- package/dist/types/web/search/providers/codex.d.ts +1 -1
- package/dist/types/web/search/providers/gemini.d.ts +9 -6
- package/package.json +11 -11
- package/src/auto-thinking/classifier.ts +1 -5
- package/src/commit/model-selection.ts +3 -6
- package/src/config/api-key-resolver.ts +10 -3
- package/src/config/keybindings.ts +1 -1
- package/src/config/model-discovery.ts +60 -46
- package/src/config/model-registry.ts +21 -8
- package/src/config/model-resolver.ts +57 -3
- package/src/config/settings-schema.ts +601 -153
- package/src/eval/completion-bridge.ts +1 -5
- package/src/export/html/template.generated.ts +1 -1
- package/src/export/html/template.js +13 -6
- package/src/internal-urls/docs-index.generated.ts +5 -5
- package/src/internal-urls/issue-pr-protocol.ts +10 -4
- package/src/memories/index.ts +2 -10
- package/src/mnemopi/backend.ts +30 -8
- package/src/mnemopi/config.ts +6 -1
- package/src/mnemopi/state.ts +6 -0
- package/src/modes/components/extensions/inspector-panel.ts +6 -2
- package/src/modes/components/plan-review-overlay.ts +15 -17
- package/src/modes/components/plugin-settings.ts +22 -5
- package/src/modes/components/settings-defs.ts +19 -4
- package/src/modes/components/settings-selector.ts +493 -93
- package/src/modes/components/status-line/component.ts +3 -1
- package/src/modes/components/status-line/segments.ts +3 -1
- package/src/modes/components/tool-execution.ts +69 -12
- package/src/modes/components/transcript-container.ts +26 -0
- package/src/modes/components/tree-selector.ts +16 -6
- package/src/modes/controllers/command-controller.ts +37 -7
- package/src/modes/controllers/event-controller.ts +1 -0
- package/src/modes/controllers/input-controller.ts +68 -6
- package/src/modes/controllers/selector-controller.ts +81 -61
- package/src/modes/interactive-mode.ts +4 -2
- package/src/modes/rpc/rpc-mode.ts +2 -1
- package/src/modes/shared.ts +2 -0
- package/src/modes/theme/theme.ts +100 -7
- package/src/modes/utils/context-usage.ts +3 -1
- package/src/modes/utils/hotkeys-markdown.ts +1 -1
- package/src/modes/utils/ui-helpers.ts +9 -5
- package/src/prompts/system/personalities/default.md +26 -0
- package/src/prompts/system/personalities/friendly.md +17 -0
- package/src/prompts/system/personalities/pragmatic.md +15 -0
- package/src/prompts/system/snapcompact-system-frames-note.md +1 -0
- package/src/prompts/system/snapcompact-system-stub.md +1 -0
- package/src/prompts/system/snapcompact-toolresult-note.md +1 -0
- package/src/prompts/system/system-prompt.md +5 -22
- package/src/prompts/tools/task.md +3 -3
- package/src/sdk.ts +22 -1
- package/src/session/agent-session.ts +91 -24
- package/src/session/auth-storage.ts +1 -0
- package/src/session/session-dump-format.ts +8 -1
- package/src/session/session-manager.ts +5 -5
- package/src/session/snapcompact-inline.ts +187 -0
- package/src/slash-commands/helpers/active-oauth-account.ts +44 -0
- package/src/slash-commands/helpers/usage-report.ts +24 -3
- package/src/system-prompt.ts +15 -1
- package/src/task/render.ts +29 -19
- package/src/tool-discovery/tool-index.ts +2 -0
- package/src/tools/bash.ts +10 -3
- package/src/tools/eval-render.ts +13 -8
- package/src/tools/gh.ts +39 -1
- package/src/tools/image-gen.ts +114 -78
- package/src/tools/inspect-image.ts +1 -5
- package/src/tools/job.ts +25 -5
- package/src/tools/read.ts +1 -57
- package/src/tools/render-utils.ts +29 -31
- package/src/tools/ssh.ts +3 -3
- package/src/tools/tts.ts +40 -20
- package/src/utils/clipboard.ts +56 -4
- package/src/utils/commit-message-generator.ts +1 -5
- package/src/utils/session-color.ts +83 -9
- package/src/utils/title-generator.ts +1 -1
- package/src/web/kagi.ts +26 -27
- package/src/web/search/providers/codex.ts +42 -40
- package/src/web/search/providers/gemini.ts +42 -22
- package/src/web/search/providers/perplexity.ts +22 -10
package/src/web/kagi.ts
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
* through the shared {@link AuthStorage} broker (Bearer token), and responses
|
|
7
7
|
* are categorized result buckets rather than the legacy flat object array.
|
|
8
8
|
*/
|
|
9
|
-
import type
|
|
9
|
+
import { type AuthStorage, type FetchImpl, withAuth } from "@oh-my-pi/pi-ai";
|
|
10
10
|
import { withHardTimeout } from "./search/providers/utils";
|
|
11
11
|
|
|
12
12
|
const KAGI_SEARCH_URL = "https://kagi.com/api/v1/search";
|
|
@@ -173,14 +173,6 @@ export interface KagiSearchResult {
|
|
|
173
173
|
answer?: string;
|
|
174
174
|
}
|
|
175
175
|
|
|
176
|
-
export async function findKagiApiKey(
|
|
177
|
-
authStorage: AuthStorage,
|
|
178
|
-
sessionId?: string,
|
|
179
|
-
signal?: AbortSignal,
|
|
180
|
-
): Promise<string | null> {
|
|
181
|
-
return (await authStorage.getApiKey("kagi", sessionId, { signal })) ?? null;
|
|
182
|
-
}
|
|
183
|
-
|
|
184
176
|
/**
|
|
185
177
|
* Compute a YYYY-MM-DD date string `recency` units before now, in UTC.
|
|
186
178
|
* UTC keeps the recency window deterministic regardless of host timezone and
|
|
@@ -247,27 +239,34 @@ export async function searchWithKagi(
|
|
|
247
239
|
options: KagiSearchOptions = {},
|
|
248
240
|
authStorage: AuthStorage,
|
|
249
241
|
): Promise<KagiSearchResult> {
|
|
250
|
-
const apiKey = await findKagiApiKey(authStorage, options.sessionId, options.signal);
|
|
251
|
-
if (!apiKey) {
|
|
252
|
-
throw new KagiApiError("Kagi credentials not found. Set KAGI_API_KEY or login with 'omp /login kagi'.");
|
|
253
|
-
}
|
|
254
|
-
|
|
255
242
|
const fetchImpl = options.fetch ?? fetch;
|
|
243
|
+
const body = JSON.stringify(buildRequestBody(query, options));
|
|
244
|
+
|
|
245
|
+
const response = await withAuth(
|
|
246
|
+
authStorage.resolver("kagi", { sessionId: options.sessionId }),
|
|
247
|
+
async apiKey => {
|
|
248
|
+
const res = await fetchImpl(KAGI_SEARCH_URL, {
|
|
249
|
+
method: "POST",
|
|
250
|
+
headers: {
|
|
251
|
+
Authorization: `Bearer ${apiKey}`,
|
|
252
|
+
"Content-Type": "application/json",
|
|
253
|
+
Accept: "application/json",
|
|
254
|
+
},
|
|
255
|
+
body,
|
|
256
|
+
signal: withHardTimeout(options.signal),
|
|
257
|
+
});
|
|
258
|
+
|
|
259
|
+
if (!res.ok) {
|
|
260
|
+
throw parseKagiErrorResponse(res.status, await res.text());
|
|
261
|
+
}
|
|
256
262
|
|
|
257
|
-
|
|
258
|
-
method: "POST",
|
|
259
|
-
headers: {
|
|
260
|
-
Authorization: `Bearer ${apiKey}`,
|
|
261
|
-
"Content-Type": "application/json",
|
|
262
|
-
Accept: "application/json",
|
|
263
|
+
return res;
|
|
263
264
|
},
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
throw parseKagiErrorResponse(response.status, await response.text());
|
|
270
|
-
}
|
|
265
|
+
{
|
|
266
|
+
signal: options.signal,
|
|
267
|
+
missingKeyMessage: "Kagi credentials not found. Set KAGI_API_KEY or login with 'omp /login kagi'.",
|
|
268
|
+
},
|
|
269
|
+
);
|
|
271
270
|
|
|
272
271
|
const payload = (await response.json()) as KagiSearchResponse;
|
|
273
272
|
if (payload.error && payload.error.length > 0) {
|
|
@@ -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
|
|
10
|
+
import { type AuthStorage, type FetchImpl, type OAuthAccess, withOAuthAccess } from "@oh-my-pi/pi-ai";
|
|
11
11
|
import { decodeJwt } from "@oh-my-pi/pi-ai/oauth/openai-codex";
|
|
12
12
|
import { getBundledModels } from "@oh-my-pi/pi-catalog/models";
|
|
13
13
|
import { $env, readSseJson } from "@oh-my-pi/pi-utils";
|
|
@@ -287,12 +287,12 @@ async function findCodexAuth(
|
|
|
287
287
|
authStorage: AuthStorage,
|
|
288
288
|
sessionId: string | undefined,
|
|
289
289
|
signal: AbortSignal | undefined,
|
|
290
|
-
): Promise<{
|
|
290
|
+
): Promise<{ access: OAuthAccess; accountId: string } | null> {
|
|
291
291
|
const access = await authStorage.getOAuthAccess("openai-codex", sessionId, { signal });
|
|
292
292
|
if (!access) return null;
|
|
293
293
|
const accountId = access.accountId ?? getAccountIdFromJwt(access.accessToken);
|
|
294
294
|
if (!accountId) return null;
|
|
295
|
-
return {
|
|
295
|
+
return { access, accountId };
|
|
296
296
|
}
|
|
297
297
|
|
|
298
298
|
/**
|
|
@@ -495,8 +495,8 @@ async function callCodexSearch(
|
|
|
495
495
|
* `gpt-5-codex-mini` first on ChatGPT accounts, which OpenAI rejects.
|
|
496
496
|
*/
|
|
497
497
|
export async function searchCodex(params: SearchParams): Promise<SearchResponse> {
|
|
498
|
-
const
|
|
499
|
-
if (!
|
|
498
|
+
const seed = await findCodexAuth(params.authStorage, params.sessionId, params.signal);
|
|
499
|
+
if (!seed) {
|
|
500
500
|
throw new Error(
|
|
501
501
|
"No Codex OAuth credentials found. Login with 'omp /login openai-codex' to enable Codex web search.",
|
|
502
502
|
);
|
|
@@ -505,42 +505,44 @@ export async function searchCodex(params: SearchParams): Promise<SearchResponse>
|
|
|
505
505
|
const configuredModel = getConfiguredModel();
|
|
506
506
|
const modelCandidates = configuredModel ? [configuredModel] : getDefaultModelCandidates();
|
|
507
507
|
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
for (let index = 0; index < modelCandidates.length; index += 1) {
|
|
520
|
-
const modelId = modelCandidates[index];
|
|
521
|
-
if (!modelId) continue;
|
|
522
|
-
|
|
523
|
-
try {
|
|
524
|
-
result = await callCodexSearch(auth, params.query, {
|
|
525
|
-
signal: params.signal,
|
|
526
|
-
systemPrompt: params.systemPrompt,
|
|
527
|
-
searchContextSize: "high",
|
|
528
|
-
modelId,
|
|
529
|
-
fetch: params.fetch,
|
|
530
|
-
});
|
|
531
|
-
break;
|
|
532
|
-
} catch (error) {
|
|
533
|
-
lastError = error;
|
|
534
|
-
const isLastCandidate = index === modelCandidates.length - 1;
|
|
535
|
-
if (configuredModel || isLastCandidate || !shouldRetryWithNextDefaultModel(error)) {
|
|
536
|
-
throw error;
|
|
508
|
+
const result = await withOAuthAccess(
|
|
509
|
+
params.authStorage,
|
|
510
|
+
"openai-codex",
|
|
511
|
+
async access => {
|
|
512
|
+
// Derive ALL auth material from the access this attempt received —
|
|
513
|
+
// a refreshed/rotated credential carries a different bearer and
|
|
514
|
+
// ChatGPT account id than the seed.
|
|
515
|
+
const accountId = access.accountId ?? getAccountIdFromJwt(access.accessToken);
|
|
516
|
+
if (!accountId) {
|
|
517
|
+
throw new Error("Codex OAuth credential is missing a ChatGPT account id");
|
|
537
518
|
}
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
519
|
+
const auth = { accessToken: access.accessToken, accountId };
|
|
520
|
+
|
|
521
|
+
let lastError: unknown;
|
|
522
|
+
for (let index = 0; index < modelCandidates.length; index += 1) {
|
|
523
|
+
const modelId = modelCandidates[index];
|
|
524
|
+
if (!modelId) continue;
|
|
525
|
+
|
|
526
|
+
try {
|
|
527
|
+
return await callCodexSearch(auth, params.query, {
|
|
528
|
+
signal: params.signal,
|
|
529
|
+
systemPrompt: params.systemPrompt,
|
|
530
|
+
searchContextSize: "high",
|
|
531
|
+
modelId,
|
|
532
|
+
fetch: params.fetch,
|
|
533
|
+
});
|
|
534
|
+
} catch (error) {
|
|
535
|
+
lastError = error;
|
|
536
|
+
const isLastCandidate = index === modelCandidates.length - 1;
|
|
537
|
+
if (configuredModel || isLastCandidate || !shouldRetryWithNextDefaultModel(error)) {
|
|
538
|
+
throw error;
|
|
539
|
+
}
|
|
540
|
+
}
|
|
541
|
+
}
|
|
542
|
+
throw lastError ?? new Error("Codex search failed without returning a result");
|
|
543
|
+
},
|
|
544
|
+
{ sessionId: params.sessionId, signal: params.signal, seed: seed.access },
|
|
545
|
+
);
|
|
544
546
|
|
|
545
547
|
let sources = result.sources;
|
|
546
548
|
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
* sibling SQLite store and never POSTs the broker sentinel to a Google token
|
|
9
9
|
* endpoint.
|
|
10
10
|
*/
|
|
11
|
-
import type
|
|
11
|
+
import { type AuthStorage, type FetchImpl, type OAuthAccess, withOAuthAccess } from "@oh-my-pi/pi-ai";
|
|
12
12
|
import {
|
|
13
13
|
ANTIGRAVITY_SYSTEM_INSTRUCTION,
|
|
14
14
|
getAntigravityUserAgent,
|
|
@@ -72,25 +72,29 @@ interface GeminiAuth {
|
|
|
72
72
|
isAntigravity: boolean;
|
|
73
73
|
}
|
|
74
74
|
|
|
75
|
+
/** First configured Gemini OAuth provider plus its pre-resolved access. */
|
|
76
|
+
interface GeminiAuthSeed {
|
|
77
|
+
provider: GeminiProviderId;
|
|
78
|
+
access: OAuthAccess;
|
|
79
|
+
projectId: string;
|
|
80
|
+
}
|
|
81
|
+
|
|
75
82
|
/**
|
|
76
83
|
* Walks the configured Gemini OAuth providers in deterministic order and
|
|
77
84
|
* returns the first one that yields a usable access token + projectId via
|
|
78
85
|
* {@link AuthStorage.getOAuthAccess}. AuthStorage handles refresh + broker
|
|
79
86
|
* routing internally; this helper never touches refresh tokens directly.
|
|
87
|
+
* The resolved access seeds `withOAuthAccess` so the happy path resolves once.
|
|
80
88
|
*/
|
|
81
89
|
export async function findGeminiAuth(
|
|
82
90
|
authStorage: AuthStorage,
|
|
83
91
|
sessionId: string | undefined,
|
|
84
92
|
signal: AbortSignal | undefined,
|
|
85
|
-
): Promise<
|
|
93
|
+
): Promise<GeminiAuthSeed | null> {
|
|
86
94
|
for (const provider of GEMINI_PROVIDERS) {
|
|
87
95
|
const access = await authStorage.getOAuthAccess(provider, sessionId, { signal });
|
|
88
96
|
if (!access?.accessToken || !access.projectId) continue;
|
|
89
|
-
return {
|
|
90
|
-
accessToken: access.accessToken,
|
|
91
|
-
projectId: access.projectId,
|
|
92
|
-
isAntigravity: provider === "google-antigravity",
|
|
93
|
-
};
|
|
97
|
+
return { provider, access, projectId: access.projectId };
|
|
94
98
|
}
|
|
95
99
|
return null;
|
|
96
100
|
}
|
|
@@ -390,26 +394,42 @@ async function callGeminiSearch(
|
|
|
390
394
|
* Executes a web search using Google Gemini with Google Search grounding.
|
|
391
395
|
*/
|
|
392
396
|
export async function searchGemini(params: GeminiSearchParams): Promise<SearchResponse> {
|
|
393
|
-
const
|
|
394
|
-
if (!
|
|
397
|
+
const seed = await findGeminiAuth(params.authStorage, params.sessionId, params.signal);
|
|
398
|
+
if (!seed) {
|
|
395
399
|
throw new Error(
|
|
396
400
|
"No Gemini OAuth credentials found. Login with 'omp /login google-gemini-cli' or 'omp /login google-antigravity' to enable Gemini web search.",
|
|
397
401
|
);
|
|
398
402
|
}
|
|
399
403
|
|
|
400
|
-
const
|
|
401
|
-
|
|
402
|
-
params.
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
404
|
+
const isAntigravity = seed.provider === "google-antigravity";
|
|
405
|
+
const result = await withOAuthAccess(
|
|
406
|
+
params.authStorage,
|
|
407
|
+
seed.provider,
|
|
408
|
+
access =>
|
|
409
|
+
// Derive bearer + projectId from the access this attempt received; a
|
|
410
|
+
// re-resolved access may omit projectId, in which case the seed's
|
|
411
|
+
// project is still the right tenant for the credential. The
|
|
412
|
+
// `fetchWithRetry` transport backoff stays INSIDE this attempt — auth
|
|
413
|
+
// retry wraps transport retry.
|
|
414
|
+
callGeminiSearch(
|
|
415
|
+
{
|
|
416
|
+
accessToken: access.accessToken,
|
|
417
|
+
projectId: access.projectId ?? seed.projectId,
|
|
418
|
+
isAntigravity,
|
|
419
|
+
},
|
|
420
|
+
params.query,
|
|
421
|
+
params.system_prompt,
|
|
422
|
+
params.max_output_tokens,
|
|
423
|
+
params.temperature,
|
|
424
|
+
{
|
|
425
|
+
google_search: params.google_search,
|
|
426
|
+
code_execution: params.code_execution,
|
|
427
|
+
url_context: params.url_context,
|
|
428
|
+
},
|
|
429
|
+
params.fetch,
|
|
430
|
+
params.signal,
|
|
431
|
+
),
|
|
432
|
+
{ sessionId: params.sessionId, signal: params.signal, seed: seed.access },
|
|
413
433
|
);
|
|
414
434
|
|
|
415
435
|
let sources = result.sources;
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
* - Anonymous via `www.perplexity.ai/rest/sse/perplexity_ask`
|
|
9
9
|
*/
|
|
10
10
|
|
|
11
|
-
import { type AuthStorage, type FetchImpl, getEnvApiKey } from "@oh-my-pi/pi-ai";
|
|
11
|
+
import { type AuthStorage, type FetchImpl, getEnvApiKey, type OAuthAccess, withOAuthAccess } from "@oh-my-pi/pi-ai";
|
|
12
12
|
import { $env, readSseJson } from "@oh-my-pi/pi-utils";
|
|
13
13
|
import type {
|
|
14
14
|
PerplexityMessageOutput,
|
|
@@ -43,7 +43,7 @@ type PerplexityAuth =
|
|
|
43
43
|
}
|
|
44
44
|
| {
|
|
45
45
|
type: "oauth";
|
|
46
|
-
|
|
46
|
+
access: OAuthAccess;
|
|
47
47
|
}
|
|
48
48
|
| {
|
|
49
49
|
type: "cookies";
|
|
@@ -302,11 +302,11 @@ function jwtExpiryMs(token: string): number | undefined {
|
|
|
302
302
|
}
|
|
303
303
|
}
|
|
304
304
|
|
|
305
|
-
async function
|
|
305
|
+
async function findOAuthAccess(
|
|
306
306
|
authStorage: AuthStorage,
|
|
307
307
|
sessionId: string | undefined,
|
|
308
308
|
signal: AbortSignal | undefined,
|
|
309
|
-
): Promise<
|
|
309
|
+
): Promise<OAuthAccess | null> {
|
|
310
310
|
try {
|
|
311
311
|
// `getOAuthAccess` returns the raw OAuth bearer only — runtime/config
|
|
312
312
|
// api_key overrides and stored api_key credentials are intentionally
|
|
@@ -314,12 +314,12 @@ async function findOAuthToken(
|
|
|
314
314
|
// `www.perplexity.ai` session/SSE endpoint.
|
|
315
315
|
const access = await authStorage.getOAuthAccess("perplexity", sessionId, { signal });
|
|
316
316
|
const token = access?.accessToken;
|
|
317
|
-
if (!token) return null;
|
|
317
|
+
if (!access || !token) return null;
|
|
318
318
|
// Trust the JWT's own `exp` claim if it has one; otherwise treat as
|
|
319
319
|
// non-expiring. Perplexity session JWTs commonly omit `exp`.
|
|
320
320
|
const jwtExpiry = jwtExpiryMs(token);
|
|
321
321
|
if (jwtExpiry !== undefined && jwtExpiry <= Date.now() + OAUTH_EXPIRY_BUFFER_MS) return null;
|
|
322
|
-
return
|
|
322
|
+
return access;
|
|
323
323
|
} catch {
|
|
324
324
|
return null;
|
|
325
325
|
}
|
|
@@ -339,9 +339,9 @@ async function findPerplexityAuth(
|
|
|
339
339
|
const apiKey = findApiKey();
|
|
340
340
|
|
|
341
341
|
// 2. OAuth/session bearer from AuthStorage.
|
|
342
|
-
const
|
|
343
|
-
if (
|
|
344
|
-
return { type: "oauth",
|
|
342
|
+
const oauthAccess = await findOAuthAccess(authStorage, sessionId, signal);
|
|
343
|
+
if (oauthAccess) {
|
|
344
|
+
return { type: "oauth", access: oauthAccess };
|
|
345
345
|
}
|
|
346
346
|
|
|
347
347
|
// 3. PERPLEXITY_API_KEY env var
|
|
@@ -646,7 +646,19 @@ export async function searchPerplexity(params: PerplexitySearchParams): Promise<
|
|
|
646
646
|
const auth = await findPerplexityAuth(params.authStorage, params.sessionId, params.signal);
|
|
647
647
|
|
|
648
648
|
if (auth.type !== "api_key") {
|
|
649
|
-
|
|
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);
|
|
650
662
|
return applySourceLimit(
|
|
651
663
|
{
|
|
652
664
|
provider: "perplexity",
|