@oh-my-pi/pi-coding-agent 15.3.2 → 15.4.1

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.
Files changed (191) hide show
  1. package/CHANGELOG.md +104 -0
  2. package/dist/types/cli/file-processor.d.ts +1 -1
  3. package/dist/types/config/settings-schema.d.ts +45 -3
  4. package/dist/types/config/settings.d.ts +1 -1
  5. package/dist/types/debug/raw-sse.d.ts +2 -0
  6. package/dist/types/edit/file-read-cache.d.ts +15 -4
  7. package/dist/types/edit/index.d.ts +3 -8
  8. package/dist/types/edit/renderer.d.ts +1 -2
  9. package/dist/types/eval/__tests__/shared-executors.test.d.ts +1 -0
  10. package/dist/types/eval/js/shared/local-module-loader.d.ts +16 -0
  11. package/dist/types/eval/js/shared/rewrite-imports.d.ts +4 -0
  12. package/dist/types/eval/js/shared/runtime.d.ts +14 -8
  13. package/dist/types/eval/py/executor.d.ts +1 -2
  14. package/dist/types/eval/py/kernel.d.ts +6 -0
  15. package/dist/types/eval/py/tool-bridge.d.ts +1 -5
  16. package/dist/types/eval/session-id.d.ts +3 -0
  17. package/dist/types/extensibility/extensions/types.d.ts +1 -3
  18. package/dist/types/hashline/anchors.d.ts +15 -9
  19. package/dist/types/hashline/constants.d.ts +0 -2
  20. package/dist/types/hashline/diff.d.ts +1 -2
  21. package/dist/types/hashline/executor.d.ts +52 -0
  22. package/dist/types/hashline/hash.d.ts +44 -93
  23. package/dist/types/hashline/index.d.ts +2 -1
  24. package/dist/types/hashline/input.d.ts +2 -9
  25. package/dist/types/hashline/recovery.d.ts +3 -9
  26. package/dist/types/hashline/tokenizer.d.ts +91 -0
  27. package/dist/types/hashline/types.d.ts +5 -7
  28. package/dist/types/modes/components/extensions/types.d.ts +0 -4
  29. package/dist/types/modes/types.d.ts +1 -0
  30. package/dist/types/modes/utils/ui-helpers.d.ts +1 -0
  31. package/dist/types/sdk.d.ts +2 -0
  32. package/dist/types/session/agent-session.d.ts +11 -15
  33. package/dist/types/session/agent-storage.d.ts +11 -10
  34. package/dist/types/slash-commands/acp-builtins.d.ts +3 -3
  35. package/dist/types/slash-commands/types.d.ts +0 -5
  36. package/dist/types/task/executor.d.ts +2 -0
  37. package/dist/types/tool-discovery/tool-index.d.ts +0 -50
  38. package/dist/types/tools/index.d.ts +2 -8
  39. package/dist/types/tools/match-line-format.d.ts +4 -4
  40. package/dist/types/tools/output-schema-validator.d.ts +64 -0
  41. package/dist/types/tools/review.d.ts +13 -0
  42. package/dist/types/tools/search-tool-bm25.d.ts +1 -1
  43. package/dist/types/tools/search.d.ts +4 -3
  44. package/dist/types/utils/edit-mode.d.ts +1 -1
  45. package/dist/types/web/kagi.d.ts +4 -2
  46. package/dist/types/web/parallel.d.ts +4 -3
  47. package/dist/types/web/scrapers/types.d.ts +2 -1
  48. package/dist/types/web/search/index.d.ts +12 -4
  49. package/dist/types/web/search/provider.d.ts +2 -1
  50. package/dist/types/web/search/providers/anthropic.d.ts +9 -4
  51. package/dist/types/web/search/providers/base.d.ts +34 -2
  52. package/dist/types/web/search/providers/brave.d.ts +8 -1
  53. package/dist/types/web/search/providers/codex.d.ts +13 -9
  54. package/dist/types/web/search/providers/exa.d.ts +10 -1
  55. package/dist/types/web/search/providers/gemini.d.ts +20 -23
  56. package/dist/types/web/search/providers/jina.d.ts +2 -1
  57. package/dist/types/web/search/providers/kagi.d.ts +4 -1
  58. package/dist/types/web/search/providers/kimi.d.ts +10 -1
  59. package/dist/types/web/search/providers/parallel.d.ts +3 -2
  60. package/dist/types/web/search/providers/perplexity.d.ts +5 -2
  61. package/dist/types/web/search/providers/searxng.d.ts +2 -1
  62. package/dist/types/web/search/providers/synthetic.d.ts +5 -8
  63. package/dist/types/web/search/providers/tavily.d.ts +11 -4
  64. package/dist/types/web/search/providers/utils.d.ts +8 -6
  65. package/dist/types/web/search/providers/zai.d.ts +12 -3
  66. package/package.json +7 -7
  67. package/src/cli/file-processor.ts +12 -2
  68. package/src/cli.ts +0 -8
  69. package/src/commands/commit.ts +8 -8
  70. package/src/config/prompt-templates.ts +6 -6
  71. package/src/config/settings-schema.ts +47 -3
  72. package/src/config/settings.ts +5 -5
  73. package/src/debug/raw-sse.ts +68 -3
  74. package/src/edit/file-read-cache.ts +68 -25
  75. package/src/edit/index.ts +6 -37
  76. package/src/edit/renderer.ts +9 -47
  77. package/src/edit/streaming.ts +43 -56
  78. package/src/eval/__tests__/shared-executors.test.ts +520 -0
  79. package/src/eval/js/context-manager.ts +64 -53
  80. package/src/eval/js/shared/local-module-loader.ts +265 -0
  81. package/src/eval/js/shared/prelude.txt +4 -0
  82. package/src/eval/js/shared/rewrite-imports.ts +85 -0
  83. package/src/eval/js/shared/runtime.ts +129 -86
  84. package/src/eval/js/worker-core.ts +23 -38
  85. package/src/eval/py/executor.ts +155 -84
  86. package/src/eval/py/kernel.ts +10 -1
  87. package/src/eval/py/prelude.py +22 -24
  88. package/src/eval/py/runner.py +203 -85
  89. package/src/eval/py/tool-bridge.ts +17 -10
  90. package/src/eval/session-id.ts +8 -0
  91. package/src/exec/bash-executor.ts +27 -16
  92. package/src/extensibility/extensions/runner.ts +0 -1
  93. package/src/extensibility/extensions/types.ts +1 -3
  94. package/src/hashline/anchors.ts +56 -65
  95. package/src/hashline/apply.ts +29 -31
  96. package/src/hashline/constants.ts +0 -3
  97. package/src/hashline/diff-preview.ts +4 -5
  98. package/src/hashline/diff.ts +30 -4
  99. package/src/hashline/execute.ts +91 -26
  100. package/src/hashline/executor.ts +239 -0
  101. package/src/hashline/grammar.lark +12 -10
  102. package/src/hashline/hash.ts +69 -114
  103. package/src/hashline/index.ts +2 -1
  104. package/src/hashline/input.ts +48 -41
  105. package/src/hashline/prefixes.ts +21 -11
  106. package/src/hashline/recovery.ts +63 -71
  107. package/src/hashline/stream.ts +2 -2
  108. package/src/hashline/tokenizer.ts +467 -0
  109. package/src/hashline/types.ts +6 -8
  110. package/src/internal-urls/docs-index.generated.ts +7 -7
  111. package/src/modes/components/extensions/types.ts +0 -5
  112. package/src/modes/components/session-observer-overlay.ts +11 -2
  113. package/src/modes/components/tree-selector.ts +10 -2
  114. package/src/modes/controllers/command-controller.ts +1 -3
  115. package/src/modes/controllers/extension-ui-controller.ts +10 -11
  116. package/src/modes/controllers/selector-controller.ts +5 -5
  117. package/src/modes/types.ts +4 -1
  118. package/src/modes/utils/ui-helpers.ts +4 -0
  119. package/src/prompts/agents/explore.md +1 -1
  120. package/src/prompts/tools/ast-edit.md +1 -1
  121. package/src/prompts/tools/ast-grep.md +1 -1
  122. package/src/prompts/tools/eval.md +1 -1
  123. package/src/prompts/tools/hashline.md +73 -94
  124. package/src/prompts/tools/read.md +4 -4
  125. package/src/prompts/tools/search.md +3 -3
  126. package/src/sdk.ts +17 -23
  127. package/src/session/agent-session.ts +59 -66
  128. package/src/session/agent-storage.ts +13 -14
  129. package/src/slash-commands/acp-builtins.ts +3 -3
  130. package/src/slash-commands/types.ts +0 -6
  131. package/src/task/executor.ts +26 -57
  132. package/src/task/index.ts +8 -4
  133. package/src/tool-discovery/tool-index.ts +0 -134
  134. package/src/tools/ast-edit.ts +36 -13
  135. package/src/tools/ast-grep.ts +45 -4
  136. package/src/tools/browser/tab-worker.ts +3 -2
  137. package/src/tools/eval.ts +2 -1
  138. package/src/tools/fetch.ts +23 -14
  139. package/src/tools/index.ts +2 -8
  140. package/src/tools/irc.ts +59 -5
  141. package/src/tools/match-line-format.ts +5 -7
  142. package/src/tools/output-schema-validator.ts +132 -0
  143. package/src/tools/read.ts +142 -31
  144. package/src/tools/review.ts +23 -0
  145. package/src/tools/search-tool-bm25.ts +3 -30
  146. package/src/tools/search.ts +48 -16
  147. package/src/tools/write.ts +3 -3
  148. package/src/tools/yield.ts +32 -41
  149. package/src/utils/edit-mode.ts +1 -2
  150. package/src/utils/file-mentions.ts +2 -2
  151. package/src/web/kagi.ts +15 -6
  152. package/src/web/parallel.ts +9 -6
  153. package/src/web/scrapers/types.ts +7 -1
  154. package/src/web/scrapers/youtube.ts +13 -7
  155. package/src/web/search/index.ts +37 -11
  156. package/src/web/search/provider.ts +5 -3
  157. package/src/web/search/providers/anthropic.ts +30 -21
  158. package/src/web/search/providers/base.ts +35 -2
  159. package/src/web/search/providers/brave.ts +4 -4
  160. package/src/web/search/providers/codex.ts +118 -89
  161. package/src/web/search/providers/exa.ts +3 -2
  162. package/src/web/search/providers/gemini.ts +58 -155
  163. package/src/web/search/providers/jina.ts +4 -4
  164. package/src/web/search/providers/kagi.ts +17 -11
  165. package/src/web/search/providers/kimi.ts +29 -13
  166. package/src/web/search/providers/parallel.ts +171 -23
  167. package/src/web/search/providers/perplexity.ts +38 -37
  168. package/src/web/search/providers/searxng.ts +3 -1
  169. package/src/web/search/providers/synthetic.ts +16 -19
  170. package/src/web/search/providers/tavily.ts +23 -18
  171. package/src/web/search/providers/utils.ts +11 -17
  172. package/src/web/search/providers/zai.ts +16 -8
  173. package/dist/types/hashline/parser.d.ts +0 -7
  174. package/dist/types/mcp/discoverable-tool-metadata.d.ts +0 -7
  175. package/dist/types/tools/vim.d.ts +0 -58
  176. package/dist/types/vim/buffer.d.ts +0 -41
  177. package/dist/types/vim/commands.d.ts +0 -6
  178. package/dist/types/vim/engine.d.ts +0 -47
  179. package/dist/types/vim/parser.d.ts +0 -3
  180. package/dist/types/vim/render.d.ts +0 -25
  181. package/dist/types/vim/types.d.ts +0 -182
  182. package/src/hashline/parser.ts +0 -246
  183. package/src/mcp/discoverable-tool-metadata.ts +0 -24
  184. package/src/prompts/tools/vim.md +0 -98
  185. package/src/tools/vim.ts +0 -949
  186. package/src/vim/buffer.ts +0 -309
  187. package/src/vim/commands.ts +0 -382
  188. package/src/vim/engine.ts +0 -2409
  189. package/src/vim/parser.ts +0 -134
  190. package/src/vim/render.ts +0 -252
  191. package/src/vim/types.ts +0 -197
