webseek 0.3.0 → 0.5.0
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/README.md +109 -2
- package/dist/cli/index.cjs +439 -6
- package/dist/cli/index.mjs +439 -6
- package/dist/index.cjs +2 -1
- package/dist/index.d.cts +56 -9
- package/dist/index.d.mts +56 -9
- package/dist/index.mjs +2 -2
- package/dist/{tools-DBF9nCIs.mjs → tools-1ZllUYBx.mjs} +269 -5
- package/dist/{tools-Ci1EDrCT.cjs → tools-Brr43TiA.cjs} +274 -4
- package/package.json +15 -15
package/dist/index.d.mts
CHANGED
|
@@ -41,6 +41,24 @@ interface Citation {
|
|
|
41
41
|
startIndex?: number;
|
|
42
42
|
endIndex?: number;
|
|
43
43
|
}
|
|
44
|
+
/** What a search consumed. Token counts are absent for SERP providers. */
|
|
45
|
+
interface SearchUsage {
|
|
46
|
+
/** Prompt tokens, including cached ones and fetched search content. */
|
|
47
|
+
inputTokens?: number;
|
|
48
|
+
/** The part of `inputTokens` served from the prompt cache. */
|
|
49
|
+
cachedInputTokens?: number;
|
|
50
|
+
/** Generated tokens, including reasoning/thinking tokens. */
|
|
51
|
+
outputTokens?: number;
|
|
52
|
+
totalTokens?: number;
|
|
53
|
+
/** Searches the provider ran (web search tool calls, grounding queries, or SERP requests). */
|
|
54
|
+
searchCalls: number;
|
|
55
|
+
}
|
|
56
|
+
/** Estimated list-price cost of a search in USD (free tiers not applied). */
|
|
57
|
+
interface SearchCost {
|
|
58
|
+
totalUsd: number;
|
|
59
|
+
tokensUsd: number;
|
|
60
|
+
searchUsd: number;
|
|
61
|
+
}
|
|
44
62
|
/** The normalized result shape returned by every provider. */
|
|
45
63
|
interface NormalizedSearchResult {
|
|
46
64
|
provider: ProviderName;
|
|
@@ -53,6 +71,12 @@ interface NormalizedSearchResult {
|
|
|
53
71
|
citations: Citation[];
|
|
54
72
|
/** Queries the provider actually ran (grounded providers). */
|
|
55
73
|
searchQueries: string[];
|
|
74
|
+
/** The model that served the search (grounded providers). */
|
|
75
|
+
model?: string;
|
|
76
|
+
/** Tokens and searches consumed (always set by the built-in providers). */
|
|
77
|
+
usage?: SearchUsage;
|
|
78
|
+
/** Estimated cost; absent when the model's price is unknown. */
|
|
79
|
+
cost?: SearchCost;
|
|
56
80
|
/** The provider's raw response, included only when requested. */
|
|
57
81
|
raw?: unknown;
|
|
58
82
|
}
|
|
@@ -75,8 +99,8 @@ interface SearchProvider {
|
|
|
75
99
|
}
|
|
76
100
|
//#endregion
|
|
77
101
|
//#region src/lib/search.d.ts
|
|
78
|
-
declare const PROVIDER_NAMES: readonly ["openai", "google", "gemini"];
|
|
79
|
-
declare const GEMINI_BACKENDS: readonly ["gemini-api", "vertex-express"];
|
|
102
|
+
export declare const PROVIDER_NAMES: readonly ["openai", "google", "gemini"];
|
|
103
|
+
export declare const GEMINI_BACKENDS: readonly ["gemini-api", "vertex-express"];
|
|
80
104
|
interface RunSearchParams {
|
|
81
105
|
provider: ProviderName;
|
|
82
106
|
query: string;
|
|
@@ -87,7 +111,28 @@ interface RunSearchParams {
|
|
|
87
111
|
env?: Env;
|
|
88
112
|
fetchImpl?: typeof fetch;
|
|
89
113
|
}
|
|
90
|
-
declare function runSearch(params: RunSearchParams): Promise<NormalizedSearchResult>;
|
|
114
|
+
export declare function runSearch(params: RunSearchParams): Promise<NormalizedSearchResult>;
|
|
115
|
+
//#endregion
|
|
116
|
+
//#region src/pricing/pricing.d.ts
|
|
117
|
+
interface EstimateCostParams {
|
|
118
|
+
provider: ProviderName;
|
|
119
|
+
/** The model that served the search (ignored for SERP providers). */
|
|
120
|
+
model?: string;
|
|
121
|
+
/**
|
|
122
|
+
* Priced instead when `model` is not in the table — typically the requested
|
|
123
|
+
* model, since the served id may be a snapshot or variant the table lacks.
|
|
124
|
+
*/
|
|
125
|
+
fallbackModel?: string;
|
|
126
|
+
usage: SearchUsage;
|
|
127
|
+
/** Price as of this moment (rates can change on a set date); defaults to now. */
|
|
128
|
+
now?: Date;
|
|
129
|
+
}
|
|
130
|
+
/**
|
|
131
|
+
* Estimate what a search cost at list price, or `undefined` when it cannot be
|
|
132
|
+
* priced: the model is not in this table (e.g. newer than it), or an LLM-backed
|
|
133
|
+
* provider reported no token counts (a search-fee-only figure would understate).
|
|
134
|
+
*/
|
|
135
|
+
export declare function estimateCost(params: EstimateCostParams): SearchCost | undefined;
|
|
91
136
|
//#endregion
|
|
92
137
|
//#region src/utils/error.d.ts
|
|
93
138
|
/**
|
|
@@ -104,7 +149,7 @@ interface WebseekErrorOptions {
|
|
|
104
149
|
message: string;
|
|
105
150
|
cause?: unknown;
|
|
106
151
|
}
|
|
107
|
-
declare class WebseekError extends Error {
|
|
152
|
+
export declare class WebseekError extends Error {
|
|
108
153
|
readonly code: WebseekErrorCode;
|
|
109
154
|
constructor(options: WebseekErrorOptions);
|
|
110
155
|
}
|
|
@@ -112,12 +157,12 @@ declare class WebseekError extends Error {
|
|
|
112
157
|
* Map a thrown value to a process exit code: `2` for usage mistakes, `1` for
|
|
113
158
|
* any other failure.
|
|
114
159
|
*/
|
|
115
|
-
declare function errorExitCode(error: unknown): number;
|
|
160
|
+
export declare function errorExitCode(error: unknown): number;
|
|
116
161
|
/** Render any thrown value into a single-line, user-facing message. */
|
|
117
|
-
declare function formatError(error: unknown): string;
|
|
162
|
+
export declare function formatError(error: unknown): string;
|
|
118
163
|
//#endregion
|
|
119
164
|
//#region src/mcp/tools.d.ts
|
|
120
|
-
declare const webSearchInputShape: {
|
|
165
|
+
export declare const webSearchInputShape: {
|
|
121
166
|
query: z.ZodString;
|
|
122
167
|
provider: z.ZodEnum<{
|
|
123
168
|
gemini: "gemini";
|
|
@@ -161,6 +206,8 @@ interface CreateWebSearchToolParams {
|
|
|
161
206
|
env?: Env;
|
|
162
207
|
/** Injectable for tests; defaults to the global fetch. */
|
|
163
208
|
fetchImpl?: typeof fetch;
|
|
209
|
+
/** Called with every successful result before it is returned (e.g. to log usage). */
|
|
210
|
+
onResult?: (result: NormalizedSearchResult) => Promise<void> | void;
|
|
164
211
|
}
|
|
165
212
|
interface WebSearchTool {
|
|
166
213
|
name: string;
|
|
@@ -171,6 +218,6 @@ interface WebSearchTool {
|
|
|
171
218
|
};
|
|
172
219
|
handler: (args: WebSearchArgs) => Promise<ToolResult>;
|
|
173
220
|
}
|
|
174
|
-
declare function createWebSearchTool(params?: CreateWebSearchToolParams): WebSearchTool;
|
|
221
|
+
export declare function createWebSearchTool(params?: CreateWebSearchToolParams): WebSearchTool;
|
|
175
222
|
//#endregion
|
|
176
|
-
export {
|
|
223
|
+
export type { Citation, CreateWebSearchToolParams, Env, EstimateCostParams, GeminiBackend, NormalizedSearchResult, ProviderName, RunSearchParams, SearchCost, SearchParams, SearchProvider, SearchResultItem, SearchUsage, ToolResult, WebSearchArgs, WebSearchTool, WebseekErrorCode, WebseekErrorOptions };
|
package/dist/index.mjs
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { c as runSearch, d as formatError, i as PROVIDER_NAMES, l as
|
|
2
|
-
export { GEMINI_BACKENDS, PROVIDER_NAMES, WebseekError, createWebSearchTool, errorExitCode, formatError, runSearch, webSearchInputShape };
|
|
1
|
+
import { c as runSearch, d as errorExitCode, f as formatError, i as PROVIDER_NAMES, l as estimateCost, n as webSearchInputShape, r as GEMINI_BACKENDS, t as createWebSearchTool, u as WebseekError } from "./tools-1ZllUYBx.mjs";
|
|
2
|
+
export { GEMINI_BACKENDS, PROVIDER_NAMES, WebseekError, createWebSearchTool, errorExitCode, estimateCost, formatError, runSearch, webSearchInputShape };
|
|
@@ -99,6 +99,183 @@ function resolveGeminiConfig(params) {
|
|
|
99
99
|
};
|
|
100
100
|
}
|
|
101
101
|
//#endregion
|
|
102
|
+
//#region src/pricing/pricing.ts
|
|
103
|
+
/** OpenAI's `web_search` tool: $10 per 1k calls; search content tokens bill at model rates. */
|
|
104
|
+
const OPENAI_SEARCH_PER_1K = 10;
|
|
105
|
+
const OPENAI_PRICES = {
|
|
106
|
+
"gpt-5.5": openai({
|
|
107
|
+
input: 5,
|
|
108
|
+
cachedInput: .5,
|
|
109
|
+
output: 30
|
|
110
|
+
}),
|
|
111
|
+
"gpt-5.4": openai({
|
|
112
|
+
input: 2.5,
|
|
113
|
+
cachedInput: .25,
|
|
114
|
+
output: 15
|
|
115
|
+
}),
|
|
116
|
+
"gpt-5": openai({
|
|
117
|
+
input: 1.25,
|
|
118
|
+
cachedInput: .125,
|
|
119
|
+
output: 10
|
|
120
|
+
}),
|
|
121
|
+
"gpt-5-mini": openai({
|
|
122
|
+
input: .25,
|
|
123
|
+
cachedInput: .025,
|
|
124
|
+
output: 2
|
|
125
|
+
}),
|
|
126
|
+
"gpt-5-nano": openai({
|
|
127
|
+
input: .05,
|
|
128
|
+
cachedInput: .005,
|
|
129
|
+
output: .4
|
|
130
|
+
}),
|
|
131
|
+
"gpt-4.1": openai({
|
|
132
|
+
input: 2,
|
|
133
|
+
cachedInput: .5,
|
|
134
|
+
output: 8
|
|
135
|
+
}),
|
|
136
|
+
"gpt-4o": openai({
|
|
137
|
+
input: 2.5,
|
|
138
|
+
cachedInput: 1.25,
|
|
139
|
+
output: 10
|
|
140
|
+
}),
|
|
141
|
+
"gpt-4o-mini": openai({
|
|
142
|
+
input: .15,
|
|
143
|
+
cachedInput: .075,
|
|
144
|
+
output: .6
|
|
145
|
+
})
|
|
146
|
+
};
|
|
147
|
+
/**
|
|
148
|
+
* Gemini 3.x: "$14 per 1,000 requests", read as one charge per search query the
|
|
149
|
+
* model ran (each `webSearchQueries` entry), not per prompt.
|
|
150
|
+
*/
|
|
151
|
+
const GEMINI_3_SEARCH_PER_1K = 14;
|
|
152
|
+
/** Gemini 2.5 bills each grounded prompt, however many queries it ran ($35 per 1k). */
|
|
153
|
+
const GEMINI_2_5_SEARCH_PER_1K = 35;
|
|
154
|
+
const GEMINI_3_FLASH = {
|
|
155
|
+
rates: {
|
|
156
|
+
input: .75,
|
|
157
|
+
cachedInput: .075,
|
|
158
|
+
output: 3.75
|
|
159
|
+
},
|
|
160
|
+
next: {
|
|
161
|
+
from: "2027-01-01",
|
|
162
|
+
rates: {
|
|
163
|
+
input: 1.5,
|
|
164
|
+
cachedInput: .15,
|
|
165
|
+
output: 7.5
|
|
166
|
+
}
|
|
167
|
+
},
|
|
168
|
+
searchPer1k: GEMINI_3_SEARCH_PER_1K
|
|
169
|
+
};
|
|
170
|
+
const GEMINI_PRICES = {
|
|
171
|
+
"gemini-3.8-flash": GEMINI_3_FLASH,
|
|
172
|
+
"gemini-3.7-flash": GEMINI_3_FLASH,
|
|
173
|
+
"gemini-3.6-flash": GEMINI_3_FLASH,
|
|
174
|
+
"gemini-3-flash-preview": GEMINI_3_FLASH,
|
|
175
|
+
"gemini-3.5-flash": gemini3({
|
|
176
|
+
input: 1.5,
|
|
177
|
+
cachedInput: .15,
|
|
178
|
+
output: 9
|
|
179
|
+
}),
|
|
180
|
+
"gemini-3.5-flash-lite": gemini3({
|
|
181
|
+
input: .3,
|
|
182
|
+
cachedInput: .03,
|
|
183
|
+
output: 2.5
|
|
184
|
+
}),
|
|
185
|
+
"gemini-3.1-flash-lite": gemini3({
|
|
186
|
+
input: .25,
|
|
187
|
+
cachedInput: .025,
|
|
188
|
+
output: 1.5
|
|
189
|
+
}),
|
|
190
|
+
"gemini-3.1-pro-preview": gemini3({
|
|
191
|
+
input: 2,
|
|
192
|
+
cachedInput: .2,
|
|
193
|
+
output: 12
|
|
194
|
+
}),
|
|
195
|
+
"gemini-2.5-pro": gemini25({
|
|
196
|
+
input: 1.25,
|
|
197
|
+
cachedInput: .125,
|
|
198
|
+
output: 10
|
|
199
|
+
}),
|
|
200
|
+
"gemini-2.5-flash": gemini25({
|
|
201
|
+
input: .3,
|
|
202
|
+
cachedInput: .03,
|
|
203
|
+
output: 2.5
|
|
204
|
+
}),
|
|
205
|
+
"gemini-2.5-flash-lite": gemini25({
|
|
206
|
+
input: .1,
|
|
207
|
+
cachedInput: .01,
|
|
208
|
+
output: .4
|
|
209
|
+
})
|
|
210
|
+
};
|
|
211
|
+
/** Google Custom Search: $5 per 1k queries (each paginated request is a query). */
|
|
212
|
+
const GOOGLE_CSE_PER_1K = 5;
|
|
213
|
+
function openai(rates) {
|
|
214
|
+
return {
|
|
215
|
+
rates,
|
|
216
|
+
searchPer1k: OPENAI_SEARCH_PER_1K
|
|
217
|
+
};
|
|
218
|
+
}
|
|
219
|
+
function gemini3(rates) {
|
|
220
|
+
return {
|
|
221
|
+
rates,
|
|
222
|
+
searchPer1k: GEMINI_3_SEARCH_PER_1K
|
|
223
|
+
};
|
|
224
|
+
}
|
|
225
|
+
function gemini25(rates) {
|
|
226
|
+
return {
|
|
227
|
+
rates,
|
|
228
|
+
searchPer1k: GEMINI_2_5_SEARCH_PER_1K,
|
|
229
|
+
searchPerPrompt: true
|
|
230
|
+
};
|
|
231
|
+
}
|
|
232
|
+
/**
|
|
233
|
+
* Estimate what a search cost at list price, or `undefined` when it cannot be
|
|
234
|
+
* priced: the model is not in this table (e.g. newer than it), or an LLM-backed
|
|
235
|
+
* provider reported no token counts (a search-fee-only figure would understate).
|
|
236
|
+
*/
|
|
237
|
+
function estimateCost(params) {
|
|
238
|
+
const { usage } = params;
|
|
239
|
+
if (params.provider === "google") {
|
|
240
|
+
const searchUsd = usage.searchCalls * GOOGLE_CSE_PER_1K / 1e3;
|
|
241
|
+
return {
|
|
242
|
+
totalUsd: searchUsd,
|
|
243
|
+
tokensUsd: 0,
|
|
244
|
+
searchUsd
|
|
245
|
+
};
|
|
246
|
+
}
|
|
247
|
+
const table = params.provider === "openai" ? OPENAI_PRICES : GEMINI_PRICES;
|
|
248
|
+
const price = [params.model, params.fallbackModel].filter((model) => model !== void 0).map((model) => lookupPrice({
|
|
249
|
+
table,
|
|
250
|
+
model
|
|
251
|
+
})).find((found) => found !== void 0);
|
|
252
|
+
if (price === void 0) return;
|
|
253
|
+
if (usage.inputTokens === void 0 && usage.outputTokens === void 0) return;
|
|
254
|
+
const rates = ratesAt({
|
|
255
|
+
price,
|
|
256
|
+
now: params.now ?? /* @__PURE__ */ new Date()
|
|
257
|
+
});
|
|
258
|
+
const inputTokens = usage.inputTokens ?? 0;
|
|
259
|
+
const cachedInputTokens = Math.min(usage.cachedInputTokens ?? 0, inputTokens);
|
|
260
|
+
const tokensUsd = ((inputTokens - cachedInputTokens) * rates.input + cachedInputTokens * rates.cachedInput + (usage.outputTokens ?? 0) * rates.output) / 1e6;
|
|
261
|
+
const searchUsd = (price.searchPerPrompt ? Math.min(usage.searchCalls, 1) : usage.searchCalls) * price.searchPer1k / 1e3;
|
|
262
|
+
return {
|
|
263
|
+
totalUsd: tokensUsd + searchUsd,
|
|
264
|
+
tokensUsd,
|
|
265
|
+
searchUsd
|
|
266
|
+
};
|
|
267
|
+
}
|
|
268
|
+
function lookupPrice(params) {
|
|
269
|
+
const { table } = params;
|
|
270
|
+
const id = params.model.replace(/^(?:.*\/)?models\//, "").replace(/-\d{4}-\d{2}-\d{2}$/, "").toLowerCase();
|
|
271
|
+
return Object.hasOwn(table, id) ? table[id] : void 0;
|
|
272
|
+
}
|
|
273
|
+
function ratesAt(params) {
|
|
274
|
+
const { price } = params;
|
|
275
|
+
if (price.next && params.now.toISOString().slice(0, 10) >= price.next.from) return price.next.rates;
|
|
276
|
+
return price.rates;
|
|
277
|
+
}
|
|
278
|
+
//#endregion
|
|
102
279
|
//#region src/providers/gemini.ts
|
|
103
280
|
/**
|
|
104
281
|
* Gemini web search provider via "Grounding with Google Search".
|
|
@@ -151,8 +328,18 @@ const candidateSchema = z.looseObject({
|
|
|
151
328
|
content: z.looseObject({ parts: z.array(z.looseObject({ text: z.string().optional() })).optional() }).optional(),
|
|
152
329
|
groundingMetadata: groundingMetadataSchema.optional()
|
|
153
330
|
});
|
|
331
|
+
const usageMetadataSchema = z.looseObject({
|
|
332
|
+
promptTokenCount: z.number().optional(),
|
|
333
|
+
cachedContentTokenCount: z.number().optional(),
|
|
334
|
+
candidatesTokenCount: z.number().optional(),
|
|
335
|
+
thoughtsTokenCount: z.number().optional(),
|
|
336
|
+
toolUsePromptTokenCount: z.number().optional(),
|
|
337
|
+
totalTokenCount: z.number().optional()
|
|
338
|
+
});
|
|
154
339
|
const responseSchema$2 = z.looseObject({
|
|
155
340
|
candidates: z.array(candidateSchema).optional(),
|
|
341
|
+
usageMetadata: usageMetadataSchema.optional(),
|
|
342
|
+
modelVersion: z.string().optional(),
|
|
156
343
|
error: z.looseObject({ message: z.string().optional() }).optional()
|
|
157
344
|
});
|
|
158
345
|
function createGeminiProvider(params) {
|
|
@@ -183,6 +370,11 @@ function createGeminiProvider(params) {
|
|
|
183
370
|
body
|
|
184
371
|
});
|
|
185
372
|
const { answer, citations, searchQueries } = extract$1(parsed.data);
|
|
373
|
+
const servedModel = parsed.data.modelVersion ?? model;
|
|
374
|
+
const usage = toUsage$1({
|
|
375
|
+
usage: parsed.data.usageMetadata,
|
|
376
|
+
searchCalls: searchQueries.length
|
|
377
|
+
});
|
|
186
378
|
return {
|
|
187
379
|
provider: "gemini",
|
|
188
380
|
query: searchParams.query,
|
|
@@ -190,6 +382,14 @@ function createGeminiProvider(params) {
|
|
|
190
382
|
answer,
|
|
191
383
|
citations,
|
|
192
384
|
searchQueries,
|
|
385
|
+
model: servedModel,
|
|
386
|
+
usage,
|
|
387
|
+
cost: estimateCost({
|
|
388
|
+
provider: "gemini",
|
|
389
|
+
model: servedModel,
|
|
390
|
+
fallbackModel: model,
|
|
391
|
+
usage
|
|
392
|
+
}),
|
|
193
393
|
raw: searchParams.includeRaw ? body : void 0
|
|
194
394
|
};
|
|
195
395
|
}
|
|
@@ -208,6 +408,23 @@ function extract$1(data) {
|
|
|
208
408
|
searchQueries: metadata?.webSearchQueries ?? []
|
|
209
409
|
};
|
|
210
410
|
}
|
|
411
|
+
function toUsage$1(params) {
|
|
412
|
+
const { usage } = params;
|
|
413
|
+
if (usage === void 0) return { searchCalls: params.searchCalls };
|
|
414
|
+
const inputTokens = sumDefined([usage.promptTokenCount, usage.toolUsePromptTokenCount]);
|
|
415
|
+
const outputTokens = sumDefined([usage.candidatesTokenCount, usage.thoughtsTokenCount]);
|
|
416
|
+
return {
|
|
417
|
+
inputTokens,
|
|
418
|
+
cachedInputTokens: usage.cachedContentTokenCount,
|
|
419
|
+
outputTokens,
|
|
420
|
+
totalTokens: usage.totalTokenCount,
|
|
421
|
+
searchCalls: params.searchCalls
|
|
422
|
+
};
|
|
423
|
+
}
|
|
424
|
+
function sumDefined(values) {
|
|
425
|
+
const defined = values.filter((value) => value !== void 0);
|
|
426
|
+
return defined.length > 0 ? defined.reduce((total, value) => total + value, 0) : void 0;
|
|
427
|
+
}
|
|
211
428
|
function toError$2(params) {
|
|
212
429
|
const parsed = responseSchema$2.safeParse(params.body);
|
|
213
430
|
const message = (parsed.success ? parsed.data.error?.message : void 0) ?? `Gemini grounding request failed (HTTP ${params.status}).`;
|
|
@@ -262,6 +479,7 @@ function createGoogleCseProvider(params) {
|
|
|
262
479
|
const desired = clampDesired(searchParams.maxResults ?? MAX_PER_REQUEST);
|
|
263
480
|
const items = [];
|
|
264
481
|
let lastRaw;
|
|
482
|
+
let requests = 0;
|
|
265
483
|
for (let start = 1; start <= MAX_TOTAL_RESULTS && items.length < desired; start += MAX_PER_REQUEST) {
|
|
266
484
|
const num = Math.min(MAX_PER_REQUEST, desired - items.length);
|
|
267
485
|
const response = await fetchImpl(buildUrl({
|
|
@@ -270,6 +488,7 @@ function createGoogleCseProvider(params) {
|
|
|
270
488
|
start,
|
|
271
489
|
num
|
|
272
490
|
}));
|
|
491
|
+
requests += 1;
|
|
273
492
|
const body = await response.json().catch(() => void 0);
|
|
274
493
|
lastRaw = body;
|
|
275
494
|
const parsed = responseSchema$1.safeParse(body);
|
|
@@ -281,6 +500,7 @@ function createGoogleCseProvider(params) {
|
|
|
281
500
|
items.push(...page);
|
|
282
501
|
if (page.length < num) break;
|
|
283
502
|
}
|
|
503
|
+
const usage = { searchCalls: requests };
|
|
284
504
|
return {
|
|
285
505
|
provider: "google",
|
|
286
506
|
query: searchParams.query,
|
|
@@ -292,6 +512,11 @@ function createGoogleCseProvider(params) {
|
|
|
292
512
|
})),
|
|
293
513
|
citations: [],
|
|
294
514
|
searchQueries: [searchParams.query],
|
|
515
|
+
usage,
|
|
516
|
+
cost: estimateCost({
|
|
517
|
+
provider: "google",
|
|
518
|
+
usage
|
|
519
|
+
}),
|
|
295
520
|
raw: searchParams.includeRaw ? lastRaw : void 0
|
|
296
521
|
};
|
|
297
522
|
}
|
|
@@ -358,9 +583,17 @@ const outputItemSchema = z.looseObject({
|
|
|
358
583
|
content: z.array(contentSchema).optional(),
|
|
359
584
|
action: z.looseObject({ query: z.string().optional() }).optional()
|
|
360
585
|
});
|
|
586
|
+
const usageSchema = z.looseObject({
|
|
587
|
+
input_tokens: z.number().optional(),
|
|
588
|
+
input_tokens_details: z.looseObject({ cached_tokens: z.number().optional() }).nullish(),
|
|
589
|
+
output_tokens: z.number().optional(),
|
|
590
|
+
total_tokens: z.number().optional()
|
|
591
|
+
});
|
|
361
592
|
const responseSchema = z.looseObject({
|
|
362
593
|
output: z.array(outputItemSchema).optional(),
|
|
363
594
|
output_text: z.string().optional(),
|
|
595
|
+
model: z.string().optional(),
|
|
596
|
+
usage: usageSchema.nullish(),
|
|
364
597
|
error: z.looseObject({ message: z.string().optional() }).nullish()
|
|
365
598
|
});
|
|
366
599
|
function createOpenAIProvider(params) {
|
|
@@ -388,7 +621,12 @@ function createOpenAIProvider(params) {
|
|
|
388
621
|
status: response.status,
|
|
389
622
|
body
|
|
390
623
|
});
|
|
391
|
-
const { answer, citations, searchQueries } = extract(parsed.data);
|
|
624
|
+
const { answer, citations, searchQueries, searchCalls } = extract(parsed.data);
|
|
625
|
+
const servedModel = parsed.data.model ?? model;
|
|
626
|
+
const usage = toUsage({
|
|
627
|
+
usage: parsed.data.usage,
|
|
628
|
+
searchCalls
|
|
629
|
+
});
|
|
392
630
|
return {
|
|
393
631
|
provider: "openai",
|
|
394
632
|
query: searchParams.query,
|
|
@@ -396,6 +634,14 @@ function createOpenAIProvider(params) {
|
|
|
396
634
|
answer,
|
|
397
635
|
citations,
|
|
398
636
|
searchQueries,
|
|
637
|
+
model: servedModel,
|
|
638
|
+
usage,
|
|
639
|
+
cost: estimateCost({
|
|
640
|
+
provider: "openai",
|
|
641
|
+
model: servedModel,
|
|
642
|
+
fallbackModel: model,
|
|
643
|
+
usage
|
|
644
|
+
}),
|
|
399
645
|
raw: searchParams.includeRaw ? body : void 0
|
|
400
646
|
};
|
|
401
647
|
}
|
|
@@ -405,8 +651,12 @@ function extract(data) {
|
|
|
405
651
|
const textParts = [];
|
|
406
652
|
const citations = [];
|
|
407
653
|
const searchQueries = [];
|
|
654
|
+
let searchCalls = 0;
|
|
408
655
|
for (const item of data.output ?? []) {
|
|
409
|
-
if (item.type === "web_search_call"
|
|
656
|
+
if (item.type === "web_search_call") {
|
|
657
|
+
searchCalls += 1;
|
|
658
|
+
if (item.action?.query) searchQueries.push(item.action.query);
|
|
659
|
+
}
|
|
410
660
|
for (const content of item.content ?? []) {
|
|
411
661
|
if (content.type === "output_text" && content.text) textParts.push(content.text);
|
|
412
662
|
for (const annotation of content.annotations ?? []) if (annotation.type === "url_citation" && annotation.url) citations.push({
|
|
@@ -420,7 +670,18 @@ function extract(data) {
|
|
|
420
670
|
return {
|
|
421
671
|
answer: textParts.length > 0 ? textParts.join("") : data.output_text ?? "",
|
|
422
672
|
citations,
|
|
423
|
-
searchQueries
|
|
673
|
+
searchQueries,
|
|
674
|
+
searchCalls
|
|
675
|
+
};
|
|
676
|
+
}
|
|
677
|
+
function toUsage(params) {
|
|
678
|
+
const { usage } = params;
|
|
679
|
+
return {
|
|
680
|
+
inputTokens: usage?.input_tokens,
|
|
681
|
+
cachedInputTokens: usage?.input_tokens_details?.cached_tokens,
|
|
682
|
+
outputTokens: usage?.output_tokens,
|
|
683
|
+
totalTokens: usage?.total_tokens,
|
|
684
|
+
searchCalls: params.searchCalls
|
|
424
685
|
};
|
|
425
686
|
}
|
|
426
687
|
function toError(params) {
|
|
@@ -515,7 +776,7 @@ function createWebSearchTool(params = {}) {
|
|
|
515
776
|
name: "web_search",
|
|
516
777
|
config: {
|
|
517
778
|
title: "Web Search",
|
|
518
|
-
description: "Search the web using a provider's API key (OpenAI, Google Custom Search, or Gemini). Returns a normalized JSON result with SERP results and/or a grounded answer with citations.",
|
|
779
|
+
description: "Search the web using a provider's API key (OpenAI, Google Custom Search, or Gemini). Returns a normalized JSON result with SERP results and/or a grounded answer with citations, plus the tokens and searches used and an estimated USD cost.",
|
|
519
780
|
inputSchema: webSearchInputShape
|
|
520
781
|
},
|
|
521
782
|
handler: async (args) => {
|
|
@@ -530,6 +791,9 @@ function createWebSearchTool(params = {}) {
|
|
|
530
791
|
env: params.env,
|
|
531
792
|
fetchImpl: params.fetchImpl
|
|
532
793
|
});
|
|
794
|
+
try {
|
|
795
|
+
await params.onResult?.(result);
|
|
796
|
+
} catch {}
|
|
533
797
|
return { content: [{
|
|
534
798
|
type: "text",
|
|
535
799
|
text: JSON.stringify(result, null, 2)
|
|
@@ -547,4 +811,4 @@ function createWebSearchTool(params = {}) {
|
|
|
547
811
|
};
|
|
548
812
|
}
|
|
549
813
|
//#endregion
|
|
550
|
-
export { coerceGeminiBackend as a, runSearch as c,
|
|
814
|
+
export { coerceGeminiBackend as a, runSearch as c, errorExitCode as d, formatError as f, PROVIDER_NAMES as i, estimateCost as l, webSearchInputShape as n, coerceMaxResults as o, GEMINI_BACKENDS as r, coerceProvider as s, createWebSearchTool as t, WebseekError as u };
|