pi-web-providers 2.4.2 → 2.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/README.md +80 -46
  2. package/dist/index.js +2204 -1590
  3. package/package.json +21 -20
package/dist/index.js CHANGED
@@ -22,7 +22,7 @@ import {
22
22
  visibleWidth,
23
23
  wrapTextWithAnsi
24
24
  } from "@mariozechner/pi-tui";
25
- import { Type as Type16 } from "typebox";
25
+ import { Type as Type15 } from "typebox";
26
26
 
27
27
  // src/config.ts
28
28
  import { mkdir, readFile, writeFile } from "node:fs/promises";
@@ -95,6 +95,13 @@ function trimSnippet(input, maxLength = 300) {
95
95
  if (text.length <= maxLength) return text;
96
96
  return `${text.slice(0, maxLength - 1)}\u2026`;
97
97
  }
98
+ function normalizeContentText(input) {
99
+ const text = (input ?? "").replace(/\r/g, "").trim();
100
+ if (!text) {
101
+ return "";
102
+ }
103
+ return text.split("\n").map((line) => line.replace(/[ \t]+$/g, "")).join("\n").replace(/\n{3,}/g, "\n\n");
104
+ }
98
105
  function asJsonObject(value) {
99
106
  return value ? { ...value } : {};
100
107
  }
@@ -102,7 +109,38 @@ function formatJson(value) {
102
109
  return JSON.stringify(value, null, 2);
103
110
  }
104
111
  function getApiKeyStatus(apiKeyReference) {
105
- return resolveConfigValue(apiKeyReference) ? { state: "ready" } : { state: "missing_api_key" };
112
+ try {
113
+ return resolveConfigValue(apiKeyReference) ? { state: "ready" } : { state: "missing_api_key" };
114
+ } catch (error) {
115
+ return {
116
+ state: "invalid_config",
117
+ detail: formatConfigValueError(error)
118
+ };
119
+ }
120
+ }
121
+ function formatConfigValueError(error) {
122
+ const message = error instanceof Error ? error.message : String(error);
123
+ return message.replace(/\s+/g, " ").trim() || "Failed to resolve config value";
124
+ }
125
+
126
+ // src/providers/definition.ts
127
+ function defineCapability(definition) {
128
+ return definition;
129
+ }
130
+ function defineProvider(definition) {
131
+ return definition;
132
+ }
133
+ function defineProviders(providers) {
134
+ return providers;
135
+ }
136
+ async function executeProviderCapability(definition, capability, input, context) {
137
+ const handler = definition.capabilities[capability];
138
+ if (!handler) {
139
+ throw new Error(
140
+ `Provider '${definition.id}' does not support '${capability}'.`
141
+ );
142
+ }
143
+ return await handler.execute(input, context);
106
144
  }
107
145
 
108
146
  // src/providers/claude.ts
@@ -186,7 +224,7 @@ var claudeOptionsSchema = Type2.Object(
186
224
  },
187
225
  { description: "Claude options." }
188
226
  );
