pi-web-providers 3.0.0 → 3.1.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 +29 -24
  2. package/dist/index.js +1164 -300
  3. package/package.json +7 -7
package/dist/index.js CHANGED
@@ -2,14 +2,14 @@
2
2
  import { randomUUID } from "node:crypto";
3
3
  import { mkdir as mkdir2, writeFile as writeFile2 } from "node:fs/promises";
4
4
  import { tmpdir } from "node:os";
5
- import { dirname as dirname2, join as join2, relative } from "node:path";
5
+ import { basename, dirname as dirname2, join as join2, relative } from "node:path";
6
6
  import {
7
7
  DEFAULT_MAX_BYTES,
8
8
  DEFAULT_MAX_LINES,
9
- formatSize,
9
+ formatSize as formatSize2,
10
10
  getMarkdownTheme,
11
11
  truncateHead
12
- } from "@mariozechner/pi-coding-agent";
12
+ } from "@earendil-works/pi-coding-agent";
13
13
  import {
14
14
  Box,
15
15
  Editor,
@@ -21,13 +21,13 @@ import {
21
21
  truncateToWidth,
22
22
  visibleWidth,
23
23
  wrapTextWithAnsi
24
- } from "@mariozechner/pi-tui";
24
+ } from "@earendil-works/pi-tui";
25
25
  import { Type as Type16 } from "typebox";
26
26
 
27
27
  // src/config.ts
28
28
  import { mkdir, readFile, writeFile } from "node:fs/promises";
29
29
  import { dirname, join } from "node:path";
30
- import { getAgentDir } from "@mariozechner/pi-coding-agent";
30
+ import { getAgentDir } from "@earendil-works/pi-coding-agent";
31
31
 
32
32
  // src/config-values.ts
33
33
  import { execSync } from "node:child_process";
@@ -117,7 +117,13 @@ function asJsonObject(value) {
117
117
  function formatJson(value) {
118
118
  return JSON.stringify(value, null, 2);
119
119
  }
120
- function getApiKeyStatus(apiKeyReference) {
120
+ function getApiKeyStatus(apiKeyReference, options = {}) {
121
+ if (!apiKeyReference) {
122
+ return { state: "missing_api_key" };
123
+ }
124
+ if (options.resolveSecrets === false && isSecretReference(apiKeyReference)) {
125
+ return { state: "deferred_secret" };
126
+ }
121
127
  try {
122
128
  return resolveConfigValue(apiKeyReference) ? { state: "ready" } : { state: "missing_api_key" };
123
129
  } catch (error) {
@@ -127,6 +133,9 @@ function getApiKeyStatus(apiKeyReference) {
127
133
  };
128
134
  }
129
135
  }
136
+ function isSecretReference(reference) {
137
+ return reference.startsWith("!") || /^[A-Z][A-Z0-9_]*$/.test(reference);
138
+ }
130
139
  function formatConfigValueError(error) {
131
140
  const message = error instanceof Error ? error.message : String(error);
132
141
  return message.replace(/\s+/g, " ").trim() || "Failed to resolve config value";
@@ -385,8 +394,14 @@ var braveSearchPromptGuidelines = [
385
394
  ];
386
395
  var braveAnswerOptionsSchema = Type.Object(
387
396
  {
397
+ model: Type.Optional(
398
+ Type.Enum({ brave: "brave", bravePro: "brave-pro" }, {
399
+ description: "Brave Answers model. Defaults to 'brave'."
400
+ })
401
+ ),
388
402
  country: Type.Optional(Type.String()),
389
403
  language: Type.Optional(Type.String()),
404
+ safesearch: safesearchOption,
390
405
  enable_citations: Type.Optional(Type.Boolean()),
391
406
  enable_entities: Type.Optional(Type.Boolean()),
392
407
  max_completion_tokens: Type.Optional(Type.Integer({ minimum: 1 }))
@@ -395,8 +410,14 @@ var braveAnswerOptionsSchema = Type.Object(
395
410
  );
396
411
  var braveResearchOptionsSchema = Type.Object(
397
412
  {
413
+ model: Type.Optional(
414
+ Type.Enum({ brave: "brave", bravePro: "brave-pro" }, {
415
+ description: "Brave Answers model. Defaults to 'brave'."
416
+ })
417
+ ),
398
418
  country: Type.Optional(Type.String()),
399
419
  language: Type.Optional(Type.String()),
420
+ safesearch: safesearchOption,
400
421
  enable_entities: Type.Optional(Type.Boolean()),
401
422
  enable_citations: Type.Optional(
402
423
  Type.Boolean({
@@ -448,19 +469,19 @@ var braveImplementation = {
448
469
  options: {}
449
470
  };
450
471
  },
451
- getCapabilityStatus(config, _cwd, tool) {
472
+ getCapabilityStatus(config, _cwd, tool, options) {
452
473
  const key = tool === "answer" || tool === "research" ? config?.credentials?.answers : config?.credentials?.search;
453
- try {
454
- if (tool)
455
- return resolveConfigValue(key) ? { state: "ready" } : { state: "missing_api_key" };
456
- return [
457
- config?.credentials?.search,
458
- config?.credentials?.answers,
459
- config?.credentials?.autosuggest
460
- ].some((v) => resolveConfigValue(v)) ? { state: "ready" } : { state: "missing_api_key" };
461
- } catch (error) {
462
- return { state: "invalid_config", detail: formatConfigValueError(error) };
463
- }
474
+ if (tool) {
475
+ return getApiKeyStatus(key, options);
476
+ }
477
+ const statuses = [
478
+ config?.credentials?.search,
479
+ config?.credentials?.answers,
480
+ config?.credentials?.autosuggest
481
+ ].map((value) => getApiKeyStatus(value, options));
482
+ return statuses.find((status) => status.state === "ready") ?? statuses.find((status) => status.state === "deferred_secret") ?? statuses.find((status) => status.state === "invalid_config") ?? {
483
+ state: "missing_api_key"
484
+ };
464
485
  },
465
486
  async search(query2, maxResults, config, context, options) {
466
487
  const apiKey = requireKey(config.credentials?.search, "Brave search");
@@ -945,25 +966,25 @@ function placeRating(place, details) {
945
966
  return rating === void 0 ? void 0 : `Rating: ${rating}`;
946
967
  }
947
968
  function buildAnswerRequest(raw) {
948
- const answerOptions = pick(raw, [
969
+ const webSearchOptions = pick(raw, [
949
970
  "country",
950
971
  "language",
951
972
  "safesearch",
952
973
  "enable_entities",
953
974
  "enable_citations"
954
975
  ]);
955
- if (answerOptions.enable_citations === void 0) {
956
- answerOptions.enable_citations = true;
976
+ if (webSearchOptions.enable_citations === void 0) {
977
+ webSearchOptions.enable_citations = true;
957
978
  }
958
- const stream = answerOptions.enable_citations === true || answerOptions.enable_entities === true;
979
+ const stream = webSearchOptions.enable_citations === true || webSearchOptions.enable_entities === true;
959
980
  return {
960
981
  stream,
961
- ...pick(raw, ["max_completion_tokens", "metadata", "seed"]),
962
- ...answerOptions
982
+ ...pick(raw, ["model", "max_completion_tokens", "metadata", "seed"]),
983
+ web_search_options: webSearchOptions
963
984
  };
964
985
  }
965
986
  function buildResearchRequest(raw) {
966
- const researchOptions = pick(raw, [
987
+ const webSearchOptions = pick(raw, [
967
988
  "country",
968
989
  "language",
969
990
  "safesearch",
@@ -975,12 +996,12 @@ function buildResearchRequest(raw) {
975
996
  "research_maximum_number_of_seconds",
976
997
  "research_maximum_number_of_results_per_query"
977
998
  ]);
999
+ webSearchOptions.enable_research = true;
1000
+ webSearchOptions.enable_citations = false;
978
1001
  return {
979
1002
  stream: true,
980
- ...pick(raw, ["max_completion_tokens", "metadata", "seed"]),
981
- ...researchOptions,
982
- enable_research: true,
983
- enable_citations: false
1003
+ ...pick(raw, ["model", "max_completion_tokens", "metadata", "seed"]),
1004
+ web_search_options: webSearchOptions
984
1005
  };
985
1006
  }
986
1007
  async function completion(input, config, context, request) {
@@ -1197,10 +1218,11 @@ var braveProvider = defineProvider({
1197
1218
  },
1198
1219
  optionCapabilities: ["search", "answer", "research"]
1199
1220
  },
1200
- getCapabilityStatus: (config, cwd, tool) => braveImplementation.getCapabilityStatus(
1221
+ getCapabilityStatus: (config, cwd, tool, options) => braveImplementation.getCapabilityStatus(
1201
1222
  config,
1202
1223
  cwd,
1203
- tool
1224
+ tool,
1225
+ options
1204
1226
  ),
1205
1227
  capabilities: {
1206
1228
  search: defineCapability({
@@ -1666,10 +1688,12 @@ var cloudflareImplementation = {
1666
1688
  }
1667
1689
  };
1668
1690
  },
1669
- getCapabilityStatus(config) {
1670
- const apiTokenStatus = getApiKeyStatus(config?.credentials?.api);
1691
+ getCapabilityStatus(config, _cwd, _tool, options) {
1692
+ const apiTokenStatus = getApiKeyStatus(config?.credentials?.api, options);
1671
1693
  if (apiTokenStatus.state !== "ready") {
1672
- return apiTokenStatus;
1694
+ if (apiTokenStatus.state !== "deferred_secret") {
1695
+ return apiTokenStatus;
1696
+ }
1673
1697
  }
1674
1698
  try {
1675
1699
  if (!resolveConfigValue(config?.accountId)) {
@@ -1681,7 +1705,7 @@ var cloudflareImplementation = {
1681
1705
  detail: formatConfigValueError(error)
1682
1706
  };
1683
1707
  }
1684
- return { state: "ready" };
1708
+ return apiTokenStatus.state === "deferred_secret" ? apiTokenStatus : { state: "ready" };
1685
1709
  },
1686
1710
  async contents(urls, config, context, options) {
1687
1711
  const client = createClient(config);
@@ -1740,10 +1764,11 @@ var cloudflareProvider = defineProvider({
1740
1764
  createTemplate: () => cloudflareImplementation.createTemplate(),
1741
1765
  fields: ["credentials", "accountId", "options", "settings"]
1742
1766
  },
1743
- getCapabilityStatus: (config, cwd, tool) => cloudflareImplementation.getCapabilityStatus(
1767
+ getCapabilityStatus: (config, cwd, tool, options) => cloudflareImplementation.getCapabilityStatus(
1744
1768
  config,
1745
1769
  cwd,
1746
- tool
1770
+ tool,
1771
+ options
1747
1772
  ),
1748
1773
  capabilities: {
1749
1774
  contents: defineCapability({
@@ -2876,8 +2901,8 @@ var exaImplementation = {
2876
2901
  }
2877
2902
  };
2878
2903
  },
2879
- getCapabilityStatus(config) {
2880
- return getApiKeyStatus(config?.credentials?.api);
2904
+ getCapabilityStatus(config, _cwd, _tool, options) {
2905
+ return getApiKeyStatus(config?.credentials?.api, options);
2881
2906
  },
2882
2907
  async search(query2, maxResults, config, _context, searchOptions) {
2883
2908
  const client = createClient2(config);
@@ -3012,10 +3037,11 @@ var exaProvider = defineProvider({
3012
3037
  fields: ["credentials", "baseUrl", "options", "settings"],
3013
3038
  optionCapabilities: ["search"]
3014
3039
  },
3015
- getCapabilityStatus: (config, cwd, tool) => exaImplementation.getCapabilityStatus(
3040
+ getCapabilityStatus: (config, cwd, tool, options) => exaImplementation.getCapabilityStatus(
3016
3041
  config,
3017
3042
  cwd,
3018
- tool
3043
+ tool,
3044
+ options
3019
3045
  ),
3020
3046
  capabilities: {
3021
3047
  search: defineCapability({
@@ -3070,6 +3096,7 @@ var exaProvider = defineProvider({
3070
3096
  // src/providers/firecrawl.ts
3071
3097
  import FirecrawlClient from "@mendable/firecrawl-js";
3072
3098
  import { Type as Type7 } from "typebox";
3099
+ var FIRECRAWL_CLOUD_HOST = "api.firecrawl.dev";
3073
3100
  var firecrawlSearchOptionsSchema = Type7.Object(
3074
3101
  {
3075
3102
  lang: Type7.Optional(
@@ -3201,8 +3228,8 @@ var firecrawlImplementation = {
3201
3228
  }
3202
3229
  };
3203
3230
  },
3204
- getCapabilityStatus(config) {
3205
- return getApiKeyStatus(config?.credentials?.api);
3231
+ getCapabilityStatus(config, _cwd, _tool, options) {
3232
+ return getFirecrawlCapabilityStatus(config, options);
3206
3233
  },
3207
3234
  async search(query2, maxResults, config, _context, options) {
3208
3235
  const client = createClient3(config);
@@ -3255,15 +3282,26 @@ var firecrawlImplementation = {
3255
3282
  }
3256
3283
  };
3257
3284
  function createClient3(config) {
3285
+ const apiUrl = resolveConfigValue(config.baseUrl);
3258
3286
  const apiKey = resolveConfigValue(config.credentials?.api);
3259
- if (!apiKey) {
3287
+ if (isFirecrawlCloudApiUrl(apiUrl) && !apiKey) {
3260
3288
  throw new Error("is missing an API key");
3261
3289
  }
3262
3290
  return new FirecrawlClient({
3263
3291
  apiKey,
3264
- apiUrl: resolveConfigValue(config.baseUrl)
3292
+ apiUrl
3265
3293
  });
3266
3294
  }
3295
+ function getFirecrawlCapabilityStatus(config, options) {
3296
+ if (!config?.baseUrl || isFirecrawlCloudApiUrl(config.baseUrl)) {
3297
+ return getApiKeyStatus(config?.credentials?.api, options);
3298
+ }
3299
+ const apiKeyStatus = getApiKeyStatus(config.credentials?.api, options);
3300
+ return apiKeyStatus.state === "missing_api_key" ? { state: "ready" } : apiKeyStatus;
3301
+ }
3302
+ function isFirecrawlCloudApiUrl(apiUrl) {
3303
+ return !apiUrl || apiUrl.includes(FIRECRAWL_CLOUD_HOST);
3304
+ }
3267
3305
  function flattenSearchResults(response) {
3268
3306
  return ["web", "news", "images"].flatMap(
3269
3307
  (source) => (response[source] ?? []).map((entry) => toSearchResult(source, entry)).filter((entry) => entry !== null)
@@ -3321,10 +3359,11 @@ var firecrawlProvider = defineProvider({
3321
3359
  createTemplate: () => firecrawlImplementation.createTemplate(),
3322
3360
  fields: ["credentials", "baseUrl", "options", "settings"]
3323
3361
  },
3324
- getCapabilityStatus: (config, cwd, tool) => firecrawlImplementation.getCapabilityStatus(
3362
+ getCapabilityStatus: (config, cwd, tool, options) => firecrawlImplementation.getCapabilityStatus(
3325
3363
  config,
3326
3364
  cwd,
3327
- tool
3365
+ tool,
3366
+ options
3328
3367
  ),
3329
3368
  capabilities: {
3330
3369
  search: defineCapability({
@@ -3479,8 +3518,8 @@ var geminiImplementation = {
3479
3518
  }
3480
3519
  };
3481
3520
  },
3482
- getCapabilityStatus(config) {
3483
- return getApiKeyStatus(config?.credentials?.api);
3521
+ getCapabilityStatus(config, _cwd, _tool, options) {
3522
+ return getApiKeyStatus(config?.credentials?.api, options);
3484
3523
  },
3485
3524
  async search(query2, maxResults, config, context, options) {
3486
3525
  const ai = this.createClient(config);
@@ -4109,10 +4148,11 @@ var geminiProvider = defineProvider({
4109
4148
  createTemplate: () => geminiImplementation.createTemplate(),
4110
4149
  fields: ["credentials", "options", "settings"]
4111
4150
  },
4112
- getCapabilityStatus: (config, cwd, tool) => geminiImplementation.getCapabilityStatus(
4151
+ getCapabilityStatus: (config, cwd, tool, options) => geminiImplementation.getCapabilityStatus(
4113
4152
  config,
4114
4153
  cwd,
4115
- tool
4154
+ tool,
4155
+ options
4116
4156
  ),
4117
4157
  capabilities: {
4118
4158
  search: defineCapability({
@@ -4220,8 +4260,8 @@ var linkupImplementation = {
4220
4260
  credentials: { api: "LINKUP_API_KEY" }
4221
4261
  };
4222
4262
  },
4223
- getCapabilityStatus(config) {
4224
- return getApiKeyStatus(config?.credentials?.api);
4263
+ getCapabilityStatus(config, _cwd, _tool, options) {
4264
+ return getApiKeyStatus(config?.credentials?.api, options);
4225
4265
  },
4226
4266
  async search(query2, maxResults, config, _context, options) {
4227
4267
  const client = createClient4(config);
@@ -4365,10 +4405,11 @@ var linkupProvider = defineProvider({
4365
4405
  createTemplate: () => linkupImplementation.createTemplate(),
4366
4406
  fields: ["credentials", "baseUrl", "options", "settings"]
4367
4407
  },
4368
- getCapabilityStatus: (config, cwd, tool) => linkupImplementation.getCapabilityStatus(
4408
+ getCapabilityStatus: (config, cwd, tool, options) => linkupImplementation.getCapabilityStatus(
4369
4409
  config,
4370
4410
  cwd,
4371
- tool
4411
+ tool,
4412
+ options
4372
4413
  ),
4373
4414
  capabilities: {
4374
4415
  search: defineCapability({
@@ -4414,8 +4455,8 @@ var ollamaProvider = defineProvider({
4414
4455
  },
4415
4456
  fields: ["credentials", "baseUrl", "settings"]
4416
4457
  },
4417
- getCapabilityStatus(config) {
4418
- return getApiKeyStatus(config?.credentials?.api);
4458
+ getCapabilityStatus(config, _cwd, _tool, options) {
4459
+ return getApiKeyStatus(config?.credentials?.api, options);
4419
4460
  },
4420
4461
  capabilities: {
4421
4462
  search: defineCapability({
@@ -4679,8 +4720,8 @@ var openaiImplementation = {
4679
4720
  }
4680
4721
  };
4681
4722
  },
4682
- getCapabilityStatus(config) {
4683
- return getApiKeyStatus(config?.credentials?.api);
4723
+ getCapabilityStatus(config, _cwd, _tool, options) {
4724
+ return getApiKeyStatus(config?.credentials?.api, options);
4684
4725
  },
4685
4726
  async search(query2, maxResults, config, context, options) {
4686
4727
  const client = createClient5(config);
@@ -5036,10 +5077,11 @@ var openaiProvider = defineProvider({
5036
5077
  fields: ["credentials", "baseUrl", "options", "settings"],
5037
5078
  optionCapabilities: ["search", "answer", "research"]
5038
5079
  },
5039
- getCapabilityStatus: (config, cwd, tool) => openaiImplementation.getCapabilityStatus(
5080
+ getCapabilityStatus: (config, cwd, tool, options) => openaiImplementation.getCapabilityStatus(
5040
5081
  config,
5041
5082
  cwd,
5042
- tool
5083
+ tool,
5084
+ options
5043
5085
  ),
5044
5086
  capabilities: {
5045
5087
  search: defineCapability({
@@ -5134,8 +5176,8 @@ var parallelImplementation = {
5134
5176
  }
5135
5177
  };
5136
5178
  },
5137
- getCapabilityStatus(config) {
5138
- return getApiKeyStatus(config?.credentials?.api);
5179
+ getCapabilityStatus(config, _cwd, _tool, options) {
5180
+ return getApiKeyStatus(config?.credentials?.api, options);
5139
5181
  },
5140
5182
  async search(query2, maxResults, config, context, options) {
5141
5183
  const client = createClient6(config);
@@ -5219,10 +5261,11 @@ var parallelProvider = defineProvider({
5219
5261
  createTemplate: () => parallelImplementation.createTemplate(),
5220
5262
  fields: ["credentials", "baseUrl", "options", "settings"]
5221
5263
  },
5222
- getCapabilityStatus: (config, cwd, tool) => parallelImplementation.getCapabilityStatus(
5264
+ getCapabilityStatus: (config, cwd, tool, options) => parallelImplementation.getCapabilityStatus(
5223
5265
  config,
5224
5266
  cwd,
5225
- tool
5267
+ tool,
5268
+ options
5226
5269
  ),
5227
5270
  capabilities: {
5228
5271
  search: defineCapability({
@@ -5327,8 +5370,8 @@ var perplexityImplementation = {
5327
5370
  }
5328
5371
  };
5329
5372
  },
5330
- getCapabilityStatus(config) {
5331
- return getApiKeyStatus(config?.credentials?.api);
5373
+ getCapabilityStatus(config, _cwd, _tool, options) {
5374
+ return getApiKeyStatus(config?.credentials?.api, options);
5332
5375
  },
5333
5376
  async search(query2, maxResults, config, context, options) {
5334
5377
  const client = createClient7(config);
@@ -5538,10 +5581,11 @@ var perplexityProvider = defineProvider({
5538
5581
  createTemplate: () => perplexityImplementation.createTemplate(),
5539
5582
  fields: ["credentials", "baseUrl", "options", "settings"]
5540
5583
  },
5541
- getCapabilityStatus: (config, cwd, tool) => perplexityImplementation.getCapabilityStatus(
5584
+ getCapabilityStatus: (config, cwd, tool, options) => perplexityImplementation.getCapabilityStatus(
5542
5585
  config,
5543
5586
  cwd,
5544
- tool
5587
+ tool,
5588
+ options
5545
5589
  ),
5546
5590
  capabilities: {
5547
5591
  search: defineCapability({
@@ -5584,9 +5628,103 @@ var perplexityProvider = defineProvider({
5584
5628
 
5585
5629
  // src/providers/serper.ts
5586
5630
  import { Type as Type13 } from "typebox";
5631
+
5632
+ // src/types.ts
5633
+ var TOOLS = ["search", "contents", "answer", "research"];
5634
+ var SERPER_SEARCH_MODE_VALUES = {
5635
+ search: "search",
5636
+ images: "images",
5637
+ videos: "videos",
5638
+ places: "places",
5639
+ maps: "maps",
5640
+ reviews: "reviews",
5641
+ news: "news",
5642
+ shopping: "shopping",
5643
+ productReviews: "product-reviews",
5644
+ lens: "lens",
5645
+ scholar: "scholar",
5646
+ patents: "patents",
5647
+ autocomplete: "autocomplete",
5648
+ webpage: "webpage"
5649
+ };
5650
+
5651
+ // src/providers/serper.ts
5587
5652
  var DEFAULT_BASE_URL3 = "https://google.serper.dev";
5653
+ var DEFAULT_SCRAPE_URL = "https://scrape.serper.dev";
5654
+ var SERPER_SEARCH_MODES = Object.values(SERPER_SEARCH_MODE_VALUES);
5655
+ var SERPER_SEARCH_MODE_SET = new Set(SERPER_SEARCH_MODES);
5656
+ var RESERVED_REQUEST_OPTION_KEYS = [
5657
+ "q",
5658
+ "num",
5659
+ "mode",
5660
+ "url",
5661
+ "productId",
5662
+ "nextPageToken",
5663
+ "ll",
5664
+ "placeId",
5665
+ "cid",
5666
+ "fid",
5667
+ "sortBy",
5668
+ "topicId",
5669
+ "includeMarkdown",
5670
+ "includeImages",
5671
+ "includeLinks",
5672
+ "includeVideos",
5673
+ "location",
5674
+ "gl",
5675
+ "hl",
5676
+ "tbs",
5677
+ "page",
5678
+ "autocorrect"
5679
+ ];
5680
+ var PRIMARY_RESULT_FIELDS_BY_MODE = {
5681
+ search: ["organic"],
5682
+ images: ["images"],
5683
+ videos: ["videos"],
5684
+ places: ["places"],
5685
+ maps: ["maps", "places"],
5686
+ reviews: ["reviews"],
5687
+ news: ["news"],
5688
+ shopping: ["shopping"],
5689
+ "product-reviews": ["reviews", "productReviews"],
5690
+ lens: ["visualMatches", "organic", "images"],
5691
+ scholar: ["organic"],
5692
+ patents: ["organic"],
5693
+ autocomplete: ["suggestions"],
5694
+ webpage: []
5695
+ };
5696
+ var CONTEXT_ARRAY_FIELDS = [
5697
+ "peopleAlsoAsk",
5698
+ "relatedSearches",
5699
+ "topStories",
5700
+ "news",
5701
+ "images",
5702
+ "videos",
5703
+ "places",
5704
+ "maps",
5705
+ "shopping",
5706
+ "reviews",
5707
+ "productReviews",
5708
+ "visualMatches",
5709
+ "suggestions"
5710
+ ];
5711
+ var serperSearchPromptGuidelines = [
5712
+ "Use Serper news mode for recent journalism, current events, announcements, or time-sensitive reporting.",
5713
+ "Use Serper images or videos mode when the user asks for visual references, screenshots, diagrams, clips, tutorials, or media results.",
5714
+ "Use Serper places or maps mode for local businesses, venues, addresses, ratings, phone numbers, opening details, or nearby/in-location searches.",
5715
+ "Use Serper reviews mode when the task needs Google business reviews. Prefer cid, fid, or placeId from a maps or places result when available; otherwise use the search query as the place identifier.",
5716
+ "Use Serper shopping mode for product listings, prices, merchants, offers, or purchase comparisons, and use product-reviews mode when the task needs reviews for a known product ID.",
5717
+ "Use Serper scholar mode for academic papers and patents mode for patent searches.",
5718
+ "Use Serper autocomplete mode when the task is to discover search suggestions or query completions rather than source pages.",
5719
+ "Use Serper lens mode for reverse image search with an image URL, and use webpage mode to scrape a specific URL. Webpage mode includes Markdown by default."
5720
+ ];
5588
5721
  var serperSearchOptionsSchema = Type13.Object(
5589
5722
  {
5723
+ mode: Type13.Optional(
5724
+ Type13.Enum(SERPER_SEARCH_MODE_VALUES, {
5725
+ description: "Serper search type. Use 'search' for web results, 'news' for recent journalism/current events, 'images' for visual references, 'videos' for clips/tutorials, 'places' or 'maps' for local businesses/venues, 'reviews' for Google business reviews by place ID/CID/FID or query, 'shopping' for products, 'product-reviews' for product reviews, 'lens' for reverse image search, 'scholar' for scholarly articles, 'patents' for patents, 'autocomplete' for suggestions, and 'webpage' to scrape a URL."
5726
+ })
5727
+ ),
5590
5728
  gl: Type13.Optional(
5591
5729
  Type13.String({
5592
5730
  description: "Country code hint for Google results (for example 'us')."
@@ -5608,10 +5746,63 @@ var serperSearchOptionsSchema = Type13.Object(
5608
5746
  description: "1-based results page to request from Serper."
5609
5747
  })
5610
5748
  ),
5749
+ tbs: Type13.Optional(
5750
+ Type13.String({
5751
+ description: "Google time/date or vertical-specific filter string passed through to Serper, for example 'qdr:d' for past day."
5752
+ })
5753
+ ),
5611
5754
  autocorrect: Type13.Optional(
5612
5755
  Type13.Boolean({
5613
5756
  description: "Enable or disable Serper query autocorrection."
5614
5757
  })
5758
+ ),
5759
+ url: Type13.Optional(
5760
+ Type13.String({
5761
+ description: "URL for modes that need one: image URL for 'lens', or page URL for 'webpage'. Defaults to the query string when omitted."
5762
+ })
5763
+ ),
5764
+ ll: Type13.Optional(
5765
+ Type13.String({
5766
+ description: "Google Maps latitude/longitude/zoom hint, for example '@40.6973709,-74.1444871,11z'."
5767
+ })
5768
+ ),
5769
+ placeId: Type13.Optional(
5770
+ Type13.String({ description: "Google place ID for maps or reviews." })
5771
+ ),
5772
+ cid: Type13.Optional(
5773
+ Type13.String({ description: "Google CID for maps or reviews." })
5774
+ ),
5775
+ fid: Type13.Optional(Type13.String({ description: "Google FID for reviews." })),
5776
+ sortBy: Type13.Optional(
5777
+ Type13.String({ description: "Review sort order for reviews mode." })
5778
+ ),
5779
+ topicId: Type13.Optional(
5780
+ Type13.String({ description: "Review topic ID for reviews mode." })
5781
+ ),
5782
+ productId: Type13.Optional(
5783
+ Type13.String({
5784
+ description: "Google product ID for product-reviews mode. Defaults to the query string when omitted."
5785
+ })
5786
+ ),
5787
+ nextPageToken: Type13.Optional(
5788
+ Type13.String({
5789
+ description: "Pagination token for reviews or product-reviews modes."
5790
+ })
5791
+ ),
5792
+ includeMarkdown: Type13.Optional(
5793
+ Type13.Boolean({
5794
+ default: true,
5795
+ description: "Include Markdown content in webpage mode. Defaults to true."
5796
+ })
5797
+ ),
5798
+ includeImages: Type13.Optional(
5799
+ Type13.Boolean({ description: "Include image metadata in webpage mode." })
5800
+ ),
5801
+ includeLinks: Type13.Optional(
5802
+ Type13.Boolean({ description: "Include link metadata in webpage mode." })
5803
+ ),
5804
+ includeVideos: Type13.Optional(
5805
+ Type13.Boolean({ description: "Include video metadata in webpage mode." })
5615
5806
  )
5616
5807
  },
5617
5808
  { description: "Serper search options." }
@@ -5631,58 +5822,218 @@ var serperImplementation = {
5631
5822
  createTemplate() {
5632
5823
  return {
5633
5824
  credentials: { api: "SERPER_API_KEY" },
5634
- options: {}
5825
+ options: {
5826
+ search: {
5827
+ includeMarkdown: true
5828
+ }
5829
+ }
5635
5830
  };
5636
5831
  },
5637
- getCapabilityStatus(config) {
5638
- return getApiKeyStatus(config?.credentials?.api);
5832
+ getCapabilityStatus(config, _cwd, _tool, options) {
5833
+ return getApiKeyStatus(config?.credentials?.api, options);
5639
5834
  },
5640
5835
  async search(query2, maxResults, config, context, options) {
5641
5836
  const apiKey = resolveConfigValue(config.credentials?.api);
5642
5837
  if (!apiKey) {
5643
5838
  throw new Error("is missing an API key");
5644
5839
  }
5645
- const defaults = asJsonObject(config.options?.search) ?? {};
5840
+ const defaults = asJsonObject(config.options?.search);
5646
5841
  const callOptions = asJsonObject(options);
5647
- const {
5648
- q: _ignoredQuery,
5649
- num: _ignoredNum,
5650
- ...providerOptions
5651
- } = {
5842
+ const requestOptions = readRequestOptions({
5652
5843
  ...defaults,
5653
- ...callOptions ?? {}
5654
- };
5655
- const response = await fetch(joinUrl(resolveConfigValue(config.baseUrl)), {
5656
- method: "POST",
5657
- headers: {
5658
- "content-type": "application/json",
5659
- "x-api-key": apiKey
5660
- },
5661
- body: JSON.stringify({
5662
- q: query2,
5663
- num: clampMaxResults2(maxResults),
5664
- ...providerOptions
5665
- }),
5666
- signal: context.signal
5844
+ ...callOptions
5667
5845
  });
5846
+ const requestBody = buildRequestBody(
5847
+ query2,
5848
+ clampMaxResults2(maxResults),
5849
+ requestOptions
5850
+ );
5851
+ const response = await fetch(
5852
+ joinUrl(resolveConfigValue(config.baseUrl), requestOptions.mode),
5853
+ {
5854
+ method: "POST",
5855
+ headers: {
5856
+ "content-type": "application/json",
5857
+ "x-api-key": apiKey
5858
+ },
5859
+ body: JSON.stringify(requestBody),
5860
+ signal: context.signal
5861
+ }
5862
+ );
5668
5863
  if (!response.ok) {
5669
5864
  throw new Error(await buildHttpError2(response));
5670
5865
  }
5671
5866
  const payload = await response.json();
5672
- const responseRecord = asRecord3(payload) ?? {};
5673
- const organic = asArray(responseRecord.organic) ?? [];
5674
- const searchContext = buildSearchContext(responseRecord);
5867
+ const responseRecord = enrichResponseRecord(
5868
+ asRecord3(payload) ?? {},
5869
+ requestOptions.mode,
5870
+ requestBody
5871
+ );
5872
+ const results = readPrimaryResults(responseRecord, requestOptions.mode);
5873
+ const searchContext = buildSearchContext(
5874
+ responseRecord,
5875
+ requestOptions.mode
5876
+ );
5675
5877
  return {
5676
5878
  provider: serperImplementation.id,
5677
- results: organic.map((entry) => toSearchResult3(entry, searchContext)).filter(
5879
+ results: results.map(
5880
+ (entry) => toSearchResult3(entry, searchContext, requestOptions.mode)
5881
+ ).filter(
5678
5882
  (result) => result !== null
5679
5883
  ).slice(0, clampMaxResults2(maxResults))
5680
5884
  };
5681
5885
  }
5682
5886
  };
5683
- function joinUrl(baseUrl) {
5887
+ function joinUrl(baseUrl, mode = "search") {
5684
5888
  const base2 = (baseUrl ?? DEFAULT_BASE_URL3).replace(/\/+$/, "");
5685
- return `${base2}/search`;
5889
+ if (mode === "webpage" && base2 === DEFAULT_BASE_URL3) {
5890
+ return DEFAULT_SCRAPE_URL;
5891
+ }
5892
+ return `${base2}/${mode}`;
5893
+ }
5894
+ function readRequestOptions(options) {
5895
+ const result = {
5896
+ mode: readSearchMode(options.mode),
5897
+ extra: extractExtraMetadata(options, RESERVED_REQUEST_OPTION_KEYS)
5898
+ };
5899
+ copyStringOption(result, "gl", options.gl);
5900
+ copyStringOption(result, "hl", options.hl);
5901
+ copyStringOption(result, "location", options.location);
5902
+ copyStringOption(result, "tbs", options.tbs);
5903
+ copyStringOption(result, "url", options.url);
5904
+ copyStringOption(result, "ll", options.ll);
5905
+ copyStringOption(result, "placeId", options.placeId);
5906
+ copyStringOption(result, "cid", options.cid);
5907
+ copyStringOption(result, "fid", options.fid);
5908
+ copyStringOption(result, "sortBy", options.sortBy);
5909
+ copyStringOption(result, "topicId", options.topicId);
5910
+ copyStringOption(result, "productId", options.productId);
5911
+ copyStringOption(result, "nextPageToken", options.nextPageToken);
5912
+ copyBooleanOption(result, "autocorrect", options.autocorrect);
5913
+ copyBooleanOption(result, "includeMarkdown", options.includeMarkdown);
5914
+ copyBooleanOption(result, "includeImages", options.includeImages);
5915
+ copyBooleanOption(result, "includeLinks", options.includeLinks);
5916
+ copyBooleanOption(result, "includeVideos", options.includeVideos);
5917
+ const page = readInteger2(options.page);
5918
+ if (page !== void 0) {
5919
+ result.page = Math.max(1, page);
5920
+ }
5921
+ return result;
5922
+ }
5923
+ function buildRequestBody(query2, maxResults, options) {
5924
+ const common = omitUndefined({
5925
+ location: options.location,
5926
+ gl: options.gl,
5927
+ hl: options.hl
5928
+ });
5929
+ const withExtra = (body) => ({
5930
+ ...body,
5931
+ ...options.extra
5932
+ });
5933
+ switch (options.mode) {
5934
+ case "webpage":
5935
+ return withExtra(
5936
+ omitUndefined({
5937
+ url: options.url ?? query2,
5938
+ includeMarkdown: options.includeMarkdown ?? true,
5939
+ includeImages: options.includeImages,
5940
+ includeLinks: options.includeLinks,
5941
+ includeVideos: options.includeVideos
5942
+ })
5943
+ );
5944
+ case "product-reviews":
5945
+ return withExtra(
5946
+ omitUndefined({
5947
+ productId: options.productId ?? query2,
5948
+ nextPageToken: options.nextPageToken,
5949
+ ...common,
5950
+ num: maxResults
5951
+ })
5952
+ );
5953
+ case "autocomplete":
5954
+ return withExtra({ q: query2, ...common });
5955
+ case "maps":
5956
+ return withExtra(
5957
+ omitUndefined({
5958
+ q: query2,
5959
+ num: maxResults,
5960
+ ...common,
5961
+ ll: options.ll,
5962
+ placeId: options.placeId,
5963
+ cid: options.cid,
5964
+ page: options.page
5965
+ })
5966
+ );
5967
+ case "reviews": {
5968
+ const hasExplicitPlaceIdentifier = firstNonEmptyString(options.cid, options.fid, options.placeId) !== void 0;
5969
+ return withExtra(
5970
+ omitUndefined({
5971
+ q: hasExplicitPlaceIdentifier ? void 0 : query2,
5972
+ cid: options.cid,
5973
+ fid: options.fid,
5974
+ placeId: options.placeId,
5975
+ gl: options.gl,
5976
+ hl: options.hl,
5977
+ sortBy: options.sortBy,
5978
+ topicId: options.topicId,
5979
+ nextPageToken: options.nextPageToken
5980
+ })
5981
+ );
5982
+ }
5983
+ case "lens":
5984
+ return withExtra(
5985
+ omitUndefined({
5986
+ url: options.url ?? query2,
5987
+ ...common,
5988
+ tbs: options.tbs
5989
+ })
5990
+ );
5991
+ case "scholar":
5992
+ return withExtra(
5993
+ omitUndefined({
5994
+ q: query2,
5995
+ ...common,
5996
+ autocorrect: options.autocorrect,
5997
+ tbs: options.tbs,
5998
+ page: options.page
5999
+ })
6000
+ );
6001
+ default:
6002
+ return withExtra(
6003
+ omitUndefined({
6004
+ q: query2,
6005
+ num: maxResults,
6006
+ ...common,
6007
+ autocorrect: options.autocorrect,
6008
+ tbs: options.tbs,
6009
+ page: options.page
6010
+ })
6011
+ );
6012
+ }
6013
+ }
6014
+ function enrichResponseRecord(response, mode, requestBody) {
6015
+ if (mode !== "webpage") {
6016
+ return response;
6017
+ }
6018
+ return omitUndefined({
6019
+ ...response,
6020
+ url: readString4(response.url) ?? readString4(requestBody.url)
6021
+ });
6022
+ }
6023
+ function readSearchMode(value) {
6024
+ return typeof value === "string" && SERPER_SEARCH_MODE_SET.has(value) ? value : "search";
6025
+ }
6026
+ function readPrimaryResults(response, mode) {
6027
+ if (mode === "webpage") {
6028
+ return [response];
6029
+ }
6030
+ for (const field of PRIMARY_RESULT_FIELDS_BY_MODE[mode]) {
6031
+ const values = asArray(response[field]);
6032
+ if (values) {
6033
+ return values;
6034
+ }
6035
+ }
6036
+ return [];
5686
6037
  }
5687
6038
  function clampMaxResults2(value) {
5688
6039
  return Math.max(1, Math.min(20, Math.trunc(value || 0)));
@@ -5709,18 +6060,52 @@ async function readErrorDetail2(response) {
5709
6060
  return text;
5710
6061
  }
5711
6062
  }
5712
- function toSearchResult3(entry, searchContext) {
6063
+ function toSearchResult3(entry, searchContext, mode) {
6064
+ if (typeof entry === "string") {
6065
+ return {
6066
+ title: entry,
6067
+ url: mode === "autocomplete" ? toGoogleSearchUrl(entry) : "",
6068
+ snippet: entry,
6069
+ metadata: {
6070
+ source: mode,
6071
+ ...searchContext ? { searchContext } : {}
6072
+ }
6073
+ };
6074
+ }
5713
6075
  const record = asRecord3(entry);
5714
6076
  if (!record) {
5715
6077
  return null;
5716
6078
  }
5717
- const url2 = readString4(record.link) ?? "";
5718
- const title = readString4(record.title) || url2 || "Untitled";
6079
+ const responseMetadata = asRecord3(record.metadata);
6080
+ const user = asRecord3(record.user);
6081
+ const resultUrl = firstString(record.link, record.website, record.url, record.imageUrl) ?? "";
6082
+ const title = firstNonEmptyString(
6083
+ record.title,
6084
+ responseMetadata?.title,
6085
+ record.name,
6086
+ record.query,
6087
+ record.value,
6088
+ user?.name,
6089
+ formatReviewTitle(record, user),
6090
+ resultUrl
6091
+ ) ?? "Untitled";
6092
+ const url2 = resultUrl || (mode === "autocomplete" ? toGoogleSearchUrl(title) : "");
5719
6093
  const snippet = trimSnippet(
5720
- readString4(record.snippet) ?? readString4(record.richSnippet) ?? readString4(record.date) ?? ""
6094
+ firstNonEmptyString(
6095
+ record.snippet,
6096
+ record.richSnippet,
6097
+ record.markdown,
6098
+ record.text,
6099
+ record.address,
6100
+ record.price,
6101
+ record.date,
6102
+ record.name,
6103
+ record.value,
6104
+ record.url
6105
+ ) ?? ""
5721
6106
  );
5722
6107
  const metadata = omitUndefined({
5723
- source: "organic",
6108
+ source: readString4(record.source) ?? (mode === "search" ? "organic" : mode),
5724
6109
  position: readNumber(record.position),
5725
6110
  date: readString4(record.date),
5726
6111
  attributes: asRecord3(record.attributes),
@@ -5728,7 +6113,16 @@ function toSearchResult3(entry, searchContext) {
5728
6113
  rating: readNumber(record.rating),
5729
6114
  ratingCount: readNumber(record.ratingCount),
5730
6115
  cid: readString4(record.cid),
5731
- ...extractExtraMetadata(record, ["title", "link", "snippet"]),
6116
+ ...extractExtraMetadata(record, [
6117
+ "title",
6118
+ "name",
6119
+ "query",
6120
+ "value",
6121
+ "link",
6122
+ "website",
6123
+ "url",
6124
+ "snippet"
6125
+ ]),
5732
6126
  ...searchContext ? { searchContext } : {}
5733
6127
  });
5734
6128
  return {
@@ -5738,23 +6132,72 @@ function toSearchResult3(entry, searchContext) {
5738
6132
  ...Object.keys(metadata).length > 0 ? { metadata } : {}
5739
6133
  };
5740
6134
  }
5741
- function buildSearchContext(response) {
6135
+ function buildSearchContext(response, mode) {
5742
6136
  const context = omitUndefined({
5743
6137
  searchParameters: asRecord3(response.searchParameters),
5744
6138
  searchInformation: asRecord3(response.searchInformation),
5745
6139
  credits: readNumber(response.credits),
5746
6140
  answerBox: asRecord3(response.answerBox),
5747
- knowledgeGraph: asRecord3(response.knowledgeGraph),
5748
- peopleAlsoAsk: asArray(response.peopleAlsoAsk),
5749
- relatedSearches: asArray(response.relatedSearches),
5750
- topStories: asArray(response.topStories),
5751
- news: asArray(response.news),
5752
- images: asArray(response.images),
5753
- videos: asArray(response.videos),
5754
- places: asArray(response.places)
6141
+ knowledgeGraph: asRecord3(response.knowledgeGraph)
5755
6142
  });
6143
+ const primaryResultFields = new Set(
6144
+ PRIMARY_RESULT_FIELDS_BY_MODE[mode]
6145
+ );
6146
+ for (const field of CONTEXT_ARRAY_FIELDS) {
6147
+ if (primaryResultFields.has(field)) {
6148
+ continue;
6149
+ }
6150
+ const value = asArray(response[field]);
6151
+ if (value) {
6152
+ context[field] = value;
6153
+ }
6154
+ }
5756
6155
  return Object.keys(context).length > 0 ? context : void 0;
5757
6156
  }
6157
+ function copyStringOption(target, key, value) {
6158
+ const text = readString4(value);
6159
+ if (text !== void 0) {
6160
+ target[key] = text;
6161
+ }
6162
+ }
6163
+ function copyBooleanOption(target, key, value) {
6164
+ const flag = readBoolean(value);
6165
+ if (flag !== void 0) {
6166
+ target[key] = flag;
6167
+ }
6168
+ }
6169
+ function firstString(...values) {
6170
+ return values.find((value) => typeof value === "string");
6171
+ }
6172
+ function toGoogleSearchUrl(query2) {
6173
+ return `https://www.google.com/search?q=${encodeURIComponent(query2)}`;
6174
+ }
6175
+ function formatReviewTitle(record, user) {
6176
+ const userName = readString4(user?.name);
6177
+ const rating = readNumber(record.rating);
6178
+ const date = readString4(record.date) ?? readString4(record.isoDate);
6179
+ if (userName && rating !== void 0) {
6180
+ return `${userName} (${rating}-star review)`;
6181
+ }
6182
+ if (userName) {
6183
+ return `${userName}'s review`;
6184
+ }
6185
+ if (rating !== void 0 && date) {
6186
+ return `${rating}-star review from ${date}`;
6187
+ }
6188
+ if (rating !== void 0) {
6189
+ return `${rating}-star review`;
6190
+ }
6191
+ if (date) {
6192
+ return `Review from ${date}`;
6193
+ }
6194
+ return void 0;
6195
+ }
6196
+ function firstNonEmptyString(...values) {
6197
+ return values.find(
6198
+ (value) => typeof value === "string" && value.length > 0
6199
+ );
6200
+ }
5758
6201
  function extractExtraMetadata(record, ignoredKeys) {
5759
6202
  return Object.fromEntries(
5760
6203
  Object.entries(record).filter(
@@ -5779,6 +6222,12 @@ function readString4(value) {
5779
6222
  function readNumber(value) {
5780
6223
  return typeof value === "number" && Number.isFinite(value) ? value : void 0;
5781
6224
  }
6225
+ function readInteger2(value) {
6226
+ return typeof value === "number" && Number.isInteger(value) ? value : void 0;
6227
+ }
6228
+ function readBoolean(value) {
6229
+ return typeof value === "boolean" ? value : void 0;
6230
+ }
5782
6231
  var serperProvider = defineProvider({
5783
6232
  id: "serper",
5784
6233
  label: serperImplementation.label,
@@ -5788,14 +6237,16 @@ var serperProvider = defineProvider({
5788
6237
  fields: ["credentials", "baseUrl", "options", "settings"],
5789
6238
  optionCapabilities: ["search"]
5790
6239
  },
5791
- getCapabilityStatus: (config, cwd, tool) => serperImplementation.getCapabilityStatus(
6240
+ getCapabilityStatus: (config, cwd, tool, options) => serperImplementation.getCapabilityStatus(
5792
6241
  config,
5793
6242
  cwd,
5794
- tool
6243
+ tool,
6244
+ options
5795
6245
  ),
5796
6246
  capabilities: {
5797
6247
  search: defineCapability({
5798
6248
  options: serperImplementation.getToolOptionsSchema?.("search"),
6249
+ promptGuidelines: serperSearchPromptGuidelines,
5799
6250
  async execute(input, ctx) {
5800
6251
  const { query: query2, maxResults, options } = input;
5801
6252
  return await serperImplementation.search(
@@ -5920,8 +6371,8 @@ var tavilyImplementation = {
5920
6371
  }
5921
6372
  };
5922
6373
  },
5923
- getCapabilityStatus(config) {
5924
- return getApiKeyStatus(config?.credentials?.api);
6374
+ getCapabilityStatus(config, _cwd, _tool, options) {
6375
+ return getApiKeyStatus(config?.credentials?.api, options);
5925
6376
  },
5926
6377
  async search(query2, maxResults, config, _context, options) {
5927
6378
  const client = createClient8(config);
@@ -6019,10 +6470,11 @@ var tavilyProvider = defineProvider({
6019
6470
  createTemplate: () => tavilyImplementation.createTemplate(),
6020
6471
  fields: ["credentials", "baseUrl", "options", "settings"]
6021
6472
  },
6022
- getCapabilityStatus: (config, cwd, tool) => tavilyImplementation.getCapabilityStatus(
6473
+ getCapabilityStatus: (config, cwd, tool, options) => tavilyImplementation.getCapabilityStatus(
6023
6474
  config,
6024
6475
  cwd,
6025
- tool
6476
+ tool,
6477
+ options
6026
6478
  ),
6027
6479
  capabilities: {
6028
6480
  search: defineCapability({
@@ -6126,8 +6578,8 @@ var valyuImplementation = {
6126
6578
  }
6127
6579
  };
6128
6580
  },
6129
- getCapabilityStatus(config) {
6130
- return getApiKeyStatus(config?.credentials?.api);
6581
+ getCapabilityStatus(config, _cwd, _tool, options) {
6582
+ return getApiKeyStatus(config?.credentials?.api, options);
6131
6583
  },
6132
6584
  async search(query2, maxResults, config, _context, searchOptions) {
6133
6585
  const client = createClient9(config);
@@ -6302,10 +6754,11 @@ var valyuProvider = defineProvider({
6302
6754
  fields: ["credentials", "baseUrl", "options", "settings"],
6303
6755
  optionCapabilities: ["search", "answer", "research"]
6304
6756
  },
6305
- getCapabilityStatus: (config, cwd, tool) => valyuImplementation.getCapabilityStatus(
6757
+ getCapabilityStatus: (config, cwd, tool, options) => valyuImplementation.getCapabilityStatus(
6306
6758
  config,
6307
6759
  cwd,
6308
- tool
6760
+ tool,
6761
+ options
6309
6762
  ),
6310
6763
  capabilities: {
6311
6764
  search: defineCapability({
@@ -6380,9 +6833,6 @@ var PROVIDERS_BY_ID = PROVIDERS;
6380
6833
  var PROVIDER_LIST = Object.values(PROVIDERS);
6381
6834
  var PROVIDER_IDS = Object.keys(PROVIDERS);
6382
6835
 
6383
- // src/types.ts
6384
- var TOOLS = ["search", "contents", "answer", "research"];
6385
-
6386
6836
  // src/provider-tools.ts
6387
6837
  var TOOL_INFO = {
6388
6838
  search: {
@@ -7138,17 +7588,21 @@ function isPlainObject4(value) {
7138
7588
  function getMappedProviderIdForTool(config, tool) {
7139
7589
  return getMappedProviderForTool(config, tool);
7140
7590
  }
7141
- function getProviderCapabilityStatus(config, cwd, providerId, tool) {
7591
+ function getProviderCapabilityStatus(config, cwd, providerId, tool, options) {
7142
7592
  const provider = PROVIDERS[providerId];
7143
7593
  return provider.getCapabilityStatus(
7144
7594
  getEffectiveProviderConfig(config, providerId),
7145
7595
  cwd,
7146
- tool
7596
+ tool,
7597
+ options
7147
7598
  );
7148
7599
  }
7149
7600
  function isProviderCapabilityReady(status) {
7150
7601
  return status.state === "ready";
7151
7602
  }
7603
+ function isProviderCapabilityExposable(status) {
7604
+ return status.state === "ready" || status.state === "deferred_secret";
7605
+ }
7152
7606
  function getProviderSetupState(config, providerId) {
7153
7607
  if (providerId === "claude" || providerId === "codex") {
7154
7608
  return "builtin";
@@ -7163,12 +7617,17 @@ function getProviderSetupState(config, providerId) {
7163
7617
  if (providerId === "cloudflare") {
7164
7618
  return providerConfig.credentials !== void 0 || providerConfig.accountId !== void 0 ? "configured" : "none";
7165
7619
  }
7620
+ if (providerId === "firecrawl") {
7621
+ return providerConfig.credentials !== void 0 || providerConfig.baseUrl !== void 0 ? "configured" : "none";
7622
+ }
7166
7623
  return providerConfig.credentials !== void 0 ? "configured" : "none";
7167
7624
  }
7168
7625
  function formatProviderCapabilityStatus(status, providerId, tool) {
7169
7626
  switch (status.state) {
7170
7627
  case "ready":
7171
7628
  return "Ready";
7629
+ case "deferred_secret":
7630
+ return "Secret resolved on first use";
7172
7631
  case "missing_api_key":
7173
7632
  return "Missing API key";
7174
7633
  case "missing_executable":
@@ -8812,6 +9271,158 @@ function cleanupCapabilityOptions(config, keys) {
8812
9271
  cleanupEmpty(config, "options");
8813
9272
  }
8814
9273
 
9274
+ // src/tool-display.ts
9275
+ import { formatSize } from "@earendil-works/pi-coding-agent";
9276
+ var ANSWER_EXCERPT_MAX_LENGTH = 100;
9277
+ function buildSearchToolDisplay(details) {
9278
+ return buildToolDisplay(details.provider, buildSearchSummaryParts(details));
9279
+ }
9280
+ function buildProgressDisplay(providerId, action) {
9281
+ return {
9282
+ provider: getProviderDisplay(providerId),
9283
+ progress: { action }
9284
+ };
9285
+ }
9286
+ function buildProviderToolDisplay({
9287
+ capability,
9288
+ providerId,
9289
+ details,
9290
+ text,
9291
+ outputBytes,
9292
+ outputTruncated,
9293
+ failedItemCount
9294
+ }) {
9295
+ const summary = capability === "contents" && details.tool === "web_contents" ? buildContentsDisplaySummary(details, text, {
9296
+ outputBytes,
9297
+ outputTruncated,
9298
+ failedItemCount
9299
+ }) : capability === "research" && text ? { success: text } : buildCollapsedProviderToolSummaryParts(details, text);
9300
+ return buildToolDisplay(providerId, summary);
9301
+ }
9302
+ function buildCollapsedProviderToolSummary(details, text) {
9303
+ const summary = buildCollapsedProviderToolSummaryParts(details, text);
9304
+ return summary.failure ? `${summary.success}, ${summary.failure}` : summary.success;
9305
+ }
9306
+ function buildCollapsedProviderToolSummaryParts(details, text) {
9307
+ if (details?.tool === "web_answer") {
9308
+ return buildAnswerCollapsedSummary(details, text);
9309
+ }
9310
+ if (details?.tool === "web_contents") {
9311
+ return buildContentsSummary(details, text);
9312
+ }
9313
+ const baseSummary = getCompactProviderToolSummary(details) ?? getFirstLine(text) ?? `${details?.tool ?? "tool"} output available`;
9314
+ return { success: baseSummary };
9315
+ }
9316
+ function buildSearchSummaryParts({
9317
+ queryCount,
9318
+ resultCount,
9319
+ failedQueryCount
9320
+ }) {
9321
+ const success = typeof resultCount === "number" ? `${resultCount} result${resultCount === 1 ? "" : "s"}` : "Search output available";
9322
+ if (failedQueryCount && failedQueryCount > 0 && queryCount) {
9323
+ return {
9324
+ success,
9325
+ failure: `${failedQueryCount} of ${queryCount} ${queryCount === 1 ? "query" : "queries"} failed`
9326
+ };
9327
+ }
9328
+ return { success };
9329
+ }
9330
+ function buildToolDisplay(providerId, outcome) {
9331
+ return {
9332
+ provider: getProviderDisplay(providerId),
9333
+ outcome
9334
+ };
9335
+ }
9336
+ function getProviderDisplay(providerId) {
9337
+ const provider = PROVIDERS_BY_ID[providerId];
9338
+ return { id: providerId, label: provider?.label ?? providerId };
9339
+ }
9340
+ function buildContentsDisplaySummary(details, text, metadata) {
9341
+ const totalCount = details.itemCount ?? inferContentsPageCount(text);
9342
+ const failedCount = metadata.failedItemCount ?? inferContentsFailureCount(text);
9343
+ const successCount = totalCount === void 0 ? void 0 : Math.max(0, totalCount - (failedCount ?? 0));
9344
+ const sizeSummary = typeof metadata.outputBytes === "number" ? `${formatSize(metadata.outputBytes)}${metadata.outputTruncated ? " (truncated)" : ""}` : void 0;
9345
+ const success = successCount === void 0 ? sizeSummary ?? "Contents output available" : successCount === 1 && sizeSummary ? sizeSummary : `${successCount} page${successCount === 1 ? "" : "s"}${sizeSummary ? `, ${sizeSummary}` : ""}`;
9346
+ if (failedCount && failedCount > 0 && totalCount) {
9347
+ return {
9348
+ success,
9349
+ failure: `${failedCount} of ${totalCount} ${totalCount === 1 ? "page" : "pages"} failed`
9350
+ };
9351
+ }
9352
+ return { success };
9353
+ }
9354
+ function buildAnswerCollapsedSummary(details, text) {
9355
+ if (typeof details.queryCount === "number" && (details.queryCount > 1 || (details.failedQueryCount ?? 0) > 0)) {
9356
+ return buildAnswerSummary(details);
9357
+ }
9358
+ return { success: buildAnswerExcerpt(text) ?? "Answer output available" };
9359
+ }
9360
+ function buildAnswerSummary(details) {
9361
+ const queryCount = details.queryCount ?? 0;
9362
+ const failedQueryCount = details.failedQueryCount ?? 0;
9363
+ const answerCount = Math.max(0, queryCount - failedQueryCount);
9364
+ const success = `${answerCount} answer${answerCount === 1 ? "" : "s"}`;
9365
+ if (failedQueryCount > 0) {
9366
+ return {
9367
+ success,
9368
+ failure: `${failedQueryCount} of ${queryCount} ${queryCount === 1 ? "question" : "questions"} failed`
9369
+ };
9370
+ }
9371
+ return { success };
9372
+ }
9373
+ function buildAnswerExcerpt(text) {
9374
+ const excerpt = getFirstLine(text);
9375
+ if (!excerpt) {
9376
+ return void 0;
9377
+ }
9378
+ if (excerpt.length <= ANSWER_EXCERPT_MAX_LENGTH) {
9379
+ return excerpt;
9380
+ }
9381
+ return `${excerpt.slice(0, ANSWER_EXCERPT_MAX_LENGTH - 1).trimEnd()}\u2026`;
9382
+ }
9383
+ function buildContentsSummary(details, text) {
9384
+ const totalCount = details.itemCount ?? inferContentsPageCount(text);
9385
+ const failedCount = inferContentsFailureCount(text);
9386
+ const successCount = totalCount === void 0 ? void 0 : Math.max(0, totalCount - (failedCount ?? 0));
9387
+ const success = successCount === void 0 ? "Contents output available" : `${successCount} page${successCount === 1 ? "" : "s"}`;
9388
+ if (failedCount && failedCount > 0 && totalCount) {
9389
+ return {
9390
+ success,
9391
+ failure: `${failedCount} of ${totalCount} ${totalCount === 1 ? "page" : "pages"} failed`
9392
+ };
9393
+ }
9394
+ return { success };
9395
+ }
9396
+ function getCompactProviderToolSummary(details) {
9397
+ if (!details) {
9398
+ return void 0;
9399
+ }
9400
+ if (details.tool === "web_contents" && typeof details.itemCount === "number") {
9401
+ return `${details.itemCount} page${details.itemCount === 1 ? "" : "s"}`;
9402
+ }
9403
+ if (details.tool === "web_research") {
9404
+ return "Research";
9405
+ }
9406
+ return void 0;
9407
+ }
9408
+ function inferContentsPageCount(text) {
9409
+ if (!text) {
9410
+ return void 0;
9411
+ }
9412
+ const pageMatches = text.match(/^##\s+/gm);
9413
+ return pageMatches?.length;
9414
+ }
9415
+ function inferContentsFailureCount(text) {
9416
+ if (!text) {
9417
+ return void 0;
9418
+ }
9419
+ const failureMatches = text.match(/^##\s+(?:\d+\.\s+)?Error:/gm);
9420
+ return failureMatches?.length;
9421
+ }
9422
+ function getFirstLine(text) {
9423
+ return text?.split("\n").map((line) => line.trim()).find((line) => line.length > 0);
9424
+ }
9425
+
8815
9426
  // src/index.ts
8816
9427
  var DEFAULT_MAX_RESULTS = 5;
8817
9428
  var MAX_ALLOWED_RESULTS = 20;
@@ -8828,6 +9439,10 @@ var CAPABILITY_TOOL_NAMES = {
8828
9439
  research: "web_research"
8829
9440
  };
8830
9441
  var MANAGED_TOOL_NAMES = Object.values(CAPABILITY_TOOL_NAMES);
9442
+ var DEFAULT_SUMMARY_SYMBOLS = {
9443
+ success: "\u2714",
9444
+ failure: "\u2718"
9445
+ };
8831
9446
  function webProvidersExtension(pi) {
8832
9447
  const activeWebResearchRequests = /* @__PURE__ */ new Map();
8833
9448
  let latestWidgetContext;
@@ -8873,7 +9488,7 @@ function webProvidersExtension(pi) {
8873
9488
  if ("registerMessageRenderer" in pi) {
8874
9489
  pi.registerMessageRenderer(
8875
9490
  WEB_RESEARCH_RESULT_MESSAGE_TYPE,
8876
- renderWebResearchResultMessage
9491
+ (message, state, theme) => renderWebResearchResultMessage(message, state, theme)
8877
9492
  );
8878
9493
  }
8879
9494
  pi.registerCommand("web-providers", {
@@ -8934,7 +9549,7 @@ function registerWebSearchTool(pi, providerIds) {
8934
9549
  pi.registerTool({
8935
9550
  name: "web_search",
8936
9551
  label: "Web Search",
8937
- description: `Find likely sources on the public web for up to ${MAX_SEARCH_QUERIES} queries in a single call and return titles, URLs, and snippets grouped by query. Output is truncated to ${DEFAULT_MAX_LINES} lines or ${formatSize(DEFAULT_MAX_BYTES)} when needed.`,
9552
+ description: `Find likely sources on the public web for up to ${MAX_SEARCH_QUERIES} queries in a single call and return titles, URLs, and snippets grouped by query. Output is truncated to ${DEFAULT_MAX_LINES} lines or ${formatSize2(DEFAULT_MAX_BYTES)} when needed.`,
8938
9553
  promptGuidelines: buildPromptGuidelines("search", selectedProviderId, [
8939
9554
  "Batch related searches when grouped comparison matters; use separate sibling web_search calls when independent results should surface as soon as they are ready."
8940
9555
  ]),
@@ -9179,12 +9794,20 @@ function getAvailableProviderIdsForCapability(config, cwd, capability) {
9179
9794
  if (!providerId) {
9180
9795
  return [];
9181
9796
  }
9182
- try {
9183
- resolveProviderForTool(config, cwd, capability);
9184
- return [providerId];
9185
- } catch {
9797
+ const provider = PROVIDERS_BY_ID[providerId];
9798
+ if (!supportsTool2(provider, capability)) {
9186
9799
  return [];
9187
9800
  }
9801
+ const status = getProviderCapabilityStatus(
9802
+ config,
9803
+ cwd,
9804
+ providerId,
9805
+ capability,
9806
+ {
9807
+ resolveSecrets: false
9808
+ }
9809
+ );
9810
+ return isProviderCapabilityExposable(status) ? [providerId] : [];
9188
9811
  }
9189
9812
  function getProviderStatusForTool(config, cwd, providerId, capability) {
9190
9813
  return getProviderCapabilityStatus(config, cwd, providerId, capability);
@@ -9324,6 +9947,7 @@ async function executeSearchToolInternal({
9324
9947
  );
9325
9948
  const batchProgress = searchQueries.length > 1 ? createBatchCompletionReporter(
9326
9949
  "Searching",
9950
+ provider.id,
9327
9951
  provider.label,
9328
9952
  searchQueries.length,
9329
9953
  progressReporter.report
@@ -9372,7 +9996,7 @@ async function executeSearchToolInternal({
9372
9996
  progressReporter.stop();
9373
9997
  }
9374
9998
  if (outcomes.every((outcome) => outcome.error !== void 0)) {
9375
- throw buildSearchBatchError(outcomes);
9999
+ throw buildSearchBatchError(outcomes, provider.label);
9376
10000
  }
9377
10001
  const prefetch = prefetchOptions !== void 0 && executionOverrides === void 0 ? await startContentsPrefetch({
9378
10002
  config,
@@ -9384,9 +10008,11 @@ async function executeSearchToolInternal({
9384
10008
  formatSearchResponses(outcomes, prefetch),
9385
10009
  "web-search"
9386
10010
  );
10011
+ const details = buildWebSearchDetails(provider.id, outcomes);
9387
10012
  return {
9388
10013
  content: [{ type: "text", text: rendered }],
9389
- details: buildWebSearchDetails(provider.id, outcomes)
10014
+ details,
10015
+ display: buildSearchToolDisplay2(details)
9390
10016
  };
9391
10017
  }
9392
10018
  async function executeRawProviderRequest({
@@ -9461,16 +10087,22 @@ async function executeRawProviderRequest({
9461
10087
  input
9462
10088
  });
9463
10089
  }
9464
- function buildSearchBatchError(outcomes) {
10090
+ function buildSearchBatchError(outcomes, providerLabel) {
9465
10091
  const failed = outcomes.filter((outcome) => outcome.error !== void 0);
9466
10092
  if (failed.length === 1) {
9467
- return new Error(failed[0]?.error ?? "web_search failed.");
10093
+ return new Error(
10094
+ formatProviderCapabilityFailure(
10095
+ providerLabel,
10096
+ "search",
10097
+ failed[0]?.error ?? ""
10098
+ )
10099
+ );
9468
10100
  }
9469
10101
  const summary = failed.map(
9470
10102
  (outcome, index) => `${index + 1}. ${formatQuotedPreview(outcome.query, 40)} \u2014 ${outcome.error}`
9471
10103
  ).join("; ");
9472
10104
  return new Error(
9473
- `All ${failed.length} web_search queries failed: ${summary}`
10105
+ `${providerLabel} search failed for ${failed.length} queries: ${summary}`
9474
10106
  );
9475
10107
  }
9476
10108
  async function executeSingleSearchQuery({
@@ -9546,6 +10178,7 @@ async function executeAnswerToolInternal({
9546
10178
  );
9547
10179
  const batchProgress = answerQueries.length > 1 ? createBatchCompletionReporter(
9548
10180
  "Answering",
10181
+ provider.id,
9549
10182
  provider.label,
9550
10183
  answerQueries.length,
9551
10184
  progressReporter.report
@@ -9588,7 +10221,7 @@ async function executeAnswerToolInternal({
9588
10221
  progressReporter.stop();
9589
10222
  }
9590
10223
  if (outcomes.every((outcome) => outcome.error !== void 0)) {
9591
- throw buildAnswerBatchError(outcomes);
10224
+ throw buildAnswerBatchError(outcomes, provider.label);
9592
10225
  }
9593
10226
  const text = await truncateAndSave(
9594
10227
  formatAnswerResponses(outcomes),
@@ -9597,19 +10230,31 @@ async function executeAnswerToolInternal({
9597
10230
  const details = buildWebAnswerDetails(provider.id, outcomes);
9598
10231
  return {
9599
10232
  content: [{ type: "text", text }],
9600
- details
10233
+ details,
10234
+ display: buildProviderToolDisplay2({
10235
+ capability: "answer",
10236
+ providerId: provider.id,
10237
+ details,
10238
+ text
10239
+ })
9601
10240
  };
9602
10241
  }
9603
- function buildAnswerBatchError(outcomes) {
10242
+ function buildAnswerBatchError(outcomes, providerLabel) {
9604
10243
  const failed = outcomes.filter((outcome) => outcome.error !== void 0);
9605
10244
  if (failed.length === 1) {
9606
- return new Error(failed[0]?.error ?? "web_answer failed.");
10245
+ return new Error(
10246
+ formatProviderCapabilityFailure(
10247
+ providerLabel,
10248
+ "answer",
10249
+ failed[0]?.error ?? ""
10250
+ )
10251
+ );
9607
10252
  }
9608
10253
  const summary = failed.map(
9609
10254
  (outcome, index) => `${index + 1}. ${formatQuotedPreview(outcome.query, 40)} \u2014 ${outcome.error}`
9610
10255
  ).join("; ");
9611
10256
  return new Error(
9612
- `All ${failed.length} web_answer queries failed: ${summary}`
10257
+ `${providerLabel} answer failed for ${failed.length} questions: ${summary}`
9613
10258
  );
9614
10259
  }
9615
10260
  function formatAnswerResponses(outcomes) {
@@ -9671,13 +10316,24 @@ async function executeProviderOperation({
9671
10316
  });
9672
10317
  }
9673
10318
  if (capability === "contents") {
10319
+ const urlCount = (urls ?? []).length;
9674
10320
  onProgress?.(
9675
- `Fetching contents via ${provider.label} for ${(urls ?? []).length} URL(s)`
10321
+ `Fetching contents via ${provider.label} for ${urlCount} URL(s)`,
10322
+ buildProgressDisplay2(
10323
+ provider.id,
10324
+ urlCount === 1 ? "Fetching page" : `Fetching ${urlCount} pages`
10325
+ )
9676
10326
  );
9677
10327
  } else if (capability === "answer") {
9678
- onProgress?.(`Answering via ${provider.label}`);
10328
+ onProgress?.(
10329
+ `Answering via ${provider.label}`,
10330
+ buildProgressDisplay2(provider.id, "Answering")
10331
+ );
9679
10332
  } else if (capability === "research") {
9680
- onProgress?.(`Researching via ${provider.label}`);
10333
+ onProgress?.(
10334
+ `Researching via ${provider.label}`,
10335
+ buildProgressDisplay2(provider.id, "Researching")
10336
+ );
9681
10337
  }
9682
10338
  const result = executionOverride ? await executeProviderExecution(executionOverride, {
9683
10339
  cwd: ctx.cwd,
@@ -9782,18 +10438,36 @@ async function executeProviderToolInternal({
9782
10438
  } finally {
9783
10439
  progressReporter.stop();
9784
10440
  }
9785
- const details = {
9786
- tool: `web_${capability}`,
9787
- provider: response.provider,
9788
- itemCount: isContentsResponse2(response) ? response.answers.length : response.itemCount
9789
- };
9790
- const text = await truncateAndSave(
10441
+ const rendered = await truncateAndSaveWithMetadata(
9791
10442
  isContentsResponse2(response) ? formatContentsResponse(response) : response.text,
9792
10443
  capability
9793
10444
  );
10445
+ const details = isContentsResponse2(response) ? {
10446
+ tool: "web_contents",
10447
+ provider: response.provider,
10448
+ itemCount: response.answers.length
10449
+ } : capability === "answer" ? {
10450
+ tool: "web_answer",
10451
+ provider: response.provider,
10452
+ itemCount: response.itemCount,
10453
+ queryCount: 1,
10454
+ failedQueryCount: 0
10455
+ } : {
10456
+ tool: "web_research",
10457
+ provider: response.provider
10458
+ };
9794
10459
  return {
9795
- content: [{ type: "text", text }],
9796
- details
10460
+ content: [{ type: "text", text: rendered.text }],
10461
+ details,
10462
+ display: buildProviderToolDisplay2({
10463
+ capability,
10464
+ providerId: response.provider,
10465
+ details,
10466
+ text: rendered.text,
10467
+ outputBytes: capability === "contents" ? rendered.totalBytes : void 0,
10468
+ outputTruncated: capability === "contents" ? rendered.truncated : void 0,
10469
+ failedItemCount: isContentsResponse2(response) ? response.answers.filter((answer) => answer.error !== void 0).length : void 0
10470
+ })
9797
10471
  };
9798
10472
  }
9799
10473
  async function dispatchWebResearch({
@@ -9857,7 +10531,13 @@ async function dispatchWebResearchInternal({
9857
10531
  text: `Started web research via ${provider.label}.`
9858
10532
  }
9859
10533
  ],
9860
- details: request
10534
+ details: request,
10535
+ display: buildProviderToolDisplay2({
10536
+ capability: "research",
10537
+ providerId: provider.id,
10538
+ details: { tool: "web_research", provider: provider.id },
10539
+ text: "started"
10540
+ })
9861
10541
  };
9862
10542
  }
9863
10543
  async function runDispatchedWebResearch({
@@ -10089,6 +10769,7 @@ async function executeBatchedContentsTool({
10089
10769
  }
10090
10770
  const batchProgress = createBatchCompletionReporter(
10091
10771
  "Fetching contents",
10772
+ provider.id,
10092
10773
  provider.label,
10093
10774
  urls.length,
10094
10775
  progressReport
@@ -10140,7 +10821,11 @@ async function executeBatchedContentsTool({
10140
10821
  );
10141
10822
  if (successful.length === 0 && failures.length > 0) {
10142
10823
  throw new Error(
10143
- failures.length === 1 ? failures[0]?.error ?? "web_contents failed." : `web_contents failed for all ${failures.length} URL(s): ${failures.map(
10824
+ failures.length === 1 ? formatProviderCapabilityFailure(
10825
+ provider.label,
10826
+ "contents",
10827
+ failures[0]?.error ?? ""
10828
+ ) : `${provider.label} fetch failed for ${failures.length} pages: ${failures.map(
10144
10829
  (failure, index) => `${index + 1}. ${failure.url} \u2014 ${failure.error}`
10145
10830
  ).join("; ")}`
10146
10831
  );
@@ -10205,10 +10890,11 @@ function createProgressEmitter(onUpdate) {
10205
10890
  if (!onUpdate) {
10206
10891
  return void 0;
10207
10892
  }
10208
- return (message) => {
10893
+ return (message, display) => {
10209
10894
  onUpdate({
10210
10895
  content: [{ type: "text", text: message }],
10211
- details: {}
10896
+ details: {},
10897
+ display
10212
10898
  });
10213
10899
  };
10214
10900
  }
@@ -10217,7 +10903,7 @@ function createToolProgressReporter(capability, providerId, progress) {
10217
10903
  return { report: void 0, stop: () => {
10218
10904
  } };
10219
10905
  }
10220
- const emit = (message) => progress(message);
10906
+ const emit = (message, display) => progress(message, display);
10221
10907
  const startedAt = Date.now();
10222
10908
  let lastUpdateAt = startedAt;
10223
10909
  let timer;
@@ -10228,14 +10914,17 @@ function createToolProgressReporter(capability, providerId, progress) {
10228
10914
  }
10229
10915
  const providerLabel = PROVIDERS_BY_ID[providerId]?.label ?? providerId;
10230
10916
  const elapsed = formatElapsed(Date.now() - startedAt);
10231
- emit(`Researching via ${providerLabel} (${elapsed} elapsed)`);
10917
+ emit(
10918
+ `Researching via ${providerLabel} (${elapsed} elapsed)`,
10919
+ buildProgressDisplay2(providerId, `Researching ${elapsed}`)
10920
+ );
10232
10921
  lastUpdateAt = Date.now();
10233
10922
  }, RESEARCH_HEARTBEAT_MS);
10234
10923
  }
10235
10924
  return {
10236
- report: (message) => {
10925
+ report: (message, display) => {
10237
10926
  lastUpdateAt = Date.now();
10238
- emit(message);
10927
+ emit(message, display);
10239
10928
  },
10240
10929
  stop: () => {
10241
10930
  if (timer) {
@@ -10306,77 +10995,125 @@ function renderQuestionCallHeader(params, theme) {
10306
10995
  );
10307
10996
  }
10308
10997
  function renderResearchCallHeader(params, theme) {
10309
- return renderListCallHeader("web_research", [params.input], theme);
10998
+ return renderListCallHeader(
10999
+ "web_research",
11000
+ [params.input],
11001
+ theme,
11002
+ void 0,
11003
+ { quoteSingleItem: true }
11004
+ );
10310
11005
  }
10311
- function renderSearchToolResult(result, expanded, isPartial, theme) {
11006
+ function renderWebToolResult(result, state, theme, config, symbols = DEFAULT_SUMMARY_SYMBOLS) {
10312
11007
  const text = extractTextContent(result.content);
10313
- const isError = Boolean(result.isError);
10314
- if (isPartial) {
10315
- return renderSimpleText(text ?? "Working\u2026", theme, "warning");
11008
+ if (state.isPartial) {
11009
+ return renderToolProgress(result.display, text, theme);
10316
11010
  }
10317
- if (isError) {
10318
- return renderBlockText(text ?? "web_search failed", theme, "error");
11011
+ if (result.isError) {
11012
+ return renderFailureText(
11013
+ buildFailureSummary({
11014
+ text,
11015
+ details: result.details,
11016
+ capability: config.capability,
11017
+ fallback: config.failureText
11018
+ }),
11019
+ theme,
11020
+ symbols
11021
+ );
10319
11022
  }
10320
- const details = result.details;
10321
- if (!details || expanded) {
10322
- return renderMarkdownBlock(text ?? "");
11023
+ const details = config.getDetails(result.details);
11024
+ if (state.expanded) {
11025
+ return config.renderExpanded(details, text);
10323
11026
  }
10324
- return renderCollapsedSearchSummary(details, text, theme);
11027
+ const summary = config.preferDisplaySummary === false ? config.getCollapsedSummary(details, text) : getDisplaySummaryParts(result.display) ?? config.getCollapsedSummary(details, text);
11028
+ return renderCollapsedSummary(summary, theme, symbols);
10325
11029
  }
10326
- function renderWebResearchDispatchResult(result, expanded, theme) {
10327
- const text = extractTextContent(result.content) ?? "Started web research.";
10328
- const details = isWebResearchRequest(result.details) ? result.details : void 0;
10329
- if (expanded) {
10330
- const expandedText = details ? [
10331
- text,
10332
- "",
10333
- "## Research brief",
10334
- "",
10335
- details.input,
10336
- "",
10337
- "## Report path",
10338
- "",
10339
- `\`${details.outputPath}\``
10340
- ].join("\n") : text;
10341
- return renderMarkdownBlock(expandedText);
10342
- }
10343
- const summary = details ? `Started web research via ${PROVIDERS_BY_ID[details.provider]?.label ?? details.provider}` : text;
10344
- let summaryText = theme.fg("success", summary);
10345
- summaryText += theme.fg("muted", ` (${getExpandHint()})`);
10346
- return new Text(summaryText, 0, 0);
11030
+ function renderSearchToolResult(result, expanded, isPartial, theme, symbols = DEFAULT_SUMMARY_SYMBOLS) {
11031
+ return renderWebToolResult(
11032
+ result,
11033
+ { expanded, isPartial },
11034
+ theme,
11035
+ {
11036
+ capability: "search",
11037
+ failureText: "web_search failed",
11038
+ getDetails: (details) => details,
11039
+ getCollapsedSummary: (details, text) => details ? buildSearchSummaryParts2(details) : { success: getFirstLine2(text) ?? "web_search output available" },
11040
+ renderExpanded: (_details, text) => renderMarkdownBlock(text ?? "")
11041
+ },
11042
+ symbols
11043
+ );
10347
11044
  }
10348
- function renderWebResearchResultMessage(message, { expanded }, theme) {
11045
+ function renderWebResearchDispatchResult(result, expanded, theme, symbols = DEFAULT_SUMMARY_SYMBOLS) {
11046
+ return renderWebToolResult(
11047
+ result,
11048
+ { expanded },
11049
+ theme,
11050
+ {
11051
+ capability: "research",
11052
+ failureText: "web_research failed",
11053
+ getDetails: (details) => isWebResearchRequest(details) ? details : void 0,
11054
+ getCollapsedSummary: () => ({ success: "started" }),
11055
+ renderExpanded: (details, text) => renderMarkdownBlock(
11056
+ details ? renderWebResearchRequestMarkdown(details) : text ?? "Started web research."
11057
+ ),
11058
+ preferDisplaySummary: false
11059
+ },
11060
+ symbols
11061
+ );
11062
+ }
11063
+ function renderWebResearchResultMessage(message, { expanded }, theme, symbols = DEFAULT_SUMMARY_SYMBOLS) {
10349
11064
  const text = typeof message.content === "string" ? message.content : extractTextContent(message.content);
10350
11065
  const details = isWebResearchResult(message.details) ? message.details : void 0;
10351
11066
  const isSuccess = details?.status === "completed";
10352
11067
  const accent = isSuccess ? "success" : "error";
10353
11068
  const box = new Box(1, 1, (value) => theme.bg("customMessageBg", value));
10354
11069
  if (!expanded) {
10355
- const lines = details ? buildWebResearchResultSummaryLines(details, theme) : [theme.fg(accent, "Web research update")];
10356
- lines.push(theme.fg("muted", `(${getExpandHint()})`));
10357
- box.addChild(new Text(lines.join("\n"), 0, 0));
11070
+ const summary = details ? buildWebResearchResultSummaryLine(details, theme, symbols) : theme.fg(accent, "Web research update");
11071
+ box.addChild(
11072
+ new Text(`${summary}${theme.fg("muted", ` (${getExpandHint()})`)}`, 0, 0)
11073
+ );
10358
11074
  return box;
10359
11075
  }
10360
11076
  box.addChild(
10361
- isSuccess ? renderMarkdownBlock(text ?? "") : renderBlockText(text ?? "", theme, "error")
11077
+ details ? renderMarkdownBlock(renderWebResearchResultMarkdown(details)) : isSuccess ? renderMarkdownBlock(text ?? "") : renderBlockText(text ?? "", theme, "error")
10362
11078
  );
10363
11079
  return box;
10364
11080
  }
10365
- function buildWebResearchResultSummaryLines(result, theme) {
11081
+ function renderWebResearchRequestMarkdown(request) {
11082
+ return [
11083
+ "### Web research",
11084
+ "",
11085
+ `**Brief:** ${request.input}`,
11086
+ "",
11087
+ "**Status:** running ",
11088
+ `**Elapsed:** ${formatSummaryElapsed(Date.now() - Date.parse(request.startedAt))} `,
11089
+ `**Artifact:** \`${request.outputPath}\``
11090
+ ].join("\n");
11091
+ }
11092
+ function renderWebResearchResultMarkdown(result) {
11093
+ const status = result.status === "completed" ? "completed" : result.status;
11094
+ return [
11095
+ "### Web research",
11096
+ "",
11097
+ `**Brief:** ${result.input}`,
11098
+ "",
11099
+ `**Status:** ${status} `,
11100
+ `**Duration:** ${formatSummaryElapsed(result.elapsedMs)} `,
11101
+ `**Artifact:** \`${result.outputPath}\``,
11102
+ ...result.error ? ["", `**Error:** ${result.error}`] : []
11103
+ ].join("\n");
11104
+ }
11105
+ function buildWebResearchResultSummaryLine(result, theme, symbols) {
10366
11106
  const providerLabel = PROVIDERS_BY_ID[result.provider]?.label ?? result.provider;
10367
- const statusLine = result.status === "completed" ? `Web research completed via ${providerLabel}` : `Web research failed via ${providerLabel}`;
10368
- const lines = [
10369
- theme.fg(result.status === "completed" ? "success" : "error", statusLine)
10370
- ];
10371
- lines.push(
10372
- theme.fg("muted", `\u25CB start: ${result.startedAt}`),
10373
- theme.fg("muted", `\u25F4 duration: ${formatElapsed(result.elapsedMs)}`),
10374
- theme.fg("muted", `\u21B3 file: ${result.outputPath}`)
10375
- );
10376
- if (result.error) {
10377
- lines.push(theme.fg("muted", `\u2715 error: ${result.error}`));
11107
+ if (result.status === "completed") {
11108
+ return renderSuccessSummary(
11109
+ `${formatSummaryElapsed(result.elapsedMs)} \xB7 ${basename(result.outputPath)}`,
11110
+ theme,
11111
+ symbols
11112
+ );
10378
11113
  }
10379
- return lines;
11114
+ const statusText = result.status === "cancelled" ? `${providerLabel} research canceled after ${formatSummaryElapsed(result.elapsedMs)}` : `${providerLabel} research failed after ${formatSummaryElapsed(result.elapsedMs)}`;
11115
+ const errorSuffix = result.error ? `: ${normalizeProviderFailureDetail(providerLabel, result.error)}` : "";
11116
+ return renderFailureSummary(`${statusText}${errorSuffix}`, theme, symbols);
10380
11117
  }
10381
11118
  function isWebResearchRequest(details) {
10382
11119
  return typeof details === "object" && details !== null && "tool" in details && details.tool === "web_research" && "startedAt" in details && "outputPath" in details && !("status" in details);
@@ -10385,48 +11122,58 @@ function isWebResearchResult(details) {
10385
11122
  return typeof details === "object" && details !== null && "tool" in details && details.tool === "web_research" && "status" in details && "completedAt" in details;
10386
11123
  }
10387
11124
  function renderProviderToolResult(result, expanded, isPartial, failureText, theme, options = {}) {
10388
- const text = extractTextContent(result.content);
10389
- if (isPartial) {
10390
- return renderSimpleText(text ?? "Working\u2026", theme, "warning");
10391
- }
10392
- if (result.isError) {
10393
- return renderBlockText(text ?? failureText, theme, "error");
10394
- }
10395
- if (expanded) {
10396
- return options.markdownWhenExpanded ? renderMarkdownBlock(text ?? "") : renderBlockText(text ?? "", theme, "toolOutput");
10397
- }
10398
- const details = result.details;
10399
- const summary = renderCollapsedProviderToolSummary(details, text);
10400
- let summaryText = theme.fg("success", summary);
10401
- summaryText += theme.fg("muted", ` (${getExpandHint()})`);
10402
- return new Text(summaryText, 0, 0);
11125
+ return renderWebToolResult(
11126
+ result,
11127
+ { expanded, isPartial },
11128
+ theme,
11129
+ {
11130
+ capability: toolFromFailureText(failureText),
11131
+ failureText,
11132
+ getDetails: (details) => details,
11133
+ getCollapsedSummary: buildCollapsedProviderToolSummary2,
11134
+ renderExpanded: (_details, text) => options.markdownWhenExpanded ? renderMarkdownBlock(text ?? "") : renderBlockText(text ?? "", theme, "toolOutput")
11135
+ },
11136
+ options.symbols
11137
+ );
10403
11138
  }
10404
11139
  function renderCollapsedProviderToolSummary(details, text) {
10405
- if (details?.tool === "web_answer" && typeof details.queryCount === "number" && details.queryCount > 1) {
10406
- const providerLabel = PROVIDERS_BY_ID[details.provider]?.label ?? details.provider;
10407
- const failureSuffix = details.failedQueryCount && details.failedQueryCount > 0 ? `, ${details.failedQueryCount} failed` : "";
10408
- return `${details.queryCount} questions via ${providerLabel}${failureSuffix}`;
10409
- }
10410
- const baseSummary = getCompactProviderToolSummary(details) ?? getFirstLine(text) ?? `${details?.tool ?? "tool"} output available`;
10411
- if (!details?.provider) {
10412
- return baseSummary;
10413
- }
10414
- return appendProviderSummary(baseSummary, details.provider);
11140
+ return buildCollapsedProviderToolSummary(details, text);
10415
11141
  }
10416
- function getCompactProviderToolSummary(details) {
10417
- if (!details) {
10418
- return void 0;
10419
- }
10420
- if (details.tool === "web_contents" && typeof details.itemCount === "number") {
10421
- return `${details.itemCount} page${details.itemCount === 1 ? "" : "s"}`;
10422
- }
10423
- if (details.tool === "web_answer") {
10424
- return "Answer";
10425
- }
10426
- if (details.tool === "web_research") {
10427
- return "Research";
10428
- }
10429
- return void 0;
11142
+ function buildCollapsedProviderToolSummary2(details, text) {
11143
+ return buildCollapsedProviderToolSummaryParts(details, text);
11144
+ }
11145
+ function renderCollapsedSummary(summary, theme, symbols = DEFAULT_SUMMARY_SYMBOLS) {
11146
+ let rendered = renderSummary(summary, theme, symbols);
11147
+ rendered += theme.fg("muted", ` (${getExpandHint()})`);
11148
+ return new Text(rendered, 0, 0);
11149
+ }
11150
+ function getDisplaySummaryParts(display) {
11151
+ return display?.outcome;
11152
+ }
11153
+ function buildSearchToolDisplay2(details) {
11154
+ return buildSearchToolDisplay(details);
11155
+ }
11156
+ function buildProgressDisplay2(providerId, action) {
11157
+ return buildProgressDisplay(providerId, action);
11158
+ }
11159
+ function buildProviderToolDisplay2({
11160
+ capability,
11161
+ providerId,
11162
+ details,
11163
+ text,
11164
+ outputBytes,
11165
+ outputTruncated,
11166
+ failedItemCount
11167
+ }) {
11168
+ return buildProviderToolDisplay({
11169
+ capability,
11170
+ providerId,
11171
+ details,
11172
+ text,
11173
+ outputBytes,
11174
+ outputTruncated,
11175
+ failedItemCount
11176
+ });
10430
11177
  }
10431
11178
  function getProviderSettings(providerId) {
10432
11179
  return getProviderConfigManifest(providerId).settings;
@@ -10486,12 +11233,19 @@ function resolveProviderSelectionValue(providerIds, value) {
10486
11233
  );
10487
11234
  }
10488
11235
  function getReadyCompatibleProvidersForTool(config, cwd, toolId) {
11236
+ const mappedProviderId = getMappedProviderIdForTool(config, toolId);
10489
11237
  return sortProviderIdsForSettings(
10490
- getCompatibleProviders(toolId).filter(
10491
- (providerId) => isProviderCapabilityReady(
10492
- getProviderCapabilityStatus(config, cwd, providerId, toolId)
10493
- )
10494
- )
11238
+ getCompatibleProviders(toolId).filter((providerId) => {
11239
+ const setupState = getProviderSetupState(config, providerId);
11240
+ if (setupState === "none" && providerId !== mappedProviderId) {
11241
+ return false;
11242
+ }
11243
+ return isProviderCapabilityExposable(
11244
+ getProviderCapabilityStatus(config, cwd, providerId, toolId, {
11245
+ resolveSecrets: false
11246
+ })
11247
+ );
11248
+ })
10495
11249
  );
10496
11250
  }
10497
11251
  function sortProviderIdsForSettings(providerIds) {
@@ -10571,6 +11325,9 @@ function ensureSettings(config) {
10571
11325
  return config.settings;
10572
11326
  }
10573
11327
  function cleanupSettings(config) {
11328
+ if (config.settings?.search && Object.keys(config.settings.search).length === 0) {
11329
+ delete config.settings.search;
11330
+ }
10574
11331
  if (config.settings && Object.keys(config.settings).length === 0) {
10575
11332
  delete config.settings;
10576
11333
  }
@@ -10718,19 +11475,21 @@ var WebProvidersSettingsView = class {
10718
11475
  id: `tool:${toolId}`,
10719
11476
  label: TOOL_INFO[toolId].label,
10720
11477
  currentValue,
10721
- description: `Press Enter to configure web_${toolId}. ${TOOL_INFO[toolId].help} Route web_${toolId} to one compatible provider or turn it off.` + (compatibleLabels.length > 0 ? ` Ready compatible providers: ${compatibleLabels.join(", ")}.` : ""),
11478
+ description: `Press Enter to configure web_${toolId}. ${TOOL_INFO[toolId].help} Route web_${toolId} to one compatible provider or turn it off.` + (compatibleLabels.length > 0 ? ` Compatible providers: ${compatibleLabels.join(", ")}.` : ""),
10722
11479
  kind: "action"
10723
11480
  };
10724
11481
  });
10725
11482
  }
10726
11483
  buildSettingsSectionItems() {
10727
- return SETTING_IDS.map((id) => ({
10728
- id: `settings:${id}`,
10729
- label: SETTING_META[id].label,
10730
- currentValue: getSharedSettingDisplayValue(this.config, id),
10731
- description: SETTING_META[id].help,
10732
- kind: "text"
10733
- }));
11484
+ return [
11485
+ ...SETTING_IDS.map((id) => ({
11486
+ id: `settings:${id}`,
11487
+ label: SETTING_META[id].label,
11488
+ currentValue: getSharedSettingDisplayValue(this.config, id),
11489
+ description: SETTING_META[id].help,
11490
+ kind: "text"
11491
+ }))
11492
+ ];
10734
11493
  }
10735
11494
  getSectionEntries(section) {
10736
11495
  if (section === "provider") return this.buildProviderSectionItems();
@@ -11040,7 +11799,7 @@ var ToolSettingsSubmenu = class {
11040
11799
  id: "provider",
11041
11800
  label: "Provider",
11042
11801
  currentValue: currentProviderValue,
11043
- description: `Route web_${this.toolId} to one compatible ready provider or turn it off.`,
11802
+ description: `Route web_${this.toolId} to one compatible provider or turn it off.`,
11044
11803
  kind: "cycle",
11045
11804
  values: providerValues
11046
11805
  }
@@ -11452,17 +12211,24 @@ function formatProviderSetupState(state) {
11452
12211
  function getProviderReadinessSummary(config, cwd, providerId) {
11453
12212
  const tools = getProviderTools(providerId);
11454
12213
  const statuses = tools.map(
11455
- (tool) => getProviderCapabilityStatus(config, cwd, providerId, tool)
12214
+ (tool) => getProviderCapabilityStatus(config, cwd, providerId, tool, {
12215
+ resolveSecrets: false
12216
+ })
11456
12217
  );
11457
12218
  if (statuses.some((status) => status.state === "ready")) {
11458
12219
  return "Ready";
11459
12220
  }
12221
+ if (statuses.some((status) => status.state === "deferred_secret")) {
12222
+ return "Secrets resolved on first use";
12223
+ }
11460
12224
  return formatProviderCapabilityStatus(statuses[0], providerId, tools[0]);
11461
12225
  }
11462
12226
  function getProviderReadinessSummaryForProviderConfig(providerId, providerConfig) {
11463
12227
  const status = PROVIDERS_BY_ID[providerId].getCapabilityStatus(
11464
12228
  providerConfig ?? PROVIDERS_BY_ID[providerId].config.createTemplate(),
11465
- ""
12229
+ "",
12230
+ void 0,
12231
+ { resolveSecrets: false }
11466
12232
  );
11467
12233
  return formatProviderCapabilityStatus(status, providerId);
11468
12234
  }
@@ -11511,7 +12277,7 @@ function getSearchQueriesForDisplay(queries) {
11511
12277
  function getAnswerQueriesForDisplay(queries) {
11512
12278
  return getSearchQueriesForDisplay(queries);
11513
12279
  }
11514
- function createBatchCompletionReporter(verb, providerLabel, total, report) {
12280
+ function createBatchCompletionReporter(verb, providerId, providerLabel, total, report) {
11515
12281
  if (!report) {
11516
12282
  return {
11517
12283
  start: () => {
@@ -11529,7 +12295,8 @@ function createBatchCompletionReporter(verb, providerLabel, total, report) {
11529
12295
  if (failedCount > 0) {
11530
12296
  message += `, ${failedCount} failed`;
11531
12297
  }
11532
- report(message);
12298
+ const action = verb === "Fetching contents" ? `Fetching ${completedCount}/${total} pages` : `${verb} ${completedCount}/${total}`;
12299
+ report(message, buildProgressDisplay2(providerId, action));
11533
12300
  };
11534
12301
  return {
11535
12302
  start: emit,
@@ -11590,34 +12357,52 @@ ${rendered}`, 0, 0);
11590
12357
  function renderSimpleText(text, theme, color) {
11591
12358
  return new Text(theme.fg(color, text), 0, 0);
11592
12359
  }
11593
- function renderCollapsedSearchSummary(details, text, theme) {
12360
+ function renderSummary(summary, theme, symbols) {
12361
+ let rendered = renderSuccessSummary(summary.success, theme, symbols);
12362
+ if (summary.failure) {
12363
+ rendered += `, ${renderFailureSummary(summary.failure, theme, symbols)}`;
12364
+ }
12365
+ return rendered;
12366
+ }
12367
+ function renderSuccessSummary(text, theme, symbols) {
12368
+ return theme.fg("success", prefixWithSymbol(text, symbols.success));
12369
+ }
12370
+ function renderFailureSummary(text, theme, symbols) {
12371
+ return theme.fg("error", prefixWithSymbol(text, symbols.failure));
12372
+ }
12373
+ function renderFailureText(text, theme, symbols) {
12374
+ return new Text(renderFailureSummary(text, theme, symbols), 0, 0);
12375
+ }
12376
+ function prefixWithSymbol(text, symbol) {
12377
+ return symbol ? `${symbol} ${text}` : text;
12378
+ }
12379
+ function renderToolProgress(display, fallbackText, theme) {
12380
+ const progress = display?.progress;
12381
+ const providerLabel = display?.provider?.label;
12382
+ if (!progress || !providerLabel) {
12383
+ return renderSimpleText(fallbackText ?? "Working\u2026", theme, "warning");
12384
+ }
12385
+ return new Text(
12386
+ `${theme.fg("warning", progress.action)} ${theme.fg("muted", `via ${providerLabel}`)}`,
12387
+ 0,
12388
+ 0
12389
+ );
12390
+ }
12391
+ function renderCollapsedSearchSummary(details, text, theme, symbols = DEFAULT_SUMMARY_SYMBOLS) {
11594
12392
  const queryCount = typeof details?.queryCount === "number" ? details.queryCount : inferSearchQueryCount(text);
11595
12393
  const resultCount = typeof details?.resultCount === "number" ? details.resultCount : inferSearchResultCount(text);
11596
12394
  const failedQueryCount = typeof details?.failedQueryCount === "number" ? details.failedQueryCount : inferSearchFailureCount(text);
11597
- const providerLabel = typeof details?.provider === "string" ? PROVIDERS_BY_ID[details.provider]?.label ?? details.provider : void 0;
11598
- let base2 = buildSearchSummaryText({
12395
+ const summary = buildSearchSummaryParts2({
11599
12396
  queryCount,
11600
- resultCount
12397
+ resultCount,
12398
+ failedQueryCount
11601
12399
  });
11602
- if (providerLabel) {
11603
- base2 = `${base2} via ${providerLabel}`;
11604
- }
11605
- if (failedQueryCount && failedQueryCount > 0) {
11606
- base2 += `, ${failedQueryCount} failed`;
11607
- }
11608
- let summary = theme.fg("success", base2);
11609
- summary += theme.fg("muted", ` (${getExpandHint()})`);
11610
- return new Text(summary, 0, 0);
12400
+ let rendered = renderSummary(summary, theme, symbols);
12401
+ rendered += theme.fg("muted", ` (${getExpandHint()})`);
12402
+ return new Text(rendered, 0, 0);
11611
12403
  }
11612
- function buildSearchSummaryText({
11613
- queryCount,
11614
- resultCount
11615
- }) {
11616
- const countSummary = typeof resultCount === "number" ? `${resultCount} result${resultCount === 1 ? "" : "s"}` : "Search output available";
11617
- if (queryCount && queryCount > 1) {
11618
- return `${queryCount} queries, ${countSummary}`;
11619
- }
11620
- return countSummary;
12404
+ function buildSearchSummaryParts2(options) {
12405
+ return buildSearchSummaryParts(options);
11621
12406
  }
11622
12407
  function inferSearchQueryCount(text) {
11623
12408
  if (!text) {
@@ -11643,12 +12428,77 @@ function inferSearchFailureCount(text) {
11643
12428
  const failureMatches = text.match(/^Search failed:/gm);
11644
12429
  return failureMatches?.length;
11645
12430
  }
11646
- function appendProviderSummary(summary, provider) {
11647
- const providerLabel = PROVIDERS_BY_ID[provider]?.label ?? provider;
11648
- const providerSuffix = `via ${providerLabel}`;
11649
- return summary.toLowerCase().includes(providerSuffix.toLowerCase()) ? summary : `${summary} ${providerSuffix}`;
12431
+ function buildFailureSummary({
12432
+ text,
12433
+ details,
12434
+ capability,
12435
+ fallback
12436
+ }) {
12437
+ const detail = stripTrailingSentencePunctuation(getFirstLine2(text) ?? "");
12438
+ const providerLabel = details?.provider !== void 0 ? PROVIDERS_BY_ID[details.provider]?.label ?? details.provider : void 0;
12439
+ if (!providerLabel) {
12440
+ return detail || fallback;
12441
+ }
12442
+ return formatProviderCapabilityFailure(providerLabel, capability, detail);
11650
12443
  }
11651
- function getFirstLine(text) {
12444
+ function formatProviderCapabilityFailure(providerLabel, capability, detail) {
12445
+ const action = getFailureAction(capability);
12446
+ const base2 = `${providerLabel} ${action} failed`;
12447
+ if (!detail || detail === base2) {
12448
+ return base2;
12449
+ }
12450
+ if (detail.toLowerCase().startsWith(base2.toLowerCase())) {
12451
+ return detail;
12452
+ }
12453
+ const normalizedDetail = normalizeProviderFailureDetail(
12454
+ providerLabel,
12455
+ detail
12456
+ );
12457
+ return `${base2}: ${normalizedDetail}`;
12458
+ }
12459
+ function normalizeProviderFailureDetail(providerLabel, detail) {
12460
+ const normalized = stripTrailingSentencePunctuation(detail);
12461
+ const providerPrefix = `${providerLabel}:`;
12462
+ return normalized.toLowerCase().startsWith(providerPrefix.toLowerCase()) ? normalized.slice(providerPrefix.length).trim() : normalized;
12463
+ }
12464
+ function getFailureAction(capability) {
12465
+ switch (capability) {
12466
+ case "contents":
12467
+ return "fetch";
12468
+ case "search":
12469
+ case "answer":
12470
+ case "research":
12471
+ return capability;
12472
+ }
12473
+ }
12474
+ function toolFromFailureText(text) {
12475
+ if (text.startsWith("web_contents")) {
12476
+ return "contents";
12477
+ }
12478
+ if (text.startsWith("web_answer")) {
12479
+ return "answer";
12480
+ }
12481
+ if (text.startsWith("web_research")) {
12482
+ return "research";
12483
+ }
12484
+ return "search";
12485
+ }
12486
+ function stripTrailingSentencePunctuation(text) {
12487
+ return text.trim().replace(/[.\s]+$/u, "");
12488
+ }
12489
+ function formatSummaryElapsed(ms) {
12490
+ const totalSeconds = Math.max(0, Math.floor(ms / 1e3));
12491
+ const minutes = Math.floor(totalSeconds / 60);
12492
+ const seconds = totalSeconds % 60;
12493
+ if (minutes > 0 && seconds === 0) {
12494
+ return `${minutes}m`;
12495
+ }
12496
+ if (minutes > 0) {
12497
+ return `${minutes}m ${seconds}s`;
12498
+ }
12499
+ return `${seconds}s`;
12500
+ }
12501
+ function getFirstLine2(text) {
11652
12502
  if (!text) {
11653
12503
  return void 0;
11654
12504
  }
@@ -11729,18 +12579,32 @@ function escapeMarkdownText(text) {
11729
12579
  return text.replaceAll("\\", "\\\\").replaceAll("*", "\\*").replaceAll("_", "\\_").replaceAll("`", "\\`").replaceAll("#", "\\#").replaceAll("[", "\\[").replaceAll("]", "\\]");
11730
12580
  }
11731
12581
  async function truncateAndSave(text, prefix) {
12582
+ return (await truncateAndSaveWithMetadata(text, prefix)).text;
12583
+ }
12584
+ async function truncateAndSaveWithMetadata(text, prefix) {
12585
+ const totalBytes = Buffer.byteLength(text, "utf-8");
11732
12586
  const truncation = truncateHead(text, {
11733
12587
  maxLines: DEFAULT_MAX_LINES,
11734
12588
  maxBytes: DEFAULT_MAX_BYTES
11735
12589
  });
11736
- if (!truncation.truncated) return truncation.content;
12590
+ if (!truncation.truncated) {
12591
+ return {
12592
+ text: truncation.content,
12593
+ totalBytes,
12594
+ truncated: false
12595
+ };
12596
+ }
11737
12597
  const dir = join2(tmpdir(), `pi-web-providers-${prefix}-${Date.now()}`);
11738
12598
  await mkdir2(dir, { recursive: true });
11739
12599
  const fullPath = join2(dir, "output.txt");
11740
12600
  await writeFile2(fullPath, text, "utf-8");
11741
- return truncation.content + `
12601
+ return {
12602
+ text: truncation.content + `
11742
12603
 
11743
- [Output truncated: ${truncation.outputLines} of ${truncation.totalLines} lines (${formatSize(truncation.outputBytes)} of ${formatSize(truncation.totalBytes)}). Full output saved to: ${fullPath}]`;
12604
+ [Output truncated: ${truncation.outputLines} of ${truncation.totalLines} lines (${formatSize2(truncation.outputBytes)} of ${formatSize2(truncation.totalBytes)}). Full output saved to: ${fullPath}]`,
12605
+ totalBytes,
12606
+ truncated: true
12607
+ };
11744
12608
  }
11745
12609
  function truncateInline(text, maxLength) {
11746
12610
  if (text.length <= maxLength) return text;