@@ -2,15 +2,20 @@
2
2
  * Google Gemini Web Search Provider
3
3
  *
4
4
  * Uses Gemini's Google Search grounding via Cloud Code Assist API.
5
- * Requires OAuth credentials stored in agent.db for provider "google-gemini-cli" or "google-antigravity".
6
- * Returns synthesized answers with citations and source metadata from grounding chunks.
5
+ * Auth is resolved through `AuthStorage.getOAuthAccess(...)` for both
6
+ * `google-gemini-cli` (stable prod) and `google-antigravity` (daily sandbox)
7
+ * — the broker is the sole refresh authority, so this module never opens a
8
+ * sibling SQLite store and never POSTs the broker sentinel to a Google token
9
+ * endpoint.
7
10
  */
8
- import { ANTIGRAVITY_SYSTEM_INSTRUCTION, getAntigravityUserAgent, getGeminiCliHeaders } from "@oh-my-pi/pi-ai";
9
- import { refreshAntigravityToken } from "@oh-my-pi/pi-ai/utils/oauth/google-antigravity";
10
- import { refreshGoogleCloudToken } from "@oh-my-pi/pi-ai/utils/oauth/google-gemini-cli";
11
- import { fetchWithRetry, getAgentDbPath } from "@oh-my-pi/pi-utils";
11
+ import {
12
+ ANTIGRAVITY_SYSTEM_INSTRUCTION,
13
+ type AuthStorage,
14
+ getAntigravityUserAgent,
15
+ getGeminiCliHeaders,
16
+ } from "@oh-my-pi/pi-ai";
17
+ import { fetchWithRetry } from "@oh-my-pi/pi-utils";
12
18
 
