Skip to content

feat: add web search tool for grok models#2

Merged
kenryu42 merged 9 commits into
mainfrom
web-search-tool-for-grok
Jun 3, 2026
Merged

feat: add web search tool for grok models#2
kenryu42 merged 9 commits into
mainfrom
web-search-tool-for-grok

Conversation

@kenryu42
Copy link
Copy Markdown
Owner

@kenryu42 kenryu42 commented Jun 3, 2026

Summary by CodeRabbit

  • New Features

    • Added WebSearch tool providing web search capabilities when pi-web-access is installed.
    • WebSearch automatically disables for grok-cli model provider.
  • Chores

    • Updated peer dependencies; pi-tui and pi-web-access now marked as optional.

kenryu42 added 4 commits June 3, 2026 16:32
Register WebSearch when pi-web-access is optional-installed, delegate to
its web_search tool, and reconcile active tools so grok-cli uses WebSearch
instead of web_search. Bind the delegate on session_start.
- webSearch.ts: 47% → 100% (renderCall/renderResult branches, tool_call interceptor)
- sanitize.ts: 91% → 96% (.jpg, file:// protocol, unsupported ext, string output parts)
- rendering.ts: 91% → 95% (globToRegExp wildcards, grep fallback path include)
- search.ts: 91% → 93% (sort tiebreaker, grep fallback with path include)
- shell.ts: added timeout kill test
- webSearchDelegate.ts: added bind/ensure/getLoadError tests
@greptile-apps
Copy link
Copy Markdown

greptile-apps Bot commented Jun 3, 2026

Greptile Summary

This PR adds optional WebSearch tool support for grok-cli models, delegating to pi-web-access's web_search execute function when that extension is installed, and suppressing the raw web_search tool for grok-cli to prevent duplicate search paths.

  • webSearchDelegate.ts discovers pi-web-access on disk, loads it dynamically via jiti with aliased shared packages, extracts the web_search execute function, and wires runtime calls back to the live ExtensionAPI; module-level state is reset on each session_start via bindLivePiWebAccess.
  • webSearch.ts registers the WebSearch tool that proxies all calls to the captured delegate, adds rendering for single/multi-query display, and installs a tool_call interceptor that blocks the raw web_search for grok-cli when pi-web-access is installed.
  • toolScope.ts / register.ts export a revised set of name constants (GROK_SHIM_TOOL_NAMES, GROK_TOOL_NAMES_FOR_SCOPE, GROK_SUPPRESSED_TOOL_NAMES) to correctly scope WebSearch activation and web_search suppression without affecting other providers.

Confidence Score: 4/5

Safe to merge for most environments; the WebSearch feature works correctly when pi-web-access is installed, and nothing changes for users without it.

The core delegation pattern, tool scoping, and interceptor logic are sound and thoroughly tested. The main concern is in webSearchDelegate.ts: the loader path dist/core/extensions/loader.js is an undocumented internal of @earendil-works/pi-coding-agent, and resolvePiCodingAgentRoot() derives the package root with a single dirname + .. step that assumes the entry point is exactly one directory deep. A package restructure could silently make WebSearch non-functional for the session without a clear error pointing at the bad path. This is not a regression risk for existing functionality, but it is a maintenance hazard for the new feature.

src/tools/webSearchDelegate.ts — specifically the loader path resolution and the permanent-cache-on-failure behavior of ensureWebSearchDelegate

Important Files Changed

Filename Overview
src/tools/webSearchDelegate.ts New file — dynamically discovers and loads pi-web-access via jiti, capturing its web_search execute function; relies on a hardcoded internal path into pi-coding-agent's dist directory that could break on package updates
src/tools/webSearch.ts New file — registers WebSearch tool that transparently delegates to the captured web_search execute, with proper rendering and a tool_call interceptor to block web_search for grok-cli models
src/provider/register.ts session_start handler now always registered; calls bindLivePiWebAccess and ensureWebSearchDelegate on each session start when pi-web-access is installed
src/provider/toolScope.ts Updated to suppress web_search and conditionally activate WebSearch for grok-cli models; non-grok providers are unaffected
src/tools/register.ts Exports split into GROK_SHIM_TOOL_NAMES, GROK_TOOL_NAMES_FOR_SCOPE, GROK_SUPPRESSED_TOOL_NAMES and grokToolsToActivate() to support optional WebSearch registration
tests/tools/webSearch.test.ts Comprehensive tests for WebSearch registration, delegation, missing-delegate error path, renderCall/renderResult states, and the tool_call interceptor
tests/tools/webSearchDelegate.test.ts New test file covering delegate lifecycle: set/get/clear for tests, bindLivePiWebAccess reset behavior, and early return when pi-web-access is not installed

Sequence Diagram

sequenceDiagram
    participant Host as pi host
    participant Register as registerGrokCli
    participant Delegate as webSearchDelegate
    participant PiWebAccess as pi-web-access (jiti)

    Host->>Register: extension load
    Register->>Delegate: isPiWebAccessInstalled()?
    alt installed
        Register->>Register: registerWebSearchTool(pi)
        Note over Register: WebSearch tool + tool_call interceptor registered
    end

    Host->>Register: session_start event
    Register->>Delegate: isPiWebAccessInstalled()?
    alt installed
        Register->>Delegate: bindLivePiWebAccess(pi)
        Note over Delegate: reset webSearchExecute + loadPromise
        Register->>Delegate: ensureWebSearchDelegate(pi)
        Delegate->>PiWebAccess: importPiExtensionLoader() → internal loader.js
        Delegate->>PiWebAccess: importPiWebAccessFactory(entry) via jiti
        PiWebAccess-->>Delegate: factory(fakeApi) → extension.tools.get("web_search")
        Delegate->>Delegate: store webSearchExecute (bound execute fn)
    end

    Host->>Register: tool_call: web_search (grok-cli model)
    Register-->>Host: block: true (interceptor in webSearch.ts)

    Host->>Register: WebSearch tool execute
    Register->>Delegate: ensureWebSearchDelegate(pi) [no-op if loaded]
    Register->>Delegate: getWebSearchDelegate()
    Delegate-->>Register: webSearchExecute fn
    Register->>PiWebAccess: delegate(toolCallId, params, signal, onUpdate, ctx)
    PiWebAccess-->>Register: AgentToolResult
    Register-->>Host: result
Loading

Fix All in Codex

Reviews (1): Last reviewed commit: "chore: improve test coverage from 80% to..." | Re-trigger Greptile

Comment thread src/tools/webSearchDelegate.ts Outdated
Comment on lines +30 to +46
function resolvePiCodingAgentRoot() {
const mainEntry = fileURLToPath(import.meta.resolve('@earendil-works/pi-coding-agent'));
return join(dirname(mainEntry), '..');
}

async function importPiExtensionLoader() {
return import(join(resolvePiCodingAgentRoot(), 'dist/core/extensions/loader.js')) as Promise<{
createExtensionRuntime: () => Record<string, unknown>;
loadExtensionFromFactory: (
factory: (api: ExtensionAPI) => void | Promise<void>,
cwd: string,
eventBus: ReturnType<typeof createEventBus>,
runtime: Record<string, unknown>,
extensionPath?: string,
) => Promise<{ tools: Map<string, { definition: { execute: WebSearchExecute } }> }>;
}>;
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Fragile dependency on internal package path

resolvePiCodingAgentRoot() derives the package root by going one level up from the main entry (dirname(mainEntry) + ".."``), then importPiExtensionLoaderappends the hardcoded internal pathdist/core/extensions/loader.js. Both steps are coupled to undocumented internals of @earendil-works/pi-coding-agent. If the package's main entry moves deeper than one level inside dist/(e.g.dist/esm/index.js), resolvePiCodingAgentRoot()would return/distinstead of, making the constructed loader path wrong. If the internal file is ever renamed or relocated in a patch release, the dynamic import()would throw, settinglastLoadErrorand leavingWebSearch` permanently broken for the session with no diagnostic about the path that failed.

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

Fix in Codex

Comment thread src/tools/webSearchDelegate.ts Outdated
Comment on lines +168 to +188
export async function ensureWebSearchDelegate(pi?: ExtensionAPI) {
if (!isPiWebAccessInstalled()) return;

const livePi = pi ?? boundLivePi;
if (!livePi) return;

if (webSearchExecute) return;
if (loadPromise) return loadPromise;

loadPromise = (async () => {
lastLoadError = undefined;
try {
await captureWebSearchFromLivePi(livePi);
} catch (err) {
lastLoadError = err instanceof Error ? err.message : String(err);
webSearchExecute = undefined;
}
})();

return loadPromise;
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Failed load is permanently cached within a session

When captureWebSearchFromLivePi throws, the inner IIFE catches the error, sets lastLoadError, and resolves loadPromise. On every subsequent call to ensureWebSearchDelegate, the if (loadPromise) return loadPromise guard fires immediately — returning the already-settled promise — so no reload is attempted for the rest of the session. The user will see the missingDelegateMessage on every WebSearch invocation with no actionable suggestion beyond restarting. Consider at least logging the path that was attempted inside the catch block to make the failure diagnosable without a full session restart.

Fix in Codex

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Jun 3, 2026

Review Change Stack

Warning

Review limit reached

@kenryu42, we couldn't start this review because you've reached your PR review rate limit.

More reviews will be available in 23 minutes and 36 seconds. Learn how PR review limits work.

Your organization has run out of usage credits. Purchase more in the billing tab.

⌛ How to resolve this issue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans include higher PR review limits than trial, open-source, and free plans. In all cases, reviews become available again over time. During sustained high-volume PR review activity, CodeRabbit may temporarily slow when the next review becomes available.

Please see our Fair Usage Limits Policy for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 692845e7-924e-4405-8298-2c6fb98c04f4

📥 Commits

Reviewing files that changed from the base of the PR and between b2eb29c and 7241929.

📒 Files selected for processing (9)
  • src/provider/toolScope.ts
  • src/tools/shell.ts
  • src/tools/webSearch.ts
  • src/tools/webSearchDelegate.ts
  • tests/provider/toolScope.test.ts
  • tests/tools/shell.test.ts
  • tests/tools/webSearch.test.ts
  • tests/tools/webSearchDelegate.test.ts
  • tests/tools/webSearchDelegateRetry.test.ts
📝 Walkthrough

Walkthrough

This PR adds a WebSearch tool that conditionally depends on the optional pi-web-access module. It introduces dynamic module loading for the underlying web search implementation, registers a new WebSearch tool with full UI rendering support, conditionally activates it based on availability, and implements tool scoping logic that manages web_search and WebSearch across different model providers.

Changes

WebSearch tool with pi-web-access integration

Layer / File(s) Summary
WebSearch delegate infrastructure
src/tools/webSearchDelegate.ts, tests/tools/webSearchDelegate.test.ts
Dynamic loading and lazy initialization of the web_search tool from pi-web-access, including installation detection, jiti-based module resolution with aliases, runtime wiring to a live ExtensionAPI, and test injection helpers for mocking.
WebSearch tool registration and UI rendering
src/tools/webSearch.ts, tests/tools/webSearch.test.ts
The WebSearch tool itself, with query normalization, delegate-backed execution, fallback messages when the delegate is unavailable, custom renderToolCall and renderToolResult UI formatting, and a tool_call interceptor that blocks web_search for grok-cli providers.
Tool name constants and activation logic
src/tools/register.ts, tests/tools/register.test.ts
Renamed tool-name constant from GROK_TOOL_NAMES to GROK_SHIM_TOOL_NAMES, added scope-tracking and suppression constants, and grokToolsToActivate() factory that conditionally includes WebSearch based on isPiWebAccessInstalled().
Tool scoping for grok-cli provider
src/provider/toolScope.ts, tests/provider/toolScope.test.ts
Updated syncGrokTools to filter scope-tracked tools and conditionally restore/suppress tools based on provider, removing web_search and enabling WebSearch when switching to grok-cli.
Provider session initialization and wiring
src/provider/register.ts, tests/provider/register.test.ts
Added session_start handler that binds live pi-web-access and ensures the web search delegate is loaded, plus updated test mocking to drive WebSearch availability based on pi-web-access installation state.
Documentation and configuration
README.md, package.json, tests/provider/package.test.ts
Updated README with WebSearch availability notes, added pi-web-access as an optional peer dependency, and updated package layout tests.
Test support and coverage
tests/tools/toolTestHelpers.ts, tests/tools/rendering.test.ts, tests/tools/search.test.ts, tests/tools/shell.test.ts, tests/payload/sanitize.test.ts
Extended test helpers for event handler mocking, added glob-to-regex test coverage, grep fallback pattern testing, shell timeout handling, and payload sanitization for images and function call outputs.

🎯 3 (Moderate) | ⏱️ ~20 minutes

🐰 A tool called WebSearch hops in,

With web-access it takes a dip,

Grok-cli says "use that one instead,"

While others browse—delegation spread! 📚✨

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 3.85% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'feat: add web search tool for grok models' accurately summarizes the main change: introducing a new WebSearch tool functionality specifically for grok-cli provider support with conditional pi-web-access integration.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch web-search-tool-for-grok

Warning

Review ran into problems

🔥 Problems

Stopped waiting for pipeline failures after 30000ms. One of your pipelines takes longer than our 30000ms fetch window to run, so review may not consider pipeline-failure results for inline comments if any failures occurred after the fetch window. Increase the timeout if you want to wait longer or run a @coderabbit review after the pipeline has finished.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 5

🧹 Nitpick comments (1)
tests/provider/toolScope.test.ts (1)

22-49: ⚡ Quick win

Add a provider round-trip regression test.

These cases never assert openai -> grok-cli -> openai, so they miss the current loss of web_search after suppression. One round-trip test here would lock down the intended restoration behavior and catch this regression immediately.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/provider/toolScope.test.ts` around lines 22 - 49, The tests for
syncGrokTools/syncForGrokCli are missing a provider round-trip asserting that
switching from openai to grok-cli and back to openai preserves/restores
web_search; add a new test that (1) starts with an openai active toolset that
includes 'web_search', (2) applies syncGrokTools or syncForGrokCli to simulate
switching to grok-cli (verifying suppression of 'web_search' and addition of
'WebSearch' as applicable), then (3) switches back to openai and asserts that
'web_search' is restored and grok shims ('WebSearch') are removed. Target the
existing helpers syncGrokTools and syncForGrokCli in the test to validate the
full round-trip behavior.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/provider/toolScope.ts`:
- Around line 17-28: The current logic that computes nextTools when switching
providers drops suppressed tools permanently because the non-grok branch simply
assigns baseTools; update the tool-switch logic to restore previously active
non-Grok tools by preserving suppressed entries and re-applying them when
provider !== 'grok-cli'. Specifically, record the set of suppressed tool names
(GROK_SUPPRESSED_TOOL_NAMES) or the filtered-out tools when computing nextTools
for provider === 'grok-cli' (use the same filtering code that references
baseTools and GROK_SUPPRESSED_TOOL_NAMES), store that set (e.g., in a
preservedSuppressedTools variable or a savedActiveTools structure) and on the
else branch rebuild nextTools from baseTools plus any preserved non-Grok active
tools (or simply restore the savedActiveTools) so that functions/variables like
nextTools, provider, baseTools, GROK_SUPPRESSED_TOOL_NAMES, and
grokToolsToActivate() are used to both suppress and later restore the
appropriate tools.

In `@src/tools/webSearch.ts`:
- Around line 93-97: In execute(), normalize and validate the incoming params
before calling the delegate: create a normalizedParams copy of params, and if it
contains a queries array (or a single query string), trim each string and filter
out empty/whitespace-only entries (e.g., queries =
queries.map(s=>s.trim()).filter(Boolean)); if the resulting queries list is
empty set queries to [] (or remove the field) so the delegate never receives
blank queries; then pass normalizedParams to delegate(toolCallId,
normalizedParams, ...). Update the execute function (and keep using
ensureWebSearchDelegate, getWebSearchDelegate, missingDelegateMessage as-is) so
rendered UI and delegated input are consistent.

In `@src/tools/webSearchDelegate.ts`:
- Around line 135-139: bindLivePiWebAccess currently clears globals but allows
stale async capture to overwrite webSearchExecute and leaves loadPromise set on
failure so future loads never retry; add a generation/token (e.g., increase a
numeric bindGeneration) on bindLivePiWebAccess and store the current generation
in captureWebSearchFromLivePi/ensureWebSearchDelegate; before assigning
webSearchExecute or loadPromise, verify the generation still matches the latest
boundGeneration (and that boundLivePi is the same), and on any load failure
ensure loadPromise is cleared (set to undefined) so subsequent
ensureWebSearchDelegate() calls can retry.

In `@tests/tools/shell.test.ts`:
- Around line 105-116: The test currently can pass without exercising timeout
logic; update the test in tests/tools/shell.test.ts to run a more portable
long-running command (e.g., use Node to sleep: command: 'node -e
"setTimeout(()=>{},10000)"') when calling
executeTool(collectTools(registerShellTool).get('Shell'), ...), and replace the
loose assertions at firstText(result) and result.details.exitCode with an
explicit timeout/killed assertion: expect(result.details.signal).toBeDefined()
(or expect(result.details.signal).toMatch(/TERM|KILL/)) or assert exitCode is
null and signal is set, so the test verifies the process was killed by the
timeout rather than failing immediately. Ensure you still check
result.details.command === 'node -e "setTimeout(()=>{},10000)"' to confirm the
invoked command.

In `@tests/tools/webSearchDelegate.test.ts`:
- Around line 52-56: The test is mocking the filesystem-backed probe
isPiWebAccessInstalled; instead, modify tests/tools/webSearchDelegate.test.ts to
avoid spying on internal logic by either (a) creating the real filesystem state
the probe expects (e.g., create/remove the pi-web-access marker/path in a temp
dir) before calling ensureWebSearchDelegate(), or (b) refactor so the filesystem
probe is injected (e.g., pass a probe function or a small wrapper module) and
stub that boundary in the test; reference ensureWebSearchDelegate and
isPiWebAccessInstalled to locate the code and update the test setup/teardown to
actually reflect installed/uninstalled state rather than using
vi.spyOn(...).mockReturnValue.

---

Nitpick comments:
In `@tests/provider/toolScope.test.ts`:
- Around line 22-49: The tests for syncGrokTools/syncForGrokCli are missing a
provider round-trip asserting that switching from openai to grok-cli and back to
openai preserves/restores web_search; add a new test that (1) starts with an
openai active toolset that includes 'web_search', (2) applies syncGrokTools or
syncForGrokCli to simulate switching to grok-cli (verifying suppression of
'web_search' and addition of 'WebSearch' as applicable), then (3) switches back
to openai and asserts that 'web_search' is restored and grok shims ('WebSearch')
are removed. Target the existing helpers syncGrokTools and syncForGrokCli in the
test to validate the full round-trip behavior.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 193fdeac-81da-41f6-aab6-00b41d923ba9

📥 Commits

Reviewing files that changed from the base of the PR and between dd0ae3b and b2eb29c.

📒 Files selected for processing (18)
  • README.md
  • package.json
  • src/provider/register.ts
  • src/provider/toolScope.ts
  • src/tools/register.ts
  • src/tools/webSearch.ts
  • src/tools/webSearchDelegate.ts
  • tests/payload/sanitize.test.ts
  • tests/provider/package.test.ts
  • tests/provider/register.test.ts
  • tests/provider/toolScope.test.ts
  • tests/tools/register.test.ts
  • tests/tools/rendering.test.ts
  • tests/tools/search.test.ts
  • tests/tools/shell.test.ts
  • tests/tools/toolTestHelpers.ts
  • tests/tools/webSearch.test.ts
  • tests/tools/webSearchDelegate.test.ts

Comment thread src/provider/toolScope.ts Outdated
Comment thread src/tools/webSearch.ts Outdated
Comment on lines +93 to +97
async execute(toolCallId, params, signal, onUpdate, ctx) {
await ensureWebSearchDelegate(pi);
const delegate = getWebSearchDelegate();
if (!delegate) return missingDelegateMessage();
return delegate(toolCallId, params as Record<string, unknown>, signal, onUpdate, ctx);
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Validate and normalize queries before delegating.

renderCall() trims/filter blanks, but execute() forwards the raw params object unchanged. That means {} still reaches the delegate even though the UI shows (no query), and { queries: [' ', 'foo'] } renders as one query while sending two downstream.

Suggested fix
     async execute(toolCallId, params, signal, onUpdate, ctx) {
+      const normalizedQueries = queryListFromArgs(params as Record<string, unknown>);
+      if (normalizedQueries.length === 0) {
+        return {
+          content: [
+            {
+              type: 'text' as const,
+              text: 'WebSearch requires at least one non-empty query.',
+            },
+          ],
+          details: { error: 'missing query' },
+        };
+      }
+
+      const normalizedParams =
+        normalizedQueries.length === 1
+          ? { ...params, query: normalizedQueries[0] }
+          : { ...params, queries: normalizedQueries };
+
       await ensureWebSearchDelegate(pi);
       const delegate = getWebSearchDelegate();
       if (!delegate) return missingDelegateMessage();
-      return delegate(toolCallId, params as Record<string, unknown>, signal, onUpdate, ctx);
+      return delegate(
+        toolCallId,
+        normalizedParams as Record<string, unknown>,
+        signal,
+        onUpdate,
+        ctx,
+      );
     },
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
async execute(toolCallId, params, signal, onUpdate, ctx) {
await ensureWebSearchDelegate(pi);
const delegate = getWebSearchDelegate();
if (!delegate) return missingDelegateMessage();
return delegate(toolCallId, params as Record<string, unknown>, signal, onUpdate, ctx);
async execute(toolCallId, params, signal, onUpdate, ctx) {
const normalizedQueries = queryListFromArgs(params as Record<string, unknown>);
if (normalizedQueries.length === 0) {
return {
content: [
{
type: 'text' as const,
text: 'WebSearch requires at least one non-empty query.',
},
],
details: { error: 'missing query' },
};
}
const normalizedParams =
normalizedQueries.length === 1
? { ...params, query: normalizedQueries[0] }
: { ...params, queries: normalizedQueries };
await ensureWebSearchDelegate(pi);
const delegate = getWebSearchDelegate();
if (!delegate) return missingDelegateMessage();
return delegate(
toolCallId,
normalizedParams as Record<string, unknown>,
signal,
onUpdate,
ctx,
);
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/tools/webSearch.ts` around lines 93 - 97, In execute(), normalize and
validate the incoming params before calling the delegate: create a
normalizedParams copy of params, and if it contains a queries array (or a single
query string), trim each string and filter out empty/whitespace-only entries
(e.g., queries = queries.map(s=>s.trim()).filter(Boolean)); if the resulting
queries list is empty set queries to [] (or remove the field) so the delegate
never receives blank queries; then pass normalizedParams to delegate(toolCallId,
normalizedParams, ...). Update the execute function (and keep using
ensureWebSearchDelegate, getWebSearchDelegate, missingDelegateMessage as-is) so
rendered UI and delegated input are consistent.

Comment thread src/tools/webSearchDelegate.ts
Comment thread tests/tools/shell.test.ts
Comment thread tests/tools/webSearchDelegate.test.ts
@kenryu42 kenryu42 merged commit 93da6e6 into main Jun 3, 2026
3 checks passed
@kenryu42 kenryu42 deleted the web-search-tool-for-grok branch June 3, 2026 08:28
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant