pi-web-providers 2.2.0 → 2.4.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.
Files changed (3) hide show
  1. package/README.md +109 -10
  2. package/dist/index.js +1263 -418
  3. package/package.json +6 -3
package/dist/index.js CHANGED
@@ -23,7 +23,7 @@ import {
23
23
  visibleWidth,
24
24
  wrapTextWithAnsi
25
25
  } from "@mariozechner/pi-tui";
26
- import { Type as Type14 } from "@sinclair/typebox";
26
+ import { Type as Type16 } from "@sinclair/typebox";
27
27
 
28
28
  // src/config.ts
29
29
  import { execSync } from "node:child_process";
@@ -41,8 +41,10 @@ var PROVIDER_TOOLS_BY_ID = {
41
41
  firecrawl: ["search", "contents"],
42
42
  gemini: ["search", "answer", "research"],
43
43
  linkup: ["search", "contents"],
44
- perplexity: ["search", "answer", "research"],
44
+ openai: ["search", "answer", "research"],
45
45
  parallel: ["search", "contents"],
46
+ perplexity: ["search", "answer", "research"],
47
+ serper: ["search"],
46
48
  tavily: ["search", "contents"],
47
49
  valyu: ["search", "contents", "answer", "research"]
48
50
  };
@@ -86,8 +88,10 @@ var PROVIDER_IDS = [
86
88
  "firecrawl",
87
89
  "gemini",
88
90
  "linkup",
89
- "perplexity",
91
+ "openai",
90
92
  "parallel",
93
+ "perplexity",
94
+ "serper",
91
95
  "tavily",
92
96
  "valyu"
93
97
  ];
@@ -271,10 +275,45 @@ function normalizeProvider(providerId, raw, source) {
271
275
  options: readOptionalObject,
272
276
  settings: parseOptionalExecutionSettings
273
277
  });
278
+ case "openai":
279
+ return parseProviderWithShape(raw, source, providerId, {
280
+ apiKey: readOptionalString,
281
+ baseUrl: readOptionalString,
282
+ options: (value, innerSource, field) => parseOptionalCapabilityOptions(
283
+ value,
284
+ innerSource,
285
+ field,
286
+ ["search", "answer", "research"]
287
+ ),
288
+ settings: parseOptionalExecutionSettings
289
+ });
274
290
  case "firecrawl":
275
291
  case "linkup":
276
292
  case "parallel":
277
293
  case "perplexity":
294
+ return parseProviderWithShape(
295
+ raw,
296
+ source,
297
+ providerId,
298
+ {
299
+ apiKey: readOptionalString,
300
+ baseUrl: readOptionalString,
301
+ options: readOptionalObject,
302
+ settings: parseOptionalExecutionSettings
303
+ }
304
+ );
305
+ case "serper":
306
+ return parseProviderWithShape(raw, source, providerId, {
307
+ apiKey: readOptionalString,
308
+ baseUrl: readOptionalString,
309
+ options: (value, innerSource, field) => parseOptionalCapabilityOptions(
310
+ value,
311
+ innerSource,
312
+ field,
313
+ ["search"]
314
+ ),
315
+ settings: parseOptionalExecutionSettings
316
+ });
278
317
  case "tavily":