189
- var claudeAdapter = {
227
+ var claudeImplementation = {
190
228
  id: "claude",
191
229
  label: "Claude",
192
230
  docsUrl: "https://github.com/anthropics/claude-agent-sdk-typescript",
@@ -427,533 +465,207 @@ function readString(value, key) {
427
465
  }
428
466
  return entry;
429
467
  }
468
+ var claudeProvider = defineProvider({
469
+ id: "claude",
470
+ label: claudeImplementation.label,
471
+ docsUrl: claudeImplementation.docsUrl,
472
+ config: {
473
+ createTemplate: () => claudeImplementation.createTemplate(),
474
+ fields: ["pathToClaudeCodeExecutable", "options", "settings"]
475
+ },
476
+ getCapabilityStatus: (config, cwd, tool) => claudeImplementation.getCapabilityStatus(
477
+ config,
478
+ cwd,
479
+ tool
480
+ ),
481
+ capabilities: {
482
+ search: defineCapability({
483
+ options: claudeImplementation.getToolOptionsSchema?.("search"),
484
+ async execute(input, ctx) {
485
+ const { query: query2, maxResults, options } = input;
486
+ return await claudeImplementation.search(
487
+ query2,
488
+ maxResults,
489
+ ctx.config,
490
+ ctx,
491
+ options
492
+ );
493
+ }
494
+ }),
495
+ answer: defineCapability({
496
+ options: claudeImplementation.getToolOptionsSchema?.("answer"),
497
+ async execute(input, ctx) {
498
+ return await claudeImplementation.answer(
499
+ input.query,
500
+ ctx.config,
501
+ ctx,
502
+ input.options
503
+ );
504
+ }
505
+ })
506
+ }
507
+ });
430
508
 
431
509
  // src/providers/cloudflare.ts
432
- import { Type as Type3 } from "typebox";
433
510
  import CloudflareClient from "cloudflare";
434
-
435
- // src/execution-policy-defaults.ts
436
- var DEFAULT_REQUEST_TIMEOUT_MS = 3e4;
437
- var DEFAULT_RETRY_COUNT = 3;
438
- var DEFAULT_RETRY_DELAY_MS = 2e3;
439
- var DEFAULT_RESEARCH_POLL_INTERVAL_MS = 3e3;
440
- var DEFAULT_RESEARCH_TIMEOUT_MS = 18e5;
441
- var DEFAULT_RESEARCH_MAX_CONSECUTIVE_POLL_ERRORS = 3;
442
- var DEFAULT_GEMINI_RESEARCH_MAX_CONSECUTIVE_POLL_ERRORS = 10;
443
- function createDefaultExecutionSettings(overrides = {}) {
444
- return {
445
- requestTimeoutMs: DEFAULT_REQUEST_TIMEOUT_MS,
446
- retryCount: DEFAULT_RETRY_COUNT,
447
- retryDelayMs: DEFAULT_RETRY_DELAY_MS,
448
- researchTimeoutMs: DEFAULT_RESEARCH_TIMEOUT_MS,
449
- ...overrides
450
- };
451
- }
452
-
453
- // src/provider-diagnostics.ts
454
- function normalizeDiagnosticDetail(detail) {
455
- return detail.trim().replace(/[.\s]+$/u, "");
456
- }
457
- function startsWithProviderLabel(providerLabel, detail) {
458
- return detail.toLowerCase().startsWith(providerLabel.toLowerCase());
459
- }
460
- function readsLikeProviderClause(detail) {
461
- return /^(is|has|was|returned|did|does|could|cannot|must|should|search\b|contents\b|answer\b|research\b|output\b|response\b|result\b|query\b|no\b|missing\b|deep research\b)/iu.test(
462
- detail
463
- );
464
- }
465
- function formatProviderDiagnostic(providerLabel, detail) {
466
- const normalized = normalizeDiagnosticDetail(detail);
467
- if (!normalized) {
468
- return `${providerLabel} failed.`;
469
- }
470
- if (startsWithProviderLabel(providerLabel, normalized)) {
471
- return `${normalized}.`;
472
- }
473
- if (readsLikeProviderClause(normalized)) {
474
- return `${providerLabel} ${normalized}.`;
475
- }
476
- return `${providerLabel}: ${normalized}.`;
477
- }
478
- function formatResearchTerminalDiagnostic(providerLabel, status, detail) {
479
- const normalized = detail ? normalizeDiagnosticDetail(detail) : "";
480
- if (!normalized) {
481
- return status === "cancelled" ? `${providerLabel} research was canceled.` : `${providerLabel} research failed.`;
482
- }
483
- if (startsWithProviderLabel(providerLabel, normalized)) {
484
- return `${normalized}.`;
511
+ import { Type as Type3 } from "typebox";
512
+ var cloudflareContentsOptionsSchema = Type3.Object(
513
+ {
514
+ gotoOptions: Type3.Optional(
515
+ Type3.Object(
516
+ {
517
+ waitUntil: Type3.Optional(
518
+ literalUnion(
519
+ ["load", "domcontentloaded", "networkidle0", "networkidle2"],
520
+ { description: "When to consider navigation complete." }
521
+ )
522
+ )
523
+ },
524
+ {
525
+ description: "Navigation options."
526
+ }
527
+ )
528
+ )
529
+ },
530
+ {
531
+ description: "Cloudflare Browser Rendering options."
485
532
  }
486
- if (/^research\b/iu.test(normalized)) {
487
- return `${providerLabel} ${normalized}.`;
533
+ );
534
+ var cloudflareImplementation = {
535
+ id: "cloudflare",
536
+ label: "Cloudflare",
537
+ docsUrl: "https://developers.cloudflare.com/browser-rendering/rest-api/markdown-endpoint/",
538
+ getToolOptionsSchema(capability) {
539
+ switch (capability) {
540
+ case "contents":
541
+ return cloudflareContentsOptionsSchema;
542
+ default:
543
+ return void 0;
544
+ }
545
+ },
546
+ createTemplate() {
547
+ return {
548
+ apiToken: "CLOUDFLARE_API_TOKEN",
549
+ accountId: "CLOUDFLARE_ACCOUNT_ID",
550
+ options: {
551
+ gotoOptions: {
552
+ waitUntil: "networkidle0"
553
+ }
554
+ }
555
+ };
556
+ },
557
+ getCapabilityStatus(config) {
558
+ const apiTokenStatus = getApiKeyStatus(config?.apiToken);
559
+ if (apiTokenStatus.state !== "ready") {
560
+ return apiTokenStatus;
561
+ }
562
+ try {
563
+ if (!resolveConfigValue(config?.accountId)) {
564
+ return { state: "invalid_config", detail: "Missing account ID" };
565
+ }
566
+ } catch (error) {
567
+ return {
568
+ state: "invalid_config",
569
+ detail: formatConfigValueError(error)
570
+ };
571
+ }
572
+ return { state: "ready" };
573
+ },
574
+ async contents(urls, config, context, options) {
575
+ const client = createClient(config);
576
+ const accountId = resolveConfigValue(config.accountId);
577
+ if (!accountId) {
578
+ throw new Error("is missing an account ID");
579
+ }
580
+ const defaults = asJsonObject(config.options);
581
+ const answers = await Promise.all(
582
+ urls.map(async (url) => {
583
+ try {
584
+ const markdown = await client.browserRendering.markdown.create(
585
+ {
586
+ ...defaults ?? {},
587
+ ...options ?? {},
588
+ account_id: accountId,
589
+ url
590
+ },
591
+ buildRequestOptions(context)
592
+ );
593
+ return {
594
+ url,
595
+ content: markdown
596
+ };
597
+ } catch (error) {
598
+ return {
599
+ url,
600
+ error: error instanceof Error ? error.message : String(error)
601
+ };
602
+ }
603
+ })
604
+ );
605
+ return {
606
+ provider: cloudflareImplementation.id,
607
+ answers
608
+ };
488
609
  }
489
- return status === "cancelled" ? `${providerLabel} research was canceled: ${normalized}.` : `${providerLabel} research failed: ${normalized}.`;
490
- }
491
-
492
- // src/execution-policy.ts
493
- var MAX_RETRY_DELAY_MS = 3e4;
494
- var RequestTimeoutError = class extends Error {
495
- name = "RequestTimeoutError";
496
610
  };
497
- function stripLocalExecutionOptions(options) {
498
- if (!options) {
499
- return void 0;
611
+ function createClient(config) {
612
+ const apiToken = resolveConfigValue(config.apiToken);
613
+ if (!apiToken) {
614
+ throw new Error("is missing an API token");
500
615
  }
501
- const {
502
- requestTimeoutMs: _requestTimeoutMs,
503
- retryCount: _retryCount,
504
- retryDelayMs: _retryDelayMs,
505
- prefetch: _prefetch,
506
- ...rest
507
- } = options;
508
- return Object.keys(rest).length > 0 ? rest : void 0;
616
+ return new CloudflareClient({
617
+ apiToken
618
+ });
509
619
  }
510
- function parseLocalExecutionOptions(options) {
511
- return {
512
- requestTimeoutMs: parseOptionalPositiveIntegerOption(
513
- options,
514
- "requestTimeoutMs"
515
- ),
516
- retryCount: parseOptionalNonNegativeIntegerOption(options, "retryCount"),
517
- retryDelayMs: parseOptionalPositiveIntegerOption(options, "retryDelayMs")
518
- };
620
+ function buildRequestOptions(context) {
621
+ return context.signal ? { signal: context.signal } : void 0;
519
622
  }
520
- async function runWithExecutionPolicy(label, operation, settings, context) {
521
- const maxAttempts = Math.max(1, settings.retryCount + 1);
522
- for (let attempt = 1; attempt <= maxAttempts; attempt += 1) {
523
- throwIfAborted(context.signal);
524
- const {
525
- context: attemptContext,
526
- abort,
527
- cleanup
528
- } = createAttemptContext(context);
529
- try {
530
- const result = operation(attemptContext);
531
- const timeoutMessage = settings.requestTimeoutMs === void 0 ? void 0 : `${label} timed out after ${formatDuration(settings.requestTimeoutMs)}.`;
532
- return await withAbortAndOptionalTimeout(
533
- result,
534
- settings.requestTimeoutMs,
535
- context.signal,
536
- timeoutMessage,
537
- timeoutMessage ? () => abort(new RequestTimeoutError(timeoutMessage)) : void 0
538
- );
539
- } catch (error) {
540
- if (!shouldRetryError(error, settings) || attempt >= maxAttempts) {
541
- throw error;
623
+ var cloudflareProvider = defineProvider({
624
+ id: "cloudflare",
625
+ label: cloudflareImplementation.label,
626
+ docsUrl: cloudflareImplementation.docsUrl,
627
+ config: {
628
+ createTemplate: () => cloudflareImplementation.createTemplate(),
629
+ fields: ["apiToken", "accountId", "options", "settings"]
630
+ },
631
+ getCapabilityStatus: (config, cwd, tool) => cloudflareImplementation.getCapabilityStatus(
632
+ config,
633
+ cwd,
634
+ tool
635
+ ),
636
+ capabilities: {
637
+ contents: defineCapability({
638
+ options: cloudflareImplementation.getToolOptionsSchema?.("contents"),
639
+ async execute(input, ctx) {
640
+ return await cloudflareImplementation.contents(
641
+ input.urls,
642
+ ctx.config,
643
+ ctx,
644
+ input.options
645
+ );
542
646
  }
543
- const delayMs = Math.min(
544
- settings.retryDelayMs * 2 ** (attempt - 1),
545
- MAX_RETRY_DELAY_MS
546
- );
547
- context.onProgress?.(
548
- `${label} failed (${formatErrorMessage(error)}). Retrying in ${formatDuration(delayMs)} (attempt ${attempt + 1}/${maxAttempts}).`
549
- );
550
- await sleep(delayMs, context.signal);
551
- } finally {
552
- cleanup();
553
- }
647
+ })
554
648
  }
555
- throw new Error(`${label} failed.`);
556
- }
557
- async function executeAsyncResearch({
558
- providerLabel,
559
- providerId,
560
- context,
561
- pollIntervalMs = DEFAULT_RESEARCH_POLL_INTERVAL_MS,
562
- timeoutMs = DEFAULT_RESEARCH_TIMEOUT_MS,
563
- maxConsecutivePollErrors = DEFAULT_RESEARCH_MAX_CONSECUTIVE_POLL_ERRORS,
564
- start,
565
- poll
566
- }) {
567
- const timeoutMessage = `${providerLabel} research exceeded ${formatDuration(timeoutMs)}.`;
568
- const deadline = createDeadlineSignal(
569
- context.signal,
570
- timeoutMs,
571
- timeoutMessage
572
- );
573
- const researchContext = {
574
- ...context,
575
- signal: deadline.signal
576
- };
577
- let lastStatus;
578
- let lastProgressStatus;
579
- const startedAt = Date.now();
580
- try {
581
- researchContext.onProgress?.(`Starting research via ${providerLabel}`);
582
- const job = await withAbortAndOptionalTimeout(
583
- start(researchContext),
584
- void 0,
585
- researchContext.signal,
586
- void 0
587
- );
588
- const jobId = job.id;
589
- if (!jobId) {
590
- throw new Error(`${providerLabel} research did not return a job id.`);
591
- }
592
- researchContext.onProgress?.(`${providerLabel} research started: ${jobId}`);
593
- let consecutivePollErrors = 0;
594
- while (true) {
595
- throwIfAborted(
596
- researchContext.signal,
597
- `${providerLabel} research aborted.`
598
- );
599
- try {
600
- const result = await withAbortAndOptionalTimeout(
601
- poll(jobId, researchContext),
602
- void 0,
603
- researchContext.signal,
604
- void 0
605
- );
606
- consecutivePollErrors = 0;
607
- const progressStatus = result.statusText ?? result.status;
608
- if (result.status !== lastStatus || progressStatus !== lastProgressStatus) {
609
- researchContext.onProgress?.(
610
- `Research via ${providerLabel}: ${progressStatus} (${formatElapsed(Date.now() - startedAt)} elapsed)`
611
- );
612
- lastStatus = result.status;
613
- lastProgressStatus = progressStatus;
614
- }
615
- if (result.status === "completed") {
616
- return result.output ?? {
617
- provider: providerId,
618
- text: `${providerLabel} research completed without textual output.`
619
- };
620
- }
621
- if (result.status === "failed" || result.status === "cancelled") {
622
- throw new Error(
623
- formatResearchTerminalDiagnostic(
624
- providerLabel,
625
- result.status,
626
- result.error
627
- )
628
- );
629
- }
630
- } catch (error) {
631
- if (isAbortErrorFromSignal(researchContext.signal, error)) {
632
- throw error;
633
- }
634
- if (!isRetryableError(error)) {
635
- throw normalizeError(error);
636
- }
637
- consecutivePollErrors += 1;
638
- if (consecutivePollErrors >= maxConsecutivePollErrors) {
639
- throw new Error(
640
- `${providerLabel} research polling failed too many times in a row: ${formatErrorMessage(error)}`
641
- );
642
- }
643
- researchContext.onProgress?.(
644
- `${providerLabel} research poll is still retrying after transient errors (${consecutivePollErrors}/${maxConsecutivePollErrors} consecutive poll failures). Background job id: ${jobId}`
645
- );
646
- }
647
- await sleep(pollIntervalMs, researchContext.signal);
648
- }
649
- } catch (error) {
650
- if (isAbortErrorFromSignal(researchContext.signal, error)) {
651
- throw new Error(
652
- formatProviderDiagnostic(providerLabel, formatErrorMessage(error))
653
- );
654
- }
655
- throw new Error(
656
- formatProviderDiagnostic(providerLabel, formatErrorMessage(error))
657
- );
658
- } finally {
659
- deadline.cleanup();
660
- }
661
- }
662
- function shouldRetryError(error, settings) {
663
- if (error instanceof RequestTimeoutError) {
664
- return settings.retryOnTimeout === true;
665
- }
666
- return isRetryableError(error);
667
- }
668
- function isRetryableError(error) {
669
- if (error instanceof RequestTimeoutError) {
670
- return false;
671
- }
672
- const message = formatErrorMessage(error).toLowerCase();
673
- if (!message || message === "operation aborted.") {
674
- return false;
675
- }
676
- return /429|500|502|503|504|deadline exceeded|econnreset|ecanceled|ehostunreach|eai_again|enotfound|etimedout|fetch failed|gateway timeout|internal error|network|overloaded|rate limit|resource exhausted|socket hang up|temporarily unavailable|timeout|unavailable/.test(
677
- message
678
- );
679
- }
680
- function formatErrorMessage(error) {
681
- if (error instanceof Error) {
682
- return error.message;
683
- }
684
- if (typeof error === "object" && error !== null && "message" in error && typeof error.message === "string") {
685
- return error.message;
686
- }
687
- return String(error);
688
- }
689
- function formatElapsed(ms) {
690
- const totalSeconds = Math.max(0, Math.floor(ms / 1e3));
691
- const minutes = Math.floor(totalSeconds / 60);
692
- const seconds = totalSeconds % 60;
693
- if (minutes > 0) {
694
- return `${minutes}m ${seconds}s`;
695
- }
696
- return `${totalSeconds}s`;
697
- }
698
- function formatDuration(ms) {
699
- if (ms >= 6e4) {
700
- return formatElapsed(ms);
701
- }
702
- if (ms >= 1e3) {
703
- return `${Math.floor(ms / 1e3)}s`;
704
- }
705
- return `${ms}ms`;
706
- }
707
- async function sleep(ms, signal) {
708
- throwIfAborted(signal);
709
- await new Promise((resolve2, reject) => {
710
- const timer = setTimeout(() => {
711
- signal?.removeEventListener("abort", onAbort);
712
- resolve2();
713
- }, ms);
714
- const onAbort = () => {
715
- clearTimeout(timer);
716
- signal?.removeEventListener("abort", onAbort);
717
- reject(getAbortError(signal));
718
- };
719
- signal?.addEventListener("abort", onAbort, { once: true });
720
- });
721
- }
722
- function throwIfAborted(signal, message = "Operation aborted.") {
723
- if (signal?.aborted) {
724
- throw getAbortError(signal, message);
725
- }
726
- }
727
- function createAttemptContext(context) {
728
- const controller = new AbortController();
729
- if (context.signal?.aborted) {
730
- controller.abort(getAbortError(context.signal));
731
- }
732
- const onAbort = () => {
733
- controller.abort(getAbortError(context.signal));
734
- };
735
- context.signal?.addEventListener("abort", onAbort, { once: true });
736
- return {
737
- context: {
738
- ...context,
739
- signal: controller.signal
740
- },
741
- abort: (reason) => controller.abort(reason),
742
- cleanup: () => context.signal?.removeEventListener("abort", onAbort)
743
- };
744
- }
745
- async function withAbortAndOptionalTimeout(promise, timeoutMs, signal, message, onTimeout) {
746
- if (timeoutMs === void 0 && !signal) {
747
- return await promise;
748
- }
749
- throwIfAborted(signal);
750
- return await new Promise((resolve2, reject) => {
751
- const timer = timeoutMs === void 0 ? void 0 : setTimeout(() => {
752
- onTimeout?.();
753
- cleanup();
754
- reject(
755
- new RequestTimeoutError(
756
- message ?? `Operation timed out after ${formatDuration(timeoutMs)}.`
757
- )
758
- );
759
- }, timeoutMs);
760
- const onAbort = () => {
761
- cleanup();
762
- reject(getAbortError(signal));
763
- };
764
- const cleanup = () => {
765
- if (timer) {
766
- clearTimeout(timer);
767
- }
768
- signal?.removeEventListener("abort", onAbort);
769
- };
770
- signal?.addEventListener("abort", onAbort, { once: true });
771
- promise.then(
772
- (value) => {
773
- cleanup();
774
- resolve2(value);
775
- },
776
- (error) => {
777
- cleanup();
778
- reject(error);
779
- }
780
- );
781
- });
782
- }
783
- function getAbortError(signal, message = "Operation aborted.") {
784
- const reason = signal?.reason;
785
- if (reason instanceof Error) {
786
- return reason;
787
- }
788
- if (typeof reason === "string" && reason.length > 0) {
789
- return new Error(reason);
790
- }
791
- return new Error(message);
792
- }
793
- function isAbortErrorFromSignal(signal, error) {
794
- return signal?.aborted === true && signal.reason === error;
795
- }
796
- function createDeadlineSignal(signal, timeoutMs, timeoutMessage) {
797
- const controller = new AbortController();
798
- if (signal?.aborted) {
799
- controller.abort(getAbortError(signal));
800
- }
801
- const onAbort = () => {
802
- controller.abort(getAbortError(signal));
803
- };
804
- signal?.addEventListener("abort", onAbort, { once: true });
805
- const timer = setTimeout(() => {
806
- controller.abort(new RequestTimeoutError(timeoutMessage));
807
- }, timeoutMs);
808
- return {
809
- signal: controller.signal,
810
- cleanup: () => {
811
- clearTimeout(timer);
812
- signal?.removeEventListener("abort", onAbort);
813
- }
814
- };
815
- }
816
- function normalizeError(error) {
817
- return error instanceof Error ? error : new Error(formatErrorMessage(error));
818
- }
819
- function parseOptionalPositiveIntegerOption(options, key) {
820
- const value = options?.[key];
821
- if (value === void 0) {
822
- return void 0;
823
- }
824
- if (typeof value !== "number" || !Number.isInteger(value) || value < 1) {
825
- throw new Error(`options.${key} must be a positive integer.`);
826
- }
827
- return value;
828
- }
829
- function parseOptionalNonNegativeIntegerOption(options, key) {
830
- const value = options?.[key];
831
- if (value === void 0) {
832
- return void 0;
833
- }
834
- if (typeof value !== "number" || !Number.isInteger(value) || value < 0) {
835
- throw new Error(`options.${key} must be a non-negative integer.`);
836
- }
837
- return value;
838
- }
839
-
840
- // src/providers/cloudflare.ts
841
- var cloudflareContentsOptionsSchema = Type3.Object(
842
- {
843
- gotoOptions: Type3.Optional(
844
- Type3.Object(
845
- {
846
- waitUntil: Type3.Optional(
847
- literalUnion(
848
- ["load", "domcontentloaded", "networkidle0", "networkidle2"],
849
- { description: "When to consider navigation complete." }
850
- )
851
- )
852
- },
853
- {
854
- description: "Navigation options."
855
- }
856
- )
857
- )
858
- },
859
- {
860
- description: "Cloudflare Browser Rendering options."
861
- }
862
- );
863
- var cloudflareAdapter = {
864
- id: "cloudflare",
865
- label: "Cloudflare",
866
- docsUrl: "https://developers.cloudflare.com/browser-rendering/rest-api/markdown-endpoint/",
867
- getToolOptionsSchema(capability) {
868
- switch (capability) {
869
- case "contents":
870
- return cloudflareContentsOptionsSchema;
871
- default:
872
- return void 0;
873
- }
874
- },
875
- createTemplate() {
876
- return {
877
- apiToken: "CLOUDFLARE_API_TOKEN",
878
- accountId: "CLOUDFLARE_ACCOUNT_ID",
879
- options: {
880
- gotoOptions: {
881
- waitUntil: "networkidle0"
882
- }
883
- }
884
- };
885
- },
886
- getCapabilityStatus(config) {
887
- if (!resolveConfigValue(config?.apiToken)) {
888
- return { state: "missing_api_key" };
889
- }
890
- if (!resolveConfigValue(config?.accountId)) {
891
- return { state: "invalid_config", detail: "Missing account ID" };
892
- }
893
- return { state: "ready" };
894
- },
895
- async contents(urls, config, context, options) {
896
- const client = createClient(config);
897
- const accountId = resolveConfigValue(config.accountId);
898
- if (!accountId) {
899
- throw new Error("is missing an account ID");
900
- }
901
- const defaults = stripLocalExecutionOptions(asJsonObject(config.options));
902
- const answers = await Promise.all(
903
- urls.map(async (url) => {
904
- try {
905
- const markdown = await client.browserRendering.markdown.create(
906
- {
907
- ...defaults ?? {},
908
- ...options ?? {},
909
- account_id: accountId,
910
- url
911
- },
912
- buildRequestOptions(context)
913
- );
914
- return {
915
- url,
916
- content: markdown
917
- };
918
- } catch (error) {
919
- return {
920
- url,
921
- error: error instanceof Error ? error.message : String(error)
922
- };
923
- }
924
- })
925
- );
926
- return {
927
- provider: cloudflareAdapter.id,
928
- answers
929
- };
930
- }
931
- };
932
- function createClient(config) {
933
- const apiToken = resolveConfigValue(config.apiToken);
934
- if (!apiToken) {
935
- throw new Error("is missing an API token");
936
- }
937
- return new CloudflareClient({
938
- apiToken
939
- });
940
- }
941
- function buildRequestOptions(context) {
942
- return context.signal ? { signal: context.signal } : void 0;
943
- }
649
+ });
944
650
 
945
651
  // src/providers/codex.ts
946
652
  import { Codex as CodexClient } from "@openai/codex-sdk";
947
653
  import { Type as Type4 } from "typebox";
948
- var codexOutputSchema = Type4.Object({
949
- sources: Type4.Array(
950
- Type4.Object({
951
- title: Type4.String(),
952
- url: Type4.String(),
953
- snippet: Type4.String()
954
- })
955
- )
956
- });
654
+ var codexOutputSchema = Type4.Object(
655
+ {
656
+ sources: Type4.Array(
657
+ Type4.Object(
658
+ {
659
+ title: Type4.String(),
660
+ url: Type4.String(),
661
+ snippet: Type4.String()
662
+ },
663
+ { additionalProperties: false }
664
+ )
665
+ )
666
+ },
667
+ { additionalProperties: false }
668
+ );
957
669
  var codexSearchOptionsSchema = Type4.Object(
958
670
  {
959
671
  model: Type4.Optional(Type4.String({ description: "Codex model override." })),
@@ -982,7 +694,7 @@ var codexSearchOptionsSchema = Type4.Object(
982
694
  },
983
695
  { description: "Codex search options." }
984
696
  );
985
- var codexAdapter = {
697
+ var codexImplementation = {
986
698
  id: "codex",
987
699
  label: "Codex",
988
700
  docsUrl: "https://github.com/openai/codex/tree/main/sdk/typescript",
@@ -995,492 +707,955 @@ var codexAdapter = {
995
707
  }
996
708
  },
997
709
  createTemplate() {
998
- return {
999
- options: {
1000
- networkAccessEnabled: true,
1001
- webSearchEnabled: true,
1002
- webSearchMode: "live"
1003
- }
1004
- };
710
+ return {
711
+ options: {
712
+ networkAccessEnabled: true,
713
+ webSearchEnabled: true,
714
+ webSearchMode: "live"
715
+ }
716
+ };
717
+ },
718
+ getCapabilityStatus(config, _cwd) {
719
+ const effectiveConfig = config ?? codexImplementation.createTemplate();
720
+ try {
721
+ new CodexClient({
722
+ codexPathOverride: effectiveConfig.codexPath,
723
+ config: effectiveConfig.config
724
+ });
725
+ } catch (error) {
726
+ return {
727
+ state: "invalid_config",
728
+ detail: error.message
729
+ };
730
+ }
731
+ return { state: "ready" };
732
+ },
733
+ async search(query2, maxResults, config, context, options) {
734
+ const codex = new CodexClient({
735
+ codexPathOverride: config.codexPath,
736
+ baseUrl: config.baseUrl,
737
+ apiKey: resolveConfigValue(config.apiKey),
738
+ config: config.config,
739
+ env: resolveEnvMap(config.env)
740
+ });
741
+ const thread = codex.startThread(
742
+ buildCodexSearchThreadOptions(config, context.cwd, options)
743
+ );
744
+ const prompt = [
745
+ "You are performing web research for another coding agent.",
746
+ "Search the public web and return only a JSON object matching the provided schema.",
747
+ "Do not include markdown fences or extra commentary.",
748
+ `Return at most ${maxResults} sources.`,
749
+ "Prefer primary or official sources when they are available.",
750
+ "Each snippet should be short and specific.",
751
+ "",
752
+ `User query: ${query2}`
753
+ ].join("\n");
754
+ const streamed = await thread.runStreamed(prompt, {
755
+ outputSchema: codexOutputSchema,
756
+ signal: context.signal
757
+ });
758
+ let finalResponse = "";
759
+ for await (const event of streamed.events) {
760
+ if (event.type === "item.completed" && event.item.type === "agent_message") {
761
+ finalResponse = event.item.text;
762
+ }
763
+ if (event.type === "turn.failed") {
764
+ throw new Error(event.error.message);
765
+ }
766
+ }
767
+ const parsed = parseOutput(finalResponse);
768
+ return {
769
+ provider: codexImplementation.id,
770
+ results: parsed.sources.slice(0, maxResults).map((source) => ({
771
+ title: source.title.trim(),
772
+ url: source.url.trim(),
773
+ snippet: trimSnippet(source.snippet)
774
+ }))
775
+ };
776
+ }
777
+ };
778
+ function buildCodexSearchThreadOptions(config, cwd, options) {
779
+ const callOptions = getCodexSearchCallOptions(options);
780
+ const providerOptions = config.options;
781
+ return {
782
+ additionalDirectories: providerOptions?.additionalDirectories,
783
+ approvalPolicy: "never",
784
+ model: callOptions.model ?? providerOptions?.model,
785
+ modelReasoningEffort: callOptions.modelReasoningEffort ?? providerOptions?.modelReasoningEffort,
786
+ networkAccessEnabled: providerOptions?.networkAccessEnabled ?? true,
787
+ sandboxMode: "read-only",
788
+ skipGitRepoCheck: true,
789
+ webSearchEnabled: providerOptions?.webSearchEnabled ?? true,
790
+ webSearchMode: callOptions.webSearchMode ?? providerOptions?.webSearchMode ?? "live",
791
+ workingDirectory: cwd
792
+ };
793
+ }
794
+ function getCodexSearchCallOptions(options) {
795
+ if (!options) {
796
+ return {};
797
+ }
798
+ const model = readNonEmptyString2(options.model);
799
+ const modelReasoningEffort = readEnum2(options.modelReasoningEffort, [
800
+ "minimal",
801
+ "low",
802
+ "medium",
803
+ "high",
804
+ "xhigh"
805
+ ]);
806
+ const webSearchMode = readEnum2(options.webSearchMode, [
807
+ "disabled",
808
+ "cached",
809
+ "live"
810
+ ]);
811
+ return {
812
+ ...model ? { model } : {},
813
+ ...modelReasoningEffort ? { modelReasoningEffort } : {},
814
+ ...webSearchMode ? { webSearchMode } : {}
815
+ };
816
+ }
817
+ function readNonEmptyString2(value) {
818
+ return typeof value === "string" && value.trim().length > 0 ? value : void 0;
819
+ }
820
+ function readEnum2(value, values) {
821
+ return typeof value === "string" && values.includes(value) ? value : void 0;
822
+ }
823
+ function isJsonObject(value) {
824
+ return typeof value === "object" && value !== null && !Array.isArray(value);
825
+ }
826
+ function parseOutput(raw) {
827
+ const json = extractJsonObject(raw);
828
+ if (!isJsonObject(json) || !Array.isArray(json.sources) || json.sources.some(
829
+ (source) => !isJsonObject(source) || typeof source.title !== "string" || typeof source.url !== "string" || typeof source.snippet !== "string"
830
+ )) {
831
+ throw new Error("returned invalid JSON output");
832
+ }
833
+ return json;
834
+ }
835
+ function extractJsonObject(raw) {
836
+ if (!raw.trim()) {
837
+ throw new Error("returned an empty response");
838
+ }
839
+ try {
840
+ return JSON.parse(raw);
841
+ } catch {
842
+ const match = raw.match(/\{[\s\S]*\}/);
843
+ if (!match) {
844
+ throw new Error("returned invalid JSON output");
845
+ }
846
+ try {
847
+ return JSON.parse(match[0]);
848
+ } catch {
849
+ throw new Error("returned invalid JSON output");
850
+ }
851
+ }
852
+ }
853
+ var codexProvider = defineProvider({
854
+ id: "codex",
855
+ label: codexImplementation.label,
856
+ docsUrl: codexImplementation.docsUrl,
857
+ config: {
858
+ createTemplate: () => codexImplementation.createTemplate(),
859
+ fields: [
860
+ "codexPath",
861
+ "baseUrl",
862
+ "apiKey",
863
+ "env",
864
+ "config",
865
+ "options",
866
+ "settings"
867
+ ]
868
+ },
869
+ getCapabilityStatus: (config, cwd, tool) => codexImplementation.getCapabilityStatus(
870
+ config,
871
+ cwd,
872
+ tool
873
+ ),
874
+ capabilities: {
875
+ search: defineCapability({
876
+ options: codexImplementation.getToolOptionsSchema?.("search"),
877
+ async execute(input, ctx) {
878
+ const { query: query2, maxResults, options } = input;
879
+ return await codexImplementation.search(
880
+ query2,
881
+ maxResults,
882
+ ctx.config,
883
+ ctx,
884
+ options
885
+ );
886
+ }
887
+ })
888
+ }
889
+ });
890
+
891
+ // src/providers/cli-json.ts
892
+ import { spawn } from "node:child_process";
893
+ import { isAbsolute, resolve } from "node:path";
894
+ async function runCliJsonCommand({
895
+ command,
896
+ payload,
897
+ context,
898
+ label
899
+ }) {
900
+ const argv = normalizeArgv(command);
901
+ const cwd = resolveCommandCwd(command.cwd, context.cwd);
902
+ const env = {
903
+ ...process.env,
904
+ ...resolveEnvMap(command.env) ?? {}
905
+ };
906
+ return await new Promise((resolvePromise, rejectPromise) => {
907
+ let settled = false;
908
+ let stdout = "";
909
+ let stderr = "";
910
+ let abortTimer;
911
+ const child = spawn(argv[0], argv.slice(1), {
912
+ cwd,
913
+ env,
914
+ stdio: ["pipe", "pipe", "pipe"]
915
+ });
916
+ const rejectOnce = (error) => {
917
+ if (settled) {
918
+ return;
919
+ }
920
+ settled = true;
921
+ if (abortTimer) {
922
+ clearTimeout(abortTimer);
923
+ }
924
+ context.signal?.removeEventListener("abort", onAbort);
925
+ rejectPromise(error);
926
+ };
927
+ const resolveOnce = (value) => {
928
+ if (settled) {
929
+ return;
930
+ }
931
+ settled = true;
932
+ if (abortTimer) {
933
+ clearTimeout(abortTimer);
934
+ }
935
+ context.signal?.removeEventListener("abort", onAbort);
936
+ resolvePromise(value);
937
+ };
938
+ const onAbort = () => {
939
+ child.kill("SIGTERM");
940
+ abortTimer = setTimeout(() => {
941
+ child.kill("SIGKILL");
942
+ }, 1e3);
943
+ };
944
+ if (context.signal?.aborted) {
945
+ onAbort();
946
+ } else {
947
+ context.signal?.addEventListener("abort", onAbort, { once: true });
948
+ }
949
+ child.stdout.setEncoding("utf8");
950
+ child.stdout.on("data", (chunk) => {
951
+ stdout += chunk;
952
+ });
953
+ child.stderr.setEncoding("utf8");
954
+ child.stderr.on("data", (chunk) => {
955
+ stderr += chunk;
956
+ });
957
+ child.on("error", (error) => {
958
+ rejectOnce(
959
+ new Error(
960
+ `${label} failed to start: ${error.message || String(error)}`
961
+ )
962
+ );
963
+ });
964
+ child.on("close", (code, signal) => {
965
+ if (context.signal?.aborted) {
966
+ rejectOnce(new Error(`${label} was aborted.`));
967
+ return;
968
+ }
969
+ if (code !== 0) {
970
+ const detail = stderr.trim() || `exit code ${code ?? "unknown"}`;
971
+ rejectOnce(
972
+ new Error(
973
+ signal ? `${label} exited via signal ${signal}: ${detail}` : `${label} failed with exit code ${code}: ${detail}`
974
+ )
975
+ );
976
+ return;
977
+ }
978
+ const trimmed = stdout.trim();
979
+ if (!trimmed) {
980
+ rejectOnce(new Error(`${label} did not write JSON to stdout.`));
981
+ return;
982
+ }
983
+ try {
984
+ resolveOnce(JSON.parse(trimmed));
985
+ } catch (error) {
986
+ rejectOnce(
987
+ new Error(
988
+ `${label} returned invalid JSON: ${error.message}`
989
+ )
990
+ );
991
+ }
992
+ });
993
+ child.stdin.on("error", () => {
994
+ });
995
+ child.stdin.end(`${JSON.stringify(payload)}
996
+ `);
997
+ });
998
+ }
999
+ function normalizeArgv(command) {
1000
+ const argv = command.argv?.filter((entry) => entry.trim().length > 0) ?? [];
1001
+ if (argv.length === 0) {
1002
+ throw new Error("command is missing argv");
1003
+ }
1004
+ return argv;
1005
+ }
1006
+ function resolveCommandCwd(commandCwd, fallbackCwd) {
1007
+ if (!commandCwd || commandCwd.trim().length === 0) {
1008
+ return fallbackCwd;
1009
+ }
1010
+ return isAbsolute(commandCwd) ? commandCwd : resolve(fallbackCwd, commandCwd);
1011
+ }
1012
+
1013
+ // src/providers/custom.ts
1014
+ var customImplementation = {
1015
+ id: "custom",
1016
+ label: "Custom",
1017
+ docsUrl: "https://github.com/mavam/pi-web-providers#custom-provider",
1018
+ getToolOptionsSchema(_capability) {
1019
+ return void 0;
1020
+ },
1021
+ createTemplate() {
1022
+ return {};
1005
1023
  },
1006
- getCapabilityStatus(config, _cwd) {
1007
- const effectiveConfig = config ?? codexAdapter.createTemplate();
1008
- try {
1009
- new CodexClient({
1010
- codexPathOverride: effectiveConfig.codexPath,
1011
- config: effectiveConfig.config
1012
- });
1013
- } catch (error) {
1014
- return {
1015
- state: "invalid_config",
1016
- detail: error.message
1017
- };
1024
+ getCapabilityStatus(config, _cwd, capability) {
1025
+ if (capability) {
1026
+ return hasCommandForCapability(config, capability) ? { state: "ready" } : { state: "missing_command" };
1018
1027
  }
1019
- return { state: "ready" };
1028
+ return hasAnyCommand(config) ? { state: "ready" } : { state: "missing_command" };
1020
1029
  },
1021
1030
  async search(query2, maxResults, config, context, options) {
1022
- const codex = new CodexClient({
1023
- codexPathOverride: config.codexPath,
1024
- baseUrl: config.baseUrl,
1025
- apiKey: resolveConfigValue(config.apiKey),
1026
- config: config.config,
1027
- env: resolveEnvMap(config.env)
1031
+ const output = await runCommand({
1032
+ capability: "search",
1033
+ payload: {
1034
+ capability: "search",
1035
+ query: query2,
1036
+ maxResults,
1037
+ ...options ? { options } : {}
1038
+ },
1039
+ config,
1040
+ context
1028
1041
  });
1029
- const thread = codex.startThread(
1030
- buildCodexSearchThreadOptions(config, context.cwd, options)
1031
- );
1032
- const prompt = [
1033
- "You are performing web research for another coding agent.",
1034
- "Search the public web and return only a JSON object matching the provided schema.",
1035
- "Do not include markdown fences or extra commentary.",
1036
- `Return at most ${maxResults} sources.`,
1037
- "Prefer primary or official sources when they are available.",
1038
- "Each snippet should be short and specific.",
1039
- "",
1040
- `User query: ${query2}`
1041
- ].join("\n");
1042
- const streamed = await thread.runStreamed(prompt, {
1043
- outputSchema: codexOutputSchema,
1044
- signal: context.signal
1042
+ return parseSearchResponse(output, customImplementation.id);
1043
+ },
1044
+ async contents(urls, config, context, options) {
1045
+ const output = await runCommand({
1046
+ capability: "contents",
1047
+ payload: {
1048
+ capability: "contents",
1049
+ urls,
1050
+ ...options ? { options } : {}
1051
+ },
1052
+ config,
1053
+ context
1045
1054
  });
1046
- let finalResponse = "";
1047
- for await (const event of streamed.events) {
1048
- if (event.type === "item.completed" && event.item.type === "agent_message") {
1049
- finalResponse = event.item.text;
1050
- }
1051
- if (event.type === "turn.failed") {
1052
- throw new Error(event.error.message);
1053
- }
1054
- }
1055
- const parsed = parseOutput(finalResponse);
1056
- return {
1057
- provider: codexAdapter.id,
1058
- results: parsed.sources.slice(0, maxResults).map((source) => ({
1059
- title: source.title.trim(),
1060
- url: source.url.trim(),
1061
- snippet: trimSnippet(source.snippet)
1062
- }))
1063
- };
1055
+ return parseContentsResponse(output, customImplementation.id);
1056
+ },
1057
+ async answer(query2, config, context, options) {
1058
+ const output = await runCommand({
1059
+ capability: "answer",
1060
+ payload: {
1061
+ capability: "answer",
1062
+ query: query2,
1063
+ ...options ? { options } : {}
1064
+ },
1065
+ config,
1066
+ context
1067
+ });
1068
+ return parseToolOutput(output, customImplementation.id);
1069
+ },
1070
+ async research(input, config, context, options) {
1071
+ const output = await runCommand({
1072
+ capability: "research",
1073
+ payload: {
1074
+ capability: "research",
1075
+ input,
1076
+ ...options ? { options } : {}
1077
+ },
1078
+ config,
1079
+ context
1080
+ });
1081
+ return parseToolOutput(output, customImplementation.id);
1064
1082
  }
1065
1083
  };
1066
- function buildCodexSearchThreadOptions(config, cwd, options) {
1067
- const runtimeOptions = getCodexSearchRuntimeOptions(options);
1068
- const providerOptions = config.options;
1084
+ async function runCommand({
1085
+ capability,
1086
+ payload,
1087
+ config,
1088
+ context
1089
+ }) {
1090
+ const command = getCommandConfig(config, capability);
1091
+ if (!command) {
1092
+ throw new Error(`has no command configured for ${capability}`);
1093
+ }
1094
+ return await runCliJsonCommand({
1095
+ command,
1096
+ payload: {
1097
+ ...payload,
1098
+ cwd: context.cwd
1099
+ },
1100
+ context,
1101
+ label: `Custom ${capability}`
1102
+ });
1103
+ }
1104
+ function getCommandConfig(config, capability) {
1105
+ return config?.options?.[capability];
1106
+ }
1107
+ function hasCommandForCapability(config, capability) {
1108
+ return normalizeConfiguredArgv(getCommandConfig(config, capability)).length > 0;
1109
+ }
1110
+ function hasAnyCommand(config) {
1111
+ return hasCommandForCapability(config, "search") || hasCommandForCapability(config, "contents") || hasCommandForCapability(config, "answer") || hasCommandForCapability(config, "research");
1112
+ }
1113
+ function normalizeConfiguredArgv(command) {
1114
+ return command?.argv?.filter((entry) => entry.trim().length > 0) ?? [];
1115
+ }
1116
+ function parseSearchResponse(value, providerId) {
1117
+ const response = requireObject(value, "search output must be a JSON object");
1118
+ if (!Array.isArray(response.results)) {
1119
+ throw new Error("search output must include a 'results' array");
1120
+ }
1069
1121
  return {
1070
- additionalDirectories: providerOptions?.additionalDirectories,
1071
- approvalPolicy: "never",
1072
- model: runtimeOptions.model ?? providerOptions?.model,
1073
- modelReasoningEffort: runtimeOptions.modelReasoningEffort ?? providerOptions?.modelReasoningEffort,
1074
- networkAccessEnabled: providerOptions?.networkAccessEnabled ?? true,
1075
- sandboxMode: "read-only",
1076
- skipGitRepoCheck: true,
1077
- webSearchEnabled: providerOptions?.webSearchEnabled ?? true,
1078
- webSearchMode: runtimeOptions.webSearchMode ?? providerOptions?.webSearchMode ?? "live",
1079
- workingDirectory: cwd
1122
+ provider: providerId,
1123
+ results: response.results.map(
1124
+ (entry, index) => parseSearchResult(entry, index)
1125
+ )
1126
+ };
1127
+ }
1128
+ function parseSearchResult(entry, index) {
1129
+ const value = requireObject(
1130
+ entry,
1131
+ `search result at index ${index} must be a JSON object`
1132
+ );
1133
+ const metadata = readLenientJsonObject(value.metadata);
1134
+ return {
1135
+ title: readRequiredString(value.title, `results[${index}].title`),
1136
+ url: readRequiredString(value.url, `results[${index}].url`),
1137
+ snippet: readRequiredString(value.snippet, `results[${index}].snippet`),
1138
+ ...typeof value.score === "number" ? { score: value.score } : {},
1139
+ ...metadata !== void 0 ? { metadata } : {}
1140
+ };
1141
+ }
1142
+ function parseContentsResponse(value, providerId) {
1143
+ const response = requireObject(
1144
+ value,
1145
+ "contents output must be a JSON object"
1146
+ );
1147
+ if (!Array.isArray(response.answers)) {
1148
+ throw new Error("contents output must include an 'answers' array");
1149
+ }
1150
+ return {
1151
+ provider: providerId,
1152
+ answers: response.answers.map(
1153
+ (entry, index) => parseContentsAnswer(entry, index)
1154
+ )
1155
+ };
1156
+ }
1157
+ function parseContentsAnswer(entry, index) {
1158
+ const value = requireObject(
1159
+ entry,
1160
+ `contents answer at index ${index} must be a JSON object`
1161
+ );
1162
+ const url = readRequiredString(value.url, `answers[${index}].url`);
1163
+ const content = readOptionalString(
1164
+ value.content,
1165
+ `answers[${index}].content`
1166
+ );
1167
+ const summary = value.summary;
1168
+ const metadata = readRequiredJsonObject(
1169
+ value.metadata,
1170
+ `answers[${index}].metadata`
1171
+ );
1172
+ const error = readOptionalString(value.error, `answers[${index}].error`);
1173
+ if (content === void 0 && error === void 0) {
1174
+ throw new Error(
1175
+ `contents answer at index ${index} must include 'content' or 'error'`
1176
+ );
1177
+ }
1178
+ return {
1179
+ url,
1180
+ ...content !== void 0 ? { content } : {},
1181
+ ...summary !== void 0 ? { summary } : {},
1182
+ ...metadata !== void 0 ? { metadata } : {},
1183
+ ...error !== void 0 ? { error } : {}
1184
+ };
1185
+ }
1186
+ function parseToolOutput(value, providerId) {
1187
+ const output = requireObject(value, "output must be a JSON object");
1188
+ const metadata = readLenientJsonObject(output.metadata);
1189
+ return {
1190
+ provider: providerId,
1191
+ text: readRequiredString(output.text, "text"),
1192
+ ...readOptionalNonNegativeInteger(output.itemCount),
1193
+ ...metadata !== void 0 ? { metadata } : {}
1080
1194
  };
1081
1195
  }
1082
- function getCodexSearchRuntimeOptions(options) {
1083
- if (!options) {
1084
- return {};
1196
+ function readRequiredJsonObject(value, field) {
1197
+ if (value === void 0) {
1198
+ return void 0;
1085
1199
  }
1086
- const model = readNonEmptyString2(options.model);
1087
- const modelReasoningEffort = readEnum2(options.modelReasoningEffort, [
1088
- "minimal",
1089
- "low",
1090
- "medium",
1091
- "high",
1092
- "xhigh"
1093
- ]);
1094
- const webSearchMode = readEnum2(options.webSearchMode, [
1095
- "disabled",
1096
- "cached",
1097
- "live"
1098
- ]);
1200
+ return requireObject(value, `output field '${field}' must be a JSON object`);
1201
+ }
1202
+ function readLenientJsonObject(value) {
1203
+ return isJsonObject2(value) ? value : void 0;
1204
+ }
1205
+ function readRequiredString(value, field) {
1206
+ if (typeof value !== "string") {
1207
+ throw new Error(`output field '${field}' must be a string`);
1208
+ }
1209
+ return value;
1210
+ }
1211
+ function readOptionalString(value, field) {
1212
+ if (value === void 0) {
1213
+ return void 0;
1214
+ }
1215
+ return readRequiredString(value, field);
1216
+ }
1217
+ function readOptionalNonNegativeInteger(value) {
1218
+ return typeof value === "number" && Number.isInteger(value) && value >= 0 ? { itemCount: value } : {};
1219
+ }
1220
+ function requireObject(value, message) {
1221
+ if (!isJsonObject2(value)) {
1222
+ throw new Error(message);
1223
+ }
1224
+ return value;
1225
+ }
1226
+ function isJsonObject2(value) {
1227
+ return typeof value === "object" && value !== null && !Array.isArray(value);
1228
+ }
1229
+ var customProvider = defineProvider({
1230
+ id: "custom",
1231
+ label: customImplementation.label,
1232
+ docsUrl: customImplementation.docsUrl,
1233
+ config: {
1234
+ createTemplate: () => customImplementation.createTemplate(),
1235
+ fields: ["customOptions", "settings"]
1236
+ },
1237
+ getCapabilityStatus: (config, cwd, tool) => customImplementation.getCapabilityStatus(
1238
+ config,
1239
+ cwd,
1240
+ tool
1241
+ ),
1242
+ capabilities: {
1243
+ search: defineCapability({
1244
+ options: customImplementation.getToolOptionsSchema?.("search"),
1245
+ async execute(input, ctx) {
1246
+ const { query: query2, maxResults, options } = input;
1247
+ return await customImplementation.search(
1248
+ query2,
1249
+ maxResults,
1250
+ ctx.config,
1251
+ ctx,
1252
+ options
1253
+ );
1254
+ }
1255
+ }),
1256
+ contents: defineCapability({
1257
+ options: customImplementation.getToolOptionsSchema?.("contents"),
1258
+ async execute(input, ctx) {
1259
+ return await customImplementation.contents(
1260
+ input.urls,
1261
+ ctx.config,
1262
+ ctx,
1263
+ input.options
1264
+ );
1265
+ }
1266
+ }),
1267
+ answer: defineCapability({
1268
+ options: customImplementation.getToolOptionsSchema?.("answer"),
1269
+ async execute(input, ctx) {
1270
+ return await customImplementation.answer(
1271
+ input.query,
1272
+ ctx.config,
1273
+ ctx,
1274
+ input.options
1275
+ );
1276
+ }
1277
+ }),
1278
+ research: defineCapability({
1279
+ options: customImplementation.getToolOptionsSchema?.("research"),
1280
+ async execute(input, ctx) {
1281
+ return await customImplementation.research(
1282
+ input.input,
1283
+ ctx.config,
1284
+ ctx,
1285
+ input.options
1286
+ );
1287
+ }
1288
+ })
1289
+ }
1290
+ });
1291
+
1292
+ // src/providers/exa.ts
1293
+ import { Exa as ExaClient } from "exa-js";
1294
+ import { Type as Type5 } from "typebox";
1295
+
1296
+ // src/execution-policy-defaults.ts
1297
+ var DEFAULT_REQUEST_TIMEOUT_MS = 3e4;
1298
+ var DEFAULT_RETRY_COUNT = 3;
1299
+ var DEFAULT_RETRY_DELAY_MS = 2e3;
1300
+ var DEFAULT_RESEARCH_POLL_INTERVAL_MS = 3e3;
1301
+ var DEFAULT_RESEARCH_TIMEOUT_MS = 18e5;
1302
+ var DEFAULT_RESEARCH_MAX_CONSECUTIVE_POLL_ERRORS = 3;
1303
+ var DEFAULT_GEMINI_RESEARCH_MAX_CONSECUTIVE_POLL_ERRORS = 10;
1304
+ function createDefaultExecutionSettings(overrides = {}) {
1099
1305
  return {
1100
- ...model ? { model } : {},
1101
- ...modelReasoningEffort ? { modelReasoningEffort } : {},
1102
- ...webSearchMode ? { webSearchMode } : {}
1306
+ requestTimeoutMs: DEFAULT_REQUEST_TIMEOUT_MS,
1307
+ retryCount: DEFAULT_RETRY_COUNT,
1308
+ retryDelayMs: DEFAULT_RETRY_DELAY_MS,
1309
+ researchTimeoutMs: DEFAULT_RESEARCH_TIMEOUT_MS,
1310
+ ...overrides
1103
1311
  };
1104
1312
  }
1105
- function readNonEmptyString2(value) {
1106
- return typeof value === "string" && value.trim().length > 0 ? value : void 0;
1313
+
1314
+ // src/provider-diagnostics.ts
1315
+ function normalizeDiagnosticDetail(detail) {
1316
+ return detail.trim().replace(/[.\s]+$/u, "");
1107
1317
  }
1108
- function readEnum2(value, values) {
1109
- return typeof value === "string" && values.includes(value) ? value : void 0;
1318
+ function startsWithProviderLabel(providerLabel, detail) {
1319
+ return detail.toLowerCase().startsWith(providerLabel.toLowerCase());
1110
1320
  }
1111
- function isJsonObject(value) {
1112
- return typeof value === "object" && value !== null && !Array.isArray(value);
1321
+ function readsLikeProviderClause(detail) {
1322
+ return /^(is|has|was|returned|did|does|could|cannot|must|should|search\b|contents\b|answer\b|research\b|output\b|response\b|result\b|query\b|no\b|missing\b|deep research\b)/iu.test(
1323
+ detail
1324
+ );
1113
1325
  }
1114
- function parseOutput(raw) {
1115
- const json = extractJsonObject(raw);
1116
- if (!isJsonObject(json) || !Array.isArray(json.sources) || json.sources.some(
1117
- (source) => !isJsonObject(source) || typeof source.title !== "string" || typeof source.url !== "string" || typeof source.snippet !== "string"
1118
- )) {
1119
- throw new Error("returned invalid JSON output");
1326
+ function formatProviderDiagnostic(providerLabel, detail) {
1327
+ const normalized = normalizeDiagnosticDetail(detail);
1328
+ if (!normalized) {
1329
+ return `${providerLabel} failed.`;
1120
1330
  }
1121
- return json;
1331
+ if (startsWithProviderLabel(providerLabel, normalized)) {
1332
+ return `${normalized}.`;
1333
+ }
1334
+ if (readsLikeProviderClause(normalized)) {
1335
+ return `${providerLabel} ${normalized}.`;
1336
+ }
1337
+ return `${providerLabel}: ${normalized}.`;
1122
1338
  }
1123
- function extractJsonObject(raw) {
1124
- if (!raw.trim()) {
1125
- throw new Error("returned an empty response");
1339
+ function formatResearchTerminalDiagnostic(providerLabel, status, detail) {
1340
+ const normalized = detail ? normalizeDiagnosticDetail(detail) : "";
1341
+ if (!normalized) {
1342
+ return status === "cancelled" ? `${providerLabel} research was canceled.` : `${providerLabel} research failed.`;
1126
1343
  }
1127
- try {
1128
- return JSON.parse(raw);
1129
- } catch {
1130
- const match = raw.match(/\{[\s\S]*\}/);
1131
- if (!match) {
1132
- throw new Error("returned invalid JSON output");
1133
- }
1134
- try {
1135
- return JSON.parse(match[0]);
1136
- } catch {
1137
- throw new Error("returned invalid JSON output");
1138
- }
1344
+ if (startsWithProviderLabel(providerLabel, normalized)) {
1345
+ return `${normalized}.`;
1346
+ }
1347
+ if (/^research\b/iu.test(normalized)) {
1348
+ return `${providerLabel} ${normalized}.`;
1139
1349
  }
1350
+ return status === "cancelled" ? `${providerLabel} research was canceled: ${normalized}.` : `${providerLabel} research failed: ${normalized}.`;
1140
1351
  }
1141
1352
 
1142
- // src/providers/cli-json.ts
1143
- import { spawn } from "node:child_process";
1144
- import { isAbsolute, resolve } from "node:path";
1145
- async function runCliJsonCommand({
1146
- command,
1147
- payload,
1148
- context,
1149
- label
1150
- }) {
1151
- const argv = normalizeArgv(command);
1152
- const cwd = resolveCommandCwd(command.cwd, context.cwd);
1153
- const env = {
1154
- ...process.env,
1155
- ...resolveEnvMap(command.env) ?? {}
1156
- };
1157
- return await new Promise((resolvePromise, rejectPromise) => {
1158
- let settled = false;
1159
- let stdout = "";
1160
- let stderr = "";
1161
- let abortTimer;
1162
- const child = spawn(argv[0], argv.slice(1), {
1163
- cwd,
1164
- env,
1165
- stdio: ["pipe", "pipe", "pipe"]
1166
- });
1167
- const rejectOnce = (error) => {
1168
- if (settled) {
1169
- return;
1170
- }
1171
- settled = true;
1172
- if (abortTimer) {
1173
- clearTimeout(abortTimer);
1174
- }
1175
- context.signal?.removeEventListener("abort", onAbort);
1176
- rejectPromise(error);
1177
- };
1178
- const resolveOnce = (value) => {
1179
- if (settled) {
1180
- return;
1181
- }
1182
- settled = true;
1183
- if (abortTimer) {
1184
- clearTimeout(abortTimer);
1353
+ // src/execution-policy.ts
1354
+ var MAX_RETRY_DELAY_MS = 3e4;
1355
+ var RequestTimeoutError = class extends Error {
1356
+ name = "RequestTimeoutError";
1357
+ };
1358
+ async function runWithExecutionPolicy(label, operation, settings, context) {
1359
+ const maxAttempts = Math.max(1, settings.retryCount + 1);
1360
+ for (let attempt = 1; attempt <= maxAttempts; attempt += 1) {
1361
+ throwIfAborted(context.signal);
1362
+ const {
1363
+ context: attemptContext,
1364
+ abort,
1365
+ cleanup
1366
+ } = createAttemptContext(context);
1367
+ try {
1368
+ const result = operation(attemptContext);
1369
+ const timeoutMessage = settings.requestTimeoutMs === void 0 ? void 0 : `${label} timed out after ${formatDuration(settings.requestTimeoutMs)}.`;
1370
+ return await withAbortAndOptionalTimeout(
1371
+ result,
1372
+ settings.requestTimeoutMs,
1373
+ context.signal,
1374
+ timeoutMessage,
1375
+ timeoutMessage ? () => abort(new RequestTimeoutError(timeoutMessage)) : void 0
1376
+ );
1377
+ } catch (error) {
1378
+ if (!shouldRetryError(error, settings) || attempt >= maxAttempts) {
1379
+ throw error;
1185
1380
  }
1186
- context.signal?.removeEventListener("abort", onAbort);
1187
- resolvePromise(value);
1188
- };
1189
- const onAbort = () => {
1190
- child.kill("SIGTERM");
1191
- abortTimer = setTimeout(() => {
1192
- child.kill("SIGKILL");
1193
- }, 1e3);
1194
- };
1195
- if (context.signal?.aborted) {
1196
- onAbort();
1197
- } else {
1198
- context.signal?.addEventListener("abort", onAbort, { once: true });
1381
+ const delayMs = Math.min(
1382
+ settings.retryDelayMs * 2 ** (attempt - 1),
1383
+ MAX_RETRY_DELAY_MS
1384
+ );
1385
+ context.onProgress?.(
1386
+ `${label} failed (${formatErrorMessage(error)}). Retrying in ${formatDuration(delayMs)} (attempt ${attempt + 1}/${maxAttempts}).`
1387
+ );
1388
+ await sleep(delayMs, context.signal);
1389
+ } finally {
1390
+ cleanup();
1391
+ }
1392
+ }
1393
+ throw new Error(`${label} failed.`);
1394
+ }
1395
+ async function executeAsyncResearch({
1396
+ providerLabel,
1397
+ providerId,
1398
+ context,
1399
+ pollIntervalMs = DEFAULT_RESEARCH_POLL_INTERVAL_MS,
1400
+ timeoutMs = DEFAULT_RESEARCH_TIMEOUT_MS,
1401
+ maxConsecutivePollErrors = DEFAULT_RESEARCH_MAX_CONSECUTIVE_POLL_ERRORS,
1402
+ start,
1403
+ poll
1404
+ }) {
1405
+ const timeoutMessage = `${providerLabel} research exceeded ${formatDuration(timeoutMs)}.`;
1406
+ const deadline = createDeadlineSignal(
1407
+ context.signal,
1408
+ timeoutMs,
1409
+ timeoutMessage
1410
+ );
1411
+ const researchContext = {
1412
+ ...context,
1413
+ signal: deadline.signal
1414
+ };
1415
+ let lastStatus;
1416
+ let lastProgressStatus;
1417
+ const startedAt = Date.now();
1418
+ try {
1419
+ researchContext.onProgress?.(`Starting research via ${providerLabel}`);
1420
+ const job = await withAbortAndOptionalTimeout(
1421
+ start(researchContext),
1422
+ void 0,
1423
+ researchContext.signal,
1424
+ void 0
1425
+ );
1426
+ const jobId = job.id;
1427
+ if (!jobId) {
1428
+ throw new Error(`${providerLabel} research did not return a job id.`);
1199
1429
  }
1200
- child.stdout.setEncoding("utf8");
1201
- child.stdout.on("data", (chunk) => {
1202
- stdout += chunk;
1203
- });
1204
- child.stderr.setEncoding("utf8");
1205
- child.stderr.on("data", (chunk) => {
1206
- stderr += chunk;
1207
- });
1208
- child.on("error", (error) => {
1209
- rejectOnce(
1210
- new Error(
1211
- `${label} failed to start: ${error.message || String(error)}`
1212
- )
1430
+ researchContext.onProgress?.(`${providerLabel} research started: ${jobId}`);
1431
+ let consecutivePollErrors = 0;
1432
+ while (true) {
1433
+ throwIfAborted(
1434
+ researchContext.signal,
1435
+ `${providerLabel} research aborted.`
1213
1436
  );
1214
- });
1215
- child.on("close", (code, signal) => {
1216
- if (context.signal?.aborted) {
1217
- rejectOnce(new Error(`${label} was aborted.`));
1218
- return;
1219
- }
1220
- if (code !== 0) {
1221
- const detail = stderr.trim() || `exit code ${code ?? "unknown"}`;
1222
- rejectOnce(
1223
- new Error(
1224
- signal ? `${label} exited via signal ${signal}: ${detail}` : `${label} failed with exit code ${code}: ${detail}`
1225
- )
1226
- );
1227
- return;
1228
- }
1229
- const trimmed = stdout.trim();
1230
- if (!trimmed) {
1231
- rejectOnce(new Error(`${label} did not write JSON to stdout.`));
1232
- return;
1233
- }
1234
1437
  try {
1235
- resolveOnce(JSON.parse(trimmed));
1438
+ const result = await withAbortAndOptionalTimeout(
1439
+ poll(jobId, researchContext),
1440
+ void 0,
1441
+ researchContext.signal,
1442
+ void 0
1443
+ );
1444
+ consecutivePollErrors = 0;
1445
+ const progressStatus = result.statusText ?? result.status;
1446
+ if (result.status !== lastStatus || progressStatus !== lastProgressStatus) {
1447
+ researchContext.onProgress?.(
1448
+ `Research via ${providerLabel}: ${progressStatus} (${formatElapsed(Date.now() - startedAt)} elapsed)`
1449
+ );
1450
+ lastStatus = result.status;
1451
+ lastProgressStatus = progressStatus;
1452
+ }
1453
+ if (result.status === "completed") {
1454
+ return result.output ?? {
1455
+ provider: providerId,
1456
+ text: `${providerLabel} research completed without textual output.`
1457
+ };
1458
+ }
1459
+ if (result.status === "failed" || result.status === "cancelled") {
1460
+ throw new Error(
1461
+ formatResearchTerminalDiagnostic(
1462
+ providerLabel,
1463
+ result.status,
1464
+ result.error
1465
+ )
1466
+ );
1467
+ }
1236
1468
  } catch (error) {
1237
- rejectOnce(
1238
- new Error(
1239
- `${label} returned invalid JSON: ${error.message}`
1240
- )
1469
+ if (isAbortErrorFromSignal(researchContext.signal, error)) {
1470
+ throw error;
1471
+ }
1472
+ if (!isRetryableError(error)) {
1473
+ throw normalizeError(error);
1474
+ }
1475
+ consecutivePollErrors += 1;
1476
+ if (consecutivePollErrors >= maxConsecutivePollErrors) {
1477
+ throw new Error(
1478
+ `${providerLabel} research polling failed too many times in a row: ${formatErrorMessage(error)}`
1479
+ );
1480
+ }
1481
+ researchContext.onProgress?.(
1482
+ `${providerLabel} research poll is still retrying after transient errors (${consecutivePollErrors}/${maxConsecutivePollErrors} consecutive poll failures). Background job id: ${jobId}`
1241
1483
  );
1242
1484
  }
1243
- });
1244
- child.stdin.on("error", () => {
1245
- });
1246
- child.stdin.end(`${JSON.stringify(payload)}
1247
- `);
1248
- });
1249
- }
1250
- function normalizeArgv(command) {
1251
- const argv = command.argv?.filter((entry) => entry.trim().length > 0) ?? [];
1252
- if (argv.length === 0) {
1253
- throw new Error("command is missing argv");
1485
+ await sleep(pollIntervalMs, researchContext.signal);
1486
+ }
1487
+ } catch (error) {
1488
+ if (isAbortErrorFromSignal(researchContext.signal, error)) {
1489
+ throw new Error(
1490
+ formatProviderDiagnostic(providerLabel, formatErrorMessage(error))
1491
+ );
1492
+ }
1493
+ throw new Error(
1494
+ formatProviderDiagnostic(providerLabel, formatErrorMessage(error))
1495
+ );
1496
+ } finally {
1497
+ deadline.cleanup();
1254
1498
  }
1255
- return argv;
1256
1499
  }
1257
- function resolveCommandCwd(commandCwd, fallbackCwd) {
1258
- if (!commandCwd || commandCwd.trim().length === 0) {
1259
- return fallbackCwd;
1500
+ function shouldRetryError(error, settings) {
1501
+ if (error instanceof RequestTimeoutError) {
1502
+ return settings.retryOnTimeout === true;
1260
1503
  }
1261
- return isAbsolute(commandCwd) ? commandCwd : resolve(fallbackCwd, commandCwd);
1504
+ return isRetryableError(error);
1262
1505
  }
1263
-
1264
- // src/providers/custom.ts
1265
- var customAdapter = {
1266
- id: "custom",
1267
- label: "Custom",
1268
- docsUrl: "https://github.com/mavam/pi-web-providers#custom-provider",
1269
- getToolOptionsSchema(_capability) {
1270
- return void 0;
1271
- },
1272
- createTemplate() {
1273
- return {};
1274
- },
1275
- getCapabilityStatus(config, _cwd, capability) {
1276
- if (capability) {
1277
- return hasCommandForCapability(config, capability) ? { state: "ready" } : { state: "missing_command" };
1278
- }
1279
- return hasAnyCommand(config) ? { state: "ready" } : { state: "missing_command" };
1280
- },
1281
- async search(query2, maxResults, config, context, options) {
1282
- const output = await runCommand({
1283
- capability: "search",
1284
- payload: {
1285
- capability: "search",
1286
- query: query2,
1287
- maxResults,
1288
- ...options ? { options } : {}
1289
- },
1290
- config,
1291
- context
1292
- });
1293
- return parseSearchResponse(output, customAdapter.id);
1294
- },
1295
- async contents(urls, config, context, options) {
1296
- const output = await runCommand({
1297
- capability: "contents",
1298
- payload: {
1299
- capability: "contents",
1300
- urls,
1301
- ...options ? { options } : {}
1302
- },
1303
- config,
1304
- context
1305
- });
1306
- return parseContentsResponse(output, customAdapter.id);
1307
- },
1308
- async answer(query2, config, context, options) {
1309
- const output = await runCommand({
1310
- capability: "answer",
1311
- payload: {
1312
- capability: "answer",
1313
- query: query2,
1314
- ...options ? { options } : {}
1315
- },
1316
- config,
1317
- context
1318
- });
1319
- return parseToolOutput(output, customAdapter.id);
1320
- },
1321
- async research(input, config, context, options) {
1322
- const output = await runCommand({
1323
- capability: "research",
1324
- payload: {
1325
- capability: "research",
1326
- input,
1327
- ...options ? { options } : {}
1328
- },
1329
- config,
1330
- context
1331
- });
1332
- return parseToolOutput(output, customAdapter.id);
1506
+ function isRetryableError(error) {
1507
+ if (error instanceof RequestTimeoutError) {
1508
+ return false;
1333
1509
  }
1334
- };
1335
- async function runCommand({
1336
- capability,
1337
- payload,
1338
- config,
1339
- context
1340
- }) {
1341
- const command = getCommandConfig(config, capability);
1342
- if (!command) {
1343
- throw new Error(`has no command configured for ${capability}`);
1510
+ const message = formatErrorMessage(error).toLowerCase();
1511
+ if (!message || message === "operation aborted.") {
1512
+ return false;
1344
1513
  }
1345
- return await runCliJsonCommand({
1346
- command,
1347
- payload: {
1348
- ...payload,
1349
- cwd: context.cwd
1350
- },
1351
- context,
1352
- label: `Custom ${capability}`
1353
- });
1354
- }
1355
- function getCommandConfig(config, capability) {
1356
- return config?.options?.[capability];
1357
- }
1358
- function hasCommandForCapability(config, capability) {
1359
- return normalizeConfiguredArgv(getCommandConfig(config, capability)).length > 0;
1514
+ return /429|500|502|503|504|deadline exceeded|econnreset|ecanceled|ehostunreach|eai_again|enotfound|etimedout|fetch failed|gateway timeout|internal error|network|overloaded|rate limit|resource exhausted|socket hang up|temporarily unavailable|timeout|unavailable/.test(
1515
+ message
1516
+ );
1360
1517
  }
1361
- function hasAnyCommand(config) {
1362
- return hasCommandForCapability(config, "search") || hasCommandForCapability(config, "contents") || hasCommandForCapability(config, "answer") || hasCommandForCapability(config, "research");
1518
+ function formatErrorMessage(error) {
1519
+ if (error instanceof Error) {
1520
+ return error.message;
1521
+ }
1522
+ if (typeof error === "object" && error !== null && "message" in error && typeof error.message === "string") {
1523
+ return error.message;
1524
+ }
1525
+ return String(error);
1363
1526
  }
1364
- function normalizeConfiguredArgv(command) {
1365
- return command?.argv?.filter((entry) => entry.trim().length > 0) ?? [];
1527
+ function formatElapsed(ms) {
1528
+ const totalSeconds = Math.max(0, Math.floor(ms / 1e3));
1529
+ const minutes = Math.floor(totalSeconds / 60);
1530
+ const seconds = totalSeconds % 60;
1531
+ if (minutes > 0) {
1532
+ return `${minutes}m ${seconds}s`;
1533
+ }
1534
+ return `${totalSeconds}s`;
1366
1535
  }
1367
- function parseSearchResponse(value, providerId) {
1368
- const response = requireObject(value, "search output must be a JSON object");
1369
- if (!Array.isArray(response.results)) {
1370
- throw new Error("search output must include a 'results' array");
1536
+ function formatDuration(ms) {
1537
+ if (ms >= 6e4) {
1538
+ return formatElapsed(ms);
1371
1539
  }
1372
- return {
1373
- provider: providerId,
1374
- results: response.results.map(
1375
- (entry, index) => parseSearchResult(entry, index)
1376
- )
1377
- };
1540
+ if (ms >= 1e3) {
1541
+ return `${Math.floor(ms / 1e3)}s`;
1542
+ }
1543
+ return `${ms}ms`;
1378
1544
  }
1379
- function parseSearchResult(entry, index) {
1380
- const value = requireObject(
1381
- entry,
1382
- `search result at index ${index} must be a JSON object`
1383
- );
1384
- const metadata = readLenientJsonObject(value.metadata);
1385
- return {
1386
- title: readRequiredString(value.title, `results[${index}].title`),
1387
- url: readRequiredString(value.url, `results[${index}].url`),
1388
- snippet: readRequiredString(value.snippet, `results[${index}].snippet`),
1389
- ...typeof value.score === "number" ? { score: value.score } : {},
1390
- ...metadata !== void 0 ? { metadata } : {}
1391
- };
1545
+ async function sleep(ms, signal) {
1546
+ throwIfAborted(signal);
1547
+ await new Promise((resolve2, reject) => {
1548
+ const timer = setTimeout(() => {
1549
+ signal?.removeEventListener("abort", onAbort);
1550
+ resolve2();
1551
+ }, ms);
1552
+ const onAbort = () => {
1553
+ clearTimeout(timer);
1554
+ signal?.removeEventListener("abort", onAbort);
1555
+ reject(getAbortError(signal));
1556
+ };
1557
+ signal?.addEventListener("abort", onAbort, { once: true });
1558
+ });
1392
1559
  }
1393
- function parseContentsResponse(value, providerId) {
1394
- const response = requireObject(
1395
- value,
1396
- "contents output must be a JSON object"
1397
- );
1398
- if (!Array.isArray(response.answers)) {
1399
- throw new Error("contents output must include an 'answers' array");
1560
+ function throwIfAborted(signal, message = "Operation aborted.") {
1561
+ if (signal?.aborted) {
1562
+ throw getAbortError(signal, message);
1400
1563
  }
1401
- return {
1402
- provider: providerId,
1403
- answers: response.answers.map(
1404
- (entry, index) => parseContentsAnswer(entry, index)
1405
- )
1406
- };
1407
1564
  }
1408
- function parseContentsAnswer(entry, index) {
1409
- const value = requireObject(
1410
- entry,
1411
- `contents answer at index ${index} must be a JSON object`
1412
- );
1413
- const url = readRequiredString(value.url, `answers[${index}].url`);
1414
- const content = readOptionalString(
1415
- value.content,
1416
- `answers[${index}].content`
1417
- );
1418
- const summary = value.summary;
1419
- const metadata = readRequiredJsonObject(
1420
- value.metadata,
1421
- `answers[${index}].metadata`
1422
- );
1423
- const error = readOptionalString(value.error, `answers[${index}].error`);
1424
- if (content === void 0 && error === void 0) {
1425
- throw new Error(
1426
- `contents answer at index ${index} must include 'content' or 'error'`
1427
- );
1565
+ function createAttemptContext(context) {
1566
+ const controller = new AbortController();
1567
+ if (context.signal?.aborted) {
1568
+ controller.abort(getAbortError(context.signal));
1428
1569
  }
1429
- return {
1430
- url,
1431
- ...content !== void 0 ? { content } : {},
1432
- ...summary !== void 0 ? { summary } : {},
1433
- ...metadata !== void 0 ? { metadata } : {},
1434
- ...error !== void 0 ? { error } : {}
1570
+ const onAbort = () => {
1571
+ controller.abort(getAbortError(context.signal));
1435
1572
  };
1436
- }
1437
- function parseToolOutput(value, providerId) {
1438
- const output = requireObject(value, "output must be a JSON object");
1439
- const metadata = readLenientJsonObject(output.metadata);
1573
+ context.signal?.addEventListener("abort", onAbort, { once: true });
1440
1574
  return {
1441
- provider: providerId,
1442
- text: readRequiredString(output.text, "text"),
1443
- ...readOptionalNonNegativeInteger(output.itemCount),
1444
- ...metadata !== void 0 ? { metadata } : {}
1575
+ context: {
1576
+ ...context,
1577
+ signal: controller.signal
1578
+ },
1579
+ abort: (reason) => controller.abort(reason),
1580
+ cleanup: () => context.signal?.removeEventListener("abort", onAbort)
1445
1581
  };
1446
1582
  }
1447
- function readRequiredJsonObject(value, field) {
1448
- if (value === void 0) {
1449
- return void 0;
1583
+ async function withAbortAndOptionalTimeout(promise, timeoutMs, signal, message, onTimeout) {
1584
+ if (timeoutMs === void 0 && !signal) {
1585
+ return await promise;
1450
1586
  }
1451
- return requireObject(value, `output field '${field}' must be a JSON object`);
1452
- }
1453
- function readLenientJsonObject(value) {
1454
- return isJsonObject2(value) ? value : void 0;
1587
+ throwIfAborted(signal);
1588
+ return await new Promise((resolve2, reject) => {
1589
+ const timer = timeoutMs === void 0 ? void 0 : setTimeout(() => {
1590
+ onTimeout?.();
1591
+ cleanup();
1592
+ reject(
1593
+ new RequestTimeoutError(
1594
+ message ?? `Operation timed out after ${formatDuration(timeoutMs)}.`
1595
+ )
1596
+ );
1597
+ }, timeoutMs);
1598
+ const onAbort = () => {
1599
+ cleanup();
1600
+ reject(getAbortError(signal));
1601
+ };
1602
+ const cleanup = () => {
1603
+ if (timer) {
1604
+ clearTimeout(timer);
1605
+ }
1606
+ signal?.removeEventListener("abort", onAbort);
1607
+ };
1608
+ signal?.addEventListener("abort", onAbort, { once: true });
1609
+ promise.then(
1610
+ (value) => {
1611
+ cleanup();
1612
+ resolve2(value);
1613
+ },
1614
+ (error) => {
1615
+ cleanup();
1616
+ reject(error);
1617
+ }
1618
+ );
1619
+ });
1455
1620
  }
1456
- function readRequiredString(value, field) {
1457
- if (typeof value !== "string") {
1458
- throw new Error(`output field '${field}' must be a string`);
1621
+ function getAbortError(signal, message = "Operation aborted.") {
1622
+ const reason = signal?.reason;
1623
+ if (reason instanceof Error) {
1624
+ return reason;
1459
1625
  }
1460
- return value;
1461
- }
1462
- function readOptionalString(value, field) {
1463
- if (value === void 0) {
1464
- return void 0;
1626
+ if (typeof reason === "string" && reason.length > 0) {
1627
+ return new Error(reason);
1465
1628
  }
1466
- return readRequiredString(value, field);
1629
+ return new Error(message);
1467
1630
  }
1468
- function readOptionalNonNegativeInteger(value) {
1469
- return typeof value === "number" && Number.isInteger(value) && value >= 0 ? { itemCount: value } : {};
1631
+ function isAbortErrorFromSignal(signal, error) {
1632
+ return signal?.aborted === true && signal.reason === error;
1470
1633
  }
1471
- function requireObject(value, message) {
1472
- if (!isJsonObject2(value)) {
1473
- throw new Error(message);
1634
+ function createDeadlineSignal(signal, timeoutMs, timeoutMessage) {
1635
+ const controller = new AbortController();
1636
+ if (signal?.aborted) {
1637
+ controller.abort(getAbortError(signal));
1474
1638
  }
1475
- return value;
1639
+ const onAbort = () => {
1640
+ controller.abort(getAbortError(signal));
1641
+ };
1642
+ signal?.addEventListener("abort", onAbort, { once: true });
1643
+ const timer = setTimeout(() => {
1644
+ controller.abort(new RequestTimeoutError(timeoutMessage));
1645
+ }, timeoutMs);
1646
+ return {
1647
+ signal: controller.signal,
1648
+ cleanup: () => {
1649
+ clearTimeout(timer);
1650
+ signal?.removeEventListener("abort", onAbort);
1651
+ }
1652
+ };
1476
1653
  }
1477
- function isJsonObject2(value) {
1478
- return typeof value === "object" && value !== null && !Array.isArray(value);
1654
+ function normalizeError(error) {
1655
+ return error instanceof Error ? error : new Error(formatErrorMessage(error));
1479
1656
  }
1480
1657
 
1481
1658
  // src/providers/exa.ts
1482
- import { Type as Type5 } from "typebox";
1483
- import { Exa as ExaClient } from "exa-js";
1484
1659
  var exaSearchOptionsSchema = Type5.Object(
1485
1660
  {
1486
1661
  type: Type5.Optional(
@@ -1562,7 +1737,7 @@ var exaSearchOptionsSchema = Type5.Object(
1562
1737
  },
1563
1738
  { description: "Exa search options." }
1564
1739
  );
1565
- var exaAdapter = {
1740
+ var exaImplementation = {
1566
1741
  id: "exa",
1567
1742
  label: "Exa",
1568
1743
  docsUrl: "https://exa.ai/docs/sdks/typescript-sdk-specification",
@@ -1593,13 +1768,13 @@ var exaAdapter = {
1593
1768
  async search(query2, maxResults, config, _context, searchOptions) {
1594
1769
  const client = createClient2(config);
1595
1770
  const options = {
1596
- ...stripLocalExecutionOptions(asJsonObject(config.options?.search)) ?? {},
1771
+ ...asJsonObject(config.options?.search) ?? {},
1597
1772
  ...searchOptions ?? {},
1598
1773
  numResults: maxResults
1599
1774
  };
1600
1775
  const response = await client.search(query2, options);
1601
1776
  return {
1602
- provider: exaAdapter.id,
1777
+ provider: exaImplementation.id,
1603
1778
  results: (response.results ?? []).slice(0, maxResults).map((result) => ({
1604
1779
  title: String(result.title ?? result.url ?? "Untitled"),
1605
1780
  url: String(result.url ?? ""),
@@ -1615,7 +1790,7 @@ var exaAdapter = {
1615
1790
  const response = await client.getContents(urls, options);
1616
1791
  const results = response.results ?? [];
1617
1792
  return {
1618
- provider: exaAdapter.id,
1793
+ provider: exaImplementation.id,
1619
1794
  answers: urls.map((url, index) => {
1620
1795
  const result = results[index];
1621
1796
  if (!result) {
@@ -1652,18 +1827,23 @@ var exaAdapter = {
1652
1827
  }
1653
1828
  }
1654
1829
  return {
1655
- provider: exaAdapter.id,
1830
+ provider: exaImplementation.id,
1656
1831
  text: lines.join("\n").trimEnd(),
1657
1832
  itemCount: citations.length
1658
1833
  };
1659
1834
  },
1660
1835
  async research(input, config, context, options) {
1661
1836
  return await executeAsyncResearch({
1662
- providerLabel: exaAdapter.label,
1663
- providerId: exaAdapter.id,
1837
+ providerLabel: exaImplementation.label,
1838
+ providerId: exaImplementation.id,
1664
1839
  context,
1665
- start: (researchContext) => exaAdapter.startResearch(input, config, researchContext, options),
1666
- poll: (id, researchContext) => exaAdapter.pollResearch(id, config, researchContext, options)
1840
+ start: (researchContext) => exaImplementation.startResearch(
1841
+ input,
1842
+ config,
1843
+ researchContext,
1844
+ options
1845
+ ),
1846
+ poll: (id, researchContext) => exaImplementation.pollResearch(id, config, researchContext, options)
1667
1847
  });
1668
1848
  },
1669
1849
  async startResearch(input, config, _context, options) {
@@ -1682,7 +1862,7 @@ var exaAdapter = {
1682
1862
  return {
1683
1863
  status: "completed",
1684
1864
  output: {
1685
- provider: exaAdapter.id,
1865
+ provider: exaImplementation.id,
1686
1866
  text: typeof content === "string" ? content : content !== void 0 ? formatJson(content) : "Exa research completed without textual output."
1687
1867
  }
1688
1868
  };
@@ -1709,6 +1889,69 @@ function createClient2(config) {
1709
1889
  }
1710
1890
  return new ExaClient(apiKey, resolveConfigValue(config.baseUrl));
1711
1891
  }
1892
+ var exaProvider = defineProvider({
1893
+ id: "exa",
1894
+ label: exaImplementation.label,
1895
+ docsUrl: exaImplementation.docsUrl,
1896
+ config: {
1897
+ createTemplate: () => exaImplementation.createTemplate(),
1898
+ fields: ["apiKey", "baseUrl", "options", "settings"],
1899
+ optionCapabilities: ["search"]
1900
+ },
1901
+ getCapabilityStatus: (config, cwd, tool) => exaImplementation.getCapabilityStatus(
1902
+ config,
1903
+ cwd,
1904
+ tool
1905
+ ),
1906
+ capabilities: {
1907
+ search: defineCapability({
1908
+ options: exaImplementation.getToolOptionsSchema?.("search"),
1909
+ async execute(input, ctx) {
1910
+ const { query: query2, maxResults, options } = input;
1911
+ return await exaImplementation.search(
1912
+ query2,
1913
+ maxResults,
1914
+ ctx.config,
1915
+ ctx,
1916
+ options
1917
+ );
1918
+ }
1919
+ }),
1920
+ contents: defineCapability({
1921
+ options: exaImplementation.getToolOptionsSchema?.("contents"),
1922
+ async execute(input, ctx) {
1923
+ return await exaImplementation.contents(
1924
+ input.urls,
1925
+ ctx.config,
1926
+ ctx,
1927
+ input.options
1928
+ );
1929
+ }
1930
+ }),
1931
+ answer: defineCapability({
1932
+ options: exaImplementation.getToolOptionsSchema?.("answer"),
1933
+ async execute(input, ctx) {
1934
+ return await exaImplementation.answer(
1935
+ input.query,
1936
+ ctx.config,
1937
+ ctx,
1938
+ input.options
1939
+ );
1940
+ }
1941
+ }),
1942
+ research: defineCapability({
1943
+ options: exaImplementation.getToolOptionsSchema?.("research"),
1944
+ async execute(input, ctx) {
1945
+ return await exaImplementation.research(
1946
+ input.input,
1947
+ ctx.config,
1948
+ ctx,
1949
+ input.options
1950
+ );
1951
+ }
1952
+ })
1953
+ }
1954
+ });
1712
1955
 
1713
1956
  // src/providers/firecrawl.ts
1714
1957
  import FirecrawlClient from "@mendable/firecrawl-js";
@@ -1819,7 +2062,7 @@ var firecrawlScrapeOptionsSchema = Type6.Object(
1819
2062
  },
1820
2063
  { description: "Firecrawl scrape options." }
1821
2064
  );
1822
- var firecrawlAdapter = {
2065
+ var firecrawlImplementation = {
1823
2066
  id: "firecrawl",
1824
2067
  label: "Firecrawl",
1825
2068
  docsUrl: "https://docs.firecrawl.dev/sdks/node",
@@ -1849,20 +2092,20 @@ var firecrawlAdapter = {
1849
2092
  },
1850
2093
  async search(query2, maxResults, config, _context, options) {
1851
2094
  const client = createClient3(config);
1852
- const defaults = stripLocalExecutionOptions(asJsonObject(config.options?.search)) ?? {};
2095
+ const defaults = asJsonObject(config.options?.search) ?? {};
1853
2096
  const response = await client.search(query2, {
1854
2097
  ...defaults,
1855
2098
  ...options ?? {},
1856
2099
  limit: maxResults
1857
2100
  });
1858
2101
  return {
1859
- provider: firecrawlAdapter.id,
2102
+ provider: firecrawlImplementation.id,
1860
2103
  results: flattenSearchResults(response).slice(0, maxResults)
1861
2104
  };
1862
2105
  },
1863
2106
  async contents(urls, config, _context, options) {
1864
2107
  const client = createClient3(config);
1865
- const defaults = stripLocalExecutionOptions(asJsonObject(config.options?.scrape)) ?? {};
2108
+ const defaults = asJsonObject(config.options?.scrape) ?? {};
1866
2109
  const scrapeOptions = {
1867
2110
  formats: ["markdown"],
1868
2111
  onlyMainContent: true,
@@ -1870,7 +2113,7 @@ var firecrawlAdapter = {
1870
2113
  ...options ?? {}
1871
2114
  };
1872
2115
  return {
1873
- provider: firecrawlAdapter.id,
2116
+ provider: firecrawlImplementation.id,
1874
2117
  answers: await Promise.all(
1875
2118
  urls.map(async (url) => {
1876
2119
  try {
@@ -1956,6 +2199,46 @@ function asRecord(value) {
1956
2199
  function readString2(value) {
1957
2200
  return typeof value === "string" ? value : void 0;
1958
2201
  }
2202
+ var firecrawlProvider = defineProvider({
2203
+ id: "firecrawl",
2204
+ label: firecrawlImplementation.label,
2205
+ docsUrl: firecrawlImplementation.docsUrl,
2206
+ config: {
2207
+ createTemplate: () => firecrawlImplementation.createTemplate(),
2208
+ fields: ["apiKey", "baseUrl", "options", "settings"]
2209
+ },
2210
+ getCapabilityStatus: (config, cwd, tool) => firecrawlImplementation.getCapabilityStatus(
2211
+ config,
2212
+ cwd,
2213
+ tool
2214
+ ),
2215
+ capabilities: {
2216
+ search: defineCapability({
2217
+ options: firecrawlImplementation.getToolOptionsSchema?.("search"),
2218
+ async execute(input, ctx) {
2219
+ const { query: query2, maxResults, options } = input;
2220
+ return await firecrawlImplementation.search(
2221
+ query2,
2222
+ maxResults,
2223
+ ctx.config,
2224
+ ctx,
2225
+ options
2226
+ );
2227
+ }
2228
+ }),
2229
+ contents: defineCapability({
2230
+ options: firecrawlImplementation.getToolOptionsSchema?.("contents"),
2231
+ async execute(input, ctx) {
2232
+ return await firecrawlImplementation.contents(
2233
+ input.urls,
2234
+ ctx.config,
2235
+ ctx,
2236
+ input.options
2237
+ );
2238
+ }
2239
+ })
2240
+ }
2241
+ });
1959
2242
 
1960
2243
  // src/providers/gemini.ts
1961
2244
  import { GoogleGenAI } from "@google/genai";
@@ -2025,7 +2308,7 @@ var geminiAgentConfigSchema = Type7.Object(
2025
2308
  },
2026
2309
  {
2027
2310
  additionalProperties: false,
2028
- description: "Safe Gemini deep-research agent configuration. The adapter adds the required type field."
2311
+ description: "Safe Gemini deep-research agent configuration. The provider adds the required type field."
2029
2312
  }
2030
2313
  );
2031
2314
  var geminiSearchOptionsSchema = Type7.Object(
@@ -2056,7 +2339,7 @@ var geminiResearchOptionsSchema = Type7.Object(
2056
2339
  },
2057
2340
  { additionalProperties: false, description: "Gemini research options." }
2058
2341
  );
2059
- var geminiAdapter = {
2342
+ var geminiImplementation = {
2060
2343
  id: "gemini",
2061
2344
  label: "Gemini",
2062
2345
  docsUrl: "https://github.com/googleapis/js-genai",
@@ -2704,12 +2987,63 @@ function getGeminiOptions(config) {
2704
2987
  function readNonEmptyString3(value) {
2705
2988
  return typeof value === "string" && value.trim().length > 0 ? value : void 0;
2706
2989
  }
2990
+ var geminiProvider = defineProvider({
2991
+ id: "gemini",
2992
+ label: geminiImplementation.label,
2993
+ docsUrl: geminiImplementation.docsUrl,
2994
+ config: {
2995
+ createTemplate: () => geminiImplementation.createTemplate(),
2996
+ fields: ["apiKey", "options", "settings"]
2997
+ },
2998
+ getCapabilityStatus: (config, cwd, tool) => geminiImplementation.getCapabilityStatus(
2999
+ config,
3000
+ cwd,
3001
+ tool
3002
+ ),
3003
+ capabilities: {
3004
+ search: defineCapability({
3005
+ options: geminiImplementation.getToolOptionsSchema?.("search"),
3006
+ async execute(input, ctx) {
3007
+ const { query: query2, maxResults, options } = input;
3008
+ return await geminiImplementation.search(
3009
+ query2,
3010
+ maxResults,
3011
+ ctx.config,
3012
+ ctx,
3013
+ options
3014
+ );
3015
+ }
3016
+ }),
3017
+ answer: defineCapability({
3018
+ options: geminiImplementation.getToolOptionsSchema?.("answer"),
3019
+ async execute(input, ctx) {
3020
+ return await geminiImplementation.answer(
3021
+ input.query,
3022
+ ctx.config,
3023
+ ctx,
3024
+ input.options
3025
+ );
3026
+ }
3027
+ }),
3028
+ research: defineCapability({
3029
+ options: geminiImplementation.getToolOptionsSchema?.("research"),
3030
+ async execute(input, ctx) {
3031
+ return await geminiImplementation.research(
3032
+ input.input,
3033
+ ctx.config,
3034
+ ctx,
3035
+ input.options
3036
+ );
3037
+ }
3038
+ })
3039
+ }
3040
+ });
2707
3041
 
2708
3042
  // src/providers/linkup.ts
2709
- import { Type as Type8 } from "typebox";
2710
3043
  import {
2711
3044
  LinkupClient
2712
3045
  } from "linkup-sdk";
3046
+ import { Type as Type8 } from "typebox";
2713
3047
  var linkupSearchOptionsSchema = Type8.Object(
2714
3048
  {
2715
3049
  depth: Type8.Optional(
@@ -2753,7 +3087,7 @@ var linkupContentsOptionsSchema = Type8.Object(
2753
3087
  },
2754
3088
  { description: "Linkup fetch options." }
2755
3089
  );
2756
- var linkupAdapter = {
3090
+ var linkupImplementation = {
2757
3091
  id: "linkup",
2758
3092
  label: "Linkup",
2759
3093
  docsUrl: "https://docs.linkup.so/pages/sdk/js/js",
@@ -2777,30 +3111,30 @@ var linkupAdapter = {
2777
3111
  },
2778
3112
  async search(query2, maxResults, config, _context, options) {
2779
3113
  const client = createClient4(config);
2780
- const defaults = stripLocalExecutionOptions(asJsonObject(config.options?.search)) ?? {};
3114
+ const defaults = asJsonObject(config.options?.search) ?? {};
2781
3115
  const response = await client.search(
2782
3116
  buildSearchParams(query2, maxResults, {
2783
3117
  ...defaults,
2784
- ...stripLocalExecutionOptions(options) ?? {}
3118
+ ...options ?? {}
2785
3119
  })
2786
3120
  );
2787
3121
  return {
2788
- provider: linkupAdapter.id,
3122
+ provider: linkupImplementation.id,
2789
3123
  results: (response.results ?? []).map(toSearchResult2).filter((result) => result !== null).slice(0, maxResults)
2790
3124
  };
2791
3125
  },
2792
3126
  async contents(urls, config, _context, options) {
2793
3127
  const client = createClient4(config);
2794
- const defaults = stripLocalExecutionOptions(asJsonObject(config.options?.fetch)) ?? {};
3128
+ const defaults = asJsonObject(config.options?.fetch) ?? {};
2795
3129
  return {
2796
- provider: linkupAdapter.id,
3130
+ provider: linkupImplementation.id,
2797
3131
  answers: await Promise.all(
2798
3132
  urls.map(async (url) => {
2799
3133
  try {
2800
3134
  const response = await client.fetch(
2801
3135
  buildFetchParams(url, {
2802
3136
  ...defaults,
2803
- ...stripLocalExecutionOptions(options) ?? {}
3137
+ ...options ?? {}
2804
3138
  })
2805
3139
  );
2806
3140
  return response.markdown ? {
@@ -2909,6 +3243,218 @@ function toDate(value, name) {
2909
3243
  }
2910
3244
  return date;
2911
3245
  }
3246
+ var linkupProvider = defineProvider({
3247
+ id: "linkup",
3248
+ label: linkupImplementation.label,
3249
+ docsUrl: linkupImplementation.docsUrl,
3250
+ config: {
3251
+ createTemplate: () => linkupImplementation.createTemplate(),
3252
+ fields: ["apiKey", "baseUrl", "options", "settings"]
3253
+ },
3254
+ getCapabilityStatus: (config, cwd, tool) => linkupImplementation.getCapabilityStatus(
3255
+ config,
3256
+ cwd,
3257
+ tool
3258
+ ),
3259
+ capabilities: {
3260
+ search: defineCapability({
3261
+ options: linkupImplementation.getToolOptionsSchema?.("search"),
3262
+ async execute(input, ctx) {
3263
+ const { query: query2, maxResults, options } = input;
3264
+ return await linkupImplementation.search(
3265
+ query2,
3266
+ maxResults,
3267
+ ctx.config,
3268
+ ctx,
3269
+ options
3270
+ );
3271
+ }
3272
+ }),
3273
+ contents: defineCapability({
3274
+ options: linkupImplementation.getToolOptionsSchema?.("contents"),
3275
+ async execute(input, ctx) {
3276
+ return await linkupImplementation.contents(
3277
+ input.urls,
3278
+ ctx.config,
3279
+ ctx,
3280
+ input.options
3281
+ );
3282
+ }
3283
+ })
3284
+ }
3285
+ });
3286
+
3287
+ // src/providers/ollama.ts
3288
+ var DEFAULT_BASE_URL = "https://ollama.com";
3289
+ var WEB_SEARCH_PATH = "/api/web_search";
3290
+ var WEB_FETCH_PATH = "/api/web_fetch";
3291
+ var ollamaProvider = defineProvider({
3292
+ id: "ollama",
3293
+ label: "Ollama",
3294
+ docsUrl: "https://docs.ollama.com/capabilities/web-search",
3295
+ config: {
3296
+ createTemplate() {
3297
+ return {
3298
+ apiKey: "OLLAMA_API_KEY"
3299
+ };
3300
+ },
3301
+ fields: ["apiKey", "baseUrl", "settings"]
3302
+ },
3303
+ getCapabilityStatus(config) {
3304
+ return getApiKeyStatus(config?.apiKey);
3305
+ },
3306
+ capabilities: {
3307
+ search: defineCapability({
3308
+ limits: {
3309
+ maxResults: 10
3310
+ },
3311
+ async execute({ query: query2, maxResults }, { config, signal }) {
3312
+ return await searchOllama(query2, maxResults, config, {
3313
+ signal
3314
+ });
3315
+ }
3316
+ }),
3317
+ contents: defineCapability({
3318
+ async execute({ urls }, { config, signal }) {
3319
+ return await fetchOllamaContents(urls, config, { signal });
3320
+ }
3321
+ })
3322
+ }
3323
+ });
3324
+ async function searchOllama(query2, maxResults, config, context) {
3325
+ const apiKey = resolveApiKey(config);
3326
+ const response = await fetch(
3327
+ resolveEndpoint(config.baseUrl, WEB_SEARCH_PATH),
3328
+ {
3329
+ method: "POST",
3330
+ headers: buildHeaders(apiKey),
3331
+ body: JSON.stringify({
3332
+ query: query2,
3333
+ max_results: clampMaxResults(maxResults)
3334
+ }),
3335
+ signal: context.signal
3336
+ }
3337
+ );
3338
+ if (!response.ok) {
3339
+ throw new Error(await buildHttpError(response));
3340
+ }
3341
+ const data = await response.json();
3342
+ const results = Array.isArray(data.results) ? data.results : [];
3343
+ return {
3344
+ provider: ollamaProvider.id,
3345
+ results: results.slice(0, clampMaxResults(maxResults)).map((result) => ({
3346
+ title: result.title || result.url || "Untitled",
3347
+ url: result.url ?? "",
3348
+ snippet: trimSnippet(result.content)
3349
+ }))
3350
+ };
3351
+ }
3352
+ async function fetchOllamaContents(urls, config, context) {
3353
+ const apiKey = resolveApiKey(config);
3354
+ const endpoint = resolveEndpoint(config.baseUrl, WEB_FETCH_PATH);
3355
+ return {
3356
+ provider: ollamaProvider.id,
3357
+ answers: await Promise.all(
3358
+ urls.map(async (url) => {
3359
+ try {
3360
+ const response = await fetch(endpoint, {
3361
+ method: "POST",
3362
+ headers: buildHeaders(apiKey),
3363
+ body: JSON.stringify({
3364
+ url
3365
+ }),
3366
+ signal: context.signal
3367
+ });
3368
+ if (!response.ok) {
3369
+ return {
3370
+ url,
3371
+ error: await buildHttpError(response)
3372
+ };
3373
+ }
3374
+ const data = await response.json();
3375
+ const content = normalizeContentText(data.content);
3376
+ if (!content) {
3377
+ return {
3378
+ url,
3379
+ error: "No content returned for this URL."
3380
+ };
3381
+ }
3382
+ const metadata = buildFetchMetadata(data);
3383
+ return {
3384
+ url,
3385
+ content,
3386
+ ...metadata ? { metadata } : {}
3387
+ };
3388
+ } catch (error) {
3389
+ return {
3390
+ url,
3391
+ error: error instanceof Error ? error.message : String(error)
3392
+ };
3393
+ }
3394
+ })
3395
+ )
3396
+ };
3397
+ }
3398
+ function resolveApiKey(config) {
3399
+ const apiKey = resolveConfigValue(config.apiKey);
3400
+ if (!apiKey) {
3401
+ throw new Error("is missing an API key");
3402
+ }
3403
+ return apiKey;
3404
+ }
3405
+ function buildHeaders(apiKey) {
3406
+ return {
3407
+ authorization: `Bearer ${apiKey}`,
3408
+ "content-type": "application/json"
3409
+ };
3410
+ }
3411
+ function resolveEndpoint(baseUrlReference, endpointPath) {
3412
+ const baseUrl = resolveConfigValue(baseUrlReference) ?? DEFAULT_BASE_URL;
3413
+ const base = baseUrl.replace(/\/+$/, "");
3414
+ const apiPath = endpointPath.replace(/^\/api\//, "");
3415
+ if (base.endsWith(endpointPath)) {
3416
+ return base;
3417
+ }
3418
+ if (base.endsWith("/api")) {
3419
+ return `${base}/${apiPath}`;
3420
+ }
3421
+ return `${base}${endpointPath}`;
3422
+ }
3423
+ function clampMaxResults(value) {
3424
+ return Math.max(1, Math.min(10, Math.trunc(value || 0)));
3425
+ }
3426
+ async function buildHttpError(response) {
3427
+ const detail = await readErrorDetail(response);
3428
+ const status = `${response.status}${response.statusText ? ` ${response.statusText}` : ""}`;
3429
+ return detail ? `Ollama API request failed (${status}): ${detail}` : `Ollama API request failed (${status}).`;
3430
+ }
3431
+ async function readErrorDetail(response) {
3432
+ const text = (await response.text()).trim();
3433
+ if (!text) {
3434
+ return void 0;
3435
+ }
3436
+ try {
3437
+ const parsed = JSON.parse(text);
3438
+ for (const key of ["message", "error", "detail"]) {
3439
+ if (typeof parsed[key] === "string" && parsed[key].trim()) {
3440
+ return parsed[key];
3441
+ }
3442
+ }
3443
+ return JSON.stringify(parsed);
3444
+ } catch {
3445
+ return text;
3446
+ }
3447
+ }
3448
+ function buildFetchMetadata(data) {
3449
+ const metadata = {};
3450
+ if (data.title) {
3451
+ metadata.title = data.title;
3452
+ }
3453
+ if (data.links?.length) {
3454
+ metadata.links = data.links;
3455
+ }
3456
+ return Object.keys(metadata).length > 0 ? metadata : void 0;
3457
+ }
2912
3458
 
2913
3459
  // src/providers/openai.ts
2914
3460
  import { Type as Type9 } from "typebox";
@@ -2987,7 +3533,7 @@ var searchResultSchema = {
2987
3533
  }
2988
3534
  }
2989
3535
  };
2990
- var openaiAdapter = {
3536
+ var openaiImplementation = {
2991
3537
  id: "openai",
2992
3538
  label: "OpenAI",
2993
3539
  docsUrl: "https://platform.openai.com/docs/guides/deep-research",
@@ -3040,11 +3586,16 @@ var openaiAdapter = {
3040
3586
  },
3041
3587
  async research(input, config, context, options) {
3042
3588
  return await executeAsyncResearch({
3043
- providerLabel: openaiAdapter.label,
3044
- providerId: openaiAdapter.id,
3589
+ providerLabel: openaiImplementation.label,
3590
+ providerId: openaiImplementation.id,
3045
3591
  context,
3046
- start: (researchContext) => openaiAdapter.startResearch(input, config, researchContext, options),
3047
- poll: (id, researchContext) => openaiAdapter.pollResearch(id, config, researchContext, options)
3592
+ start: (researchContext) => openaiImplementation.startResearch(
3593
+ input,
3594
+ config,
3595
+ researchContext,
3596
+ options
3597
+ ),
3598
+ poll: (id, researchContext) => openaiImplementation.pollResearch(id, config, researchContext, options)
3048
3599
  });
3049
3600
  },
3050
3601
  async startResearch(input, config, context, options) {
@@ -3219,7 +3770,7 @@ function parseSearchResponse2(response, maxResults) {
3219
3770
  }
3220
3771
  const payload = parseSearchPayload(response.output_text);
3221
3772
  return {
3222
- provider: openaiAdapter.id,
3773
+ provider: openaiImplementation.id,
3223
3774
  results: payload.sources.slice(0, maxResults).map((source) => ({
3224
3775
  title: source.title.trim(),
3225
3776
  url: source.url.trim(),
@@ -3258,7 +3809,7 @@ function formatResponseOutput(response, operation) {
3258
3809
  }
3259
3810
  }
3260
3811
  return {
3261
- provider: openaiAdapter.id,
3812
+ provider: openaiImplementation.id,
3262
3813
  text: lines.join("\n").trimEnd(),
3263
3814
  itemCount: citations.length,
3264
3815
  metadata: {
@@ -3362,6 +3913,58 @@ function readPositiveInteger2(value) {
3362
3913
  function readInteger(value) {
3363
3914
  return typeof value === "number" && Number.isInteger(value) ? value : void 0;
3364
3915
  }
3916
+ var openaiProvider = defineProvider({
3917
+ id: "openai",
3918
+ label: openaiImplementation.label,
3919
+ docsUrl: openaiImplementation.docsUrl,
3920
+ config: {
3921
+ createTemplate: () => openaiImplementation.createTemplate(),
3922
+ fields: ["apiKey", "baseUrl", "options", "settings"],
3923
+ optionCapabilities: ["search", "answer", "research"]
3924
+ },
3925
+ getCapabilityStatus: (config, cwd, tool) => openaiImplementation.getCapabilityStatus(
3926
+ config,
3927
+ cwd,
3928
+ tool
3929
+ ),
3930
+ capabilities: {
3931
+ search: defineCapability({
3932
+ options: openaiImplementation.getToolOptionsSchema?.("search"),
3933
+ async execute(input, ctx) {
3934
+ const { query: query2, maxResults, options } = input;
3935
+ return await openaiImplementation.search(
3936
+ query2,
3937
+ maxResults,
3938
+ ctx.config,
3939
+ ctx,
3940
+ options
3941
+ );
3942
+ }
3943
+ }),
3944
+ answer: defineCapability({
3945
+ options: openaiImplementation.getToolOptionsSchema?.("answer"),
3946
+ async execute(input, ctx) {
3947
+ return await openaiImplementation.answer(
3948
+ input.query,
3949
+ ctx.config,
3950
+ ctx,
3951
+ input.options
3952
+ );
3953
+ }
3954
+ }),
3955
+ research: defineCapability({
3956
+ options: openaiImplementation.getToolOptionsSchema?.("research"),
3957
+ async execute(input, ctx) {
3958
+ return await openaiImplementation.research(
3959
+ input.input,
3960
+ ctx.config,
3961
+ ctx,
3962
+ input.options
3963
+ );
3964
+ }
3965
+ })
3966
+ }
3967
+ });
3365
3968
 
3366
3969
  // src/providers/parallel.ts
3367
3970
  import { Type as Type10 } from "typebox";
@@ -3389,7 +3992,7 @@ var parallelExtractOptionsSchema = Type10.Object(
3389
3992
  },
3390
3993
  { description: "Parallel extract options." }
3391
3994
  );
3392
- var parallelAdapter = {
3995
+ var parallelImplementation = {
3393
3996
  id: "parallel",
3394
3997
  label: "Parallel",
3395
3998
  docsUrl: "https://github.com/parallel-web/parallel-sdk-typescript",
@@ -3422,7 +4025,7 @@ var parallelAdapter = {
3422
4025
  },
3423
4026
  async search(query2, maxResults, config, context, options) {
3424
4027
  const client = createClient6(config);
3425
- const defaults = stripLocalExecutionOptions(asJsonObject(config.options?.search)) ?? {};
4028
+ const defaults = asJsonObject(config.options?.search) ?? {};
3426
4029
  const response = await client.beta.search(
3427
4030
  {
3428
4031
  ...defaults,
@@ -3433,7 +4036,7 @@ var parallelAdapter = {
3433
4036
  buildRequestOptions3(context)
3434
4037
  );
3435
4038
  return {
3436
- provider: parallelAdapter.id,
4039
+ provider: parallelImplementation.id,
3437
4040
  results: response.results.slice(0, maxResults).map((result) => ({
3438
4041
  title: result.title ?? result.url,
3439
4042
  url: result.url,
@@ -3443,7 +4046,7 @@ var parallelAdapter = {
3443
4046
  },
3444
4047
  async contents(urls, config, context, options) {
3445
4048
  const client = createClient6(config);
3446
- const defaults = stripLocalExecutionOptions(asJsonObject(config.options?.extract)) ?? {};
4049
+ const defaults = asJsonObject(config.options?.extract) ?? {};
3447
4050
  const response = await client.beta.extract(
3448
4051
  {
3449
4052
  ...defaults,
@@ -3459,7 +4062,7 @@ var parallelAdapter = {
3459
4062
  response.errors.map((error) => [error.url, error])
3460
4063
  );
3461
4064
  return {
3462
- provider: parallelAdapter.id,
4065
+ provider: parallelImplementation.id,
3463
4066
  answers: urls.map((url) => {
3464
4067
  const result = resultsByUrl.get(url);
3465
4068
  if (result) {
@@ -3494,6 +4097,46 @@ function createClient6(config) {
3494
4097
  function buildRequestOptions3(context) {
3495
4098
  return context.signal ? { signal: context.signal } : void 0;
3496
4099
  }
4100
+ var parallelProvider = defineProvider({
4101
+ id: "parallel",
4102
+ label: parallelImplementation.label,
4103
+ docsUrl: parallelImplementation.docsUrl,
4104
+ config: {
4105
+ createTemplate: () => parallelImplementation.createTemplate(),
4106
+ fields: ["apiKey", "baseUrl", "options", "settings"]
4107
+ },
4108
+ getCapabilityStatus: (config, cwd, tool) => parallelImplementation.getCapabilityStatus(
4109
+ config,
4110
+ cwd,
4111
+ tool
4112
+ ),
4113
+ capabilities: {
4114
+ search: defineCapability({
4115
+ options: parallelImplementation.getToolOptionsSchema?.("search"),
4116
+ async execute(input, ctx) {
4117
+ const { query: query2, maxResults, options } = input;
4118
+ return await parallelImplementation.search(
4119
+ query2,
4120
+ maxResults,
4121
+ ctx.config,
4122
+ ctx,
4123
+ options
4124
+ );
4125
+ }
4126
+ }),
4127
+ contents: defineCapability({
4128
+ options: parallelImplementation.getToolOptionsSchema?.("contents"),
4129
+ async execute(input, ctx) {
4130
+ return await parallelImplementation.contents(
4131
+ input.urls,
4132
+ ctx.config,
4133
+ ctx,
4134
+ input.options
4135
+ );
4136
+ }
4137
+ })
4138
+ }
4139
+ });
3497
4140
 
3498
4141
  // src/providers/perplexity.ts
3499
4142
  import PerplexityClient from "@perplexity-ai/perplexity_ai";
@@ -3539,7 +4182,7 @@ var perplexityResearchOptionsSchema = Type11.Object(
3539
4182
  },
3540
4183
  { description: "Perplexity research options." }
3541
4184
  );
3542
- var perplexityAdapter = {
4185
+ var perplexityImplementation = {
3543
4186
  id: "perplexity",
3544
4187
  label: "Perplexity",
3545
4188
  docsUrl: "https://docs.perplexity.ai/docs/sdk/overview.md",
@@ -3574,7 +4217,7 @@ var perplexityAdapter = {
3574
4217
  async search(query2, maxResults, config, context, options) {
3575
4218
  const client = createClient7(config);
3576
4219
  const request = {
3577
- ...stripLocalExecutionOptions(asJsonObject(config.options?.search)) ?? {},
4220
+ ...asJsonObject(config.options?.search) ?? {},
3578
4221
  ...options ?? {},
3579
4222
  query: query2,
3580
4223
  max_results: maxResults
@@ -3584,7 +4227,7 @@ var perplexityAdapter = {
3584
4227
  buildRequestOptions4(context)
3585
4228
  );
3586
4229
  return {
3587
- provider: perplexityAdapter.id,
4230
+ provider: perplexityImplementation.id,
3588
4231
  results: response.results.slice(0, maxResults).map((result) => ({
3589
4232
  title: result.title,
3590
4233
  url: result.url,
@@ -3619,9 +4262,7 @@ var perplexityAdapter = {
3619
4262
  };
3620
4263
  async function runSilentForegroundChatTool(input, config, context, fallbackModel, label, options, isResearch = false) {
3621
4264
  const client = createClient7(config);
3622
- const defaults = stripLocalExecutionOptions(
3623
- isResearch ? asJsonObject(config.options?.research) : asJsonObject(config.options?.answer)
3624
- ) ?? {};
4265
+ const defaults = (isResearch ? asJsonObject(config.options?.research) : asJsonObject(config.options?.answer)) ?? {};
3625
4266
  const request = {
3626
4267
  ...defaults,
3627
4268
  ...options ?? {},
@@ -3646,14 +4287,14 @@ async function runSilentForegroundChatTool(input, config, context, fallbackModel
3646
4287
  }
3647
4288
  }
3648
4289
  return {
3649
- provider: perplexityAdapter.id,
4290
+ provider: perplexityImplementation.id,
3650
4291
  text: lines.join("\n").trimEnd(),
3651
4292
  itemCount: sources.length
3652
4293
  };
3653
4294
  }
3654
4295
  async function runStreamingForegroundChatTool(input, config, context, fallbackModel, label, options) {
3655
4296
  const client = createClient7(config);
3656
- const defaults = stripLocalExecutionOptions(asJsonObject(config.options?.research)) ?? {};
4297
+ const defaults = asJsonObject(config.options?.research) ?? {};
3657
4298
  const request = {
3658
4299
  ...defaults,
3659
4300
  ...options ?? {},
@@ -3688,7 +4329,7 @@ async function runStreamingForegroundChatTool(input, config, context, fallbackMo
3688
4329
  }
3689
4330
  }
3690
4331
  return {
3691
- provider: perplexityAdapter.id,
4332
+ provider: perplexityImplementation.id,
3692
4333
  text: lines.join("\n").trimEnd(),
3693
4334
  itemCount: dedupedSources.length
3694
4335
  };
@@ -3773,10 +4414,61 @@ function extractSources(response) {
3773
4414
  function buildRequestOptions4(context) {
3774
4415
  return context.signal ? { signal: context.signal } : void 0;
3775
4416
  }
4417
+ var perplexityProvider = defineProvider({
4418
+ id: "perplexity",
4419
+ label: perplexityImplementation.label,
4420
+ docsUrl: perplexityImplementation.docsUrl,
4421
+ config: {
4422
+ createTemplate: () => perplexityImplementation.createTemplate(),
4423
+ fields: ["apiKey", "baseUrl", "options", "settings"]
4424
+ },
4425
+ getCapabilityStatus: (config, cwd, tool) => perplexityImplementation.getCapabilityStatus(
4426
+ config,
4427
+ cwd,
4428
+ tool
4429
+ ),
4430
+ capabilities: {
4431
+ search: defineCapability({
4432
+ options: perplexityImplementation.getToolOptionsSchema?.("search"),
4433
+ async execute(input, ctx) {
4434
+ const { query: query2, maxResults, options } = input;
4435
+ return await perplexityImplementation.search(
4436
+ query2,
4437
+ maxResults,
4438
+ ctx.config,
4439
+ ctx,
4440
+ options
4441
+ );
4442
+ }
4443
+ }),
4444
+ answer: defineCapability({
4445
+ options: perplexityImplementation.getToolOptionsSchema?.("answer"),
4446
+ async execute(input, ctx) {
4447
+ return await perplexityImplementation.answer(
4448
+ input.query,
4449
+ ctx.config,
4450
+ ctx,
4451
+ input.options
4452
+ );
4453
+ }
4454
+ }),
4455
+ research: defineCapability({
4456
+ options: perplexityImplementation.getToolOptionsSchema?.("research"),
4457
+ async execute(input, ctx) {
4458
+ return await perplexityImplementation.research(
4459
+ input.input,
4460
+ ctx.config,
4461
+ ctx,
4462
+ input.options
4463
+ );
4464
+ }
4465
+ })
4466
+ }
4467
+ });
3776
4468
 
3777
4469
  // src/providers/serper.ts
3778
4470
  import { Type as Type12 } from "typebox";
3779
- var DEFAULT_BASE_URL = "https://google.serper.dev";
4471
+ var DEFAULT_BASE_URL2 = "https://google.serper.dev";
3780
4472
  var serperSearchOptionsSchema = Type12.Object(
3781
4473
  {
3782
4474
  gl: Type12.Optional(
@@ -3808,7 +4500,7 @@ var serperSearchOptionsSchema = Type12.Object(
3808
4500
  },
3809
4501
  { description: "Serper search options." }
3810
4502
  );
3811
- var serperAdapter = {
4503
+ var serperImplementation = {
3812
4504
  id: "serper",
3813
4505
  label: "Serper",
3814
4506
  docsUrl: "https://serper.dev/",
@@ -3834,15 +4526,15 @@ var serperAdapter = {
3834
4526
  if (!apiKey) {
3835
4527
  throw new Error("is missing an API key");
3836
4528
  }
3837
- const defaults = stripLocalExecutionOptions(asJsonObject(config.options?.search)) ?? {};
3838
- const runtimeOptions = stripLocalExecutionOptions(asJsonObject(options));
4529
+ const defaults = asJsonObject(config.options?.search) ?? {};
4530
+ const callOptions = asJsonObject(options);
3839
4531
  const {
3840
4532
  q: _ignoredQuery,
3841
4533
  num: _ignoredNum,
3842
4534
  ...providerOptions
3843
4535
  } = {
3844
4536
  ...defaults,
3845
- ...runtimeOptions ?? {}
4537
+ ...callOptions ?? {}
3846
4538
  };
3847
4539
  const response = await fetch(joinUrl(resolveConfigValue(config.baseUrl)), {
3848
4540
  method: "POST",
@@ -3852,39 +4544,39 @@ var serperAdapter = {
3852
4544
  },
3853
4545
  body: JSON.stringify({
3854
4546
  q: query2,
3855
- num: clampMaxResults(maxResults),
4547
+ num: clampMaxResults2(maxResults),
3856
4548
  ...providerOptions
3857
4549
  }),
3858
4550
  signal: context.signal
3859
4551
  });
3860
4552
  if (!response.ok) {
3861
- throw new Error(await buildHttpError(response));
4553
+ throw new Error(await buildHttpError2(response));
3862
4554
  }
3863
4555
  const payload = await response.json();
3864
4556
  const responseRecord = asRecord3(payload) ?? {};
3865
4557
  const organic = asArray(responseRecord.organic) ?? [];
3866
4558
  const searchContext = buildSearchContext(responseRecord);
3867
4559
  return {
3868
- provider: serperAdapter.id,
4560
+ provider: serperImplementation.id,
3869
4561
  results: organic.map((entry) => toSearchResult3(entry, searchContext)).filter(
3870
4562
  (result) => result !== null
3871
- ).slice(0, clampMaxResults(maxResults))
4563
+ ).slice(0, clampMaxResults2(maxResults))
3872
4564
  };
3873
4565
  }
3874
4566
  };
3875
4567
  function joinUrl(baseUrl) {
3876
- const base = (baseUrl ?? DEFAULT_BASE_URL).replace(/\/+$/, "");
4568
+ const base = (baseUrl ?? DEFAULT_BASE_URL2).replace(/\/+$/, "");
3877
4569
  return `${base}/search`;
3878
4570
  }
3879
- function clampMaxResults(value) {
4571
+ function clampMaxResults2(value) {
3880
4572
  return Math.max(1, Math.min(20, Math.trunc(value || 0)));
3881
4573
  }
3882
- async function buildHttpError(response) {
3883
- const detail = await readErrorDetail(response);
4574
+ async function buildHttpError2(response) {
4575
+ const detail = await readErrorDetail2(response);
3884
4576
  const status = `${response.status}${response.statusText ? ` ${response.statusText}` : ""}`;
3885
4577
  return detail ? `Serper API request failed (${status}): ${detail}` : `Serper API request failed (${status}).`;
3886
4578
  }
3887
- async function readErrorDetail(response) {
4579
+ async function readErrorDetail2(response) {
3888
4580
  const text = (await response.text()).trim();
3889
4581
  if (!text) {
3890
4582
  return void 0;
@@ -3971,6 +4663,36 @@ function readString4(value) {
3971
4663
  function readNumber(value) {
3972
4664
  return typeof value === "number" && Number.isFinite(value) ? value : void 0;
3973
4665
  }
4666
+ var serperProvider = defineProvider({
4667
+ id: "serper",
4668
+ label: serperImplementation.label,
4669
+ docsUrl: serperImplementation.docsUrl,
4670
+ config: {
4671
+ createTemplate: () => serperImplementation.createTemplate(),
4672
+ fields: ["apiKey", "baseUrl", "options", "settings"],
4673
+ optionCapabilities: ["search"]
4674
+ },
4675
+ getCapabilityStatus: (config, cwd, tool) => serperImplementation.getCapabilityStatus(
4676
+ config,
4677
+ cwd,
4678
+ tool
4679
+ ),
4680
+ capabilities: {
4681
+ search: defineCapability({
4682
+ options: serperImplementation.getToolOptionsSchema?.("search"),
4683
+ async execute(input, ctx) {
4684
+ const { query: query2, maxResults, options } = input;
4685
+ return await serperImplementation.search(
4686
+ query2,
4687
+ maxResults,
4688
+ ctx.config,
4689
+ ctx,
4690
+ options
4691
+ );
4692
+ }
4693
+ })
4694
+ }
4695
+ });
3974
4696
 
3975
4697
  // src/providers/tavily.ts
3976
4698
  import { Type as Type13 } from "typebox";
@@ -4054,7 +4776,7 @@ var tavilyExtractOptionsSchema = Type13.Object(
4054
4776
  },
4055
4777
  { description: "Tavily extract options." }
4056
4778
  );
4057
- var tavilyAdapter = {
4779
+ var tavilyImplementation = {
4058
4780
  id: "tavily",
4059
4781
  label: "Tavily",
4060
4782
  docsUrl: "https://docs.tavily.com/sdk/javascript/reference",
@@ -4087,14 +4809,14 @@ var tavilyAdapter = {
4087
4809
  },
4088
4810
  async search(query2, maxResults, config, _context, options) {
4089
4811
  const client = createClient8(config);
4090
- const defaults = stripLocalExecutionOptions(asJsonObject(config.options?.search)) ?? {};
4812
+ const defaults = asJsonObject(config.options?.search) ?? {};
4091
4813
  const response = await client.search(query2, {
4092
4814
  ...defaults,
4093
4815
  ...options ?? {},
4094
4816
  maxResults
4095
4817
  });
4096
4818
  return {
4097
- provider: tavilyAdapter.id,
4819
+ provider: tavilyImplementation.id,
4098
4820
  results: response.results.slice(0, maxResults).map((result) => ({
4099
4821
  title: result.title || result.url || "Untitled",
4100
4822
  url: result.url || "",
@@ -4106,7 +4828,7 @@ var tavilyAdapter = {
4106
4828
  },
4107
4829
  async contents(urls, config, _context, options) {
4108
4830
  const client = createClient8(config);
4109
- const defaults = stripLocalExecutionOptions(asJsonObject(config.options?.extract)) ?? {};
4831
+ const defaults = asJsonObject(config.options?.extract) ?? {};
4110
4832
  const response = await client.extract(urls, {
4111
4833
  ...defaults,
4112
4834
  ...options ?? {}
@@ -4118,7 +4840,7 @@ var tavilyAdapter = {
4118
4840
  response.failedResults.map((result) => [result.url, result])
4119
4841
  );
4120
4842
  return {
4121
- provider: tavilyAdapter.id,
4843
+ provider: tavilyImplementation.id,
4122
4844
  answers: urls.map((url) => {
4123
4845
  const result = resultsByUrl.get(url);
4124
4846
  if (result) {
@@ -4173,6 +4895,46 @@ function buildExtractMetadata(response, result) {
4173
4895
  };
4174
4896
  return Object.keys(metadata).length > 0 ? metadata : void 0;
4175
4897
  }
4898
+ var tavilyProvider = defineProvider({
4899
+ id: "tavily",
4900
+ label: tavilyImplementation.label,
4901
+ docsUrl: tavilyImplementation.docsUrl,
4902
+ config: {
4903
+ createTemplate: () => tavilyImplementation.createTemplate(),
4904
+ fields: ["apiKey", "baseUrl", "options", "settings"]
4905
+ },
4906
+ getCapabilityStatus: (config, cwd, tool) => tavilyImplementation.getCapabilityStatus(
4907
+ config,
4908
+ cwd,
4909
+ tool
4910
+ ),
4911
+ capabilities: {
4912
+ search: defineCapability({
4913
+ options: tavilyImplementation.getToolOptionsSchema?.("search"),
4914
+ async execute(input, ctx) {
4915
+ const { query: query2, maxResults, options } = input;
4916
+ return await tavilyImplementation.search(
4917
+ query2,
4918
+ maxResults,
4919
+ ctx.config,
4920
+ ctx,
4921
+ options
4922
+ );
4923
+ }
4924
+ }),
4925
+ contents: defineCapability({
4926
+ options: tavilyImplementation.getToolOptionsSchema?.("contents"),
4927
+ async execute(input, ctx) {
4928
+ return await tavilyImplementation.contents(
4929
+ input.urls,
4930
+ ctx.config,
4931
+ ctx,
4932
+ input.options
4933
+ );
4934
+ }
4935
+ })
4936
+ }
4937
+ });
4176
4938
 
4177
4939
  // src/providers/valyu.ts
4178
4940
  import { Type as Type14 } from "typebox";
@@ -4221,7 +4983,7 @@ var valyuResearchOptionsSchema = Type14.Object(
4221
4983
  },
4222
4984
  { description: "Valyu research options." }
4223
4985
  );
4224
- var valyuAdapter = {
4986
+ var valyuImplementation = {
4225
4987
  id: "valyu",
4226
4988
  label: "Valyu",
4227
4989
  docsUrl: "https://docs.valyu.ai/sdk/typescript-sdk",
@@ -4254,7 +5016,7 @@ var valyuAdapter = {
4254
5016
  async search(query2, maxResults, config, _context, searchOptions) {
4255
5017
  const client = createClient9(config);
4256
5018
  const options = {
4257
- ...stripLocalExecutionOptions(asJsonObject(config.options?.search)) ?? {},
5019
+ ...asJsonObject(config.options?.search) ?? {},
4258
5020
  ...searchOptions ?? {},
4259
5021
  maxNumResults: maxResults
4260
5022
  };
@@ -4263,7 +5025,7 @@ var valyuAdapter = {
4263
5025
  throw new Error(response.error || "search failed");
4264
5026
  }
4265
5027
  return {
4266
- provider: valyuAdapter.id,
5028
+ provider: valyuImplementation.id,
4267
5029
  results: (response.results ?? []).slice(0, maxResults).map((result) => ({
4268
5030
  title: result.title,
4269
5031
  url: result.url,
@@ -4287,7 +5049,7 @@ var valyuAdapter = {
4287
5049
  )
4288
5050
  );
4289
5051
  return {
4290
- provider: valyuAdapter.id,
5052
+ provider: valyuImplementation.id,
4291
5053
  answers: urls.map((url) => {
4292
5054
  const result = resultsByUrl.get(url);
4293
5055
  if (!result) {
@@ -4311,7 +5073,7 @@ var valyuAdapter = {
4311
5073
  async answer(query2, config, _context, options) {
4312
5074
  const client = createClient9(config);
4313
5075
  const response = await client.answer(query2, {
4314
- ...stripLocalExecutionOptions(asJsonObject(config.options?.answer)) ?? {},
5076
+ ...asJsonObject(config.options?.answer) ?? {},
4315
5077
  ...options ?? {},
4316
5078
  streaming: false
4317
5079
  });
@@ -4333,25 +5095,30 @@ var valyuAdapter = {
4333
5095
  }
4334
5096
  }
4335
5097
  return {
4336
- provider: valyuAdapter.id,
5098
+ provider: valyuImplementation.id,
4337
5099
  text: lines.join("\n").trimEnd(),
4338
5100
  itemCount: sources.length
4339
5101
  };
4340
5102
  },
4341
5103
  async research(input, config, context, options) {
4342
5104
  return await executeAsyncResearch({
4343
- providerLabel: valyuAdapter.label,
4344
- providerId: valyuAdapter.id,
5105
+ providerLabel: valyuImplementation.label,
5106
+ providerId: valyuImplementation.id,
4345
5107
  context,
4346
- start: (researchContext) => valyuAdapter.startResearch(input, config, researchContext, options),
4347
- poll: (id, researchContext) => valyuAdapter.pollResearch(id, config, researchContext, options)
5108
+ start: (researchContext) => valyuImplementation.startResearch(
5109
+ input,
5110
+ config,
5111
+ researchContext,
5112
+ options
5113
+ ),
5114
+ poll: (id, researchContext) => valyuImplementation.pollResearch(id, config, researchContext, options)
4348
5115
  });
4349
5116
  },
4350
5117
  async startResearch(input, config, _context, options) {
4351
5118
  const client = createClient9(config);
4352
5119
  const task = await client.deepresearch.create({
4353
5120
  input,
4354
- ...stripLocalExecutionOptions(asJsonObject(config.options?.research)) ?? {},
5121
+ ...asJsonObject(config.options?.research) ?? {},
4355
5122
  ...options ?? {}
4356
5123
  });
4357
5124
  if (!task.success || !task.deepresearch_id) {
@@ -4382,7 +5149,7 @@ var valyuAdapter = {
4382
5149
  return {
4383
5150
  status: "completed",
4384
5151
  output: {
4385
- provider: valyuAdapter.id,
5152
+ provider: valyuImplementation.id,
4386
5153
  text: lines.join("\n").trimEnd(),
4387
5154
  itemCount: sources.length
4388
5155
  }
@@ -4410,43 +5177,93 @@ function createClient9(config) {
4410
5177
  }
4411
5178
  return new ValyuClient(apiKey, resolveConfigValue(config.baseUrl));
4412
5179
  }
5180
+ var valyuProvider = defineProvider({
5181
+ id: "valyu",
5182
+ label: valyuImplementation.label,
5183
+ docsUrl: valyuImplementation.docsUrl,
5184
+ config: {
5185
+ createTemplate: () => valyuImplementation.createTemplate(),
5186
+ fields: ["apiKey", "baseUrl", "options", "settings"],
5187
+ optionCapabilities: ["search", "answer", "research"]
5188
+ },
5189
+ getCapabilityStatus: (config, cwd, tool) => valyuImplementation.getCapabilityStatus(
5190
+ config,
5191
+ cwd,
5192
+ tool
5193
+ ),
5194
+ capabilities: {
5195
+ search: defineCapability({
5196
+ options: valyuImplementation.getToolOptionsSchema?.("search"),
5197
+ async execute(input, ctx) {
5198
+ const { query: query2, maxResults, options } = input;
5199
+ return await valyuImplementation.search(
5200
+ query2,
5201
+ maxResults,
5202
+ ctx.config,
5203
+ ctx,
5204
+ options
5205
+ );
5206
+ }
5207
+ }),
5208
+ contents: defineCapability({
5209
+ options: valyuImplementation.getToolOptionsSchema?.("contents"),
5210
+ async execute(input, ctx) {
5211
+ return await valyuImplementation.contents(
5212
+ input.urls,
5213
+ ctx.config,
5214
+ ctx,
5215
+ input.options
5216
+ );
5217
+ }
5218
+ }),
5219
+ answer: defineCapability({
5220
+ options: valyuImplementation.getToolOptionsSchema?.("answer"),
5221
+ async execute(input, ctx) {
5222
+ return await valyuImplementation.answer(
5223
+ input.query,
5224
+ ctx.config,
5225
+ ctx,
5226
+ input.options
5227
+ );
5228
+ }
5229
+ }),
5230
+ research: defineCapability({
5231
+ options: valyuImplementation.getToolOptionsSchema?.("research"),
5232
+ async execute(input, ctx) {
5233
+ return await valyuImplementation.research(
5234
+ input.input,
5235
+ ctx.config,
5236
+ ctx,
5237
+ input.options
5238
+ );
5239
+ }
5240
+ })
5241
+ }
5242
+ });
4413
5243
 
4414
5244
  // src/providers/index.ts
4415
- var ADAPTERS_BY_ID = {
4416
- claude: claudeAdapter,
4417
- codex: codexAdapter,
4418
- cloudflare: cloudflareAdapter,
4419
- custom: customAdapter,
4420
- exa: exaAdapter,
4421
- firecrawl: firecrawlAdapter,
4422
- gemini: geminiAdapter,
4423
- linkup: linkupAdapter,
4424
- openai: openaiAdapter,
4425
- parallel: parallelAdapter,
4426
- perplexity: perplexityAdapter,
4427
- serper: serperAdapter,
4428
- tavily: tavilyAdapter,
4429
- valyu: valyuAdapter
4430
- };
4431
- var ADAPTERS = Object.values(ADAPTERS_BY_ID);
5245
+ var PROVIDERS = defineProviders({
5246
+ claude: claudeProvider,
5247
+ codex: codexProvider,
5248
+ cloudflare: cloudflareProvider,
5249
+ custom: customProvider,
5250
+ exa: exaProvider,
5251
+ firecrawl: firecrawlProvider,
5252
+ gemini: geminiProvider,
5253
+ linkup: linkupProvider,
5254
+ ollama: ollamaProvider,
5255
+ openai: openaiProvider,
5256
+ parallel: parallelProvider,
5257
+ perplexity: perplexityProvider,
5258
+ serper: serperProvider,
5259
+ tavily: tavilyProvider,
5260
+ valyu: valyuProvider
5261
+ });
5262
+ var PROVIDERS_BY_ID = PROVIDERS;
5263
+ var PROVIDER_LIST = Object.values(PROVIDERS);
5264
+ var PROVIDER_IDS = Object.keys(PROVIDERS);
4432
5265
 
4433
5266
  // src/types.ts
4434
- var PROVIDER_IDS = [
4435
- "claude",
4436
- "cloudflare",
4437
- "codex",
4438
- "custom",
4439
- "exa",
4440
- "firecrawl",
4441
- "gemini",
4442
- "linkup",
4443
- "openai",
4444
- "parallel",
4445
- "perplexity",
4446
- "serper",
4447
- "tavily",
4448
- "valyu"
4449
- ];
4450
5267
  var TOOLS = ["search", "contents", "answer", "research"];
4451
5268
 
4452
5269
  // src/provider-tools.ts
@@ -4469,16 +5286,16 @@ var TOOL_INFO = {
4469
5286
  }
4470
5287
  };
4471
5288
  function supportsTool(providerId, toolId) {
4472
- return typeof ADAPTERS_BY_ID[providerId][toolId] === "function";
5289
+ const capabilities = PROVIDERS[providerId].capabilities;
5290
+ return capabilities[toolId] !== void 0;
4473
5291
  }
4474
5292
  function getProviderTools(providerId) {
4475
- const provider = ADAPTERS_BY_ID[providerId];
4476
- return TOOLS.filter((tool) => typeof provider[tool] === "function");
5293
+ return TOOLS.filter((tool) => supportsTool(providerId, tool));
4477
5294
  }
4478
5295
  function getCompatibleProviders(toolId) {
4479
- return ADAPTERS.filter((provider) => supportsTool(provider.id, toolId)).map(
4480
- (provider) => provider.id
4481
- );
5296
+ return PROVIDER_LIST.filter(
5297
+ (provider) => supportsTool(provider.id, toolId)
5298
+ ).map((provider) => provider.id);
4482
5299
  }
4483
5300
  function getMappedProviderForTool(config, tool) {
4484
5301
  return config.tools?.[tool];
@@ -4565,111 +5382,52 @@ function normalizeConfig(raw, source) {
4565
5382
  return config;
4566
5383
  }
4567
5384
  function normalizeProvider(providerId, raw, source) {
4568
- switch (providerId) {
4569
- case "claude":
4570
- return parseProviderWithShape(raw, source, providerId, {
4571
- pathToClaudeCodeExecutable: readOptionalString2,
4572
- options: readOptionalObject,
4573
- settings: parseOptionalExecutionSettings
4574
- });
4575
- case "cloudflare":
4576
- return parseProviderWithShape(raw, source, providerId, {
4577
- apiToken: readOptionalString2,
4578
- accountId: readOptionalString2,
4579
- options: readOptionalObject,
4580
- settings: parseOptionalExecutionSettings
4581
- });
4582
- case "codex":
4583
- return parseProviderWithShape(raw, source, providerId, {
4584
- codexPath: readOptionalString2,
4585
- baseUrl: readOptionalString2,
4586
- apiKey: readOptionalString2,
4587
- env: readOptionalStringMap,
4588
- config: readOptionalObject,
4589
- options: readOptionalObject,
4590
- settings: parseOptionalExecutionSettings
4591
- });
4592
- case "exa":
4593
- return parseProviderWithShape(raw, source, providerId, {
4594
- apiKey: readOptionalString2,
4595
- baseUrl: readOptionalString2,
4596
- options: (value, innerSource, field) => parseOptionalCapabilityOptions(
4597
- value,
4598
- innerSource,
4599
- field,
4600
- ["search"]
4601
- ),
4602
- settings: parseOptionalExecutionSettings
4603
- });
4604
- case "valyu":
4605
- return parseProviderWithShape(raw, source, providerId, {
4606
- apiKey: readOptionalString2,
4607
- baseUrl: readOptionalString2,
4608
- options: (value, innerSource, field) => parseOptionalCapabilityOptions(
4609
- value,
4610
- innerSource,
4611
- field,
4612
- ["search", "answer", "research"]
4613
- ),
4614
- settings: parseOptionalExecutionSettings
4615
- });
4616
- case "gemini":
4617
- return parseProviderWithShape(raw, source, providerId, {
4618
- apiKey: readOptionalString2,
4619
- options: readOptionalObject,
4620
- settings: parseOptionalExecutionSettings
4621
- });
4622
- case "openai":
4623
- return parseProviderWithShape(raw, source, providerId, {
4624
- apiKey: readOptionalString2,
4625
- baseUrl: readOptionalString2,
4626
- options: (value, innerSource, field) => parseOptionalCapabilityOptions(
4627
- value,
4628
- innerSource,
4629
- field,
4630
- ["search", "answer", "research"]
4631
- ),
4632
- settings: parseOptionalExecutionSettings
4633
- });
4634
- case "firecrawl":
4635
- case "linkup":
4636
- case "parallel":
4637
- case "perplexity":
4638
- return parseProviderWithShape(
4639
- raw,
5385
+ const definition = PROVIDERS[providerId];
5386
+ return parseProviderWithShape(
5387
+ raw,
5388
+ source,
5389
+ providerId,
5390
+ buildProviderConfigShape(
5391
+ definition.config.fields,
5392
+ definition.config.optionCapabilities
5393
+ )
5394
+ );
5395
+ }
5396
+ function buildProviderConfigShape(fields, optionCapabilities) {
5397
+ return Object.fromEntries(
5398
+ fields.map((field) => [
5399
+ toProviderConfigKey(field),
5400
+ getProviderConfigFieldParser(field, optionCapabilities)
5401
+ ])
5402
+ );
5403
+ }
5404
+ function toProviderConfigKey(field) {
5405
+ return field === "customOptions" ? "options" : field;
5406
+ }
5407
+ function getProviderConfigFieldParser(field, optionCapabilities) {
5408
+ switch (field) {
5409
+ case "accountId":
5410
+ case "apiKey":
5411
+ case "apiToken":
5412
+ case "baseUrl":
5413
+ case "codexPath":
5414
+ case "pathToClaudeCodeExecutable":
5415
+ return readOptionalString2;
5416
+ case "config":
5417
+ return readOptionalObject;
5418
+ case "customOptions":
5419
+ return parseOptionalCustomProviderOptions;
5420
+ case "env":
5421
+ return readOptionalStringMap;
5422
+ case "options":
5423
+ return optionCapabilities ? (value, source, field2) => parseOptionalCapabilityOptions(
5424
+ value,
4640
5425
  source,
4641
- providerId,
4642
- {
4643
- apiKey: readOptionalString2,
4644
- baseUrl: readOptionalString2,
4645
- options: readOptionalObject,
4646
- settings: parseOptionalExecutionSettings
4647
- }
4648
- );
4649
- case "serper":
4650
- return parseProviderWithShape(raw, source, providerId, {
4651
- apiKey: readOptionalString2,
4652
- baseUrl: readOptionalString2,
4653
- options: (value, innerSource, field) => parseOptionalCapabilityOptions(
4654
- value,
4655
- innerSource,
4656
- field,
4657
- ["search"]
4658
- ),
4659
- settings: parseOptionalExecutionSettings
4660
- });
4661
- case "tavily":
4662
- return parseProviderWithShape(raw, source, providerId, {
4663
- apiKey: readOptionalString2,
4664
- baseUrl: readOptionalString2,
4665
- options: readOptionalObject,
4666
- settings: parseOptionalExecutionSettings
4667
- });
4668
- case "custom":
4669
- return parseProviderWithShape(raw, source, providerId, {
4670
- options: parseOptionalCustomProviderOptions,
4671
- settings: parseOptionalExecutionSettings
4672
- });
5426
+ field2,
5427
+ optionCapabilities
5428
+ ) : readOptionalObject;
5429
+ case "settings":
5430
+ return parseOptionalExecutionSettings;
4673
5431
  }
4674
5432
  }
4675
5433
  function parseProviderWithShape(raw, source, providerId, shape) {
@@ -4755,36 +5513,47 @@ function parseExecutionSettings(value, source, field, allowSearch) {
4755
5513
  if (unknownKeys.length > 0) {
4756
5514
  throw new Error(`'${field}' in ${source} must be a JSON object.`);
4757
5515
  }
4758
- const parsed = {
4759
- requestTimeoutMs: parseOptionalPositiveInteger(
4760
- settings.requestTimeoutMs,
4761
- source,
4762
- `${field}.requestTimeoutMs`
4763
- ),
4764
- retryCount: parseOptionalNonNegativeInteger(
4765
- settings.retryCount,
4766
- source,
4767
- `${field}.retryCount`
4768
- ),
4769
- retryDelayMs: parseOptionalPositiveInteger(
4770
- settings.retryDelayMs,
4771
- source,
4772
- `${field}.retryDelayMs`
4773
- ),
4774
- researchTimeoutMs: parseOptionalPositiveInteger(
4775
- settings.researchTimeoutMs,
5516
+ const parsed = {};
5517
+ const requestTimeoutMs = parseOptionalPositiveInteger(
5518
+ settings.requestTimeoutMs,
5519
+ source,
5520
+ `${field}.requestTimeoutMs`
5521
+ );
5522
+ if (requestTimeoutMs !== void 0) {
5523
+ parsed.requestTimeoutMs = requestTimeoutMs;
5524
+ }
5525
+ const retryCount = parseOptionalNonNegativeInteger(
5526
+ settings.retryCount,
5527
+ source,
5528
+ `${field}.retryCount`
5529
+ );
5530
+ if (retryCount !== void 0) {
5531
+ parsed.retryCount = retryCount;
5532
+ }
5533
+ const retryDelayMs = parseOptionalPositiveInteger(
5534
+ settings.retryDelayMs,
5535
+ source,
5536
+ `${field}.retryDelayMs`
5537
+ );
5538
+ if (retryDelayMs !== void 0) {
5539
+ parsed.retryDelayMs = retryDelayMs;
5540
+ }
5541
+ const researchTimeoutMs = parseOptionalPositiveInteger(
5542
+ settings.researchTimeoutMs,
5543
+ source,
5544
+ `${field}.researchTimeoutMs`
5545
+ );
5546
+ if (researchTimeoutMs !== void 0) {
5547
+ parsed.researchTimeoutMs = researchTimeoutMs;
5548
+ }
5549
+ if (allowSearch && settings.search !== void 0) {
5550
+ parsed.search = parseSearchSettings(
5551
+ settings.search,
4776
5552
  source,
4777
- `${field}.researchTimeoutMs`
4778
- ),
4779
- ...allowSearch && settings.search !== void 0 ? {
4780
- search: parseSearchSettings(
4781
- settings.search,
4782
- source,
4783
- `${field}.search`
4784
- )
4785
- } : {}
4786
- };
4787
- return Object.values(parsed).some((entry) => entry !== void 0) ? parsed : {};
5553
+ `${field}.search`
5554
+ );
5555
+ }
5556
+ return parsed;
4788
5557
  }
4789
5558
  function parseToolProviderMapping(value, source, field) {
4790
5559
  const mapping = requireObject2(
@@ -5118,93 +5887,39 @@ ${JSON.stringify(value, null, 2).trim()}
5118
5887
  }
5119
5888
 
5120
5889
  // src/options.ts
5121
- import { Type as Type15 } from "typebox";
5122
- var runtimeOptionFields = {
5123
- requestTimeoutMs: Type15.Optional(
5124
- Type15.Integer({
5125
- minimum: 1,
5126
- description: "Maximum time in milliseconds to wait for a single provider request."
5127
- })
5128
- ),
5129
- retryCount: Type15.Optional(
5130
- Type15.Integer({
5131
- minimum: 0,
5132
- description: "Number of times to retry transient failures."
5133
- })
5134
- ),
5135
- retryDelayMs: Type15.Optional(
5136
- Type15.Integer({
5137
- minimum: 1,
5138
- description: "Initial delay in milliseconds before retrying. Later retries back off automatically."
5139
- })
5140
- )
5141
- };
5142
- var runtimeOptionsSchema = Type15.Object(runtimeOptionFields, {
5143
- additionalProperties: false,
5144
- description: "Local runtime controls for this tool call."
5145
- });
5146
- var searchPrefetchOptionsSchema = Type15.Object(
5147
- {
5148
- provider: Type15.Optional(
5149
- Type15.Union([...PROVIDER_IDS.map((id) => Type15.Literal(id)), Type15.Null()], {
5150
- description: "Contents-capable provider to prefetch search result URLs. Set to enable prefetch."
5151
- })
5152
- ),
5153
- maxUrls: Type15.Optional(
5154
- Type15.Integer({
5155
- minimum: 1,
5156
- description: "Maximum number of search result URLs to prefetch."
5157
- })
5158
- ),
5159
- ttlMs: Type15.Optional(
5160
- Type15.Integer({
5161
- minimum: 1,
5162
- description: "How long prefetched contents stay reusable in the local cache, in milliseconds."
5163
- })
5164
- )
5165
- },
5166
- {
5167
- additionalProperties: false,
5168
- description: "Background contents prefetch for search result URLs. Only runs when provider is set."
5169
- }
5170
- );
5171
- var searchRuntimeOptionsSchema = Type15.Object(
5172
- {
5173
- ...runtimeOptionFields,
5174
- prefetch: Type15.Optional(searchPrefetchOptionsSchema)
5175
- },
5176
- {
5177
- additionalProperties: false,
5178
- description: "Local runtime controls for search."
5179
- }
5180
- );
5181
- function buildToolOptionsSchema(capability, providerSchema) {
5182
- const properties = {};
5183
- const runtimeSchema = getRuntimeOptionsSchema(capability);
5184
- if (runtimeSchema) {
5185
- properties.runtime = Type15.Optional(runtimeSchema);
5186
- }
5187
- if (providerSchema && Object.keys(providerSchema.properties).length > 0) {
5188
- properties.provider = Type15.Optional(providerSchema);
5189
- }
5190
- if (Object.keys(properties).length === 0) {
5890
+ function buildToolOptionsSchema(_capability, providerSchema) {
5891
+ if (!providerSchema || Object.keys(providerSchema.properties).length === 0) {
5191
5892
  return void 0;
5192
5893
  }
5193
- return Type15.Object(properties, {
5194
- additionalProperties: false,
5195
- description: "Options for this tool call split into provider-facing settings and local runtime controls."
5196
- });
5894
+ return closeObjectSchemas(providerSchema);
5197
5895
  }
5198
- function getRuntimeOptionsSchema(capability) {
5199
- switch (capability) {
5200
- case "search":
5201
- return searchRuntimeOptionsSchema;
5202
- case "contents":
5203
- case "answer":
5204
- return runtimeOptionsSchema;
5205
- default:
5206
- return void 0;
5896
+ function closeObjectSchemas(schema) {
5897
+ if (!isSchemaRecord(schema)) {
5898
+ return schema;
5207
5899
  }
5900
+ const properties = isSchemaRecord(schema.properties) ? Object.fromEntries(
5901
+ Object.entries(schema.properties).map(([key, value]) => [
5902
+ key,
5903
+ closeObjectSchemas(value)
5904
+ ])
5905
+ ) : schema.properties;
5906
+ const items = isSchemaRecord(schema.items) ? closeObjectSchemas(schema.items) : Array.isArray(schema.items) ? schema.items.map((item) => closeObjectSchemas(item)) : schema.items;
5907
+ return {
5908
+ ...schema,
5909
+ ...properties ? { properties } : {},
5910
+ ...items ? { items } : {},
5911
+ ...mapSchemaArray(schema, "anyOf"),
5912
+ ...mapSchemaArray(schema, "oneOf"),
5913
+ ...mapSchemaArray(schema, "allOf"),
5914
+ ...schema.type === "object" && isSchemaRecord(schema.properties) ? { additionalProperties: false } : {}
5915
+ };
5916
+ }
5917
+ function mapSchemaArray(schema, key) {
5918
+ const value = schema[key];
5919
+ return Array.isArray(value) ? { [key]: value.map((entry) => closeObjectSchemas(entry)) } : {};
5920
+ }
5921
+ function isSchemaRecord(value) {
5922
+ return typeof value === "object" && value !== null && !Array.isArray(value);
5208
5923
  }
5209
5924
 
5210
5925
  // src/prefetch-manager.ts
@@ -5212,19 +5927,16 @@ import { createHash } from "node:crypto";
5212
5927
 
5213
5928
  // src/provider-resolution.ts
5214
5929
  function supportsTool2(provider, tool) {
5215
- return typeof provider[tool] === "function";
5930
+ return provider.capabilities[tool] !== void 0;
5216
5931
  }
5217
5932
  function resolveSearchProvider(config, cwd, explicit) {
5218
5933
  return resolveProviderForTool(config, cwd, "search", explicit);
5219
5934
  }
5220
5935
  function getEffectiveSharedSettings(config) {
5221
- return {
5222
- ...createDefaultExecutionSettings(),
5223
- ...config.settings ?? {}
5224
- };
5936
+ return mergeExecutionSettings(createDefaultExecutionSettings(), config.settings) ?? createDefaultExecutionSettings();
5225
5937
  }
5226
5938
  function getEffectiveProviderConfig(config, providerId) {
5227
- const defaults = ADAPTERS_BY_ID[providerId].createTemplate();
5939
+ const defaults = PROVIDERS[providerId].config.createTemplate();
5228
5940
  const overrides = config.providers?.[providerId] ?? {};
5229
5941
  const providerSettings = mergeExecutionSettings(
5230
5942
  defaults.settings,
@@ -5279,7 +5991,7 @@ function getMappedProviderIdForTool(config, tool) {
5279
5991
  return getMappedProviderForTool(config, tool);
5280
5992
  }
5281
5993
  function getProviderCapabilityStatus(config, cwd, providerId, tool) {
5282
- const provider = ADAPTERS_BY_ID[providerId];
5994
+ const provider = PROVIDERS[providerId];
5283
5995
  return provider.getCapabilityStatus(
5284
5996
  getEffectiveProviderConfig(config, providerId),
5285
5997
  cwd,
@@ -5326,7 +6038,7 @@ function resolveProviderForTool(config, cwd, tool, explicit) {
5326
6038
  `No provider is configured for '${tool}'. Run /web-providers to configure tool mappings.`
5327
6039
  );
5328
6040
  }
5329
- const provider = ADAPTERS_BY_ID[providerId];
6041
+ const provider = PROVIDERS[providerId];
5330
6042
  if (!supportsTool2(provider, tool)) {
5331
6043
  throw new Error(`Provider '${providerId}' does not support '${tool}'.`);
5332
6044
  }
@@ -5342,21 +6054,27 @@ function resolveProviderForTool(config, cwd, tool, explicit) {
5342
6054
  }
5343
6055
 
5344
6056
  // src/provider-runtime.ts
5345
- async function executeProviderRequest(provider, config, request, options, context) {
6057
+ async function executeProviderRequest(provider, config, request, context) {
5346
6058
  return await executeProviderExecution(
5347
6059
  {
5348
6060
  capability: request.capability,
5349
6061
  providerLabel: provider.label,
5350
6062
  settings: config.settings,
5351
- execute: (executionContext) => executeProviderHandler(provider, config, request, executionContext)
6063
+ execute: (executionContext) => executeProviderCapability(
6064
+ provider,
6065
+ request.capability,
6066
+ providerInputFromRequest(request),
6067
+ {
6068
+ ...executionContext,
6069
+ config
6070
+ }
6071
+ )
5352
6072
  },
5353
- options,
5354
6073
  context
5355
6074
  );
5356
6075
  }
5357
- async function executeProviderExecution(execution, options, context) {
6076
+ async function executeProviderExecution(execution, context) {
5358
6077
  if (execution.capability === "research") {
5359
- rejectResearchExecutionControls(execution.providerLabel, options);
5360
6078
  const deadline = createResearchDeadlineSignal(
5361
6079
  context.signal,
5362
6080
  execution.providerLabel,
@@ -5379,7 +6097,7 @@ async function executeProviderExecution(execution, options, context) {
5379
6097
  deadline?.cleanup();
5380
6098
  }
5381
6099
  }
5382
- const requestPolicy = resolveExecutionPolicy(execution.settings, options);
6100
+ const requestPolicy = resolveExecutionPolicy(execution.settings);
5383
6101
  try {
5384
6102
  return await runWithExecutionPolicy(
5385
6103
  `${execution.providerLabel} ${execution.capability} request`,
@@ -5396,65 +6114,36 @@ async function executeProviderExecution(execution, options, context) {
5396
6114
  );
5397
6115
  }
5398
6116
  }
5399
- async function executeProviderHandler(provider, config, request, context) {
6117
+ function providerInputFromRequest(request) {
5400
6118
  switch (request.capability) {
5401
- case "search": {
5402
- if (!provider.search) {
5403
- throw unsupportedProviderTool(provider, request.capability);
5404
- }
5405
- return await provider.search(
5406
- request.query,
5407
- request.maxResults,
5408
- config,
5409
- context,
5410
- request.options
5411
- );
5412
- }
5413
- case "contents": {
5414
- if (!provider.contents) {
5415
- throw unsupportedProviderTool(provider, request.capability);
5416
- }
5417
- return await provider.contents(
5418
- request.urls,
5419
- config,
5420
- context,
5421
- request.options
5422
- );
5423
- }
5424
- case "answer": {
5425
- if (!provider.answer) {
5426
- throw unsupportedProviderTool(provider, request.capability);
5427
- }
5428
- return await provider.answer(
5429
- request.query,
5430
- config,
5431
- context,
5432
- request.options
5433
- );
5434
- }
5435
- case "research": {
5436
- if (!provider.research) {
5437
- throw unsupportedProviderTool(provider, request.capability);
5438
- }
5439
- return await provider.research(
5440
- request.input,
5441
- config,
5442
- context,
5443
- request.options
5444
- );
5445
- }
6119
+ case "search":
6120
+ return {
6121
+ query: request.query,
6122
+ maxResults: request.maxResults,
6123
+ options: request.options
6124
+ };
6125
+ case "contents":
6126
+ return {
6127
+ urls: request.urls,
6128
+ options: request.options
6129
+ };
6130
+ case "answer":
6131
+ return {
6132
+ query: request.query,
6133
+ options: request.options
6134
+ };
6135
+ case "research":
6136
+ return {
6137
+ input: request.input,
6138
+ options: request.options
6139
+ };
5446
6140
  }
5447
6141
  }
5448
- function unsupportedProviderTool(provider, tool) {
5449
- return new Error(`Provider '${provider.id}' does not support '${tool}'.`);
5450
- }
5451
- function resolveExecutionPolicy(defaults, options) {
5452
- validateRuntimeOptions(options);
5453
- const localOptions = parseLocalExecutionOptions(options);
6142
+ function resolveExecutionPolicy(defaults) {
5454
6143
  return {
5455
- requestTimeoutMs: localOptions.requestTimeoutMs ?? defaults?.requestTimeoutMs,
5456
- retryCount: localOptions.retryCount ?? defaults?.retryCount ?? 0,
5457
- retryDelayMs: localOptions.retryDelayMs ?? defaults?.retryDelayMs ?? 2e3
6144
+ requestTimeoutMs: defaults?.requestTimeoutMs,
6145
+ retryCount: defaults?.retryCount ?? 0,
6146
+ retryDelayMs: defaults?.retryDelayMs ?? 2e3
5458
6147
  };
5459
6148
  }
5460
6149
  function createResearchDeadlineSignal(signal, providerLabel, timeoutMs) {
@@ -5522,26 +6211,6 @@ async function withAbortSignal(promise, signal) {
5522
6211
  );
5523
6212
  });
5524
6213
  }
5525
- function rejectResearchExecutionControls(providerLabel, options) {
5526
- if (!options || Object.keys(options).length === 0) {
5527
- return;
5528
- }
5529
- throw new Error(`${providerLabel} research does not accept options.runtime.`);
5530
- }
5531
- function validateRuntimeOptions(options) {
5532
- if (!options) {
5533
- return;
5534
- }
5535
- const unsupportedKeys = Object.keys(options).filter(
5536
- (key) => key !== "requestTimeoutMs" && key !== "retryCount" && key !== "retryDelayMs"
5537
- );
5538
- if (unsupportedKeys.length === 0) {
5539
- return;
5540
- }
5541
- throw new Error(
5542
- `Unsupported runtime options: ${unsupportedKeys.join(", ")}.`
5543
- );
5544
- }
5545
6214
 
5546
6215
  // src/prefetch-manager.ts
5547
6216
  var CONTENT_CACHE_VERSION = 2;
@@ -5596,7 +6265,6 @@ async function resolveContentsFromStore({
5596
6265
  config,
5597
6266
  cwd,
5598
6267
  options,
5599
- runtimeOptions,
5600
6268
  signal,
5601
6269
  onProgress
5602
6270
  }) {
@@ -5608,7 +6276,6 @@ async function resolveContentsFromStore({
5608
6276
  config,
5609
6277
  cwd,
5610
6278
  options,
5611
- runtimeOptions,
5612
6279
  signal,
5613
6280
  onProgress
5614
6281
  });
@@ -5619,28 +6286,10 @@ async function resolveContentsFromStore({
5619
6286
  config,
5620
6287
  cwd,
5621
6288
  options,
5622
- runtimeOptions,
5623
6289
  signal,
5624
6290
  onProgress
5625
6291
  });
5626
6292
  }
5627
- function parseSearchContentsPrefetchOptions(options) {
5628
- const raw = options?.prefetch;
5629
- if (raw === void 0) {
5630
- return void 0;
5631
- }
5632
- if (!isJsonObject3(raw)) {
5633
- throw new Error("prefetch must be an object.");
5634
- }
5635
- const maxUrls = parseOptionalPositiveInteger2(raw.maxUrls, "maxUrls");
5636
- const provider = parseOptionalProviderId(raw.provider);
5637
- const ttlMs = parseOptionalPositiveInteger2(raw.ttlMs, "ttlMs");
5638
- return {
5639
- maxUrls,
5640
- provider,
5641
- ttlMs
5642
- };
5643
- }
5644
6293
  function mergeSearchContentsPrefetchOptions(defaults, overrides) {
5645
6294
  if (!defaults && !overrides) {
5646
6295
  return void 0;
@@ -5651,13 +6300,6 @@ function mergeSearchContentsPrefetchOptions(defaults, overrides) {
5651
6300
  ttlMs: overrides?.ttlMs !== void 0 ? overrides.ttlMs : defaults?.ttlMs
5652
6301
  };
5653
6302
  }
5654
- function stripSearchContentsPrefetchOptions(options) {
5655
- if (!options) {
5656
- return void 0;
5657
- }
5658
- const { prefetch: _prefetch, ...rest } = options;
5659
- return Object.keys(rest).length > 0 ? rest : void 0;
5660
- }
5661
6303
  function resetContentStore() {
5662
6304
  contentStoreGeneration += 1;
5663
6305
  contentCache.clear();
@@ -5669,7 +6311,6 @@ async function resolvePerUrlContents({
5669
6311
  config,
5670
6312
  cwd,
5671
6313
  options,
5672
- runtimeOptions,
5673
6314
  signal,
5674
6315
  onProgress
5675
6316
  }) {
@@ -5681,7 +6322,6 @@ async function resolvePerUrlContents({
5681
6322
  config,
5682
6323
  cwd,
5683
6324
  options,
5684
- runtimeOptions,
5685
6325
  signal,
5686
6326
  onProgress
5687
6327
  })
@@ -5728,7 +6368,6 @@ async function fetchBatchContents({
5728
6368
  config,
5729
6369
  cwd,
5730
6370
  options,
5731
- runtimeOptions,
5732
6371
  signal,
5733
6372
  onProgress,
5734
6373
  ttlMs = DEFAULT_CONTENT_TTL_MS,
@@ -5744,7 +6383,6 @@ async function fetchBatchContents({
5744
6383
  config,
5745
6384
  cwd,
5746
6385
  options,
5747
- runtimeOptions,
5748
6386
  signal,
5749
6387
  onProgress
5750
6388
  });
@@ -5775,7 +6413,6 @@ async function ensureContentsStored({
5775
6413
  config,
5776
6414
  cwd,
5777
6415
  options,
5778
- runtimeOptions,
5779
6416
  signal,
5780
6417
  onProgress,
5781
6418
  ttlMs = DEFAULT_CONTENT_TTL_MS,
@@ -5800,7 +6437,6 @@ async function ensureContentsStored({
5800
6437
  config,
5801
6438
  cwd,
5802
6439
  options,
5803
- runtimeOptions,
5804
6440
  signal,
5805
6441
  onProgress
5806
6442
  });
@@ -5831,11 +6467,10 @@ async function fetchContentsViaProvider({
5831
6467
  config,
5832
6468
  cwd,
5833
6469
  options,
5834
- runtimeOptions,
5835
6470
  signal,
5836
6471
  onProgress
5837
6472
  }) {
5838
- const provider = ADAPTERS_BY_ID[providerId];
6473
+ const provider = PROVIDERS_BY_ID[providerId];
5839
6474
  const providerConfig = getEffectiveProviderConfig(config, providerId);
5840
6475
  onProgress?.(
5841
6476
  `Fetching contents via ${provider.label} for ${urls.length} URL(s)`
@@ -5848,7 +6483,6 @@ async function fetchContentsViaProvider({
5848
6483
  urls,
5849
6484
  options
5850
6485
  },
5851
- runtimeOptions,
5852
6486
  {
5853
6487
  cwd,
5854
6488
  signal,
@@ -5948,7 +6582,7 @@ function resolveContentsProvider(config, cwd, explicitProvider) {
5948
6582
  if (!explicitProvider) {
5949
6583
  return void 0;
5950
6584
  }
5951
- const provider = ADAPTERS_BY_ID[explicitProvider];
6585
+ const provider = PROVIDERS_BY_ID[explicitProvider];
5952
6586
  if (!supportsTool(explicitProvider, "contents")) {
5953
6587
  return void 0;
5954
6588
  }
@@ -6018,27 +6652,6 @@ function formatUnknownError(error) {
6018
6652
  function isJsonObject3(value) {
6019
6653
  return typeof value === "object" && value !== null && !Array.isArray(value);
6020
6654
  }
6021
- function parseOptionalPositiveInteger2(value, field) {
6022
- if (value === void 0) {
6023
- return void 0;
6024
- }
6025
- if (!Number.isInteger(value) || Number(value) <= 0) {
6026
- throw new Error(`prefetch.${field} must be a positive integer.`);
6027
- }
6028
- return Number(value);
6029
- }
6030
- function parseOptionalProviderId(value) {
6031
- if (value === void 0) {
6032
- return void 0;
6033
- }
6034
- if (value === null) {
6035
- return null;
6036
- }
6037
- if (isProviderId(value)) {
6038
- return value;
6039
- }
6040
- throw new Error("prefetch.provider must be a valid provider id or null.");
6041
- }
6042
6655
  function isProviderId(value) {
6043
6656
  return typeof value === "string" && PROVIDER_IDS.includes(value);
6044
6657
  }
@@ -6050,7 +6663,7 @@ function isContentsAnswer(value) {
6050
6663
  }
6051
6664
 
6052
6665
  // src/provider-config-manifests.ts
6053
- var PROVIDER_CONFIG_MANIFESTS = {
6666
+ var PROVIDER_SETTINGS = {
6054
6667
  claude: {
6055
6668
  settings: [
6056
6669
  stringSetting({
@@ -6475,6 +7088,9 @@ var PROVIDER_CONFIG_MANIFESTS = {
6475
7088
  linkup: {
6476
7089
  settings: [apiKeySetting(), baseUrlSetting()]
6477
7090
  },
7091
+ ollama: {
7092
+ settings: [apiKeySetting(), baseUrlSetting()]
7093
+ },
6478
7094
  openai: {
6479
7095
  settings: [
6480
7096
  apiKeySetting(),
@@ -6711,9 +7327,20 @@ var PROVIDER_CONFIG_MANIFESTS = {
6711
7327
  ]
6712
7328
  }
6713
7329
  };
7330
+ var PROVIDER_CONFIG_MANIFESTS = deriveProviderConfigManifests();
6714
7331
  function getProviderConfigManifest(providerId) {
6715
7332
  return PROVIDER_CONFIG_MANIFESTS[providerId];
6716
7333
  }
7334
+ function deriveProviderConfigManifests() {
7335
+ return Object.fromEntries(
7336
+ Object.keys(PROVIDERS).map((providerId) => [
7337
+ providerId,
7338
+ {
7339
+ settings: PROVIDER_SETTINGS[providerId]?.settings ?? []
7340
+ }
7341
+ ])
7342
+ );
7343
+ }
6717
7344
  function stringSetting(setting) {
6718
7345
  return {
6719
7346
  kind: "text",
@@ -7084,10 +7711,6 @@ function webProvidersExtension(pi) {
7084
7711
  )
7085
7712
  );
7086
7713
  };
7087
- registerManagedTools(pi, {
7088
- activeWebResearchRequests,
7089
- updateWebResearchWidget
7090
- });
7091
7714
  if ("registerMessageRenderer" in pi) {
7092
7715
  pi.registerMessageRenderer(
7093
7716
  WEB_RESEARCH_RESULT_MESSAGE_TYPE,
@@ -7134,29 +7757,21 @@ function webProvidersExtension(pi) {
7134
7757
  stopWebResearchWidgetTimer();
7135
7758
  latestWidgetContext?.ui.setWidget(WEB_RESEARCH_WIDGET_KEY, void 0);
7136
7759
  });
7137
- registerManagedTools(pi, {
7138
- activeWebResearchRequests,
7139
- updateWebResearchWidget
7140
- });
7141
7760
  }
7142
7761
  function registerManagedTools(pi, webResearchLifecycle, providerIdsByCapability = {}) {
7143
- registerWebSearchTool(pi, providerIdsByCapability.search ?? PROVIDER_IDS);
7144
- registerWebContentsTool(
7145
- pi,
7146
- providerIdsByCapability.contents ?? getProviderIdsForCapability("contents")
7147
- );
7148
- registerWebAnswerTool(
7149
- pi,
7150
- providerIdsByCapability.answer ?? getProviderIdsForCapability("answer")
7151
- );
7762
+ registerWebSearchTool(pi, providerIdsByCapability.search ?? []);
7763
+ registerWebContentsTool(pi, providerIdsByCapability.contents ?? []);
7764
+ registerWebAnswerTool(pi, providerIdsByCapability.answer ?? []);
7152
7765
  registerWebResearchTool(
7153
7766
  pi,
7154
7767
  webResearchLifecycle,
7155
- providerIdsByCapability.research ?? getProviderIdsForCapability("research")
7768
+ providerIdsByCapability.research ?? []
7156
7769
  );
7157
7770
  }
7158
7771
  function registerWebSearchTool(pi, providerIds) {
7159
- const selectedProviderId = providerIds.length === 1 ? providerIds[0] : void 0;
7772
+ if (providerIds.length !== 1) return;
7773
+ const selectedProviderId = providerIds[0];
7774
+ const maxAllowedResults = getSearchMaxResultsLimit(selectedProviderId);
7160
7775
  pi.registerTool({
7161
7776
  name: "web_search",
7162
7777
  label: "Web Search",
@@ -7164,24 +7779,27 @@ function registerWebSearchTool(pi, providerIds) {
7164
7779
  promptGuidelines: [
7165
7780
  "Batch related searches when grouped comparison matters; use separate sibling web_search calls when independent results should surface as soon as they are ready."
7166
7781
  ],
7167
- parameters: Type16.Object({
7168
- queries: Type16.Array(Type16.String({ minLength: 1 }), {
7169
- minItems: 1,
7170
- maxItems: MAX_SEARCH_QUERIES,
7171
- description: `One or more search queries to run in one call (max ${MAX_SEARCH_QUERIES})`
7172
- }),
7173
- maxResults: Type16.Optional(
7174
- Type16.Integer({
7175
- minimum: 1,
7176
- maximum: MAX_ALLOWED_RESULTS,
7177
- description: `Maximum number of results to return (default: ${DEFAULT_MAX_RESULTS})`
7178
- })
7179
- ),
7180
- ...optionalField(
7181
- "options",
7182
- buildStructuredOptionsSchema("search", selectedProviderId)
7183
- )
7184
- }),
7782
+ parameters: Type15.Object(
7783
+ {
7784
+ queries: Type15.Array(Type15.String({ minLength: 1 }), {
7785
+ minItems: 1,
7786
+ maxItems: MAX_SEARCH_QUERIES,
7787
+ description: `One or more search queries to run in one call (max ${MAX_SEARCH_QUERIES})`
7788
+ }),
7789
+ maxResults: Type15.Optional(
7790
+ Type15.Integer({
7791
+ minimum: 1,
7792
+ maximum: maxAllowedResults,
7793
+ description: `Maximum number of results to return (default: ${DEFAULT_MAX_RESULTS})`
7794
+ })
7795
+ ),
7796
+ ...optionalField(
7797
+ "options",
7798
+ buildStructuredOptionsSchema("search", selectedProviderId)
7799
+ )
7800
+ },
7801
+ { additionalProperties: false }
7802
+ ),
7185
7803
  async execute(_toolCallId, params, signal, onUpdate, ctx) {
7186
7804
  return executeSearchTool({
7187
7805
  config: await loadConfig(),
@@ -7214,22 +7832,25 @@ function registerWebSearchTool(pi, providerIds) {
7214
7832
  });
7215
7833
  }
7216
7834
  function registerWebContentsTool(pi, providerIds) {
7217
- if (providerIds.length === 0) return;
7218
- const selectedProviderId = providerIds.length === 1 ? providerIds[0] : void 0;
7835
+ if (providerIds.length !== 1) return;
7836
+ const selectedProviderId = providerIds[0];
7219
7837
  pi.registerTool({
7220
7838
  name: "web_contents",
7221
7839
  label: "Web Contents",
7222
7840
  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.",
7223
- parameters: Type16.Object({
7224
- urls: Type16.Array(Type16.String({ minLength: 1 }), {
7225
- minItems: 1,
7226
- description: "One or more URLs to extract"
7227
- }),
7228
- ...optionalField(
7229
- "options",
7230
- buildStructuredOptionsSchema("contents", selectedProviderId)
7231
- )
7232
- }),
7841
+ parameters: Type15.Object(
7842
+ {
7843
+ urls: Type15.Array(Type15.String({ minLength: 1 }), {
7844
+ minItems: 1,
7845
+ description: "One or more URLs to extract"
7846
+ }),
7847
+ ...optionalField(
7848
+ "options",
7849
+ buildStructuredOptionsSchema("contents", selectedProviderId)
7850
+ )
7851
+ },
7852
+ { additionalProperties: false }
7853
+ ),
7233
7854
  async execute(_toolCallId, params, signal, onUpdate, ctx) {
7234
7855
  return executeProviderTool({
7235
7856
  config: await loadConfig(),
@@ -7265,24 +7886,29 @@ function registerWebContentsTool(pi, providerIds) {
7265
7886
  });
7266
7887
  }
7267
7888
  function registerWebAnswerTool(pi, providerIds) {
7268
- if (providerIds.length === 0) return;
7269
- const selectedProviderId = providerIds.length === 1 ? providerIds[0] : void 0;
7889
+ if (providerIds.length !== 1) return;
7890
+ const selectedProviderId = providerIds[0];
7270
7891
  pi.registerTool({
7271
7892
  name: "web_answer",
7272
7893
  label: "Web Answer",
7273
- description: `Answer one or more questions using web-grounded evidence (up to ${MAX_SEARCH_QUERIES} per call).`,
7274
- parameters: Type16.Object({
7275
- queries: Type16.Array(Type16.String({ minLength: 1 }), {
7276
- minItems: 1,
7277
- maxItems: MAX_SEARCH_QUERIES,
7278
- description: `One or more questions to answer in one call (max ${MAX_SEARCH_QUERIES})`
7279
- }),
7280
- ...optionalField(
7281
- "options",
7282
- buildStructuredOptionsSchema("answer", selectedProviderId)
7283
- )
7284
- }),
7894
+ description: `Answer one or more simple factual questions using web-grounded evidence (up to ${MAX_SEARCH_QUERIES} per call). Prefer web_search plus web_contents when source selection matters, and web_research for multi-step investigations.`,
7895
+ parameters: Type15.Object(
7896
+ {
7897
+ queries: Type15.Array(Type15.String({ minLength: 1 }), {
7898
+ minItems: 1,
7899
+ maxItems: MAX_SEARCH_QUERIES,
7900
+ description: `One or more simple factual questions to answer in one call (max ${MAX_SEARCH_QUERIES})`
7901
+ }),
7902
+ ...optionalField(
7903
+ "options",
7904
+ buildStructuredOptionsSchema("answer", selectedProviderId)
7905
+ )
7906
+ },
7907
+ { additionalProperties: false }
7908
+ ),
7285
7909
  promptGuidelines: [
7910
+ "Use web_answer as a quick grounded-answer shortcut for simple factual questions, not as a replacement for inspecting sources or doing deeper research.",
7911
+ "Prefer web_search plus web_contents when source selection matters or primary sources need direct inspection; prefer web_research for open-ended, controversial, or multi-step investigations.",
7286
7912
  "Batch related questions when the answers belong together; use separate sibling web_answer calls when earlier independent answers can unblock the next step."
7287
7913
  ],
7288
7914
  async execute(_toolCallId, params, signal, onUpdate, ctx) {
@@ -7320,19 +7946,22 @@ function registerWebAnswerTool(pi, providerIds) {
7320
7946
  });
7321
7947
  }
7322
7948
  function registerWebResearchTool(pi, webResearchLifecycle, providerIds) {
7323
- if (providerIds.length === 0) return;
7324
- const selectedProviderId = providerIds.length === 1 ? providerIds[0] : void 0;
7949
+ if (providerIds.length !== 1) return;
7950
+ const selectedProviderId = providerIds[0];
7325
7951
  pi.registerTool({
7326
7952
  name: "web_research",
7327
7953
  label: "Web Research",
7328
7954
  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.",
7329
- parameters: Type16.Object({
7330
- input: Type16.String({ description: "Research brief or question" }),
7331
- ...optionalField(
7332
- "options",
7333
- buildStructuredOptionsSchema("research", selectedProviderId)
7334
- )
7335
- }),
7955
+ parameters: Type15.Object(
7956
+ {
7957
+ input: Type15.String({ description: "Research brief or question" }),
7958
+ ...optionalField(
7959
+ "options",
7960
+ buildStructuredOptionsSchema("research", selectedProviderId)
7961
+ )
7962
+ },
7963
+ { additionalProperties: false }
7964
+ ),
7336
7965
  promptGuidelines: [
7337
7966
  "Use this tool for deep investigations that can finish asynchronously.",
7338
7967
  "Do not expect the final report in the same turn; tell the user that web research has started and wait for the completion message with the saved report path."
@@ -7459,10 +8088,9 @@ async function syncManagedToolAvailability(pi, nextActiveTools) {
7459
8088
  pi.setActiveTools(Array.from(nextActiveTools));
7460
8089
  }
7461
8090
  }
7462
- function getProviderIdsForCapability(capability) {
7463
- return ADAPTERS.filter((provider) => supportsTool2(provider, capability)).map(
7464
- (provider) => provider.id
7465
- );
8091
+ function getSearchMaxResultsLimit(providerId) {
8092
+ const capabilities = PROVIDERS_BY_ID[providerId].capabilities;
8093
+ return capabilities.search?.limits?.maxResults ?? MAX_ALLOWED_RESULTS;
7466
8094
  }
7467
8095
  function optionalField(name, schema) {
7468
8096
  return schema ? { [name]: schema } : {};
@@ -7470,14 +8098,14 @@ function optionalField(name, schema) {
7470
8098
  function buildStructuredOptionsSchema(capability, providerId) {
7471
8099
  const providerSchema = resolveProviderOptionsSchema(capability, providerId);
7472
8100
  const schema = buildToolOptionsSchema(capability, providerSchema);
7473
- return schema ? Type16.Optional(schema) : void 0;
8101
+ return schema ? Type15.Optional(schema) : void 0;
7474
8102
  }
7475
8103
  function resolveProviderOptionsSchema(capability, providerId) {
7476
8104
  if (!providerId) {
7477
8105
  return void 0;
7478
8106
  }
7479
- const adapter = ADAPTERS_BY_ID[providerId];
7480
- return adapter.getToolOptionsSchema?.(capability);
8107
+ const provider = PROVIDERS_BY_ID[providerId];
8108
+ return provider.capabilities[capability]?.options;
7481
8109
  }
7482
8110
  async function executeSearchTool({
7483
8111
  config,
@@ -7489,8 +8117,7 @@ async function executeSearchTool({
7489
8117
  ctx: { cwd: context.cwd },
7490
8118
  signal: context.signal,
7491
8119
  progress: context.progress,
7492
- providerOptions: request.options?.provider,
7493
- runtimeOptions: request.options?.runtime,
8120
+ providerOptions: request.options,
7494
8121
  maxResults: request.maxResults,
7495
8122
  queries: request.queries
7496
8123
  });
@@ -7502,7 +8129,6 @@ async function executeSearchToolInternal({
7502
8129
  signal,
7503
8130
  progress,
7504
8131
  providerOptions,
7505
- runtimeOptions,
7506
8132
  maxResults,
7507
8133
  queries,
7508
8134
  executionOverrides
@@ -7512,7 +8138,7 @@ async function executeSearchToolInternal({
7512
8138
  const providerConfig = getEffectiveProviderConfig(config, provider.id);
7513
8139
  const prefetchOptions = mergeSearchContentsPrefetchOptions(
7514
8140
  getSearchPrefetchDefaults(config),
7515
- parseSearchContentsPrefetchOptions(runtimeOptions)
8141
+ void 0
7516
8142
  );
7517
8143
  const searchQueries = resolveSearchQueries(queries);
7518
8144
  if (executionOverrides !== void 0 && executionOverrides.length !== searchQueries.length) {
@@ -7535,7 +8161,10 @@ async function executeSearchToolInternal({
7535
8161
  cwd: ctx.cwd,
7536
8162
  signal: signal ?? void 0
7537
8163
  };
7538
- const clampedMaxResults = clampResults(maxResults);
8164
+ const clampedMaxResults = clampResults(
8165
+ maxResults,
8166
+ getSearchMaxResultsLimit(provider.id)
8167
+ );
7539
8168
  let outcomes;
7540
8169
  try {
7541
8170
  batchProgress?.start();
@@ -7547,7 +8176,6 @@ async function executeSearchToolInternal({
7547
8176
  query: searchQuery,
7548
8177
  maxResults: clampedMaxResults,
7549
8178
  options: providerOptions,
7550
- runtimeOptions,
7551
8179
  providerContext,
7552
8180
  onProgress: searchQueries.length > 1 ? void 0 : progressReporter.report,
7553
8181
  executionOverride: executionOverrides?.[index]
@@ -7597,7 +8225,6 @@ async function executeRawProviderRequest({
7597
8225
  ctx,
7598
8226
  signal,
7599
8227
  options,
7600
- runtimeOptions,
7601
8228
  maxResults,
7602
8229
  urls,
7603
8230
  query: query2,
@@ -7610,9 +8237,11 @@ async function executeRawProviderRequest({
7610
8237
  provider: provider2,
7611
8238
  providerConfig: providerConfig2,
7612
8239
  query: query2 ?? "",
7613
- maxResults: clampResults(maxResults),
8240
+ maxResults: clampResults(
8241
+ maxResults,
8242
+ getSearchMaxResultsLimit(provider2.id)
8243
+ ),
7614
8244
  options,
7615
- runtimeOptions,
7616
8245
  providerContext: {
7617
8246
  cwd: ctx.cwd,
7618
8247
  signal: signal ?? void 0
@@ -7635,7 +8264,6 @@ async function executeRawProviderRequest({
7635
8264
  ctx,
7636
8265
  signal,
7637
8266
  options,
7638
- runtimeOptions,
7639
8267
  urls
7640
8268
  });
7641
8269
  }
@@ -7648,7 +8276,6 @@ async function executeRawProviderRequest({
7648
8276
  ctx,
7649
8277
  signal,
7650
8278
  options,
7651
- runtimeOptions,
7652
8279
  query: query2
7653
8280
  });
7654
8281
  }
@@ -7660,7 +8287,6 @@ async function executeRawProviderRequest({
7660
8287
  ctx,
7661
8288
  signal,
7662
8289
  options,
7663
- runtimeOptions,
7664
8290
  input
7665
8291
  });
7666
8292
  }
@@ -7682,7 +8308,6 @@ async function executeSingleSearchQuery({
7682
8308
  query: query2,
7683
8309
  maxResults,
7684
8310
  options,
7685
- runtimeOptions,
7686
8311
  providerContext,
7687
8312
  onProgress,
7688
8313
  executionOverride
@@ -7694,23 +8319,13 @@ async function executeSingleSearchQuery({
7694
8319
  options
7695
8320
  };
7696
8321
  onProgress?.(`Searching via ${provider.label}: ${query2}`);
7697
- const result = executionOverride ? await executeProviderExecution(
7698
- executionOverride,
7699
- stripSearchContentsPrefetchOptions(runtimeOptions),
7700
- {
7701
- ...providerContext,
7702
- onProgress
7703
- }
7704
- ) : await executeProviderRequest(
7705
- provider,
7706
- providerConfig,
7707
- request,
7708
- stripSearchContentsPrefetchOptions(runtimeOptions),
7709
- {
7710
- ...providerContext,
7711
- onProgress
7712
- }
7713
- );
8322
+ const result = executionOverride ? await executeProviderExecution(executionOverride, {
8323
+ ...providerContext,
8324
+ onProgress
8325
+ }) : await executeProviderRequest(provider, providerConfig, request, {
8326
+ ...providerContext,
8327
+ onProgress
8328
+ });
7714
8329
  if (!isSearchResponse(result)) {
7715
8330
  throw new Error(`${provider.label} search returned an invalid result.`);
7716
8331
  }
@@ -7726,8 +8341,7 @@ async function executeAnswerTool({
7726
8341
  ctx: { cwd: context.cwd },
7727
8342
  signal: context.signal,
7728
8343
  progress: context.progress,
7729
- providerOptions: request.options?.provider,
7730
- runtimeOptions: request.options?.runtime,
8344
+ providerOptions: request.options,
7731
8345
  queries: request.queries
7732
8346
  });
7733
8347
  }
@@ -7738,7 +8352,6 @@ async function executeAnswerToolInternal({
7738
8352
  signal,
7739
8353
  progress,
7740
8354
  providerOptions,
7741
- runtimeOptions,
7742
8355
  queries,
7743
8356
  executionOverrides
7744
8357
  }) {
@@ -7779,7 +8392,6 @@ async function executeAnswerToolInternal({
7779
8392
  ctx,
7780
8393
  signal,
7781
8394
  options: providerOptions,
7782
- runtimeOptions,
7783
8395
  query: answerQuery,
7784
8396
  onProgress: answerQueries.length > 1 ? void 0 : progressReporter.report,
7785
8397
  executionOverride: executionOverrides?.[index]
@@ -7861,7 +8473,6 @@ async function executeProviderOperation({
7861
8473
  ctx,
7862
8474
  signal,
7863
8475
  options,
7864
- runtimeOptions,
7865
8476
  urls,
7866
8477
  query: query2,
7867
8478
  input,
@@ -7881,7 +8492,6 @@ async function executeProviderOperation({
7881
8492
  config,
7882
8493
  cwd: ctx.cwd,
7883
8494
  options,
7884
- runtimeOptions,
7885
8495
  signal: signal ?? void 0,
7886
8496
  onProgress
7887
8497
  });
@@ -7895,21 +8505,15 @@ async function executeProviderOperation({
7895
8505
  } else if (capability === "research") {
7896
8506
  onProgress?.(`Researching via ${provider.label}`);
7897
8507
  }
7898
- const result = executionOverride ? await executeProviderExecution(executionOverride, runtimeOptions, {
8508
+ const result = executionOverride ? await executeProviderExecution(executionOverride, {
7899
8509
  cwd: ctx.cwd,
7900
8510
  signal: signal ?? void 0,
7901
8511
  onProgress
7902
- }) : await executeProviderRequest(
7903
- provider,
7904
- providerConfig,
7905
- request,
7906
- runtimeOptions,
7907
- {
7908
- cwd: ctx.cwd,
7909
- signal: signal ?? void 0,
7910
- onProgress
7911
- }
7912
- );
8512
+ }) : await executeProviderRequest(provider, providerConfig, request, {
8513
+ cwd: ctx.cwd,
8514
+ signal: signal ?? void 0,
8515
+ onProgress
8516
+ });
7913
8517
  if (isSearchResponse(result)) {
7914
8518
  throw new Error(
7915
8519
  `${provider.label} ${capability} returned an invalid result.`
@@ -7928,8 +8532,7 @@ async function executeProviderTool({
7928
8532
  ctx: { cwd: context.cwd },
7929
8533
  signal: context.signal,
7930
8534
  progress: context.progress,
7931
- providerOptions: request.options?.provider,
7932
- runtimeOptions: request.options?.runtime,
8535
+ providerOptions: request.options,
7933
8536
  urls: request.urls,
7934
8537
  query: request.query,
7935
8538
  input: request.input
@@ -7943,7 +8546,6 @@ async function executeProviderToolInternal({
7943
8546
  signal,
7944
8547
  progress,
7945
8548
  providerOptions,
7946
- runtimeOptions,
7947
8549
  urls,
7948
8550
  query: query2,
7949
8551
  input,
@@ -7973,7 +8575,6 @@ async function executeProviderToolInternal({
7973
8575
  ctx,
7974
8576
  signal,
7975
8577
  options: providerOptions,
7976
- runtimeOptions,
7977
8578
  urls: urls ?? [],
7978
8579
  progressReport: progressReporter.report,
7979
8580
  executionOverrides
@@ -7985,7 +8586,6 @@ async function executeProviderToolInternal({
7985
8586
  ctx,
7986
8587
  signal,
7987
8588
  options: providerOptions,
7988
- runtimeOptions,
7989
8589
  urls,
7990
8590
  onProgress: progressReporter.report,
7991
8591
  executionOverride
@@ -7999,7 +8599,6 @@ async function executeProviderToolInternal({
7999
8599
  ctx,
8000
8600
  signal,
8001
8601
  options: providerOptions,
8002
- runtimeOptions,
8003
8602
  query: query2,
8004
8603
  input,
8005
8604
  onProgress: progressReporter.report,
@@ -8037,7 +8636,7 @@ async function dispatchWebResearch({
8037
8636
  updateWebResearchWidget,
8038
8637
  config,
8039
8638
  ctx: context,
8040
- providerOptions: request.options?.provider,
8639
+ providerOptions: request.options,
8041
8640
  input: request.input
8042
8641
  });
8043
8642
  }
@@ -8181,7 +8780,7 @@ function slugifyWebResearchInput(input) {
8181
8780
  function buildWebResearchWidgetLines(requests, theme, now = Date.now()) {
8182
8781
  const lines = [theme.fg("accent", "Research jobs:")];
8183
8782
  for (const request of requests.slice().sort((left, right) => left.startedAt.localeCompare(right.startedAt)).slice(0, 3)) {
8184
- const providerLabel = ADAPTERS_BY_ID[request.provider]?.label ?? request.provider;
8783
+ const providerLabel = PROVIDERS_BY_ID[request.provider]?.label ?? request.provider;
8185
8784
  const elapsed = formatCompactElapsed(now - Date.parse(request.startedAt));
8186
8785
  const icon = getWebResearchWidgetIcon(request, now);
8187
8786
  lines.push(
@@ -8257,7 +8856,7 @@ async function writeWebResearchArtifact(result, reportText) {
8257
8856
  );
8258
8857
  }
8259
8858
  function formatWebResearchArtifact(result, reportText) {
8260
- const providerLabel = ADAPTERS_BY_ID[result.provider]?.label ?? result.provider;
8859
+ const providerLabel = PROVIDERS_BY_ID[result.provider]?.label ?? result.provider;
8261
8860
  const lines = [
8262
8861
  "# Web research report",
8263
8862
  "",
@@ -8305,7 +8904,6 @@ async function executeBatchedContentsTool({
8305
8904
  ctx,
8306
8905
  signal,
8307
8906
  options,
8308
- runtimeOptions,
8309
8907
  urls,
8310
8908
  progressReport,
8311
8909
  executionOverrides
@@ -8332,7 +8930,6 @@ async function executeBatchedContentsTool({
8332
8930
  ctx,
8333
8931
  signal,
8334
8932
  options,
8335
- runtimeOptions,
8336
8933
  urls: [url],
8337
8934
  onProgress: void 0,
8338
8935
  executionOverride: executionOverrides?.[index]
@@ -8455,7 +9052,7 @@ function createToolProgressReporter(capability, providerId, progress) {
8455
9052
  if (Date.now() - lastUpdateAt < RESEARCH_HEARTBEAT_MS) {
8456
9053
  return;
8457
9054
  }
8458
- const providerLabel = ADAPTERS_BY_ID[providerId]?.label ?? providerId;
9055
+ const providerLabel = PROVIDERS_BY_ID[providerId]?.label ?? providerId;
8459
9056
  const elapsed = formatElapsed(Date.now() - startedAt);
8460
9057
  emit(`Researching via ${providerLabel} (${elapsed} elapsed)`);
8461
9058
  lastUpdateAt = Date.now();
@@ -8558,7 +9155,7 @@ function renderWebResearchDispatchResult(result, expanded, theme) {
8558
9155
  if (expanded) {
8559
9156
  return renderBlockText(details?.input ?? text, theme, "toolOutput");
8560
9157
  }
8561
- const summary = details ? `Started web research via ${ADAPTERS_BY_ID[details.provider]?.label ?? details.provider}` : text;
9158
+ const summary = details ? `Started web research via ${PROVIDERS_BY_ID[details.provider]?.label ?? details.provider}` : text;
8562
9159
  let summaryText = theme.fg("success", summary);
8563
9160
  summaryText += theme.fg("muted", ` (${getExpandHint()})`);
8564
9161
  return new Text(summaryText, 0, 0);
@@ -8581,7 +9178,7 @@ function renderWebResearchResultMessage(message, { expanded }, theme) {
8581
9178
  return box;
8582
9179
  }
8583
9180
  function buildWebResearchResultSummaryLines(result, theme) {
8584
- const providerLabel = ADAPTERS_BY_ID[result.provider]?.label ?? result.provider;
9181
+ const providerLabel = PROVIDERS_BY_ID[result.provider]?.label ?? result.provider;
8585
9182
  const statusLine = result.status === "completed" ? `Web research completed via ${providerLabel}` : `Web research failed via ${providerLabel}`;
8586
9183
  const lines = [
8587
9184
  theme.fg(result.status === "completed" ? "success" : "error", statusLine)
@@ -8621,7 +9218,7 @@ function renderProviderToolResult(result, expanded, isPartial, failureText, them
8621
9218
  }
8622
9219
  function renderCollapsedProviderToolSummary(details, text) {
8623
9220
  if (details?.tool === "web_answer" && typeof details.queryCount === "number" && details.queryCount > 1) {
8624
- const providerLabel = ADAPTERS_BY_ID[details.provider]?.label ?? details.provider;
9221
+ const providerLabel = PROVIDERS_BY_ID[details.provider]?.label ?? details.provider;
8625
9222
  const failureSuffix = details.failedQueryCount && details.failedQueryCount > 0 ? `, ${details.failedQueryCount} failed` : "";
8626
9223
  return `${details.queryCount} questions via ${providerLabel}${failureSuffix}`;
8627
9224
  }
@@ -8695,12 +9292,12 @@ function renderSelectedEntryDescription(width, theme, entry) {
8695
9292
  }
8696
9293
  function formatProviderCapabilityChecks(providerId, theme) {
8697
9294
  return ["search", "contents", "answer", "research"].map(
8698
- (tool) => supportsTool2(ADAPTERS_BY_ID[providerId], tool) ? theme.fg("success", "\u2714") : " "
9295
+ (tool) => supportsTool2(PROVIDERS_BY_ID[providerId], tool) ? theme.fg("success", "\u2714") : " "
8699
9296
  ).join(" ");
8700
9297
  }
8701
9298
  function resolveProviderSelectionValue(providerIds, value) {
8702
9299
  return providerIds.find(
8703
- (candidate) => ADAPTERS_BY_ID[candidate].label === value
9300
+ (candidate) => PROVIDERS_BY_ID[candidate].label === value
8704
9301
  );
8705
9302
  }
8706
9303
  function getReadyCompatibleProvidersForTool(config, cwd, toolId) {
@@ -8714,7 +9311,7 @@ function getReadyCompatibleProvidersForTool(config, cwd, toolId) {
8714
9311
  }
8715
9312
  function sortProviderIdsForSettings(providerIds) {
8716
9313
  const displayOrder = new Map(
8717
- ADAPTERS.map((provider, index) => [provider.id, index])
9314
+ PROVIDER_LIST.map((provider, index) => [provider.id, index])
8718
9315
  );
8719
9316
  return [...providerIds].sort(
8720
9317
  (left, right) => (displayOrder.get(left) ?? Number.MAX_SAFE_INTEGER) - (displayOrder.get(right) ?? Number.MAX_SAFE_INTEGER)
@@ -8819,9 +9416,13 @@ var WebProvidersSettingsView = class {
8819
9416
  this.activeProvider = initialProvider;
8820
9417
  this.selection.provider = Math.max(
8821
9418
  0,
8822
- ADAPTERS.findIndex((provider) => provider.id === initialProvider)
9419
+ PROVIDER_LIST.findIndex((provider) => provider.id === initialProvider)
8823
9420
  );
8824
9421
  }
9422
+ tui;
9423
+ theme;
9424
+ done;
9425
+ ctx;
8825
9426
  config;
8826
9427
  activeProvider;
8827
9428
  activeSection = "tools";
@@ -8899,7 +9500,7 @@ var WebProvidersSettingsView = class {
8899
9500
  this.tui.requestRender();
8900
9501
  }
8901
9502
  buildProviderSectionItems() {
8902
- return ADAPTERS.map((provider) => {
9503
+ return PROVIDER_LIST.map((provider) => {
8903
9504
  const setupState = getProviderSetupState(this.config, provider.id);
8904
9505
  const statusSummary = getProviderReadinessSummary(
8905
9506
  this.config,
@@ -8924,9 +9525,9 @@ var WebProvidersSettingsView = class {
8924
9525
  toolId
8925
9526
  );
8926
9527
  const mappedProviderId = getMappedProviderIdForTool(this.config, toolId);
8927
- const currentValue = mappedProviderId && readyCompatibleProviders.includes(mappedProviderId) ? ADAPTERS_BY_ID[mappedProviderId].label : "off";
9528
+ const currentValue = mappedProviderId && readyCompatibleProviders.includes(mappedProviderId) ? PROVIDERS_BY_ID[mappedProviderId].label : "off";
8928
9529
  const compatibleLabels = readyCompatibleProviders.map(
8929
- (providerId) => ADAPTERS_BY_ID[providerId].label
9530
+ (providerId) => PROVIDERS_BY_ID[providerId].label
8930
9531
  );
8931
9532
  return {
8932
9533
  id: `tool:${toolId}`,
@@ -9007,7 +9608,7 @@ var WebProvidersSettingsView = class {
9007
9608
  if (this.activeSection !== "provider") {
9008
9609
  return;
9009
9610
  }
9010
- const provider = ADAPTERS[this.selection.provider];
9611
+ const provider = PROVIDER_LIST[this.selection.provider];
9011
9612
  if (!provider) {
9012
9613
  return;
9013
9614
  }
@@ -9169,6 +9770,13 @@ var ToolSettingsSubmenu = class {
9169
9770
  this.persist = persist;
9170
9771
  this.done = done;
9171
9772
  }
9773
+ tui;
9774
+ theme;
9775
+ toolId;
9776
+ cwd;
9777
+ getConfig;
9778
+ persist;
9779
+ done;
9172
9780
  selection = 0;
9173
9781
  submenu;
9174
9782
  render(width) {
@@ -9237,9 +9845,11 @@ var ToolSettingsSubmenu = class {
9237
9845
  );
9238
9846
  const providerValues = [
9239
9847
  "off",
9240
- ...readyProviderIds.map((providerId) => ADAPTERS_BY_ID[providerId].label)
9848
+ ...readyProviderIds.map(
9849
+ (providerId) => PROVIDERS_BY_ID[providerId].label
9850
+ )
9241
9851
  ];
9242
- const currentProviderValue = mappedProviderId && readyProviderIds.includes(mappedProviderId) ? ADAPTERS_BY_ID[mappedProviderId].label : "off";
9852
+ const currentProviderValue = mappedProviderId && readyProviderIds.includes(mappedProviderId) ? PROVIDERS_BY_ID[mappedProviderId].label : "off";
9243
9853
  const entries = [
9244
9854
  {
9245
9855
  id: "provider",
@@ -9261,10 +9871,10 @@ var ToolSettingsSubmenu = class {
9261
9871
  const prefetchValues = [
9262
9872
  "off",
9263
9873
  ...prefetchProviderIds.map(
9264
- (providerId) => ADAPTERS_BY_ID[providerId].label
9874
+ (providerId) => PROVIDERS_BY_ID[providerId].label
9265
9875
  )
9266
9876
  ];
9267
- const currentPrefetchProviderValue = prefetch?.provider && prefetchProviderIds.includes(prefetch.provider) ? ADAPTERS_BY_ID[prefetch.provider].label : "off";
9877
+ const currentPrefetchProviderValue = prefetch?.provider && prefetchProviderIds.includes(prefetch.provider) ? PROVIDERS_BY_ID[prefetch.provider].label : "off";
9268
9878
  entries.push(
9269
9879
  {
9270
9880
  id: "prefetchProvider",
@@ -9394,13 +10004,19 @@ var ProviderSettingsSubmenu = class {
9394
10004
  this.persist = persist;
9395
10005
  this.done = done;
9396
10006
  }
10007
+ tui;
10008
+ theme;
10009
+ providerId;
10010
+ getProviderConfig;
10011
+ persist;
10012
+ done;
9397
10013
  selection = 0;
9398
10014
  submenu;
9399
10015
  render(width) {
9400
10016
  if (this.submenu) {
9401
10017
  return this.submenu.render(width);
9402
10018
  }
9403
- const provider = ADAPTERS_BY_ID[this.providerId];
10019
+ const provider = PROVIDERS_BY_ID[this.providerId];
9404
10020
  const providerConfig = this.getProviderConfig();
9405
10021
  const entries = this.getEntries();
9406
10022
  const lines = [
@@ -9568,6 +10184,10 @@ var TextValueSubmenu = class {
9568
10184
  this.done(text.trim());
9569
10185
  };
9570
10186
  }
10187
+ theme;
10188
+ title;
10189
+ help;
10190
+ done;
9571
10191
  editor;
9572
10192
  render(width) {
9573
10193
  return [
@@ -9613,7 +10233,7 @@ function didContentsCacheInputsChange(previous, next) {
9613
10233
  }
9614
10234
  function getContentsCacheInputs(config) {
9615
10235
  const providers = {};
9616
- for (const provider of ADAPTERS) {
10236
+ for (const provider of PROVIDER_LIST) {
9617
10237
  if (!supportsTool2(provider, "contents")) {
9618
10238
  continue;
9619
10239
  }
@@ -9655,8 +10275,8 @@ function getProviderReadinessSummary(config, cwd, providerId) {
9655
10275
  return formatProviderCapabilityStatus(statuses[0], providerId, tools[0]);
9656
10276
  }
9657
10277
  function getProviderReadinessSummaryForProviderConfig(providerId, providerConfig) {
9658
- const status = ADAPTERS_BY_ID[providerId].getCapabilityStatus(
9659
- providerConfig ?? ADAPTERS_BY_ID[providerId].createTemplate(),
10278
+ const status = PROVIDERS_BY_ID[providerId].getCapabilityStatus(
10279
+ providerConfig ?? PROVIDERS_BY_ID[providerId].config.createTemplate(),
9660
10280
  ""
9661
10281
  );
9662
10282
  return formatProviderCapabilityStatus(status, providerId);
@@ -9670,9 +10290,9 @@ function summarizeStringValue(value, secret) {
9670
10290
  }
9671
10291
  return truncateInline(value, 40);
9672
10292
  }
9673
- function clampResults(value) {
9674
- if (value === void 0) return DEFAULT_MAX_RESULTS;
9675
- return Math.min(Math.max(Math.trunc(value), 1), MAX_ALLOWED_RESULTS);
10293
+ function clampResults(value, maximum = MAX_ALLOWED_RESULTS) {
10294
+ if (value === void 0) return Math.min(DEFAULT_MAX_RESULTS, maximum);
10295
+ return Math.min(Math.max(Math.trunc(value), 1), maximum);
9676
10296
  }
9677
10297
  function resolveSearchQueries(queries) {
9678
10298
  if (queries.length === 0) {
@@ -9789,7 +10409,7 @@ function renderCollapsedSearchSummary(details, text, theme) {
9789
10409
  const queryCount = typeof details?.queryCount === "number" ? details.queryCount : inferSearchQueryCount(text);
9790
10410
  const resultCount = typeof details?.resultCount === "number" ? details.resultCount : inferSearchResultCount(text);
9791
10411
  const failedQueryCount = typeof details?.failedQueryCount === "number" ? details.failedQueryCount : inferSearchFailureCount(text);
9792
- const providerLabel = typeof details?.provider === "string" ? ADAPTERS_BY_ID[details.provider]?.label ?? details.provider : void 0;
10412
+ const providerLabel = typeof details?.provider === "string" ? PROVIDERS_BY_ID[details.provider]?.label ?? details.provider : void 0;
9793
10413
  let base = buildSearchSummaryText({
9794
10414
  queryCount,
9795
10415
  resultCount
@@ -9839,7 +10459,7 @@ function inferSearchFailureCount(text) {
9839
10459
  return failureMatches?.length;
9840
10460
  }
9841
10461
  function appendProviderSummary(summary, provider) {
9842
- const providerLabel = ADAPTERS_BY_ID[provider]?.label ?? provider;
10462
+ const providerLabel = PROVIDERS_BY_ID[provider]?.label ?? provider;
9843
10463
  const providerSuffix = `via ${providerLabel}`;
9844
10464
  return summary.toLowerCase().includes(providerSuffix.toLowerCase()) ? summary : `${summary} ${providerSuffix}`;
9845
10465
  }
@@ -9969,7 +10589,6 @@ var __test__ = {
9969
10589
  signal,
9970
10590
  onUpdate,
9971
10591
  options,
9972
- runtimeOptions,
9973
10592
  queries,
9974
10593
  executionOverrides
9975
10594
  }) => executeAnswerToolInternal({
@@ -9979,7 +10598,6 @@ var __test__ = {
9979
10598
  signal,
9980
10599
  progress: createProgressEmitter(onUpdate),
9981
10600
  providerOptions: options,
9982
- runtimeOptions,
9983
10601
  queries,
9984
10602
  executionOverrides
9985
10603
  }),
@@ -9992,7 +10610,6 @@ var __test__ = {
9992
10610
  signal,
9993
10611
  onUpdate,
9994
10612
  options,
9995
- runtimeOptions,
9996
10613
  urls,
9997
10614
  query: query2,
9998
10615
  input,
@@ -10006,7 +10623,6 @@ var __test__ = {
10006
10623
  signal,
10007
10624
  progress: createProgressEmitter(onUpdate),
10008
10625
  providerOptions: options,
10009
- runtimeOptions,
10010
10626
  urls,
10011
10627
  query: query2,
10012
10628
  input,
@@ -10020,7 +10636,6 @@ var __test__ = {
10020
10636
  signal,
10021
10637
  onUpdate,
10022
10638
  options,
10023
- runtimeOptions,
10024
10639
  maxResults,
10025
10640
  queries,
10026
10641
  executionOverrides
@@ -10031,7 +10646,6 @@ var __test__ = {
10031
10646
  signal,
10032
10647
  progress: createProgressEmitter(onUpdate),
10033
10648
  providerOptions: options,
10034
- runtimeOptions,
10035
10649
  maxResults,
10036
10650
  queries,
10037
10651
  executionOverrides