@oh-my-pi/pi-coding-agent 16.2.5 → 16.2.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 +18 -1
- package/dist/cli.js +2991 -3011
- package/dist/types/cli/bench-cli.d.ts +6 -1
- package/dist/types/commands/bench.d.ts +8 -0
- package/dist/types/config/model-discovery.d.ts +6 -1
- package/dist/types/config/settings-schema.d.ts +1 -1
- package/dist/types/edit/index.d.ts +1 -0
- package/dist/types/edit/renderer.d.ts +4 -0
- package/dist/types/edit/snapshot-details.d.ts +33 -0
- package/dist/types/mcp/oauth-flow.d.ts +4 -6
- package/dist/types/modes/controllers/tool-args-reveal.d.ts +9 -2
- package/dist/types/session/agent-session.d.ts +4 -0
- package/dist/types/session/session-manager.d.ts +3 -4
- package/dist/types/web/search/providers/duckduckgo.d.ts +2 -2
- package/dist/types/web/search/types.d.ts +1 -1
- package/package.json +12 -12
- package/src/cli/args.ts +32 -1
- package/src/cli/bench-cli.ts +89 -21
- package/src/cli/web-search-cli.ts +6 -1
- package/src/commands/bench.ts +10 -1
- package/src/config/mcp-schema.json +1 -1
- package/src/config/model-discovery.ts +66 -8
- package/src/config/model-registry.ts +13 -6
- package/src/edit/hashline/execute.ts +15 -9
- package/src/edit/index.ts +19 -6
- package/src/edit/modes/patch.ts +3 -2
- package/src/edit/modes/replace.ts +3 -2
- package/src/edit/renderer.ts +4 -0
- package/src/edit/snapshot-details.ts +77 -0
- package/src/extensibility/plugins/legacy-pi-compat.ts +2 -2
- package/src/internal-urls/docs-index.generated.txt +1 -1
- package/src/mcp/oauth-flow.ts +10 -8
- package/src/mcp/transports/stdio.ts +9 -17
- package/src/modes/controllers/event-controller.ts +17 -8
- package/src/modes/controllers/tool-args-reveal.ts +100 -22
- package/src/prompts/bench.md +4 -10
- package/src/prompts/tools/irc.md +19 -29
- package/src/prompts/tools/job.md +8 -14
- package/src/prompts/tools/lsp.md +19 -30
- package/src/prompts/tools/task.md +42 -62
- package/src/sdk.ts +1 -0
- package/src/session/agent-session.ts +10 -0
- package/src/session/session-listing.ts +9 -8
- package/src/session/session-loader.ts +98 -3
- package/src/session/session-manager.ts +34 -4
- package/src/subprocess/worker-client.ts +12 -4
- package/src/tools/path-utils.ts +4 -2
- package/src/web/search/index.ts +14 -8
- package/src/web/search/providers/duckduckgo.ts +136 -78
- package/src/web/search/providers/gemini.ts +268 -185
- package/src/web/search/types.ts +1 -1
|
@@ -23,6 +23,8 @@ import { SearchProvider } from "./base";
|
|
|
23
23
|
import { classifyProviderHttpError, withHardTimeout } from "./utils";
|
|
24
24
|
|
|
25
25
|
const DEFAULT_ENDPOINT = "https://cloudcode-pa.googleapis.com";
|
|
26
|
+
const DEVELOPER_API_PROVIDER = "google";
|
|
27
|
+
const DEVELOPER_API_ENDPOINT = "https://generativelanguage.googleapis.com/v1beta";
|
|
26
28
|
const ANTIGRAVITY_DAILY_ENDPOINT = "https://daily-cloudcode-pa.googleapis.com";
|
|
27
29
|
const ANTIGRAVITY_SANDBOX_ENDPOINT = "https://daily-cloudcode-pa.sandbox.googleapis.com";
|
|
28
30
|
const ANTIGRAVITY_ENDPOINT_FALLBACKS = [ANTIGRAVITY_DAILY_ENDPOINT, ANTIGRAVITY_SANDBOX_ENDPOINT] as const;
|
|
@@ -80,6 +82,15 @@ interface GeminiAuthSeed {
|
|
|
80
82
|
projectId: string;
|
|
81
83
|
}
|
|
82
84
|
|
|
85
|
+
interface GeminiSearchResult {
|
|
86
|
+
answer: string;
|
|
87
|
+
sources: SearchSource[];
|
|
88
|
+
citations: SearchCitation[];
|
|
89
|
+
searchQueries: string[];
|
|
90
|
+
model: string;
|
|
91
|
+
usage?: { inputTokens: number; outputTokens: number; totalTokens: number };
|
|
92
|
+
}
|
|
93
|
+
|
|
83
94
|
/**
|
|
84
95
|
* Walks the configured Gemini OAuth providers in deterministic order and
|
|
85
96
|
* returns the first one that yields a usable access token + projectId via
|
|
@@ -128,22 +139,141 @@ interface GeminiGroundingMetadata {
|
|
|
128
139
|
webSearchQueries?: string[];
|
|
129
140
|
}
|
|
130
141
|
|
|
131
|
-
interface
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
parts?: Array<{ text?: string }>;
|
|
137
|
-
};
|
|
138
|
-
finishReason?: string;
|
|
139
|
-
groundingMetadata?: GeminiGroundingMetadata;
|
|
140
|
-
}>;
|
|
141
|
-
usageMetadata?: {
|
|
142
|
-
promptTokenCount?: number;
|
|
143
|
-
candidatesTokenCount?: number;
|
|
144
|
-
totalTokenCount?: number;
|
|
142
|
+
interface GeminiModelResponse {
|
|
143
|
+
candidates?: Array<{
|
|
144
|
+
content?: {
|
|
145
|
+
role: string;
|
|
146
|
+
parts?: Array<{ text?: string }>;
|
|
145
147
|
};
|
|
146
|
-
|
|
148
|
+
finishReason?: string;
|
|
149
|
+
groundingMetadata?: GeminiGroundingMetadata;
|
|
150
|
+
}>;
|
|
151
|
+
usageMetadata?: {
|
|
152
|
+
promptTokenCount?: number;
|
|
153
|
+
candidatesTokenCount?: number;
|
|
154
|
+
totalTokenCount?: number;
|
|
155
|
+
};
|
|
156
|
+
modelVersion?: string;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
interface CloudCodeResponseChunk {
|
|
160
|
+
response?: GeminiModelResponse;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
async function parseGeminiSearchStream(body: ReadableStream<Uint8Array>): Promise<GeminiSearchResult> {
|
|
164
|
+
const answerParts: string[] = [];
|
|
165
|
+
const sources: SearchSource[] = [];
|
|
166
|
+
const citations: SearchCitation[] = [];
|
|
167
|
+
const searchQueries: string[] = [];
|
|
168
|
+
const seenUrls = new Set<string>();
|
|
169
|
+
let model = DEFAULT_MODEL;
|
|
170
|
+
let usage: { inputTokens: number; outputTokens: number; totalTokens: number } | undefined;
|
|
171
|
+
|
|
172
|
+
const reader = body.getReader();
|
|
173
|
+
const decoder = new TextDecoder();
|
|
174
|
+
let buffer = "";
|
|
175
|
+
|
|
176
|
+
try {
|
|
177
|
+
while (true) {
|
|
178
|
+
const { done, value } = await reader.read();
|
|
179
|
+
if (done) break;
|
|
180
|
+
|
|
181
|
+
buffer += decoder.decode(value, { stream: true });
|
|
182
|
+
const lines = buffer.split("\n");
|
|
183
|
+
buffer = lines.pop() || "";
|
|
184
|
+
|
|
185
|
+
for (const line of lines) {
|
|
186
|
+
if (!line.startsWith("data:")) continue;
|
|
187
|
+
|
|
188
|
+
const jsonStr = line.slice(5).trim();
|
|
189
|
+
if (!jsonStr) continue;
|
|
190
|
+
|
|
191
|
+
let chunk: CloudCodeResponseChunk & GeminiModelResponse;
|
|
192
|
+
try {
|
|
193
|
+
chunk = JSON.parse(jsonStr) as CloudCodeResponseChunk & GeminiModelResponse;
|
|
194
|
+
} catch {
|
|
195
|
+
continue;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
const responseData = chunk.response ?? chunk;
|
|
199
|
+
const candidate = responseData.candidates?.[0];
|
|
200
|
+
|
|
201
|
+
if (candidate?.content?.parts) {
|
|
202
|
+
for (const part of candidate.content.parts) {
|
|
203
|
+
if (part.text) {
|
|
204
|
+
answerParts.push(part.text);
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
const groundingMetadata = candidate?.groundingMetadata;
|
|
210
|
+
if (groundingMetadata) {
|
|
211
|
+
if (groundingMetadata.groundingChunks) {
|
|
212
|
+
for (const grChunk of groundingMetadata.groundingChunks) {
|
|
213
|
+
if (grChunk.web?.uri) {
|
|
214
|
+
const sourceUrl = grChunk.web.uri;
|
|
215
|
+
if (!seenUrls.has(sourceUrl)) {
|
|
216
|
+
seenUrls.add(sourceUrl);
|
|
217
|
+
sources.push({
|
|
218
|
+
title: grChunk.web.title ?? sourceUrl,
|
|
219
|
+
url: sourceUrl,
|
|
220
|
+
});
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
if (groundingMetadata.groundingSupports && groundingMetadata.groundingChunks) {
|
|
227
|
+
for (const support of groundingMetadata.groundingSupports) {
|
|
228
|
+
const citedText = support.segment?.text;
|
|
229
|
+
const chunkIndices = support.groundingChunkIndices ?? [];
|
|
230
|
+
|
|
231
|
+
for (const idx of chunkIndices) {
|
|
232
|
+
const grChunk = groundingMetadata.groundingChunks[idx];
|
|
233
|
+
if (grChunk?.web?.uri) {
|
|
234
|
+
citations.push({
|
|
235
|
+
url: grChunk.web.uri,
|
|
236
|
+
title: grChunk.web.title ?? grChunk.web.uri,
|
|
237
|
+
citedText,
|
|
238
|
+
});
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
if (groundingMetadata.webSearchQueries) {
|
|
245
|
+
for (const q of groundingMetadata.webSearchQueries) {
|
|
246
|
+
if (!searchQueries.includes(q)) {
|
|
247
|
+
searchQueries.push(q);
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
if (responseData.usageMetadata) {
|
|
254
|
+
usage = {
|
|
255
|
+
inputTokens: responseData.usageMetadata.promptTokenCount ?? 0,
|
|
256
|
+
outputTokens: responseData.usageMetadata.candidatesTokenCount ?? 0,
|
|
257
|
+
totalTokens: responseData.usageMetadata.totalTokenCount ?? 0,
|
|
258
|
+
};
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
if (responseData.modelVersion) {
|
|
262
|
+
model = responseData.modelVersion;
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
} finally {
|
|
267
|
+
reader.releaseLock();
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
return {
|
|
271
|
+
answer: answerParts.join(""),
|
|
272
|
+
sources,
|
|
273
|
+
citations,
|
|
274
|
+
searchQueries,
|
|
275
|
+
model,
|
|
276
|
+
usage,
|
|
147
277
|
};
|
|
148
278
|
}
|
|
149
279
|
|
|
@@ -165,14 +295,7 @@ async function callGeminiSearch(
|
|
|
165
295
|
fetchImpl: FetchImpl | undefined,
|
|
166
296
|
signal: AbortSignal | undefined,
|
|
167
297
|
mode?: "auto" | "production" | "sandbox",
|
|
168
|
-
): Promise<{
|
|
169
|
-
answer: string;
|
|
170
|
-
sources: SearchSource[];
|
|
171
|
-
citations: SearchCitation[];
|
|
172
|
-
searchQueries: string[];
|
|
173
|
-
model: string;
|
|
174
|
-
usage?: { inputTokens: number; outputTokens: number; totalTokens: number };
|
|
175
|
-
}> {
|
|
298
|
+
): Promise<GeminiSearchResult> {
|
|
176
299
|
let endpoints: string[];
|
|
177
300
|
if (auth.isAntigravity) {
|
|
178
301
|
const m = mode ?? "auto";
|
|
@@ -287,13 +410,75 @@ async function callGeminiSearch(
|
|
|
287
410
|
throw new SearchProviderError("gemini", `Gemini Cloud Code API error (${status}): ${errorText}`, status);
|
|
288
411
|
}
|
|
289
412
|
|
|
413
|
+
if (!response.body) {
|
|
414
|
+
throw new SearchProviderError("gemini", "Gemini API returned no response body", 500);
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
return parseGeminiSearchStream(response.body);
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
async function callGeminiDeveloperSearch(
|
|
421
|
+
apiKey: string,
|
|
422
|
+
query: string,
|
|
423
|
+
systemPrompt: string | undefined,
|
|
424
|
+
maxOutputTokens: number | undefined,
|
|
425
|
+
temperature: number | undefined,
|
|
426
|
+
toolParams: GeminiToolParams,
|
|
427
|
+
fetchImpl: FetchImpl | undefined,
|
|
428
|
+
signal: AbortSignal | undefined,
|
|
429
|
+
): Promise<GeminiSearchResult> {
|
|
430
|
+
const normalizedSystemPrompt = systemPrompt?.toWellFormed();
|
|
431
|
+
const requestBody: Record<string, unknown> = {
|
|
432
|
+
contents: [
|
|
433
|
+
{
|
|
434
|
+
role: "user",
|
|
435
|
+
parts: [{ text: query }],
|
|
436
|
+
},
|
|
437
|
+
],
|
|
438
|
+
tools: buildGeminiRequestTools(toolParams),
|
|
439
|
+
...(normalizedSystemPrompt && {
|
|
440
|
+
systemInstruction: {
|
|
441
|
+
parts: [{ text: normalizedSystemPrompt }],
|
|
442
|
+
},
|
|
443
|
+
}),
|
|
444
|
+
};
|
|
445
|
+
|
|
446
|
+
if (maxOutputTokens !== undefined || temperature !== undefined) {
|
|
447
|
+
const generationConfig: Record<string, number> = {};
|
|
448
|
+
if (maxOutputTokens !== undefined) {
|
|
449
|
+
generationConfig.maxOutputTokens = maxOutputTokens;
|
|
450
|
+
}
|
|
451
|
+
if (temperature !== undefined) {
|
|
452
|
+
generationConfig.temperature = temperature;
|
|
453
|
+
}
|
|
454
|
+
requestBody.generationConfig = generationConfig;
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
const response = await fetchWithRetry(
|
|
458
|
+
() => `${DEVELOPER_API_ENDPOINT}/models/${DEFAULT_MODEL}:streamGenerateContent?alt=sse`,
|
|
459
|
+
{
|
|
460
|
+
method: "POST",
|
|
461
|
+
headers: {
|
|
462
|
+
"x-goog-api-key": apiKey,
|
|
463
|
+
"Content-Type": "application/json",
|
|
464
|
+
Accept: "text/event-stream",
|
|
465
|
+
},
|
|
466
|
+
body: JSON.stringify(requestBody),
|
|
467
|
+
signal: withHardTimeout(signal),
|
|
468
|
+
fetch: fetchImpl,
|
|
469
|
+
maxAttempts: MAX_RETRIES + 1,
|
|
470
|
+
defaultDelayMs: attempt => BASE_DELAY_MS * 2 ** attempt,
|
|
471
|
+
maxDelayMs: RATE_LIMIT_BUDGET_MS,
|
|
472
|
+
},
|
|
473
|
+
);
|
|
474
|
+
|
|
290
475
|
if (!response.ok) {
|
|
291
476
|
const errorText = await response.text();
|
|
292
477
|
const classified = classifyProviderHttpError("gemini", response.status, errorText);
|
|
293
478
|
if (classified) throw classified;
|
|
294
479
|
throw new SearchProviderError(
|
|
295
480
|
"gemini",
|
|
296
|
-
`Gemini
|
|
481
|
+
`Gemini Developer API error (${response.status}): ${errorText}`,
|
|
297
482
|
response.status,
|
|
298
483
|
);
|
|
299
484
|
}
|
|
@@ -302,130 +487,7 @@ async function callGeminiSearch(
|
|
|
302
487
|
throw new SearchProviderError("gemini", "Gemini API returned no response body", 500);
|
|
303
488
|
}
|
|
304
489
|
|
|
305
|
-
|
|
306
|
-
const answerParts: string[] = [];
|
|
307
|
-
const sources: SearchSource[] = [];
|
|
308
|
-
const citations: SearchCitation[] = [];
|
|
309
|
-
const searchQueries: string[] = [];
|
|
310
|
-
const seenUrls = new Set<string>();
|
|
311
|
-
let model = DEFAULT_MODEL;
|
|
312
|
-
let usage: { inputTokens: number; outputTokens: number; totalTokens: number } | undefined;
|
|
313
|
-
|
|
314
|
-
const reader = response.body.getReader();
|
|
315
|
-
const decoder = new TextDecoder();
|
|
316
|
-
let buffer = "";
|
|
317
|
-
|
|
318
|
-
try {
|
|
319
|
-
while (true) {
|
|
320
|
-
const { done, value } = await reader.read();
|
|
321
|
-
if (done) break;
|
|
322
|
-
|
|
323
|
-
buffer += decoder.decode(value, { stream: true });
|
|
324
|
-
const lines = buffer.split("\n");
|
|
325
|
-
buffer = lines.pop() || "";
|
|
326
|
-
|
|
327
|
-
for (const line of lines) {
|
|
328
|
-
if (!line.startsWith("data:")) continue;
|
|
329
|
-
|
|
330
|
-
const jsonStr = line.slice(5).trim();
|
|
331
|
-
if (!jsonStr) continue;
|
|
332
|
-
|
|
333
|
-
let chunk: CloudCodeResponseChunk;
|
|
334
|
-
try {
|
|
335
|
-
chunk = JSON.parse(jsonStr) as CloudCodeResponseChunk;
|
|
336
|
-
} catch {
|
|
337
|
-
continue;
|
|
338
|
-
}
|
|
339
|
-
|
|
340
|
-
const responseData = chunk.response;
|
|
341
|
-
if (!responseData) continue;
|
|
342
|
-
|
|
343
|
-
const candidate = responseData.candidates?.[0];
|
|
344
|
-
|
|
345
|
-
// Extract text content
|
|
346
|
-
if (candidate?.content?.parts) {
|
|
347
|
-
for (const part of candidate.content.parts) {
|
|
348
|
-
if (part.text) {
|
|
349
|
-
answerParts.push(part.text);
|
|
350
|
-
}
|
|
351
|
-
}
|
|
352
|
-
}
|
|
353
|
-
|
|
354
|
-
// Extract grounding metadata
|
|
355
|
-
const groundingMetadata = candidate?.groundingMetadata;
|
|
356
|
-
if (groundingMetadata) {
|
|
357
|
-
// Extract sources from grounding chunks
|
|
358
|
-
if (groundingMetadata.groundingChunks) {
|
|
359
|
-
for (const grChunk of groundingMetadata.groundingChunks) {
|
|
360
|
-
if (grChunk.web?.uri) {
|
|
361
|
-
const sourceUrl = grChunk.web.uri;
|
|
362
|
-
if (!seenUrls.has(sourceUrl)) {
|
|
363
|
-
seenUrls.add(sourceUrl);
|
|
364
|
-
sources.push({
|
|
365
|
-
title: grChunk.web.title ?? sourceUrl,
|
|
366
|
-
url: sourceUrl,
|
|
367
|
-
});
|
|
368
|
-
}
|
|
369
|
-
}
|
|
370
|
-
}
|
|
371
|
-
}
|
|
372
|
-
|
|
373
|
-
// Extract citations from grounding supports
|
|
374
|
-
if (groundingMetadata.groundingSupports && groundingMetadata.groundingChunks) {
|
|
375
|
-
for (const support of groundingMetadata.groundingSupports) {
|
|
376
|
-
const citedText = support.segment?.text;
|
|
377
|
-
const chunkIndices = support.groundingChunkIndices ?? [];
|
|
378
|
-
|
|
379
|
-
for (const idx of chunkIndices) {
|
|
380
|
-
const grChunk = groundingMetadata.groundingChunks[idx];
|
|
381
|
-
if (grChunk?.web?.uri) {
|
|
382
|
-
citations.push({
|
|
383
|
-
url: grChunk.web.uri,
|
|
384
|
-
title: grChunk.web.title ?? grChunk.web.uri,
|
|
385
|
-
citedText,
|
|
386
|
-
});
|
|
387
|
-
}
|
|
388
|
-
}
|
|
389
|
-
}
|
|
390
|
-
}
|
|
391
|
-
|
|
392
|
-
// Extract search queries
|
|
393
|
-
if (groundingMetadata.webSearchQueries) {
|
|
394
|
-
for (const q of groundingMetadata.webSearchQueries) {
|
|
395
|
-
if (!searchQueries.includes(q)) {
|
|
396
|
-
searchQueries.push(q);
|
|
397
|
-
}
|
|
398
|
-
}
|
|
399
|
-
}
|
|
400
|
-
}
|
|
401
|
-
|
|
402
|
-
// Extract usage metadata
|
|
403
|
-
if (responseData.usageMetadata) {
|
|
404
|
-
usage = {
|
|
405
|
-
inputTokens: responseData.usageMetadata.promptTokenCount ?? 0,
|
|
406
|
-
outputTokens: responseData.usageMetadata.candidatesTokenCount ?? 0,
|
|
407
|
-
totalTokens: responseData.usageMetadata.totalTokenCount ?? 0,
|
|
408
|
-
};
|
|
409
|
-
}
|
|
410
|
-
|
|
411
|
-
// Extract model version
|
|
412
|
-
if (responseData.modelVersion) {
|
|
413
|
-
model = responseData.modelVersion;
|
|
414
|
-
}
|
|
415
|
-
}
|
|
416
|
-
}
|
|
417
|
-
} finally {
|
|
418
|
-
reader.releaseLock();
|
|
419
|
-
}
|
|
420
|
-
|
|
421
|
-
return {
|
|
422
|
-
answer: answerParts.join(""),
|
|
423
|
-
sources,
|
|
424
|
-
citations,
|
|
425
|
-
searchQueries,
|
|
426
|
-
model,
|
|
427
|
-
usage,
|
|
428
|
-
};
|
|
490
|
+
return parseGeminiSearchStream(response.body);
|
|
429
491
|
}
|
|
430
492
|
|
|
431
493
|
/**
|
|
@@ -433,43 +495,64 @@ async function callGeminiSearch(
|
|
|
433
495
|
*/
|
|
434
496
|
export async function searchGemini(params: GeminiSearchParams): Promise<SearchResponse> {
|
|
435
497
|
const seed = await findGeminiAuth(params.authStorage, params.sessionId, params.signal);
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
498
|
+
let result: GeminiSearchResult;
|
|
499
|
+
|
|
500
|
+
if (seed) {
|
|
501
|
+
const isAntigravity = seed.provider === "google-antigravity";
|
|
502
|
+
result = await withOAuthAccess(
|
|
503
|
+
params.authStorage,
|
|
504
|
+
seed.provider,
|
|
505
|
+
access =>
|
|
506
|
+
// Derive bearer + projectId from the access this attempt received; a
|
|
507
|
+
// re-resolved access may omit projectId, in which case the seed's
|
|
508
|
+
// project is still the right tenant for the credential. The
|
|
509
|
+
// `fetchWithRetry` transport backoff stays INSIDE this attempt — auth
|
|
510
|
+
callGeminiSearch(
|
|
511
|
+
{
|
|
512
|
+
accessToken: access.accessToken,
|
|
513
|
+
projectId: access.projectId ?? seed.projectId,
|
|
514
|
+
isAntigravity,
|
|
515
|
+
},
|
|
516
|
+
params.query,
|
|
517
|
+
params.system_prompt,
|
|
518
|
+
params.max_output_tokens,
|
|
519
|
+
params.temperature,
|
|
520
|
+
{
|
|
521
|
+
google_search: params.google_search,
|
|
522
|
+
code_execution: params.code_execution,
|
|
523
|
+
url_context: params.url_context,
|
|
524
|
+
},
|
|
525
|
+
params.fetch,
|
|
526
|
+
params.signal,
|
|
527
|
+
params.antigravityEndpointMode,
|
|
528
|
+
),
|
|
529
|
+
{ sessionId: params.sessionId, signal: params.signal, seed: seed.access },
|
|
530
|
+
);
|
|
531
|
+
} else {
|
|
532
|
+
const apiKey = await params.authStorage.getApiKey(DEVELOPER_API_PROVIDER, params.sessionId, {
|
|
533
|
+
signal: params.signal,
|
|
534
|
+
});
|
|
535
|
+
if (!apiKey) {
|
|
536
|
+
throw new Error(
|
|
537
|
+
"No Gemini credentials found. Set GEMINI_API_KEY, configure an API key for provider \"google\", or login with 'omp /login google-gemini-cli' / 'omp /login google-antigravity' to enable Gemini web search.",
|
|
538
|
+
);
|
|
539
|
+
}
|
|
540
|
+
result = await callGeminiDeveloperSearch(
|
|
541
|
+
apiKey,
|
|
542
|
+
params.query,
|
|
543
|
+
params.system_prompt,
|
|
544
|
+
params.max_output_tokens,
|
|
545
|
+
params.temperature,
|
|
546
|
+
{
|
|
547
|
+
google_search: params.google_search,
|
|
548
|
+
code_execution: params.code_execution,
|
|
549
|
+
url_context: params.url_context,
|
|
550
|
+
},
|
|
551
|
+
params.fetch,
|
|
552
|
+
params.signal,
|
|
439
553
|
);
|
|
440
554
|
}
|
|
441
555
|
|
|
442
|
-
const isAntigravity = seed.provider === "google-antigravity";
|
|
443
|
-
const result = await withOAuthAccess(
|
|
444
|
-
params.authStorage,
|
|
445
|
-
seed.provider,
|
|
446
|
-
access =>
|
|
447
|
-
// Derive bearer + projectId from the access this attempt received; a
|
|
448
|
-
// re-resolved access may omit projectId, in which case the seed's
|
|
449
|
-
// project is still the right tenant for the credential. The
|
|
450
|
-
// `fetchWithRetry` transport backoff stays INSIDE this attempt — auth
|
|
451
|
-
callGeminiSearch(
|
|
452
|
-
{
|
|
453
|
-
accessToken: access.accessToken,
|
|
454
|
-
projectId: access.projectId ?? seed.projectId,
|
|
455
|
-
isAntigravity,
|
|
456
|
-
},
|
|
457
|
-
params.query,
|
|
458
|
-
params.system_prompt,
|
|
459
|
-
params.max_output_tokens,
|
|
460
|
-
params.temperature,
|
|
461
|
-
{
|
|
462
|
-
google_search: params.google_search,
|
|
463
|
-
code_execution: params.code_execution,
|
|
464
|
-
url_context: params.url_context,
|
|
465
|
-
},
|
|
466
|
-
params.fetch,
|
|
467
|
-
params.signal,
|
|
468
|
-
params.antigravityEndpointMode,
|
|
469
|
-
),
|
|
470
|
-
{ sessionId: params.sessionId, signal: params.signal, seed: seed.access },
|
|
471
|
-
);
|
|
472
|
-
|
|
473
556
|
let sources = result.sources;
|
|
474
557
|
|
|
475
558
|
if (params.num_results && sources.length > params.num_results) {
|
|
@@ -494,9 +577,9 @@ export class GeminiProvider extends SearchProvider {
|
|
|
494
577
|
|
|
495
578
|
isAvailable(authStorage: AuthStorage): boolean {
|
|
496
579
|
// Cheap, in-memory check — avoids driving the refresh pipeline during
|
|
497
|
-
// the provider-chain probe. `searchGemini`
|
|
498
|
-
//
|
|
499
|
-
return hasGeminiOAuth(authStorage);
|
|
580
|
+
// the provider-chain probe. `searchGemini` refreshes OAuth lazily on the
|
|
581
|
+
// actual request and resolves developer API keys through AuthStorage.
|
|
582
|
+
return hasGeminiOAuth(authStorage) || authStorage.hasAuth(DEVELOPER_API_PROVIDER);
|
|
500
583
|
}
|
|
501
584
|
|
|
502
585
|
search(params: SearchParams): Promise<SearchResponse> {
|
package/src/web/search/types.ts
CHANGED
|
@@ -43,7 +43,7 @@ export const SEARCH_PROVIDER_OPTIONS = [
|
|
|
43
43
|
{ value: "parallel", label: "Parallel", description: "Requires PARALLEL_API_KEY" },
|
|
44
44
|
{ value: "synthetic", label: "Synthetic", description: "Requires SYNTHETIC_API_KEY" },
|
|
45
45
|
{ value: "searxng", label: "SearXNG", description: "Requires SEARXNG_ENDPOINT or searxng.endpoint" },
|
|
46
|
-
{ value: "duckduckgo", label: "DuckDuckGo", description: "
|
|
46
|
+
{ value: "duckduckgo", label: "DuckDuckGo", description: "Scrapes the DuckDuckGo HTML frontend (no API key)" },
|
|
47
47
|
] as const;
|
|
48
48
|
|
|
49
49
|
/** Supported web search providers (every option except `auto`). */
|