279
318
  return parseProviderWithShape(raw, source, providerId, {
280
319
  apiKey: readOptionalString,
@@ -4082,57 +4121,112 @@ function toDate(value, name) {
4082
4121
  return date;
4083
4122
  }
4084
4123
 
4085
- // src/providers/parallel.ts
4124
+ // src/providers/openai.ts
4086
4125
  import { Type as Type10 } from "@sinclair/typebox";
4087
- import ParallelClient from "parallel-web";
4088
- var parallelSearchOptionsSchema = Type10.Object(
4126
+ import OpenAI from "openai";
4127
+ var DEFAULT_SEARCH_MODEL2 = "gpt-4.1";
4128
+ var DEFAULT_ANSWER_MODEL2 = "gpt-4.1";
4129
+ var DEFAULT_RESEARCH_MODEL = "o4-mini-deep-research";
4130
+ var openaiSearchOptionsSchema = Type10.Object(
4089
4131
  {
4090
- mode: Type10.Optional(
4091
- literalUnion(["agentic", "one-shot"], {
4092
- description: "Parallel search mode."
4132
+ model: Type10.Optional(
4133
+ Type10.String({
4134
+ description: "OpenAI model to use for web search (for example 'gpt-4.1')."
4135
+ })
4136
+ ),
4137
+ instructions: Type10.Optional(
4138
+ Type10.String({
4139
+ description: "Optional instructions that shape source selection and result style."
4093
4140
  })
4094
4141
  )
4095
4142
  },
4096
- { description: "Parallel search options." }
4143
+ { description: "OpenAI search options." }
4097
4144
  );
4098
- var parallelExtractOptionsSchema = Type10.Object(
4145
+ var openaiAnswerOptionsSchema = Type10.Object(
4099
4146
  {
4100
- excerpts: Type10.Optional(
4101
- Type10.Boolean({ description: "Include excerpts in extraction results." })
4147
+ model: Type10.Optional(
4148
+ Type10.String({
4149
+ description: "OpenAI model to use for grounded answers (for example 'gpt-4.1')."
4150
+ })
4102
4151
  ),
4103
- full_content: Type10.Optional(
4104
- Type10.Boolean({
4105
- description: "Include full page content in extraction results."
4152
+ instructions: Type10.Optional(
4153
+ Type10.String({
4154
+ description: "Optional instructions that shape the answer structure, tone, and source selection."
4106
4155
  })
4107
4156
  )
4108
4157
  },
4109
- { description: "Parallel extract options." }
4158
+ { description: "OpenAI answer options." }
4110
4159
  );
4111
- var parallelAdapter = {
4112
- id: "parallel",
4113
- label: "Parallel",
4114
- docsUrl: "https://github.com/parallel-web/parallel-sdk-typescript",
4115
- tools: ["search", "contents"],
4160
+ var openaiResearchOptionsSchema = Type10.Object(
4161
+ {
4162
+ model: Type10.Optional(
4163
+ Type10.String({
4164
+ description: "OpenAI deep research model to use (for example 'o4-mini-deep-research')."
4165
+ })
4166
+ ),
4167
+ instructions: Type10.Optional(
4168
+ Type10.String({
4169
+ description: "Optional instructions that shape the report structure, tone, and source selection."
4170
+ })
4171
+ ),
4172
+ max_tool_calls: Type10.Optional(
4173
+ Type10.Integer({
4174
+ minimum: 1,
4175
+ description: "Maximum number of built-in tool calls the model may make during the research run."
4176
+ })
4177
+ )
4178
+ },
4179
+ { description: "OpenAI deep research options." }
4180
+ );
4181
+ var searchResultSchema = {
4182
+ type: "object",
4183
+ additionalProperties: false,
4184
+ required: ["sources"],
4185
+ properties: {
4186
+ sources: {
4187
+ type: "array",
4188
+ items: {
4189
+ type: "object",
4190
+ additionalProperties: false,
4191
+ required: ["title", "url", "snippet"],
4192
+ properties: {
4193
+ title: { type: "string" },
4194
+ url: { type: "string" },
4195
+ snippet: { type: "string" }
4196
+ }
4197
+ }
4198
+ }
4199
+ }
4200
+ };
4201
+ var openaiAdapter = {
4202
+ id: "openai",
4203
+ label: "OpenAI",
4204
+ docsUrl: "https://platform.openai.com/docs/guides/deep-research",
4205
+ tools: ["search", "answer", "research"],
4116
4206
  getToolOptionsSchema(capability) {
4117
4207
  switch (capability) {
4118
4208
  case "search":
4119
- return parallelSearchOptionsSchema;
4120
- case "contents":
4121
- return parallelExtractOptionsSchema;
4209
+ return openaiSearchOptionsSchema;
4210
+ case "answer":
4211
+ return openaiAnswerOptionsSchema;
4212
+ case "research":
4213
+ return openaiResearchOptionsSchema;
4122
4214
  default:
4123
4215
  return void 0;
4124
4216
  }
4125
4217
  },
4126
4218
  createTemplate() {
4127
4219
  return {
4128
- apiKey: "PARALLEL_API_KEY",
4220
+ apiKey: "OPENAI_API_KEY",
4129
4221
  options: {
4130
4222
  search: {
4131
- mode: "agentic"
4223
+ model: DEFAULT_SEARCH_MODEL2
4132
4224
  },
4133
- extract: {
4134
- excerpts: false,
4135
- full_content: true
4225
+ answer: {
4226
+ model: DEFAULT_ANSWER_MODEL2
4227
+ },
4228
+ research: {
4229
+ model: DEFAULT_RESEARCH_MODEL
4136
4230
  }
4137
4231
  }
4138
4232
  };
@@ -4146,11 +4240,18 @@ var parallelAdapter = {
4146
4240
  options: config.options?.search,
4147
4241
  settings: config.settings
4148
4242
  };
4149
- case "contents":
4243
+ case "answer":
4150
4244
  return {
4151
4245
  apiKey: config.apiKey,
4152
4246
  baseUrl: config.baseUrl,
4153
- options: config.options?.extract,
4247
+ options: config.options?.answer,
4248
+ settings: config.settings
4249
+ };
4250
+ case "research":
4251
+ return {
4252
+ apiKey: config.apiKey,
4253
+ baseUrl: config.baseUrl,
4254
+ options: config.options?.research,
4154
4255
  settings: config.settings
4155
4256
  };
4156
4257
  default:
@@ -4164,11 +4265,11 @@ var parallelAdapter = {
4164
4265
  return buildProviderPlan({
4165
4266
  request,
4166
4267
  config,
4167
- providerId: parallelAdapter.id,
4168
- providerLabel: parallelAdapter.label,
4268
+ providerId: openaiAdapter.id,
4269
+ providerLabel: openaiAdapter.label,
4169
4270
  handlers: {
4170
4271
  search: {
4171
- execute: (searchRequest, providerConfig, context) => parallelAdapter.search(
4272
+ execute: (searchRequest, providerConfig, context) => openaiAdapter.search(
4172
4273
  searchRequest.query,
4173
4274
  searchRequest.maxResults,
4174
4275
  providerConfig,
@@ -4176,12 +4277,20 @@ var parallelAdapter = {
4176
4277
  searchRequest.options
4177
4278
  )
4178
4279
  },
4179
- contents: {
4180
- execute: (contentsRequest, providerConfig, context) => parallelAdapter.contents(
4181
- contentsRequest.urls,
4280
+ answer: {
4281
+ execute: (answerRequest, providerConfig, context) => openaiAdapter.answer(
4282
+ answerRequest.query,
4182
4283
  providerConfig,
4183
4284
  context,
4184
- contentsRequest.options
4285
+ answerRequest.options
4286
+ )
4287
+ },
4288
+ research: {
4289
+ execute: (researchRequest, providerConfig, context) => openaiAdapter.research(
4290
+ researchRequest.input,
4291
+ providerConfig,
4292
+ context,
4293
+ researchRequest.options
4185
4294
  )
4186
4295
  }
4187
4296
  }
@@ -4189,62 +4298,72 @@ var parallelAdapter = {
4189
4298
  },
4190
4299
  async search(query2, maxResults, config, context, options) {
4191
4300
  const client = createClient5(config);
4192
- const defaults = stripLocalExecutionOptions(asJsonObject(config.options?.search)) ?? {};
4193
- const response = await client.beta.search(
4194
- {
4195
- ...defaults,
4196
- ...options ?? {},
4197
- objective: query2,
4198
- max_results: maxResults
4199
- },
4200
- buildRequestOptions2(context)
4301
+ const response = await client.responses.create(
4302
+ buildOpenAISearchRequest(query2, maxResults, config, options),
4303
+ buildRequestOptions2(context.signal, context.idempotencyKey)
4201
4304
  );
4202
- return {
4203
- provider: parallelAdapter.id,
4204
- results: response.results.slice(0, maxResults).map((result) => ({
4205
- title: result.title ?? result.url,
4206
- url: result.url,
4207
- snippet: trimSnippet(result.excerpts?.join(" ") ?? "")
4208
- }))
4209
- };
4305
+ return parseSearchResponse2(response, maxResults);
4210
4306
  },
4211
- async contents(urls, config, context, options) {
4307
+ async answer(query2, config, context, options) {
4212
4308
  const client = createClient5(config);
4213
- const defaults = stripLocalExecutionOptions(asJsonObject(config.options?.extract)) ?? {};
4214
- const response = await client.beta.extract(
4215
- {
4216
- ...defaults,
4217
- ...options ?? {},
4218
- urls
4219
- },
4220
- buildRequestOptions2(context)
4309
+ const response = await client.responses.create(
4310
+ buildOpenAIAnswerRequest(query2, config, options),
4311
+ buildRequestOptions2(context.signal, context.idempotencyKey)
4221
4312
  );
4222
- const resultsByUrl = new Map(
4223
- response.results.map((result) => [result.url, result])
4313
+ return ensureCompletedResponse(response, "answer");
4314
+ },
4315
+ async research(input, config, context, options) {
4316
+ return await executeAsyncResearch({
4317
+ providerLabel: openaiAdapter.label,
4318
+ providerId: openaiAdapter.id,
4319
+ context,
4320
+ start: (researchContext) => openaiAdapter.startResearch(input, config, researchContext, options),
4321
+ poll: (id, researchContext) => openaiAdapter.pollResearch(id, config, researchContext, options)
4322
+ });
4323
+ },
4324
+ async startResearch(input, config, context, options) {
4325
+ const client = createClient5(config);
4326
+ const response = await client.responses.create(
4327
+ buildOpenAIResearchRequest(input, config, options),
4328
+ buildRequestOptions2(context.signal, context.idempotencyKey)
4224
4329
  );
4225
- const errorsByUrl = new Map(
4226
- response.errors.map((error) => [error.url, error])
4330
+ return { id: response.id };
4331
+ },
4332
+ async pollResearch(id, config, context, _options) {
4333
+ const client = createClient5(config);
4334
+ const response = await client.responses.retrieve(
4335
+ id,
4336
+ void 0,
4337
+ buildRequestOptions2(context.signal)
4227
4338
  );
4339
+ const status = response.status ?? "completed";
4340
+ if (status === "completed") {
4341
+ return {
4342
+ status: "completed",
4343
+ output: formatResponseOutput(response, "research")
4344
+ };
4345
+ }
4346
+ if (status === "failed") {
4347
+ return {
4348
+ status: "failed",
4349
+ error: response.error?.message ?? "research failed"
4350
+ };
4351
+ }
4352
+ if (status === "cancelled") {
4353
+ return {
4354
+ status: "cancelled",
4355
+ error: "research was canceled"
4356
+ };
4357
+ }
4358
+ if (status === "incomplete") {
4359
+ return {
4360
+ status: "failed",
4361
+ error: formatIncompleteError(response, "research")
4362
+ };
4363
+ }
4228
4364
  return {
4229
- provider: parallelAdapter.id,
4230
- answers: urls.map((url) => {
4231
- const result = resultsByUrl.get(url);
4232
- if (result) {
4233
- return {
4234
- url,
4235
- content: result.full_content ?? result.excerpts?.join("\n\n") ?? void 0,
4236
- metadata: result
4237
- };
4238
- }
4239
- const error = errorsByUrl.get(url);
4240
- return error ? {
4241
- url,
4242
- error: formatJson(error)
4243
- } : {
4244
- url,
4245
- error: "No content returned for this URL."
4246
- };
4247
- })
4365
+ status: "in_progress",
4366
+ statusText: status
4248
4367
  };
4249
4368
  }
4250
4369
  };
@@ -4253,85 +4372,521 @@ function createClient5(config) {
4253
4372
  if (!apiKey) {
4254
4373
  throw new Error("is missing an API key");
4255
4374
  }
4256
- return new ParallelClient({
4375
+ const baseUrl = resolveConfigValue(config.baseUrl);
4376
+ return new OpenAI({
4257
4377
  apiKey,
4258
- baseURL: resolveConfigValue(config.baseUrl)
4378
+ ...baseUrl ? { baseURL: baseUrl } : {}
4259
4379
  });
4260
4380
  }
4261
- function buildRequestOptions2(context) {
4262
- return context.signal ? { signal: context.signal } : void 0;
4381
+ function buildOpenAISearchRequest(query2, maxResults, config, options) {
4382
+ const mergedOptions = resolveOpenAISearchOptions(config, options);
4383
+ const model = mergedOptions.model ?? DEFAULT_SEARCH_MODEL2;
4384
+ const instructions = mergedOptions.instructions;
4385
+ return {
4386
+ model,
4387
+ input: [
4388
+ "Search the public web and return only the most relevant sources for the user's query.",
4389
+ `Return at most ${maxResults} sources.`,
4390
+ "Prefer official, primary, or highly reputable sources when available.",
4391
+ "Each snippet should be short, specific, and grounded in the retrieved source.",
4392
+ "Return only data matching the provided JSON schema.",
4393
+ "",
4394
+ `User query: ${query2}`
4395
+ ].join("\n"),
4396
+ tools: [{ type: "web_search_preview" }],
4397
+ text: {
4398
+ format: {
4399
+ type: "json_schema",
4400
+ name: "openai_web_search_results",
4401
+ schema: searchResultSchema,
4402
+ strict: true
4403
+ }
4404
+ },
4405
+ ...instructions ? { instructions } : {}
4406
+ };
4263
4407
  }
4264
-
4265
- // src/providers/perplexity.ts
4266
- import PerplexityClient from "@perplexity-ai/perplexity_ai";
4267
- import { Type as Type11 } from "@sinclair/typebox";
4268
- var DEFAULT_ANSWER_MODEL2 = "sonar";
4269
- var DEFAULT_RESEARCH_MODEL = "sonar-deep-research";
4270
- var perplexitySearchOptionsSchema = Type11.Object(
4271
- {
4272
- country: Type11.Optional(
4273
- Type11.String({ description: "Country hint for search results." })
4274
- ),
4275
- search_mode: Type11.Optional(
4276
- Type11.String({ description: "Perplexity search mode." })
4277
- ),
4278
- search_domain_filter: Type11.Optional(
4279
- Type11.Array(Type11.String(), {
4280
- description: "Restrict search results to these domains."
4281
- })
4282
- ),
4283
- search_recency_filter: Type11.Optional(
4284
- Type11.String({ description: "Recency filter for search results." })
4285
- )
4286
- },
4287
- { description: "Perplexity search options." }
4288
- );
4289
- var perplexityAnswerOptionsSchema = Type11.Object(
4290
- {
4291
- model: Type11.Optional(
4292
- Type11.String({
4293
- description: "Perplexity model to use (for example 'sonar' or 'sonar-pro')."
4294
- })
4295
- )
4296
- },
4297
- { description: "Perplexity answer options." }
4298
- );
4299
- var perplexityResearchOptionsSchema = Type11.Object(
4300
- {
4301
- model: Type11.Optional(
4302
- Type11.String({
4303
- description: "Perplexity model to use (for example 'sonar-deep-research')."
4304
- })
4305
- )
4306
- },
4307
- { description: "Perplexity research options." }
4308
- );
4309
- var perplexityAdapter = {
4310
- id: "perplexity",
4311
- label: "Perplexity",
4312
- docsUrl: "https://docs.perplexity.ai/docs/sdk/overview.md",
4313
- tools: ["search", "answer", "research"],
4314
- getToolOptionsSchema(capability) {
4315
- switch (capability) {
4316
- case "search":
4317
- return perplexitySearchOptionsSchema;
4318
- case "answer":
4319
- return perplexityAnswerOptionsSchema;
4320
- case "research":
4321
- return perplexityResearchOptionsSchema;
4322
- default:
4323
- return void 0;
4324
- }
4325
- },
4326
- createTemplate() {
4327
- return {
4408
+ function buildOpenAIAnswerRequest(query2, config, options) {
4409
+ const mergedOptions = resolveOpenAIAnswerOptions(config, options);
4410
+ const model = mergedOptions.model ?? DEFAULT_ANSWER_MODEL2;
4411
+ const instructions = mergedOptions.instructions;
4412
+ return {
4413
+ model,
4414
+ input: query2,
4415
+ tools: [{ type: "web_search_preview" }],
4416
+ ...instructions ? { instructions } : {}
4417
+ };
4418
+ }
4419
+ function buildOpenAIResearchRequest(input, config, options) {
4420
+ const mergedOptions = resolveOpenAIResearchOptions(config, options);
4421
+ const model = mergedOptions.model ?? DEFAULT_RESEARCH_MODEL;
4422
+ const instructions = mergedOptions.instructions;
4423
+ const maxToolCalls = mergedOptions.max_tool_calls;
4424
+ return {
4425
+ model,
4426
+ input,
4427
+ background: true,
4428
+ tools: [{ type: "web_search_preview" }],
4429
+ ...instructions ? { instructions } : {},
4430
+ ...maxToolCalls ? { max_tool_calls: maxToolCalls } : {}
4431
+ };
4432
+ }
4433
+ function resolveOpenAISearchOptions(config, options) {
4434
+ const mergedOptions = {
4435
+ ...config.options?.search ?? {},
4436
+ ...options ?? {}
4437
+ };
4438
+ const model = readNonEmptyString4(mergedOptions.model);
4439
+ const instructions = readNonEmptyString4(mergedOptions.instructions);
4440
+ return {
4441
+ ...model ? { model } : {},
4442
+ ...instructions ? { instructions } : {}
4443
+ };
4444
+ }
4445
+ function resolveOpenAIAnswerOptions(config, options) {
4446
+ const mergedOptions = {
4447
+ ...config.options?.answer ?? {},
4448
+ ...options ?? {}
4449
+ };
4450
+ const model = readNonEmptyString4(mergedOptions.model);
4451
+ const instructions = readNonEmptyString4(mergedOptions.instructions);
4452
+ return {
4453
+ ...model ? { model } : {},
4454
+ ...instructions ? { instructions } : {}
4455
+ };
4456
+ }
4457
+ function resolveOpenAIResearchOptions(config, options) {
4458
+ const mergedOptions = {
4459
+ ...config.options?.research ?? {},
4460
+ ...options ?? {}
4461
+ };
4462
+ const model = readNonEmptyString4(mergedOptions.model);
4463
+ const instructions = readNonEmptyString4(mergedOptions.instructions);
4464
+ const maxToolCalls = readPositiveInteger2(mergedOptions.max_tool_calls);
4465
+ return {
4466
+ ...model ? { model } : {},
4467
+ ...instructions ? { instructions } : {},
4468
+ ...maxToolCalls ? { max_tool_calls: maxToolCalls } : {}
4469
+ };
4470
+ }
4471
+ function buildRequestOptions2(signal, idempotencyKey) {
4472
+ if (!signal && !idempotencyKey) {
4473
+ return void 0;
4474
+ }
4475
+ return {
4476
+ ...signal ? { signal } : {},
4477
+ ...idempotencyKey ? { idempotencyKey } : {}
4478
+ };
4479
+ }
4480
+ function parseSearchResponse2(response, maxResults) {
4481
+ const status = response.status ?? "completed";
4482
+ if (status === "failed") {
4483
+ throw new Error(response.error?.message ?? "search failed");
4484
+ }
4485
+ if (status === "cancelled") {
4486
+ throw new Error("search was canceled");
4487
+ }
4488
+ if (status === "incomplete") {
4489
+ throw new Error(formatIncompleteError(response, "search"));
4490
+ }
4491
+ if (status !== "completed") {
4492
+ throw new Error(`search did not complete (status: ${status})`);
4493
+ }
4494
+ const payload = parseSearchPayload(response.output_text);
4495
+ return {
4496
+ provider: openaiAdapter.id,
4497
+ results: payload.sources.slice(0, maxResults).map((source) => ({
4498
+ title: source.title.trim(),
4499
+ url: source.url.trim(),
4500
+ snippet: trimSnippet(source.snippet)
4501
+ }))
4502
+ };
4503
+ }
4504
+ function ensureCompletedResponse(response, operation) {
4505
+ const status = response.status ?? "completed";
4506
+ if (status === "completed") {
4507
+ return formatResponseOutput(response, operation);
4508
+ }
4509
+ if (status === "failed") {
4510
+ throw new Error(response.error?.message ?? `${operation} failed`);
4511
+ }
4512
+ if (status === "cancelled") {
4513
+ throw new Error(`${operation} was canceled`);
4514
+ }
4515
+ if (status === "incomplete") {
4516
+ throw new Error(formatIncompleteError(response, operation));
4517
+ }
4518
+ throw new Error(`${operation} did not complete (status: ${status})`);
4519
+ }
4520
+ function formatResponseOutput(response, operation) {
4521
+ const lines = [];
4522
+ lines.push(
4523
+ response.output_text?.trim() || `OpenAI ${operation} completed without textual output.`
4524
+ );
4525
+ const citations = extractUrlCitations(response);
4526
+ if (citations.length > 0) {
4527
+ lines.push("");
4528
+ lines.push("Sources:");
4529
+ for (const [index, citation] of citations.entries()) {
4530
+ lines.push(`${index + 1}. ${citation.title}`);
4531
+ lines.push(` ${citation.url}`);
4532
+ }
4533
+ }
4534
+ return {
4535
+ provider: openaiAdapter.id,
4536
+ text: lines.join("\n").trimEnd(),
4537
+ itemCount: citations.length,
4538
+ metadata: {
4539
+ responseId: response.id,
4540
+ model: response.model,
4541
+ citations
4542
+ }
4543
+ };
4544
+ }
4545
+ function extractUrlCitations(response) {
4546
+ const citations = [];
4547
+ const seen = /* @__PURE__ */ new Set();
4548
+ for (const item of response.output) {
4549
+ if (item.type !== "message" || !item.content) {
4550
+ continue;
4551
+ }
4552
+ for (const content of item.content) {
4553
+ if (content.type !== "output_text" || !content.annotations) {
4554
+ continue;
4555
+ }
4556
+ for (const annotation of content.annotations) {
4557
+ if (annotation.type !== "url_citation") {
4558
+ continue;
4559
+ }
4560
+ const title = readNonEmptyString4(annotation.title);
4561
+ const url = readNonEmptyString4(annotation.url);
4562
+ const startIndex = readInteger(annotation.start_index);
4563
+ const endIndex = readInteger(annotation.end_index);
4564
+ if (!title || !url || startIndex === void 0 || endIndex === void 0) {
4565
+ continue;
4566
+ }
4567
+ const citation = {
4568
+ title,
4569
+ url,
4570
+ startIndex,
4571
+ endIndex
4572
+ };
4573
+ const key = [
4574
+ citation.title,
4575
+ citation.url,
4576
+ String(citation.startIndex),
4577
+ String(citation.endIndex)
4578
+ ].join("::");
4579
+ if (seen.has(key)) {
4580
+ continue;
4581
+ }
4582
+ seen.add(key);
4583
+ citations.push(citation);
4584
+ }
4585
+ }
4586
+ }
4587
+ return citations;
4588
+ }
4589
+ function parseSearchPayload(text) {
4590
+ let parsed;
4591
+ try {
4592
+ parsed = JSON.parse(text ?? "");
4593
+ } catch (error) {
4594
+ throw new Error(
4595
+ `search returned invalid JSON: ${error.message}`
4596
+ );
4597
+ }
4598
+ if (typeof parsed !== "object" || parsed === null || !("sources" in parsed) || !Array.isArray(parsed.sources)) {
4599
+ throw new Error("search output must include a 'sources' array");
4600
+ }
4601
+ return {
4602
+ sources: parsed.sources.map((source, index) => {
4603
+ if (typeof source !== "object" || source === null) {
4604
+ throw new Error(`search source at index ${index} must be an object`);
4605
+ }
4606
+ const entry = source;
4607
+ const title = readNonEmptyString4(entry.title);
4608
+ const url = readNonEmptyString4(entry.url);
4609
+ const snippet = readNonEmptyString4(entry.snippet);
4610
+ if (!title) {
4611
+ throw new Error(`search source at index ${index} is missing title`);
4612
+ }
4613
+ if (!url) {
4614
+ throw new Error(`search source at index ${index} is missing url`);
4615
+ }
4616
+ if (!snippet) {
4617
+ throw new Error(`search source at index ${index} is missing snippet`);
4618
+ }
4619
+ return { title, url, snippet };
4620
+ })
4621
+ };
4622
+ }
4623
+ function formatIncompleteError(response, operation) {
4624
+ const reason = response.incomplete_details?.reason;
4625
+ if (reason) {
4626
+ return `${operation} ended incomplete (${reason})`;
4627
+ }
4628
+ return `${operation} ended incomplete`;
4629
+ }
4630
+ function readNonEmptyString4(value) {
4631
+ return typeof value === "string" && value.trim().length > 0 ? value : void 0;
4632
+ }
4633
+ function readPositiveInteger2(value) {
4634
+ return typeof value === "number" && Number.isInteger(value) && value > 0 ? value : void 0;
4635
+ }
4636
+ function readInteger(value) {
4637
+ return typeof value === "number" && Number.isInteger(value) ? value : void 0;
4638
+ }
4639
+
4640
+ // src/providers/parallel.ts
4641
+ import { Type as Type11 } from "@sinclair/typebox";
4642
+ import ParallelClient from "parallel-web";
4643
+ var parallelSearchOptionsSchema = Type11.Object(
4644
+ {
4645
+ mode: Type11.Optional(
4646
+ literalUnion(["agentic", "one-shot"], {
4647
+ description: "Parallel search mode."
4648
+ })
4649
+ )
4650
+ },
4651
+ { description: "Parallel search options." }
4652
+ );
4653
+ var parallelExtractOptionsSchema = Type11.Object(
4654
+ {
4655
+ excerpts: Type11.Optional(
4656
+ Type11.Boolean({ description: "Include excerpts in extraction results." })
4657
+ ),
4658
+ full_content: Type11.Optional(
4659
+ Type11.Boolean({
4660
+ description: "Include full page content in extraction results."
4661
+ })
4662
+ )
4663
+ },
4664
+ { description: "Parallel extract options." }
4665
+ );
4666
+ var parallelAdapter = {
4667
+ id: "parallel",
4668
+ label: "Parallel",
4669
+ docsUrl: "https://github.com/parallel-web/parallel-sdk-typescript",
4670
+ tools: ["search", "contents"],
4671
+ getToolOptionsSchema(capability) {
4672
+ switch (capability) {
4673
+ case "search":
4674
+ return parallelSearchOptionsSchema;
4675
+ case "contents":
4676
+ return parallelExtractOptionsSchema;
4677
+ default:
4678
+ return void 0;
4679
+ }
4680
+ },
4681
+ createTemplate() {
4682
+ return {
4683
+ apiKey: "PARALLEL_API_KEY",
4684
+ options: {
4685
+ search: {
4686
+ mode: "agentic"
4687
+ },
4688
+ extract: {
4689
+ excerpts: false,
4690
+ full_content: true
4691
+ }
4692
+ }
4693
+ };
4694
+ },
4695
+ getConfigForCapability(capability, config) {
4696
+ switch (capability) {
4697
+ case "search":
4698
+ return {
4699
+ apiKey: config.apiKey,
4700
+ baseUrl: config.baseUrl,
4701
+ options: config.options?.search,
4702
+ settings: config.settings
4703
+ };
4704
+ case "contents":
4705
+ return {
4706
+ apiKey: config.apiKey,
4707
+ baseUrl: config.baseUrl,
4708
+ options: config.options?.extract,
4709
+ settings: config.settings
4710
+ };
4711
+ default:
4712
+ return config;
4713
+ }
4714
+ },
4715
+ getCapabilityStatus(config) {
4716
+ return getApiKeyStatus(config?.apiKey);
4717
+ },
4718
+ buildPlan(request, config) {
4719
+ return buildProviderPlan({
4720
+ request,
4721
+ config,
4722
+ providerId: parallelAdapter.id,
4723
+ providerLabel: parallelAdapter.label,
4724
+ handlers: {
4725
+ search: {
4726
+ execute: (searchRequest, providerConfig, context) => parallelAdapter.search(
4727
+ searchRequest.query,
4728
+ searchRequest.maxResults,
4729
+ providerConfig,
4730
+ context,
4731
+ searchRequest.options
4732
+ )
4733
+ },
4734
+ contents: {
4735
+ execute: (contentsRequest, providerConfig, context) => parallelAdapter.contents(
4736
+ contentsRequest.urls,
4737
+ providerConfig,
4738
+ context,
4739
+ contentsRequest.options
4740
+ )
4741
+ }
4742
+ }
4743
+ });
4744
+ },
4745
+ async search(query2, maxResults, config, context, options) {
4746
+ const client = createClient6(config);
4747
+ const defaults = stripLocalExecutionOptions(asJsonObject(config.options?.search)) ?? {};
4748
+ const response = await client.beta.search(
4749
+ {
4750
+ ...defaults,
4751
+ ...options ?? {},
4752
+ objective: query2,
4753
+ max_results: maxResults
4754
+ },
4755
+ buildRequestOptions3(context)
4756
+ );
4757
+ return {
4758
+ provider: parallelAdapter.id,
4759
+ results: response.results.slice(0, maxResults).map((result) => ({
4760
+ title: result.title ?? result.url,
4761
+ url: result.url,
4762
+ snippet: trimSnippet(result.excerpts?.join(" ") ?? "")
4763
+ }))
4764
+ };
4765
+ },
4766
+ async contents(urls, config, context, options) {
4767
+ const client = createClient6(config);
4768
+ const defaults = stripLocalExecutionOptions(asJsonObject(config.options?.extract)) ?? {};
4769
+ const response = await client.beta.extract(
4770
+ {
4771
+ ...defaults,
4772
+ ...options ?? {},
4773
+ urls
4774
+ },
4775
+ buildRequestOptions3(context)
4776
+ );
4777
+ const resultsByUrl = new Map(
4778
+ response.results.map((result) => [result.url, result])
4779
+ );
4780
+ const errorsByUrl = new Map(
4781
+ response.errors.map((error) => [error.url, error])
4782
+ );
4783
+ return {
4784
+ provider: parallelAdapter.id,
4785
+ answers: urls.map((url) => {
4786
+ const result = resultsByUrl.get(url);
4787
+ if (result) {
4788
+ return {
4789
+ url,
4790
+ content: result.full_content ?? result.excerpts?.join("\n\n") ?? void 0,
4791
+ metadata: result
4792
+ };
4793
+ }
4794
+ const error = errorsByUrl.get(url);
4795
+ return error ? {
4796
+ url,
4797
+ error: formatJson(error)
4798
+ } : {
4799
+ url,
4800
+ error: "No content returned for this URL."
4801
+ };
4802
+ })
4803
+ };
4804
+ }
4805
+ };
4806
+ function createClient6(config) {
4807
+ const apiKey = resolveConfigValue(config.apiKey);
4808
+ if (!apiKey) {
4809
+ throw new Error("is missing an API key");
4810
+ }
4811
+ return new ParallelClient({
4812
+ apiKey,
4813
+ baseURL: resolveConfigValue(config.baseUrl)
4814
+ });
4815
+ }
4816
+ function buildRequestOptions3(context) {
4817
+ return context.signal ? { signal: context.signal } : void 0;
4818
+ }
4819
+
4820
+ // src/providers/perplexity.ts
4821
+ import PerplexityClient from "@perplexity-ai/perplexity_ai";
4822
+ import { Type as Type12 } from "@sinclair/typebox";
4823
+ var DEFAULT_ANSWER_MODEL3 = "sonar";
4824
+ var DEFAULT_RESEARCH_MODEL2 = "sonar-deep-research";
4825
+ var perplexitySearchOptionsSchema = Type12.Object(
4826
+ {
4827
+ country: Type12.Optional(
4828
+ Type12.String({ description: "Country hint for search results." })
4829
+ ),
4830
+ search_mode: Type12.Optional(
4831
+ Type12.String({ description: "Perplexity search mode." })
4832
+ ),
4833
+ search_domain_filter: Type12.Optional(
4834
+ Type12.Array(Type12.String(), {
4835
+ description: "Restrict search results to these domains."
4836
+ })
4837
+ ),
4838
+ search_recency_filter: Type12.Optional(
4839
+ Type12.String({ description: "Recency filter for search results." })
4840
+ )
4841
+ },
4842
+ { description: "Perplexity search options." }
4843
+ );
4844
+ var perplexityAnswerOptionsSchema = Type12.Object(
4845
+ {
4846
+ model: Type12.Optional(
4847
+ Type12.String({
4848
+ description: "Perplexity model to use (for example 'sonar' or 'sonar-pro')."
4849
+ })
4850
+ )
4851
+ },
4852
+ { description: "Perplexity answer options." }
4853
+ );
4854
+ var perplexityResearchOptionsSchema = Type12.Object(
4855
+ {
4856
+ model: Type12.Optional(
4857
+ Type12.String({
4858
+ description: "Perplexity model to use (for example 'sonar-deep-research')."
4859
+ })
4860
+ )
4861
+ },
4862
+ { description: "Perplexity research options." }
4863
+ );
4864
+ var perplexityAdapter = {
4865
+ id: "perplexity",
4866
+ label: "Perplexity",
4867
+ docsUrl: "https://docs.perplexity.ai/docs/sdk/overview.md",
4868
+ tools: ["search", "answer", "research"],
4869
+ getToolOptionsSchema(capability) {
4870
+ switch (capability) {
4871
+ case "search":
4872
+ return perplexitySearchOptionsSchema;
4873
+ case "answer":
4874
+ return perplexityAnswerOptionsSchema;
4875
+ case "research":
4876
+ return perplexityResearchOptionsSchema;
4877
+ default:
4878
+ return void 0;
4879
+ }
4880
+ },
4881
+ createTemplate() {
4882
+ return {
4328
4883
  apiKey: "PERPLEXITY_API_KEY",
4329
4884
  options: {
4330
4885
  answer: {
4331
- model: DEFAULT_ANSWER_MODEL2
4886
+ model: DEFAULT_ANSWER_MODEL3
4332
4887
  },
4333
4888
  research: {
4334
- model: DEFAULT_RESEARCH_MODEL
4889
+ model: DEFAULT_RESEARCH_MODEL2
4335
4890
  }
4336
4891
  }
4337
4892
  };
@@ -4375,7 +4930,7 @@ var perplexityAdapter = {
4375
4930
  });
4376
4931
  },
4377
4932
  async search(query2, maxResults, config, context, options) {
4378
- const client = createClient6(config);
4933
+ const client = createClient7(config);
4379
4934
  const request = {
4380
4935
  ...stripLocalExecutionOptions(asJsonObject(config.options?.search)) ?? {},
4381
4936
  ...options ?? {},
@@ -4384,7 +4939,7 @@ var perplexityAdapter = {
4384
4939
  };
4385
4940
  const response = await client.search.create(
4386
4941
  request,
4387
- buildRequestOptions3(context)
4942
+ buildRequestOptions4(context)
4388
4943
  );
4389
4944
  return {
4390
4945
  provider: perplexityAdapter.id,
@@ -4404,7 +4959,7 @@ var perplexityAdapter = {
4404
4959
  query2,
4405
4960
  config,
4406
4961
  context,
4407
- DEFAULT_ANSWER_MODEL2,
4962
+ DEFAULT_ANSWER_MODEL3,
4408
4963
  "Answer",
4409
4964
  options
4410
4965
  );
@@ -4414,14 +4969,14 @@ var perplexityAdapter = {
4414
4969
  input,
4415
4970
  config,
4416
4971
  context,
4417
- DEFAULT_RESEARCH_MODEL,
4972
+ DEFAULT_RESEARCH_MODEL2,
4418
4973
  "Research",
4419
4974
  options
4420
4975
  );
4421
4976
  }
4422
4977
  };
4423
4978
  async function runSilentForegroundChatTool(input, config, context, fallbackModel, label, options, isResearch = false) {
4424
- const client = createClient6(config);
4979
+ const client = createClient7(config);
4425
4980
  const defaults = stripLocalExecutionOptions(
4426
4981
  isResearch ? asJsonObject(config.options?.research) : asJsonObject(config.options?.answer)
4427
4982
  ) ?? {};
@@ -4434,7 +4989,7 @@ async function runSilentForegroundChatTool(input, config, context, fallbackModel
4434
4989
  };
4435
4990
  const response = await client.chat.completions.create(
4436
4991
  request,
4437
- buildRequestOptions3(context)
4992
+ buildRequestOptions4(context)
4438
4993
  );
4439
4994
  const content = extractMessageText(response.choices[0]?.message?.content);
4440
4995
  const sources = dedupeSources(extractSources(response));
@@ -4455,7 +5010,7 @@ async function runSilentForegroundChatTool(input, config, context, fallbackModel
4455
5010
  };
4456
5011
  }
4457
5012
  async function runStreamingForegroundChatTool(input, config, context, fallbackModel, label, options) {
4458
- const client = createClient6(config);
5013
+ const client = createClient7(config);
4459
5014
  const defaults = stripLocalExecutionOptions(asJsonObject(config.options?.research)) ?? {};
4460
5015
  const request = {
4461
5016
  ...defaults,
@@ -4466,7 +5021,7 @@ async function runStreamingForegroundChatTool(input, config, context, fallbackMo
4466
5021
  };
4467
5022
  const stream = await client.chat.completions.create(
4468
5023
  request,
4469
- buildRequestOptions3(context)
5024
+ buildRequestOptions4(context)
4470
5025
  );
4471
5026
  let partialText = "";
4472
5027
  let lastChunk;
@@ -4477,156 +5032,387 @@ async function runStreamingForegroundChatTool(input, config, context, fallbackMo
4477
5032
  if (deltaText) {
4478
5033
  partialText = `${partialText}${deltaText}`;
4479
5034
  }
4480
- sources.push(...extractSources(chunk));
5035
+ sources.push(...extractSources(chunk));
5036
+ }
5037
+ const finalText = partialText.trim() || extractMessageText(lastChunk?.choices?.[0]?.message?.content) || `No ${label.toLowerCase()} returned.`;
5038
+ const dedupedSources = dedupeSources(sources);
5039
+ const lines = [finalText];
5040
+ if (dedupedSources.length > 0) {
5041
+ lines.push("");
5042
+ lines.push("Sources:");
5043
+ for (const [index, source] of dedupedSources.entries()) {
5044
+ lines.push(`${index + 1}. ${source.title}`);
5045
+ lines.push(` ${source.url}`);
5046
+ }
5047
+ }
5048
+ return {
5049
+ provider: perplexityAdapter.id,
5050
+ text: lines.join("\n").trimEnd(),
5051
+ itemCount: dedupedSources.length
5052
+ };
5053
+ }
5054
+ function createClient7(config) {
5055
+ const apiKey = resolveConfigValue(config.apiKey);
5056
+ if (!apiKey) {
5057
+ throw new Error("is missing an API key");
5058
+ }
5059
+ return new PerplexityClient({
5060
+ apiKey,
5061
+ baseURL: resolveConfigValue(config.baseUrl)
5062
+ });
5063
+ }
5064
+ function resolveModel(optionModel, defaultModel, fallbackModel) {
5065
+ if (typeof optionModel === "string" && optionModel.trim().length > 0) {
5066
+ return optionModel;
5067
+ }
5068
+ if (typeof defaultModel === "string" && defaultModel.trim().length > 0) {
5069
+ return defaultModel;
5070
+ }
5071
+ return fallbackModel;
5072
+ }
5073
+ function extractMessageText(content) {
5074
+ if (typeof content === "string") {
5075
+ return content.trim();
5076
+ }
5077
+ if (!Array.isArray(content)) {
5078
+ return "";
5079
+ }
5080
+ return content.flatMap((chunk) => {
5081
+ if (typeof chunk === "object" && chunk !== null && "type" in chunk && chunk.type === "text" && "text" in chunk && typeof chunk.text === "string") {
5082
+ return [chunk.text.trim()];
5083
+ }
5084
+ return [];
5085
+ }).filter((text) => text.length > 0).join("\n\n").trim();
5086
+ }
5087
+ function extractDeltaText(content) {
5088
+ if (typeof content === "string") {
5089
+ return content;
5090
+ }
5091
+ if (!Array.isArray(content)) {
5092
+ return "";
5093
+ }
5094
+ return content.flatMap((chunk) => {
5095
+ if (typeof chunk === "object" && chunk !== null && "type" in chunk && chunk.type === "text" && "text" in chunk && typeof chunk.text === "string") {
5096
+ return [chunk.text];
5097
+ }
5098
+ return [];
5099
+ }).join("");
5100
+ }
5101
+ function dedupeSources(sources) {
5102
+ const seen = /* @__PURE__ */ new Set();
5103
+ const unique = [];
5104
+ for (const source of sources) {
5105
+ const title = source.title.trim() || source.url.trim() || "Untitled";
5106
+ const url = source.url.trim();
5107
+ if (!url) continue;
5108
+ const key = `${title.toLowerCase()}::${url.toLowerCase()}`;
5109
+ if (seen.has(key)) continue;
5110
+ seen.add(key);
5111
+ unique.push({ title, url });
5112
+ }
5113
+ return unique;
5114
+ }
5115
+ function extractSources(response) {
5116
+ const searchResults = response.search_results?.flatMap((result) => {
5117
+ const url = result.url?.trim() ?? "";
5118
+ if (!url) {
5119
+ return [];
5120
+ }
5121
+ return [{ title: result.title?.trim() ?? url, url }];
5122
+ }) ?? [];
5123
+ if (searchResults.length > 0) {
5124
+ return searchResults;
5125
+ }
5126
+ return response.citations?.flatMap((citation) => {
5127
+ const url = citation?.trim() ?? "";
5128
+ return url ? [{ title: url, url }] : [];
5129
+ }) ?? [];
5130
+ }
5131
+ function buildRequestOptions4(context) {
5132
+ return context.signal ? { signal: context.signal } : void 0;
5133
+ }
5134
+
5135
+ // src/providers/serper.ts
5136
+ import { Type as Type13 } from "@sinclair/typebox";
5137
+ var DEFAULT_BASE_URL = "https://google.serper.dev";
5138
+ var serperSearchOptionsSchema = Type13.Object(
5139
+ {
5140
+ gl: Type13.Optional(
5141
+ Type13.String({
5142
+ description: "Country code hint for Google results (for example 'us')."
5143
+ })
5144
+ ),
5145
+ hl: Type13.Optional(
5146
+ Type13.String({
5147
+ description: "Language code hint for Google results (for example 'en')."
5148
+ })
5149
+ ),
5150
+ location: Type13.Optional(
5151
+ Type13.String({
5152
+ description: "Geographic location hint for Google results."
5153
+ })
5154
+ ),
5155
+ page: Type13.Optional(
5156
+ Type13.Integer({
5157
+ minimum: 1,
5158
+ description: "1-based results page to request from Serper."
5159
+ })
5160
+ ),
5161
+ autocorrect: Type13.Optional(
5162
+ Type13.Boolean({
5163
+ description: "Enable or disable Serper query autocorrection."
5164
+ })
5165
+ )
5166
+ },
5167
+ { description: "Serper search options." }
5168
+ );
5169
+ var serperAdapter = {
5170
+ id: "serper",
5171
+ label: "Serper",
5172
+ docsUrl: "https://serper.dev/",
5173
+ tools: ["search"],
5174
+ getToolOptionsSchema(capability) {
5175
+ switch (capability) {
5176
+ case "search":
5177
+ return serperSearchOptionsSchema;
5178
+ default:
5179
+ return void 0;
5180
+ }
5181
+ },
5182
+ createTemplate() {
5183
+ return {
5184
+ apiKey: "SERPER_API_KEY",
5185
+ options: {}
5186
+ };
5187
+ },
5188
+ getConfigForCapability(capability, config) {
5189
+ switch (capability) {
5190
+ case "search":
5191
+ return {
5192
+ apiKey: config.apiKey,
5193
+ baseUrl: config.baseUrl,
5194
+ options: config.options?.search,
5195
+ settings: config.settings
5196
+ };
5197
+ default:
5198
+ return config;
5199
+ }
5200
+ },
5201
+ getCapabilityStatus(config) {
5202
+ return getApiKeyStatus(config?.apiKey);
5203
+ },
5204
+ buildPlan(request, config) {
5205
+ return buildProviderPlan({
5206
+ request,
5207
+ config,
5208
+ providerId: serperAdapter.id,
5209
+ providerLabel: serperAdapter.label,
5210
+ handlers: {
5211
+ search: {
5212
+ execute: (searchRequest, providerConfig, context) => serperAdapter.search(
5213
+ searchRequest.query,
5214
+ searchRequest.maxResults,
5215
+ providerConfig,
5216
+ context,
5217
+ searchRequest.options
5218
+ )
5219
+ }
5220
+ }
5221
+ });
5222
+ },
5223
+ async search(query2, maxResults, config, context, options) {
5224
+ const apiKey = resolveConfigValue(config.apiKey);
5225
+ if (!apiKey) {
5226
+ throw new Error("is missing an API key");
5227
+ }
5228
+ const defaults = stripLocalExecutionOptions(asJsonObject(config.options?.search)) ?? {};
5229
+ const runtimeOptions = stripLocalExecutionOptions(asJsonObject(options));
5230
+ const {
5231
+ q: _ignoredQuery,
5232
+ num: _ignoredNum,
5233
+ ...providerOptions
5234
+ } = {
5235
+ ...defaults,
5236
+ ...runtimeOptions ?? {}
5237
+ };
5238
+ const response = await fetch(joinUrl(resolveConfigValue(config.baseUrl)), {
5239
+ method: "POST",
5240
+ headers: {
5241
+ "content-type": "application/json",
5242
+ "x-api-key": apiKey
5243
+ },
5244
+ body: JSON.stringify({
5245
+ q: query2,
5246
+ num: clampMaxResults(maxResults),
5247
+ ...providerOptions
5248
+ }),
5249
+ signal: context.signal
5250
+ });
5251
+ if (!response.ok) {
5252
+ throw new Error(await buildHttpError(response));
5253
+ }
5254
+ const payload = await response.json();
5255
+ const responseRecord = asRecord3(payload) ?? {};
5256
+ const organic = asArray(responseRecord.organic) ?? [];
5257
+ const searchContext = buildSearchContext(responseRecord);
5258
+ return {
5259
+ provider: serperAdapter.id,
5260
+ results: organic.map((entry) => toSearchResult3(entry, searchContext)).filter(
5261
+ (result) => result !== null
5262
+ ).slice(0, clampMaxResults(maxResults))
5263
+ };
4481
5264
  }
4482
- const finalText = partialText.trim() || extractMessageText(lastChunk?.choices?.[0]?.message?.content) || `No ${label.toLowerCase()} returned.`;
4483
- const dedupedSources = dedupeSources(sources);
4484
- const lines = [finalText];
4485
- if (dedupedSources.length > 0) {
4486
- lines.push("");
4487
- lines.push("Sources:");
4488
- for (const [index, source] of dedupedSources.entries()) {
4489
- lines.push(`${index + 1}. ${source.title}`);
4490
- lines.push(` ${source.url}`);
5265
+ };
5266
+ function joinUrl(baseUrl) {
5267
+ const base = (baseUrl ?? DEFAULT_BASE_URL).replace(/\/+$/, "");
5268
+ return `${base}/search`;
5269
+ }
5270
+ function clampMaxResults(value) {
5271
+ return Math.max(1, Math.min(20, Math.trunc(value || 0)));
5272
+ }
5273
+ async function buildHttpError(response) {
5274
+ const detail = await readErrorDetail(response);
5275
+ const status = `${response.status}${response.statusText ? ` ${response.statusText}` : ""}`;
5276
+ return detail ? `Serper API request failed (${status}): ${detail}` : `Serper API request failed (${status}).`;
5277
+ }
5278
+ async function readErrorDetail(response) {
5279
+ const text = (await response.text()).trim();
5280
+ if (!text) {
5281
+ return void 0;
5282
+ }
5283
+ try {
5284
+ const parsed = JSON.parse(text);
5285
+ const record = asRecord3(parsed);
5286
+ const detail = readString4(record?.message) ?? readString4(record?.error) ?? readString4(record?.detail);
5287
+ if (detail) {
5288
+ return detail;
4491
5289
  }
5290
+ return JSON.stringify(parsed);
5291
+ } catch {
5292
+ return text;
5293
+ }
5294
+ }
5295
+ function toSearchResult3(entry, searchContext) {
5296
+ const record = asRecord3(entry);
5297
+ if (!record) {
5298
+ return null;
4492
5299
  }
5300
+ const url = readString4(record.link) ?? "";
5301
+ const title = readString4(record.title) || url || "Untitled";
5302
+ const snippet = trimSnippet(
5303
+ readString4(record.snippet) ?? readString4(record.richSnippet) ?? readString4(record.date) ?? ""
5304
+ );
5305
+ const metadata = omitUndefined({
5306
+ source: "organic",
5307
+ position: readNumber(record.position),
5308
+ date: readString4(record.date),
5309
+ attributes: asRecord3(record.attributes),
5310
+ sitelinks: asArray(record.sitelinks),
5311
+ rating: readNumber(record.rating),
5312
+ ratingCount: readNumber(record.ratingCount),
5313
+ cid: readString4(record.cid),
5314
+ ...extractExtraMetadata(record, ["title", "link", "snippet"]),
5315
+ ...searchContext ? { searchContext } : {}
5316
+ });
4493
5317
  return {
4494
- provider: perplexityAdapter.id,
4495
- text: lines.join("\n").trimEnd(),
4496
- itemCount: dedupedSources.length
5318
+ title,
5319
+ url,
5320
+ snippet,
5321
+ ...Object.keys(metadata).length > 0 ? { metadata } : {}
4497
5322
  };
4498
5323
  }
4499
- function createClient6(config) {
4500
- const apiKey = resolveConfigValue(config.apiKey);
4501
- if (!apiKey) {
4502
- throw new Error("is missing an API key");
4503
- }
4504
- return new PerplexityClient({
4505
- apiKey,
4506
- baseURL: resolveConfigValue(config.baseUrl)
5324
+ function buildSearchContext(response) {
5325
+ const context = omitUndefined({
5326
+ searchParameters: asRecord3(response.searchParameters),
5327
+ searchInformation: asRecord3(response.searchInformation),
5328
+ credits: readNumber(response.credits),
5329
+ answerBox: asRecord3(response.answerBox),
5330
+ knowledgeGraph: asRecord3(response.knowledgeGraph),
5331
+ peopleAlsoAsk: asArray(response.peopleAlsoAsk),
5332
+ relatedSearches: asArray(response.relatedSearches),
5333
+ topStories: asArray(response.topStories),
5334
+ news: asArray(response.news),
5335
+ images: asArray(response.images),
5336
+ videos: asArray(response.videos),
5337
+ places: asArray(response.places)
4507
5338
  });
5339
+ return Object.keys(context).length > 0 ? context : void 0;
4508
5340
  }
4509
- function resolveModel(optionModel, defaultModel, fallbackModel) {
4510
- if (typeof optionModel === "string" && optionModel.trim().length > 0) {
4511
- return optionModel;
4512
- }
4513
- if (typeof defaultModel === "string" && defaultModel.trim().length > 0) {
4514
- return defaultModel;
4515
- }
4516
- return fallbackModel;
5341
+ function extractExtraMetadata(record, ignoredKeys) {
5342
+ return Object.fromEntries(
5343
+ Object.entries(record).filter(
5344
+ ([key, value]) => !ignoredKeys.includes(key) && value !== void 0
5345
+ )
5346
+ );
4517
5347
  }
4518
- function extractMessageText(content) {
4519
- if (typeof content === "string") {
4520
- return content.trim();
4521
- }
4522
- if (!Array.isArray(content)) {
4523
- return "";
4524
- }
4525
- return content.flatMap((chunk) => {
4526
- if (typeof chunk === "object" && chunk !== null && "type" in chunk && chunk.type === "text" && "text" in chunk && typeof chunk.text === "string") {
4527
- return [chunk.text.trim()];
4528
- }
4529
- return [];
4530
- }).filter((text) => text.length > 0).join("\n\n").trim();
5348
+ function omitUndefined(value) {
5349
+ return Object.fromEntries(
5350
+ Object.entries(value).filter(([, entry]) => entry !== void 0)
5351
+ );
4531
5352
  }
4532
- function extractDeltaText(content) {
4533
- if (typeof content === "string") {
4534
- return content;
4535
- }
4536
- if (!Array.isArray(content)) {
4537
- return "";
4538
- }
4539
- return content.flatMap((chunk) => {
4540
- if (typeof chunk === "object" && chunk !== null && "type" in chunk && chunk.type === "text" && "text" in chunk && typeof chunk.text === "string") {
4541
- return [chunk.text];
4542
- }
4543
- return [];
4544
- }).join("");
5353
+ function asRecord3(value) {
5354
+ return typeof value === "object" && value !== null && !Array.isArray(value) ? value : void 0;
4545
5355
  }
4546
- function dedupeSources(sources) {
4547
- const seen = /* @__PURE__ */ new Set();
4548
- const unique = [];
4549
- for (const source of sources) {
4550
- const title = source.title.trim() || source.url.trim() || "Untitled";
4551
- const url = source.url.trim();
4552
- if (!url) continue;
4553
- const key = `${title.toLowerCase()}::${url.toLowerCase()}`;
4554
- if (seen.has(key)) continue;
4555
- seen.add(key);
4556
- unique.push({ title, url });
4557
- }
4558
- return unique;
5356
+ function asArray(value) {
5357
+ return Array.isArray(value) ? value : void 0;
4559
5358
  }
4560
- function extractSources(response) {
4561
- const searchResults = response.search_results?.flatMap((result) => {
4562
- const url = result.url?.trim() ?? "";
4563
- if (!url) {
4564
- return [];
4565
- }
4566
- return [{ title: result.title?.trim() ?? url, url }];
4567
- }) ?? [];
4568
- if (searchResults.length > 0) {
4569
- return searchResults;
4570
- }
4571
- return response.citations?.flatMap((citation) => {
4572
- const url = citation?.trim() ?? "";
4573
- return url ? [{ title: url, url }] : [];
4574
- }) ?? [];
5359
+ function readString4(value) {
5360
+ return typeof value === "string" ? value : void 0;
4575
5361
  }
4576
- function buildRequestOptions3(context) {
4577
- return context.signal ? { signal: context.signal } : void 0;
5362
+ function readNumber(value) {
5363
+ return typeof value === "number" && Number.isFinite(value) ? value : void 0;
4578
5364
  }
4579
5365
 
4580
5366
  // src/providers/tavily.ts
4581
- import { Type as Type12 } from "@sinclair/typebox";
5367
+ import { Type as Type14 } from "@sinclair/typebox";
4582
5368
  import {
4583
5369
  tavily
4584
5370
  } from "@tavily/core";
4585
- var tavilySearchOptionsSchema = Type12.Object(
5371
+ var tavilySearchOptionsSchema = Type14.Object(
4586
5372
  {
4587
- topic: Type12.Optional(
5373
+ topic: Type14.Optional(
4588
5374
  literalUnion(["general", "news", "finance"], {
4589
5375
  description: "Category of the search query."
4590
5376
  })
4591
5377
  ),
4592
- searchDepth: Type12.Optional(
5378
+ searchDepth: Type14.Optional(
4593
5379
  literalUnion(["basic", "advanced"], {
4594
5380
  description: "Depth of the search. 'advanced' is slower but more thorough."
4595
5381
  })
4596
5382
  ),
4597
- timeRange: Type12.Optional(
4598
- Type12.String({ description: "Named time range filter." })
5383
+ timeRange: Type14.Optional(
5384
+ Type14.String({ description: "Named time range filter." })
4599
5385
  ),
4600
- country: Type12.Optional(
4601
- Type12.String({ description: "Country hint for search results." })
5386
+ country: Type14.Optional(
5387
+ Type14.String({ description: "Country hint for search results." })
4602
5388
  ),
4603
- exactMatch: Type12.Optional(
4604
- Type12.Boolean({ description: "Prefer exact matches." })
5389
+ exactMatch: Type14.Optional(
5390
+ Type14.Boolean({ description: "Prefer exact matches." })
4605
5391
  ),
4606
- includeAnswer: Type12.Optional(
4607
- Type12.Boolean({ description: "Include a short AI-generated answer." })
5392
+ includeAnswer: Type14.Optional(
5393
+ Type14.Boolean({ description: "Include a short AI-generated answer." })
4608
5394
  ),
4609
- includeRawContent: Type12.Optional(
4610
- Type12.Boolean({ description: "Include raw page content in results." })
5395
+ includeRawContent: Type14.Optional(
5396
+ Type14.Boolean({ description: "Include raw page content in results." })
4611
5397
  ),
4612
- includeImages: Type12.Optional(
4613
- Type12.Boolean({ description: "Include related images." })
5398
+ includeImages: Type14.Optional(
5399
+ Type14.Boolean({ description: "Include related images." })
4614
5400
  ),
4615
- includeFavicon: Type12.Optional(
4616
- Type12.Boolean({ description: "Include favicon URLs." })
5401
+ includeFavicon: Type14.Optional(
5402
+ Type14.Boolean({ description: "Include favicon URLs." })
4617
5403
  ),
4618
- includeDomains: Type12.Optional(
4619
- Type12.Array(Type12.String(), {
5404
+ includeDomains: Type14.Optional(
5405
+ Type14.Array(Type14.String(), {
4620
5406
  description: "Restrict results to these domains."
4621
5407
  })
4622
5408
  ),
4623
- excludeDomains: Type12.Optional(
4624
- Type12.Array(Type12.String(), {
5409
+ excludeDomains: Type14.Optional(
5410
+ Type14.Array(Type14.String(), {
4625
5411
  description: "Exclude these domains from results."
4626
5412
  })
4627
5413
  ),
4628
- days: Type12.Optional(
4629
- Type12.Integer({
5414
+ days: Type14.Optional(
5415
+ Type14.Integer({
4630
5416
  minimum: 1,
4631
5417
  description: "Limit results to the last N days."
4632
5418
  })
@@ -4634,27 +5420,27 @@ var tavilySearchOptionsSchema = Type12.Object(
4634
5420
  },
4635
5421
  { description: "Tavily search options." }
4636
5422
  );
4637
- var tavilyExtractOptionsSchema = Type12.Object(
5423
+ var tavilyExtractOptionsSchema = Type14.Object(
4638
5424
  {
4639
- extractDepth: Type12.Optional(
4640
- Type12.String({ description: "Depth setting for extraction." })
5425
+ extractDepth: Type14.Optional(
5426
+ Type14.String({ description: "Depth setting for extraction." })
4641
5427
  ),
4642
- format: Type12.Optional(
5428
+ format: Type14.Optional(
4643
5429
  literalUnion(["markdown", "text"], {
4644
5430
  description: "Output format for extracted content."
4645
5431
  })
4646
5432
  ),
4647
- includeImages: Type12.Optional(
4648
- Type12.Boolean({ description: "Include extracted images." })
5433
+ includeImages: Type14.Optional(
5434
+ Type14.Boolean({ description: "Include extracted images." })
4649
5435
  ),
4650
- query: Type12.Optional(
4651
- Type12.String({ description: "Optional query to focus extraction." })
5436
+ query: Type14.Optional(
5437
+ Type14.String({ description: "Optional query to focus extraction." })
4652
5438
  ),
4653
- chunksPerSource: Type12.Optional(
4654
- Type12.Integer({ minimum: 1, description: "Maximum chunks per source." })
5439
+ chunksPerSource: Type14.Optional(
5440
+ Type14.Integer({ minimum: 1, description: "Maximum chunks per source." })
4655
5441
  ),
4656
- includeFavicon: Type12.Optional(
4657
- Type12.Boolean({ description: "Include favicon URLs." })
5442
+ includeFavicon: Type14.Optional(
5443
+ Type14.Boolean({ description: "Include favicon URLs." })
4658
5444
  )
4659
5445
  },
4660
5446
  { description: "Tavily extract options." }
@@ -4739,7 +5525,7 @@ var tavilyAdapter = {
4739
5525
  });
4740
5526
  },
4741
5527
  async search(query2, maxResults, config, _context, options) {
4742
- const client = createClient7(config);
5528
+ const client = createClient8(config);
4743
5529
  const defaults = stripLocalExecutionOptions(asJsonObject(config.options?.search)) ?? {};
4744
5530
  const response = await client.search(query2, {
4745
5531
  ...defaults,
@@ -4758,7 +5544,7 @@ var tavilyAdapter = {
4758
5544
  };
4759
5545
  },
4760
5546
  async contents(urls, config, _context, options) {
4761
- const client = createClient7(config);
5547
+ const client = createClient8(config);
4762
5548
  const defaults = stripLocalExecutionOptions(asJsonObject(config.options?.extract)) ?? {};
4763
5549
  const response = await client.extract(urls, {
4764
5550
  ...defaults,
@@ -4796,7 +5582,7 @@ var tavilyAdapter = {
4796
5582
  };
4797
5583
  }
4798
5584
  };
4799
- function createClient7(config) {
5585
+ function createClient8(config) {
4800
5586
  const apiKey = resolveConfigValue(config.apiKey);
4801
5587
  if (!apiKey) {
4802
5588
  throw new Error("is missing an API key");
@@ -4828,48 +5614,48 @@ function buildExtractMetadata(response, result) {
4828
5614
  }
4829
5615
 
4830
5616
  // src/providers/valyu.ts
4831
- import { Type as Type13 } from "@sinclair/typebox";
5617
+ import { Type as Type15 } from "@sinclair/typebox";
4832
5618
  import { Valyu as ValyuClient } from "valyu-js";
4833
- var valyuSearchOptionsSchema = Type13.Object(
5619
+ var valyuSearchOptionsSchema = Type15.Object(
4834
5620
  {
4835
- searchType: Type13.Optional(
5621
+ searchType: Type15.Optional(
4836
5622
  literalUnion(["all", "web", "proprietary", "news"], {
4837
5623
  description: "Valyu search type."
4838
5624
  })
4839
5625
  ),
4840
- responseLength: Type13.Optional(
5626
+ responseLength: Type15.Optional(
4841
5627
  literalUnion(["short", "medium", "large", "max"], {
4842
5628
  description: "Response length."
4843
5629
  })
4844
5630
  ),
4845
- countryCode: Type13.Optional(
4846
- Type13.String({ description: "Country code to scope search results." })
5631
+ countryCode: Type15.Optional(
5632
+ Type15.String({ description: "Country code to scope search results." })
4847
5633
  )
4848
5634
  },
4849
5635
  { description: "Valyu search options." }
4850
5636
  );
4851
- var valyuAnswerOptionsSchema = Type13.Object(
5637
+ var valyuAnswerOptionsSchema = Type15.Object(
4852
5638
  {
4853
- responseLength: Type13.Optional(
5639
+ responseLength: Type15.Optional(
4854
5640
  literalUnion(["short", "medium", "large", "max"], {
4855
5641
  description: "Response length for answers."
4856
5642
  })
4857
5643
  ),
4858
- countryCode: Type13.Optional(
4859
- Type13.String({ description: "Country code to scope answer results." })
5644
+ countryCode: Type15.Optional(
5645
+ Type15.String({ description: "Country code to scope answer results." })
4860
5646
  )
4861
5647
  },
4862
5648
  { description: "Valyu answer options." }
4863
5649
  );
4864
- var valyuResearchOptionsSchema = Type13.Object(
5650
+ var valyuResearchOptionsSchema = Type15.Object(
4865
5651
  {
4866
- responseLength: Type13.Optional(
5652
+ responseLength: Type15.Optional(
4867
5653
  literalUnion(["short", "medium", "large", "max"], {
4868
5654
  description: "Response length for research."
4869
5655
  })
4870
5656
  ),
4871
- countryCode: Type13.Optional(
4872
- Type13.String({ description: "Country code to scope research results." })
5657
+ countryCode: Type15.Optional(
5658
+ Type15.String({ description: "Country code to scope research results." })
4873
5659
  )
4874
5660
  },
4875
5661
  { description: "Valyu research options." }
@@ -4982,7 +5768,7 @@ var valyuAdapter = {
4982
5768
  });
4983
5769
  },
4984
5770
  async search(query2, maxResults, config, _context, searchOptions) {
4985
- const client = createClient8(config);
5771
+ const client = createClient9(config);
4986
5772
  const options = {
4987
5773
  ...stripLocalExecutionOptions(asJsonObject(config.options?.search)) ?? {},
4988
5774
  ...searchOptions ?? {},
@@ -5005,7 +5791,7 @@ var valyuAdapter = {
5005
5791
  };
5006
5792
  },
5007
5793
  async contents(urls, config, _context, options) {
5008
- const client = createClient8(config);
5794
+ const client = createClient9(config);
5009
5795
  const response = await client.contents(urls, options);
5010
5796
  const finalResponse = "jobId" in response ? await client.waitForJob(response.jobId, {}) : response;
5011
5797
  if (!finalResponse.success) {
@@ -5039,7 +5825,7 @@ var valyuAdapter = {
5039
5825
  };
5040
5826
  },
5041
5827
  async answer(query2, config, _context, options) {
5042
- const client = createClient8(config);
5828
+ const client = createClient9(config);
5043
5829
  const response = await client.answer(query2, {
5044
5830
  ...stripLocalExecutionOptions(asJsonObject(config.options?.answer)) ?? {},
5045
5831
  ...options ?? {},
@@ -5078,7 +5864,7 @@ var valyuAdapter = {
5078
5864
  });
5079
5865
  },
5080
5866
  async startResearch(input, config, _context, options) {
5081
- const client = createClient8(config);
5867
+ const client = createClient9(config);
5082
5868
  const task = await client.deepresearch.create({
5083
5869
  input,
5084
5870
  ...stripLocalExecutionOptions(asJsonObject(config.options?.research)) ?? {},
@@ -5090,7 +5876,7 @@ var valyuAdapter = {
5090
5876
  return { id: task.deepresearch_id };
5091
5877
  },
5092
5878
  async pollResearch(id, config, _context, _options) {
5093
- const client = createClient8(config);
5879
+ const client = createClient9(config);
5094
5880
  const result = await client.deepresearch.status(id);
5095
5881
  if (!result.success) {
5096
5882
  throw new Error(result.error || "deep research failed");
@@ -5133,7 +5919,7 @@ var valyuAdapter = {
5133
5919
  return { status: "in_progress" };
5134
5920
  }
5135
5921
  };
5136
- function createClient8(config) {
5922
+ function createClient9(config) {
5137
5923
  const apiKey = resolveConfigValue(config.apiKey);
5138
5924
  if (!apiKey) {
5139
5925
  throw new Error("is missing an API key");
@@ -5144,15 +5930,17 @@ function createClient8(config) {
5144
5930
  // src/providers/index.ts
5145
5931
  var ADAPTERS_BY_ID = {
5146
5932
  claude: claudeAdapter,
5147
- cloudflare: cloudflareAdapter,
5148
5933
  codex: codexAdapter,
5934
+ cloudflare: cloudflareAdapter,
5149
5935
  custom: customAdapter,
5150
5936
  exa: exaAdapter,
5151
5937
  firecrawl: firecrawlAdapter,
5152
5938
  gemini: geminiAdapter,
5153
5939
  linkup: linkupAdapter,
5154
- perplexity: perplexityAdapter,
5940
+ openai: openaiAdapter,
5155
5941
  parallel: parallelAdapter,
5942
+ perplexity: perplexityAdapter,
5943
+ serper: serperAdapter,
5156
5944
  tavily: tavilyAdapter,
5157
5945
  valyu: valyuAdapter
5158
5946
  };
@@ -6226,8 +7014,7 @@ var PROVIDER_CONFIG_MANIFESTS = {
6226
7014
  setValue: (config, value) => {
6227
7015
  setCustomEnv(config, "research", value);
6228
7016
  }
6229
- }),
6230
- ...requestSettings("custom")
7017
+ })
6231
7018
  ]
6232
7019
  },
6233
7020
  exa: {
@@ -6250,7 +7037,7 @@ var PROVIDER_CONFIG_MANIFESTS = {
6250
7037
  "deep-reasoning",
6251
7038
  "deep-max"
6252
7039
  ],
6253
- getValue: (config) => readString4(getExaSearchOptions(config)?.type) ?? "default",
7040
+ getValue: (config) => readString5(getExaSearchOptions(config)?.type) ?? "default",
6254
7041
  setValue: (config, value) => {
6255
7042
  const options = ensureExaSearchOptions(config);
6256
7043
  if (value === "default") {
@@ -6355,10 +7142,111 @@ var PROVIDER_CONFIG_MANIFESTS = {
6355
7142
  ]
6356
7143
  },
6357
7144
  linkup: {
7145
+ settings: [apiKeySetting(), baseUrlSetting()]
7146
+ },
7147
+ openai: {
6358
7148
  settings: [
6359
7149
  apiKeySetting(),
6360
7150
  baseUrlSetting(),
6361
- ...requestSettings("linkup")
7151
+ stringSetting({
7152
+ id: "openaiSearchModel",
7153
+ label: "Search model",
7154
+ help: "Model used for OpenAI web search runs.",
7155
+ getValue: (config) => getOpenAISearchOptions(config)?.model,
7156
+ setValue: (config, value) => {
7157
+ assignOptionalString(
7158
+ ensureOpenAISearchOptions(config),
7159
+ "model",
7160
+ value
7161
+ );
7162
+ cleanupCapabilityOptions(config, ["search", "answer", "research"]);
7163
+ }
7164
+ }),
7165
+ stringSetting({
7166
+ id: "openaiSearchInstructions",
7167
+ label: "Search instructions",
7168
+ help: "Optional default instructions for OpenAI web search runs.",
7169
+ getValue: (config) => getOpenAISearchOptions(config)?.instructions,
7170
+ setValue: (config, value) => {
7171
+ assignOptionalString(
7172
+ ensureOpenAISearchOptions(config),
7173
+ "instructions",
7174
+ value
7175
+ );
7176
+ cleanupCapabilityOptions(config, ["search", "answer", "research"]);
7177
+ }
7178
+ }),
7179
+ stringSetting({
7180
+ id: "openaiAnswerModel",
7181
+ label: "Answer model",
7182
+ help: "Model used for OpenAI grounded answers.",
7183
+ getValue: (config) => getOpenAIAnswerOptions(config)?.model,
7184
+ setValue: (config, value) => {
7185
+ assignOptionalString(
7186
+ ensureOpenAIAnswerOptions(config),
7187
+ "model",
7188
+ value
7189
+ );
7190
+ cleanupCapabilityOptions(config, ["search", "answer", "research"]);
7191
+ }
7192
+ }),
7193
+ stringSetting({
7194
+ id: "openaiAnswerInstructions",
7195
+ label: "Answer instructions",
7196
+ help: "Optional default instructions for OpenAI grounded answers.",
7197
+ getValue: (config) => getOpenAIAnswerOptions(config)?.instructions,
7198
+ setValue: (config, value) => {
7199
+ assignOptionalString(
7200
+ ensureOpenAIAnswerOptions(config),
7201
+ "instructions",
7202
+ value
7203
+ );
7204
+ cleanupCapabilityOptions(config, ["search", "answer", "research"]);
7205
+ }
7206
+ }),
7207
+ stringSetting({
7208
+ id: "openaiResearchModel",
7209
+ label: "Research model",
7210
+ help: "Model used for OpenAI deep research runs.",
7211
+ getValue: (config) => getOpenAIResearchOptions(config)?.model,
7212
+ setValue: (config, value) => {
7213
+ assignOptionalString(
7214
+ ensureOpenAIResearchOptions(config),
7215
+ "model",
7216
+ value
7217
+ );
7218
+ cleanupCapabilityOptions(config, ["search", "answer", "research"]);
7219
+ }
7220
+ }),
7221
+ stringSetting({
7222
+ id: "openaiResearchInstructions",
7223
+ label: "Research instructions",
7224
+ help: "Optional default instructions for OpenAI deep research runs.",
7225
+ getValue: (config) => getOpenAIResearchOptions(config)?.instructions,
7226
+ setValue: (config, value) => {
7227
+ assignOptionalString(
7228
+ ensureOpenAIResearchOptions(config),
7229
+ "instructions",
7230
+ value
7231
+ );
7232
+ cleanupCapabilityOptions(config, ["search", "answer", "research"]);
7233
+ }
7234
+ }),
7235
+ stringSetting({
7236
+ id: "openaiResearchMaxToolCalls",
7237
+ label: "Research max tool calls",
7238
+ help: "Optional default maximum number of built-in tool calls for OpenAI deep research runs.",
7239
+ getValue: (config) => getIntegerString(getOpenAIResearchOptions(config)?.max_tool_calls),
7240
+ setValue: (config, value) => {
7241
+ assignOptionalInteger(
7242
+ ensureOpenAIResearchOptions(config),
7243
+ "max_tool_calls",
7244
+ value,
7245
+ "OpenAI research max tool calls must be a positive integer."
7246
+ );
7247
+ cleanupCapabilityOptions(config, ["search", "answer", "research"]);
7248
+ }
7249
+ })
6362
7250
  ]
6363
7251
  },
6364
7252
  perplexity: {
@@ -6373,7 +7261,7 @@ var PROVIDER_CONFIG_MANIFESTS = {
6373
7261
  label: "Search mode",
6374
7262
  help: "Parallel search mode. 'default' uses the SDK default.",
6375
7263
  values: ["default", "agentic", "one-shot"],
6376
- getValue: (config) => readString4(getParallelOptions(config)?.search?.mode) ?? "default",
7264
+ getValue: (config) => readString5(getParallelOptions(config)?.search?.mode) ?? "default",
6377
7265
  setValue: (config, value) => {
6378
7266
  const options = ensureParallelOptions(config);
6379
7267
  options.search = asJsonObject2(options.search) ?? {};
@@ -6427,12 +7315,11 @@ var PROVIDER_CONFIG_MANIFESTS = {
6427
7315
  })
6428
7316
  ]
6429
7317
  },
7318
+ serper: {
7319
+ settings: [apiKeySetting(), baseUrlSetting()]
7320
+ },
6430
7321
  tavily: {
6431
- settings: [
6432
- apiKeySetting(),
6433
- baseUrlSetting(),
6434
- ...requestSettings("tavily")
6435
- ]
7322
+ settings: [apiKeySetting(), baseUrlSetting()]
6436
7323
  },
6437
7324
  valyu: {
6438
7325
  settings: [
@@ -6443,7 +7330,7 @@ var PROVIDER_CONFIG_MANIFESTS = {
6443
7330
  label: "Search type",
6444
7331
  help: "Valyu search type. 'default' uses the SDK default.",
6445
7332
  values: ["default", "all", "web", "proprietary", "news"],
6446
- getValue: (config) => readString4(getValyuCapabilityOptions(config, "search")?.searchType) ?? "default",
7333
+ getValue: (config) => readString5(getValyuCapabilityOptions(config, "search")?.searchType) ?? "default",
6447
7334
  setValue: (config, value) => {
6448
7335
  const options = ensureValyuCapabilityOptions(config, "search");
6449
7336
  if (value === "default") {
@@ -6459,7 +7346,7 @@ var PROVIDER_CONFIG_MANIFESTS = {
6459
7346
  label: "Search response length",
6460
7347
  help: "Valyu search response length. 'default' uses the SDK default.",
6461
7348
  values: ["default", "short", "medium", "large", "max"],
6462
- getValue: (config) => readString4(
7349
+ getValue: (config) => readString5(
6463
7350
  getValyuCapabilityOptions(config, "search")?.responseLength
6464
7351
  ) ?? "default",
6465
7352
  setValue: (config, value) => {
@@ -6471,7 +7358,7 @@ var PROVIDER_CONFIG_MANIFESTS = {
6471
7358
  label: "Answer response length",
6472
7359
  help: "Valyu answer response length. 'default' uses the SDK default.",
6473
7360
  values: ["default", "short", "medium", "large", "max"],
6474
- getValue: (config) => readString4(
7361
+ getValue: (config) => readString5(
6475
7362
  getValyuCapabilityOptions(config, "answer")?.responseLength
6476
7363
  ) ?? "default",
6477
7364
  setValue: (config, value) => {
@@ -6483,7 +7370,7 @@ var PROVIDER_CONFIG_MANIFESTS = {
6483
7370
  label: "Research response length",
6484
7371
  help: "Valyu research response length. 'default' uses the SDK default.",
6485
7372
  values: ["default", "short", "medium", "large", "max"],
6486
- getValue: (config) => readString4(
7373
+ getValue: (config) => readString5(
6487
7374
  getValyuCapabilityOptions(config, "research")?.responseLength
6488
7375
  ) ?? "default",
6489
7376
  setValue: (config, value) => {
@@ -6539,88 +7426,20 @@ function baseUrlSetting() {
6539
7426
  }
6540
7427
  });
6541
7428
  }
6542
- function requestSettings(providerId) {
6543
- const settings = [
6544
- stringSetting({
6545
- id: "requestTimeoutMs",
6546
- label: "Request timeout (ms)",
6547
- help: "Maximum time to wait for each command before failing that attempt for this provider. Leave empty to inherit the shared setting.",
6548
- getValue: (config) => getIntegerString(config?.settings?.requestTimeoutMs),
6549
- setValue: (config, value) => {
6550
- assignOptionalInteger(
6551
- ensureSettings(config),
6552
- "requestTimeoutMs",
6553
- value,
6554
- "Request timeout must be a positive integer."
6555
- );
6556
- cleanupEmpty(config, "settings");
6557
- }
6558
- }),
6559
- stringSetting({
6560
- id: "retryCount",
6561
- label: "Retry count",
6562
- help: "How many times to retry transient command failures for this provider. Leave empty to inherit the shared setting.",
6563
- getValue: (config) => getIntegerString(config?.settings?.retryCount),
6564
- setValue: (config, value) => {
6565
- assignOptionalInteger(
6566
- ensureSettings(config),
6567
- "retryCount",
6568
- value,
6569
- "Retry count must be a non-negative integer.",
6570
- { allowZero: true }
6571
- );
6572
- cleanupEmpty(config, "settings");
6573
- }
6574
- }),
6575
- stringSetting({
6576
- id: "retryDelayMs",
6577
- label: "Retry delay (ms)",
6578
- help: "Initial delay before retrying command failures for this provider. Leave empty to inherit the shared setting.",
6579
- getValue: (config) => getIntegerString(config?.settings?.retryDelayMs),
6580
- setValue: (config, value) => {
6581
- assignOptionalInteger(
6582
- ensureSettings(config),
6583
- "retryDelayMs",
6584
- value,
6585
- "Retry delay must be a positive integer."
6586
- );
6587
- cleanupEmpty(config, "settings");
6588
- }
6589
- })
6590
- ];
6591
- if (supportsTool(providerId, "research")) {
6592
- settings.push(
6593
- stringSetting({
6594
- id: "researchTimeoutMs",
6595
- label: "Research timeout (ms)",
6596
- help: "Maximum total time to allow long-running web research for this provider before aborting it. Leave empty to inherit the shared setting.",
6597
- getValue: (config) => getIntegerString(config?.settings?.researchTimeoutMs),
6598
- setValue: (config, value) => {
6599
- assignOptionalInteger(
6600
- ensureSettings(config),
6601
- "researchTimeoutMs",
6602
- value,
6603
- "Research timeout must be a positive integer."
6604
- );
6605
- cleanupEmpty(config, "settings");
6606
- }
6607
- })
6608
- );
6609
- }
6610
- return settings;
6611
- }
6612
7429
  function assignOptionalString(target, key, value) {
7430
+ const record = target;
6613
7431
  const trimmed = value.trim();
6614
7432
  if (!trimmed) {
6615
- delete target[key];
7433
+ delete record[key];
6616
7434
  } else {
6617
- target[key] = trimmed;
7435
+ record[key] = trimmed;
6618
7436
  }
6619
7437
  }
6620
7438
  function assignOptionalInteger(target, key, value, errorMessage, options) {
7439
+ const record = target;
6621
7440
  const trimmed = value.trim();
6622
7441
  if (!trimmed) {
6623
- delete target[key];
7442
+ delete record[key];
6624
7443
  return;
6625
7444
  }
6626
7445
  const parsed = Number(trimmed);
@@ -6628,7 +7447,7 @@ function assignOptionalInteger(target, key, value, errorMessage, options) {
6628
7447
  if (!Number.isInteger(parsed) || parsed < minimum) {
6629
7448
  throw new Error(errorMessage);
6630
7449
  }
6631
- target[key] = parsed;
7450
+ record[key] = parsed;
6632
7451
  }
6633
7452
  function assignOptionalBoolean(target, key, value) {
6634
7453
  if (value === "default") {
@@ -6643,16 +7462,12 @@ function getIntegerString(value) {
6643
7462
  function getBooleanValue(value) {
6644
7463
  return typeof value === "boolean" ? String(value) : "default";
6645
7464
  }
6646
- function readString4(value) {
7465
+ function readString5(value) {
6647
7466
  return typeof value === "string" ? value : void 0;
6648
7467
  }
6649
7468
  function asJsonObject2(value) {
6650
7469
  return typeof value === "object" && value !== null && !Array.isArray(value) ? value : void 0;
6651
7470
  }
6652
- function ensureSettings(config) {
6653
- config.settings = { ...config.settings ?? {} };
6654
- return config.settings;
6655
- }
6656
7471
  function cleanupEmpty(config, key) {
6657
7472
  const value = asJsonObject2(config[key]);
6658
7473
  if (value && Object.keys(value).length === 0) {
@@ -6817,6 +7632,36 @@ function ensureExaSearchOptions(config) {
6817
7632
  };
6818
7633
  return config.options.search;
6819
7634
  }
7635
+ function getOpenAISearchOptions(config) {
7636
+ return config?.options?.search;
7637
+ }
7638
+ function ensureOpenAISearchOptions(config) {
7639
+ config.options = {
7640
+ ...config.options ?? {},
7641
+ search: { ...config.options?.search ?? {} }
7642
+ };
7643
+ return config.options.search ?? (config.options.search = {});
7644
+ }
7645
+ function getOpenAIAnswerOptions(config) {
7646
+ return config?.options?.answer;
7647
+ }
7648
+ function ensureOpenAIAnswerOptions(config) {
7649
+ config.options = {
7650
+ ...config.options ?? {},
7651
+ answer: { ...config.options?.answer ?? {} }
7652
+ };
7653
+ return config.options.answer ?? (config.options.answer = {});
7654
+ }
7655
+ function getOpenAIResearchOptions(config) {
7656
+ return config?.options?.research;
7657
+ }
7658
+ function ensureOpenAIResearchOptions(config) {
7659
+ config.options = {
7660
+ ...config.options ?? {},
7661
+ research: { ...config.options?.research ?? {} }
7662
+ };
7663
+ return config.options.research ?? (config.options.research = {});
7664
+ }
6820
7665
  function getValyuCapabilityOptions(config, capability) {
6821
7666
  return config?.options?.[capability];
6822
7667
  }
@@ -6992,14 +7837,14 @@ function registerWebSearchTool(pi, providerIds) {
6992
7837
  promptGuidelines: [
6993
7838
  "Batch related searches when grouped comparison matters; use separate sibling web_search calls when independent results should surface as soon as they are ready."
6994
7839
  ],
6995
- parameters: Type14.Object({
6996
- queries: Type14.Array(Type14.String({ minLength: 1 }), {
7840
+ parameters: Type16.Object({
7841
+ queries: Type16.Array(Type16.String({ minLength: 1 }), {
6997
7842
  minItems: 1,
6998
7843
  maxItems: MAX_SEARCH_QUERIES,
6999
7844
  description: `One or more search queries to run in one call (max ${MAX_SEARCH_QUERIES})`
7000
7845
  }),
7001
- maxResults: Type14.Optional(
7002
- Type14.Integer({
7846
+ maxResults: Type16.Optional(
7847
+ Type16.Integer({
7003
7848
  minimum: 1,
7004
7849
  maximum: MAX_ALLOWED_RESULTS,
7005
7850
  description: `Maximum number of results to return (default: ${DEFAULT_MAX_RESULTS})`
@@ -7048,8 +7893,8 @@ function registerWebContentsTool(pi, providerIds) {
7048
7893
  name: "web_contents",
7049
7894
  label: "Web Contents",
7050
7895
  description: "Read and extract the main contents of one or more web pages. Batch related pages together, or use separate sibling calls when each page can be acted on independently.",
7051
- parameters: Type14.Object({
7052
- urls: Type14.Array(Type14.String({ minLength: 1 }), {
7896
+ parameters: Type16.Object({
7897
+ urls: Type16.Array(Type16.String({ minLength: 1 }), {
7053
7898
  minItems: 1,
7054
7899
  description: "One or more URLs to extract"
7055
7900
  }),
@@ -7099,8 +7944,8 @@ function registerWebAnswerTool(pi, providerIds) {
7099
7944
  name: "web_answer",
7100
7945
  label: "Web Answer",
7101
7946
  description: `Answer one or more questions using web-grounded evidence (up to ${MAX_SEARCH_QUERIES} per call).`,
7102
- parameters: Type14.Object({
7103
- queries: Type14.Array(Type14.String({ minLength: 1 }), {
7947
+ parameters: Type16.Object({
7948
+ queries: Type16.Array(Type16.String({ minLength: 1 }), {
7104
7949
  minItems: 1,
7105
7950
  maxItems: MAX_SEARCH_QUERIES,
7106
7951
  description: `One or more questions to answer in one call (max ${MAX_SEARCH_QUERIES})`
@@ -7154,8 +7999,8 @@ function registerWebResearchTool(pi, webResearchLifecycle, providerIds) {
7154
7999
  name: "web_research",
7155
8000
  label: "Web Research",
7156
8001
  description: "Start a long-running web research job. Returns immediately with a dispatch notice; the final report is saved to a file and posted later as a custom message.",
7157
- parameters: Type14.Object({
7158
- input: Type14.String({ description: "Research brief or question" }),
8002
+ parameters: Type16.Object({
8003
+ input: Type16.String({ description: "Research brief or question" }),
7159
8004
  ...optionalField(
7160
8005
  "options",
7161
8006
  buildStructuredOptionsSchema("research", selectedProviderId)
@@ -7298,7 +8143,7 @@ function optionalField(name, schema) {
7298
8143
  function buildStructuredOptionsSchema(capability, providerId) {
7299
8144
  const providerSchema = resolveProviderOptionsSchema(capability, providerId);
7300
8145
  const schema = buildToolOptionsSchema(capability, providerSchema);
7301
- return schema ? Type14.Optional(schema) : void 0;
8146
+ return schema ? Type16.Optional(schema) : void 0;
7302
8147
  }
7303
8148
  function resolveProviderOptionsSchema(capability, providerId) {
7304
8149
  if (!providerId) {
@@ -8606,7 +9451,7 @@ function getSharedSettingRawValue(config, id) {
8606
9451
  const value = config.settings?.[id];
8607
9452
  return typeof value === "number" ? String(value) : "";
8608
9453
  }
8609
- function ensureSettings2(config) {
9454
+ function ensureSettings(config) {
8610
9455
  config.settings = { ...config.settings ?? {} };
8611
9456
  return config.settings;
8612
9457
  }
@@ -8951,7 +9796,7 @@ var WebProvidersSettingsView = class {
8951
9796
  async handleSharedSettingChange(id, value) {
8952
9797
  await this.persist((config) => {
8953
9798
  const parsed = SETTING_META[id].parse(value);
8954
- const settings = ensureSettings2(config);
9799
+ const settings = ensureSettings(config);
8955
9800
  if (parsed === void 0) {
8956
9801
  delete settings[id];
8957
9802
  } else {