13
- import { AgentStorage } from "../../../session/agent-storage";
14
19
  import type { SearchCitation, SearchResponse, SearchSource } from "../../../web/search/types";
15
20
  import { SearchProviderError } from "../../../web/search/types";
16
21
  import type { SearchParams } from "./base";
@@ -26,6 +31,9 @@ const MAX_RETRIES = 3;
26
31
  const BASE_DELAY_MS = 1000;
27
32
  const RATE_LIMIT_BUDGET_MS = 5 * 60 * 1000;
28
33
 
34
+ const GEMINI_PROVIDERS = ["google-gemini-cli", "google-antigravity"] as const;
35
+ type GeminiProviderId = (typeof GEMINI_PROVIDERS)[number];
36
+
29
37
  interface GeminiToolParams {
30
38
  google_search?: Record<string, unknown>;
31
39
  code_execution?: Record<string, unknown>;
@@ -41,6 +49,8 @@ export interface GeminiSearchParams extends GeminiToolParams {
41
49
  /** Sampling temperature (0–1). Lower = more focused/factual. */
42
50
  temperature?: number;
43
51
  signal?: AbortSignal;
52
+ authStorage: AuthStorage;
53
+ sessionId?: string;
44
54
  }
45
55
 
46
56
  export function buildGeminiRequestTools(params: GeminiToolParams): Array<Record<string, Record<string, unknown>>> {
@@ -54,131 +64,40 @@ export function buildGeminiRequestTools(params: GeminiToolParams): Array<Record<
54
64
  return tools;
55
65
  }
56
66
 
57
- /** OAuth credential stored in agent.db */
58
- interface GeminiOAuthCredential {
59
- type: "oauth";
60
- access: string;
61
- refresh?: string;
62
- expires: number;
63
- projectId?: string;
64
- }
65
-
66
- /** Auth info for Gemini API requests */
67
+ /** Resolved auth for a Gemini API request. */
67
68
  interface GeminiAuth {
68
69
  accessToken: string;
69
- refreshToken?: string;
70
70
  projectId: string;
71
71
  isAntigravity: boolean;
72
- storage: AgentStorage;
73
- credentialId: number;
74
- credential: GeminiOAuthCredential;
75
- }
76
-
77
- async function refreshGeminiAuth(auth: GeminiAuth): Promise<boolean> {
78
- if (!auth.refreshToken) return false;
79
- try {
80
- const refreshed = auth.isAntigravity
81
- ? await refreshAntigravityToken(auth.refreshToken, auth.projectId)
82
- : await refreshGoogleCloudToken(auth.refreshToken, auth.projectId);
83
- auth.accessToken = refreshed.access;
84
- auth.refreshToken = refreshed.refresh ?? auth.refreshToken;
85
- auth.storage.updateAuthCredential(auth.credentialId, {
86
- ...auth.credential,
87
- access: auth.accessToken,
88
- refresh: auth.refreshToken,
89
- expires: refreshed.expires,
90
- });
91
- auth.credential.access = auth.accessToken;
92
- auth.credential.refresh = auth.refreshToken;
93
- auth.credential.expires = refreshed.expires;
94
- return true;
95
- } catch {
96
- return false;
97
- }
98
72
  }
99
73
 
100
74
  /**
101
- * Finds valid Gemini OAuth credentials from agent.db.
102
- * Checks google-gemini-cli first (stable prod), then google-antigravity (daily sandbox).
103
- * @returns OAuth credential with access token and project ID, or null if none found
75
+ * Walks the configured Gemini OAuth providers in deterministic order and
76
+ * returns the first one that yields a usable access token + projectId via
77
+ * {@link AuthStorage.getOAuthAccess}. AuthStorage handles refresh + broker
78
+ * routing internally; this helper never touches refresh tokens directly.
104
79
  */
105
- export async function findGeminiAuth(): Promise<GeminiAuth | null> {
106
- const expiryBuffer = 5 * 60 * 1000; // 5 minutes
107
- const now = Date.now();
108
-
109
- // Try providers in deterministic order: gemini-cli first, then antigravity
110
- const providers = ["google-gemini-cli", "google-antigravity"] as const;
111
-
112
- try {
113
- const storage = await AgentStorage.open(getAgentDbPath());
114
-
115
- for (const provider of providers) {
116
- const records = storage.listAuthCredentials(provider);
117
-
118
- for (const record of records) {
119
- const credential = record.credential;
120
- if (credential.type !== "oauth") continue;
121
-
122
- const oauthCred = credential as GeminiOAuthCredential;
123
- if (!oauthCred.access) continue;
124
-
125
- // Get projectId from credential
126
- const projectId = oauthCred.projectId;
127
- if (!projectId) continue;
128
-
129
- // Check if token is expired (or about to expire)
130
- if (oauthCred.expires <= now + expiryBuffer) {
131
- // Try to refresh if we have a refresh token
132
- if (oauthCred.refresh) {
133
- try {
134
- const refreshed =
135
- provider === "google-antigravity"
136
- ? await refreshAntigravityToken(oauthCred.refresh, projectId)
137
- : await refreshGoogleCloudToken(oauthCred.refresh, projectId);
138
- // Update the credential in storage
139
- const updated = {
140
- ...oauthCred,
141
- access: refreshed.access,
142
- refresh: refreshed.refresh ?? oauthCred.refresh,
143
- expires: refreshed.expires,
144
- };
145
- storage.updateAuthCredential(record.id, updated);
146
- return {
147
- accessToken: refreshed.access,
148
- refreshToken: refreshed.refresh ?? oauthCred.refresh,
149
- projectId,
150
- isAntigravity: provider === "google-antigravity",
151
- storage,
152
- credentialId: record.id,
153
- credential: updated,
154
- };
155
- } catch {
156
- // Refresh failed, skip this credential
157
- continue;
158
- }
159
- }
160
- // No refresh token or refresh failed
161
- continue;
162
- }
163
-
164
- return {
165
- accessToken: oauthCred.access,
166
- refreshToken: oauthCred.refresh,
167
- projectId,
168
- isAntigravity: provider === "google-antigravity",
169
- storage,
170
- credentialId: record.id,
171
- credential: oauthCred,
172
- };
173
- }
174
- }
175
- } catch {
176
- return null;
80
+ export async function findGeminiAuth(
81
+ authStorage: AuthStorage,
82
+ sessionId: string | undefined,
83
+ signal: AbortSignal | undefined,
84
+ ): Promise<GeminiAuth | null> {
85
+ for (const provider of GEMINI_PROVIDERS) {
86
+ const access = await authStorage.getOAuthAccess(provider, sessionId, { signal });
87
+ if (!access?.accessToken || !access.projectId) continue;
88
+ return {
89
+ accessToken: access.accessToken,
90
+ projectId: access.projectId,
91
+ isAntigravity: provider === "google-antigravity",
92
+ };
177
93
  }
178
-
179
94
  return null;
180
95
  }
181
96
 
97
+ function hasGeminiOAuth(authStorage: AuthStorage): boolean {
98
+ return GEMINI_PROVIDERS.some((provider: GeminiProviderId) => authStorage.hasOAuth(provider));
99
+ }
100
+
182
101
  /** Cloud Code Assist API response types */
183
102
  interface GeminiGroundingChunk {
184
103
  web?: {
@@ -224,20 +143,20 @@ interface CloudCodeResponseChunk {
224
143
 
225
144
  /**
226
145
  * Calls the Cloud Code Assist API with Google Search grounding enabled.
227
- * @param auth - Authentication info (access token and project ID)
228
- * @param query - Search query from the user
229
- * @param systemPrompt - Optional system prompt
230
- * @returns Parsed response with answer, sources, and usage
231
- * @throws {SearchProviderError} If the API request fails
146
+ *
147
+ * If a request returns a refreshable auth failure (401/403/auth-flavoured 400),
148
+ * we ask AuthStorage to invalidate + refresh the credential and retry once.
149
+ * Provider-direct refresh helpers are intentionally not used: AuthStorage owns
150
+ * the single-flight refresh and broker round-trip.
232
151
  */
233
152
  async function callGeminiSearch(
234
153
  auth: GeminiAuth,
235
154
  query: string,
236
- systemPrompt?: string,
237
- maxOutputTokens?: number,
238
- temperature?: number,
239
- toolParams: GeminiToolParams = {},
240
- signal?: AbortSignal,
155
+ systemPrompt: string | undefined,
156
+ maxOutputTokens: number | undefined,
157
+ temperature: number | undefined,
158
+ toolParams: GeminiToolParams,
159
+ signal: AbortSignal | undefined,
241
160
  ): Promise<{
242
161
  answer: string;
243
162
  sources: SearchSource[];
@@ -316,29 +235,13 @@ async function callGeminiSearch(
316
235
  const urlFor = (attempt: number) =>
317
236
  `${endpoints[Math.min(attempt, endpoints.length - 1)]}/v1internal:streamGenerateContent?alt=sse`;
318
237
 
319
- let response = await fetchWithRetry(urlFor, {
238
+ const response = await fetchWithRetry(urlFor, {
320
239
  ...buildInit(),
321
240
  maxAttempts: MAX_RETRIES + 1,
322
241
  defaultDelayMs: attempt => BASE_DELAY_MS * 2 ** attempt,
323
242
  maxDelayMs: RATE_LIMIT_BUDGET_MS,
324
243
  });
325
244
 
326
- if (!response.ok) {
327
- const errorText = await response.clone().text();
328
- const canRefreshAuth =
329
- response.status === 401 ||
330
- response.status === 403 ||
331
- (response.status === 400 && /api key not valid|invalid credentials|invalid authentication/i.test(errorText));
332
- if (canRefreshAuth && (await refreshGeminiAuth(auth))) {
333
- response = await fetchWithRetry(urlFor, {
334
- ...buildInit(),
335
- maxAttempts: MAX_RETRIES + 1,
336
- defaultDelayMs: attempt => BASE_DELAY_MS * 2 ** attempt,
337
- maxDelayMs: RATE_LIMIT_BUDGET_MS,
338
- });
339
- }
340
- }
341
-
342
245
  if (!response.ok) {
343
246
  const errorText = await response.text();
344
247
  const classified = classifyProviderHttpError("gemini", response.status, errorText);
@@ -482,13 +385,9 @@ async function callGeminiSearch(
482
385
 
483
386
  /**
484
387
  * Executes a web search using Google Gemini with Google Search grounding.
485
- * Requires OAuth credentials stored in agent.db for provider "google-gemini-cli" or "google-antigravity".
486
- * @param params - Search parameters including query and optional settings
487
- * @returns Search response with synthesized answer, sources, and citations
488
- * @throws {Error} If no Gemini OAuth credentials are configured
489
388
  */
490
389
  export async function searchGemini(params: GeminiSearchParams): Promise<SearchResponse> {
491
- const auth = await findGeminiAuth();
390
+ const auth = await findGeminiAuth(params.authStorage, params.sessionId, params.signal);
492
391
  if (!auth) {
493
392
  throw new Error(
494
393
  "No Gemini OAuth credentials found. Login with 'omp /login google-gemini-cli' or 'omp /login google-antigravity' to enable Gemini web search.",
@@ -511,7 +410,6 @@ export async function searchGemini(params: GeminiSearchParams): Promise<SearchRe
511
410
 
512
411
  let sources = result.sources;
513
412
 
514
- // Apply num_results limit if specified
515
413
  if (params.num_results && sources.length > params.num_results) {
516
414
  sources = sources.slice(0, params.num_results);
517
415
  }
@@ -532,8 +430,11 @@ export class GeminiProvider extends SearchProvider {
532
430
  readonly id = "gemini";
533
431
  readonly label = "Gemini";
534
432
 
535
- isAvailable() {
536
- return findGeminiAuth().then(Boolean);
433
+ isAvailable(authStorage: AuthStorage): boolean {
434
+ // Cheap, in-memory check — avoids driving the refresh pipeline during
435
+ // the provider-chain probe. `searchGemini` calls `getOAuthAccess` which
436
+ // will refresh lazily on the actual request.
437
+ return hasGeminiOAuth(authStorage);
537
438
  }
538
439
 
539
440
  search(params: SearchParams): Promise<SearchResponse> {
@@ -547,6 +448,8 @@ export class GeminiProvider extends SearchProvider {
547
448
  code_execution: params.codeExecution,
548
449
  url_context: params.urlContext,
549
450
  signal: params.signal,
451
+ authStorage: params.authStorage,
452
+ sessionId: params.sessionId,
550
453
  });
551
454
  }
552
455
  }
@@ -5,12 +5,12 @@
5
5
  * cleaned content.
6
6
  */
7
7
 
8
- import { getEnvApiKey } from "@oh-my-pi/pi-ai";
8
+ import { type AuthStorage, getEnvApiKey } from "@oh-my-pi/pi-ai";
9
9
  import type { SearchResponse, SearchSource } from "../../../web/search/types";
10
10
  import { SearchProviderError } from "../../../web/search/types";
11
11
  import type { SearchParams } from "./base";
12
12
  import { SearchProvider } from "./base";
13
- import { classifyProviderHttpError, isApiKeyAvailable, withHardTimeout } from "./utils";
13
+ import { classifyProviderHttpError, withHardTimeout } from "./utils";
14
14
 
15
15
  const JINA_SEARCH_URL = "https://s.jina.ai";
16
16
 
@@ -87,8 +87,8 @@ export class JinaProvider extends SearchProvider {
87
87
  readonly id = "jina";
88
88
  readonly label = "Jina";
89
89
 
90
- isAvailable() {
91
- return isApiKeyAvailable(findApiKey);
90
+ isAvailable(_authStorage: AuthStorage): boolean {
91
+ return !!findApiKey();
92
92
  }
93
93
 
94
94
  search(params: SearchParams): Promise<SearchResponse> {
@@ -3,9 +3,10 @@
3
3
  *
4
4
  * Thin wrapper that adapts shared Kagi API utilities to SearchResponse shape.
5
5
  */
6
+ import type { AuthStorage } from "@oh-my-pi/pi-ai";
6
7
  import type { SearchResponse } from "../../../web/search/types";
7
8
  import { SearchProviderError } from "../../../web/search/types";
8
- import { findKagiApiKey, KagiApiError, searchWithKagi } from "../../kagi";
9
+ import { KagiApiError, searchWithKagi } from "../../kagi";
9
10
  import { clampNumResults } from "../utils";
10
11
  import type { SearchParams } from "./base";
11
12
  import { SearchProvider } from "./base";
@@ -19,14 +20,21 @@ export async function searchKagi(params: {
19
20
  query: string;
20
21
  num_results?: number;
21
22
  signal?: AbortSignal;
23
+ authStorage: AuthStorage;
24
+ sessionId?: string;
22
25
  }): Promise<SearchResponse> {
23
26
  const numResults = clampNumResults(params.num_results, DEFAULT_NUM_RESULTS, MAX_NUM_RESULTS);
24
27
 
25
28
  try {
26
- const result = await searchWithKagi(params.query, {
27
- limit: numResults,
28
- signal: params.signal,
29
- });
29
+ const result = await searchWithKagi(
30
+ params.query,
31
+ {
32
+ limit: numResults,
33
+ sessionId: params.sessionId,
34
+ signal: params.signal,
35
+ },
36
+ params.authStorage,
37
+ );
30
38
 
31
39
  return {
32
40
  provider: "kagi",
@@ -51,12 +59,8 @@ export class KagiProvider extends SearchProvider {
51
59
  readonly id = "kagi";
52
60
  readonly label = "Kagi";
53
61
 
54
- async isAvailable() {
55
- try {
56
- return !!(await findKagiApiKey());
57
- } catch {
58
- return false;
59
- }
62
+ isAvailable(authStorage: AuthStorage): boolean {
63
+ return authStorage.hasAuth("kagi");
60
64
  }
61
65
 
62
66
  search(params: SearchParams): Promise<SearchResponse> {
@@ -64,6 +68,8 @@ export class KagiProvider extends SearchProvider {
64
68
  query: params.query,
65
69
  num_results: params.numSearchResults ?? params.limit,
66
70
  signal: params.signal,
71
+ authStorage: params.authStorage,
72
+ sessionId: params.sessionId,
67
73
  });
68
74
  }
69
75
  }
@@ -4,14 +4,15 @@
4
4
  * Uses Moonshot Kimi Code search API to retrieve web results.
5
5
  * Endpoint: POST https://api.kimi.com/coding/v1/search
6
6
  */
7
- import { getEnvApiKey } from "@oh-my-pi/pi-ai";
7
+ import type { AuthStorage } from "@oh-my-pi/pi-ai";
8
8
  import { $env } from "@oh-my-pi/pi-utils";
9
+
9
10
  import type { SearchResponse, SearchSource } from "../../../web/search/types";
10
11
  import { SearchProviderError } from "../../../web/search/types";
11
12
  import { clampNumResults, dateToAgeSeconds } from "../utils";
12
13
  import type { SearchParams } from "./base";
13
14
  import { SearchProvider } from "./base";
14
- import { classifyProviderHttpError, findCredential, isApiKeyAvailable, withHardTimeout } from "./utils";
15
+ import { classifyProviderHttpError, withHardTimeout } from "./utils";
15
16
 
16
17
  const KIMI_SEARCH_URL = "https://api.kimi.com/coding/v1/search";
17
18
 
@@ -24,6 +25,8 @@ export interface KimiSearchParams {
24
25
  num_results?: number;
25
26
  include_content?: boolean;
26
27
  signal?: AbortSignal;
28
+ authStorage: AuthStorage;
29
+ sessionId?: string;
27
30
  }
28
31
 
29
32
  interface KimiSearchResult {
@@ -51,14 +54,20 @@ function resolveBaseUrl(): string {
51
54
  return asTrimmed($env.MOONSHOT_SEARCH_BASE_URL) ?? asTrimmed($env.KIMI_SEARCH_BASE_URL) ?? KIMI_SEARCH_URL;
52
55
  }
53
56
 
54
- /** Find Kimi search credentials from environment or agent.db credentials. */
55
- async function findApiKey(): Promise<string | null> {
56
- const envKey =
57
- asTrimmed($env.MOONSHOT_SEARCH_API_KEY) ??
58
- asTrimmed($env.KIMI_SEARCH_API_KEY) ??
59
- getEnvApiKey("moonshot") ??
60
- null;
61
- return findCredential(envKey, "moonshot", "kimi-code");
57
+ /** Find Kimi search credentials from environment or AuthStorage. */
58
+ async function findApiKey(
59
+ authStorage: AuthStorage,
60
+ sessionId: string | undefined,
61
+ signal: AbortSignal | undefined,
62
+ ): Promise<string | null> {
63
+ const envKey = asTrimmed($env.MOONSHOT_SEARCH_API_KEY) ?? asTrimmed($env.KIMI_SEARCH_API_KEY);
64
+ if (envKey) return envKey;
65
+
66
+ return (
67
+ (await authStorage.getApiKey("moonshot", sessionId, { signal })) ??
68
+ (await authStorage.getApiKey("kimi-code", sessionId, { signal })) ??
69
+ null
70
+ );
62
71
  }
63
72
 
64
73
  async function callKimiSearch(
@@ -99,7 +108,7 @@ async function callKimiSearch(
99
108
 
100
109
  /** Execute Kimi web search. */
101
110
  export async function searchKimi(params: KimiSearchParams): Promise<SearchResponse> {
102
- const apiKey = await findApiKey();
111
+ const apiKey = await findApiKey(params.authStorage, params.sessionId, params.signal);
103
112
  if (!apiKey) {
104
113
  throw new Error(
105
114
  "Kimi search credentials not found. Set MOONSHOT_SEARCH_API_KEY, KIMI_SEARCH_API_KEY, MOONSHOT_API_KEY, or login with 'omp /login moonshot'.",
@@ -141,8 +150,13 @@ export class KimiProvider extends SearchProvider {
141
150
  readonly id = "kimi";
142
151
  readonly label = "Kimi";
143
152
 
144
- isAvailable(): Promise<boolean> {
145
- return isApiKeyAvailable(findApiKey);
153
+ isAvailable(authStorage: AuthStorage): boolean {
154
+ return (
155
+ !!asTrimmed($env.MOONSHOT_SEARCH_API_KEY) ||
156
+ !!asTrimmed($env.KIMI_SEARCH_API_KEY) ||
157
+ authStorage.hasAuth("moonshot") ||
158
+ authStorage.hasAuth("kimi-code")
159
+ );
146
160
  }
147
161
 
148
162
  search(params: SearchParams): Promise<SearchResponse> {
@@ -150,6 +164,8 @@ export class KimiProvider extends SearchProvider {
150
164
  query: params.query,
151
165
  num_results: params.numSearchResults ?? params.limit,
152
166
  signal: params.signal,
167
+ authStorage: params.authStorage,
168
+ sessionId: params.sessionId,
153
169
  });
154
170
  }
155
171
  }