Skip to content

feat(web-search): ✨ add web search tool with multi-engine support#197

Open
jorben wants to merge 7 commits into
masterfrom
feta/web-search-support
Open

feat(web-search): ✨ add web search tool with multi-engine support#197
jorben wants to merge 7 commits into
masterfrom
feta/web-search-support

Conversation

@jorben
Copy link
Copy Markdown
Contributor

@jorben jorben commented May 22, 2026

Summary

Add a built-in web search tool with multi-engine support (Tavily, Brave, Exa, Firecrawl), configurable per-engine API keys and base URLs, domain filtering, and a full settings UI in the General settings panel.

Changes

Backend (Rust)

  • New web search executor (web_search.rs): implements tool execution with standardized result format across all four engines, including time-range filtering, domain include/exclude, and country hints
  • New settings module (web_search_settings.rs): persists and loads web search settings from SQLite, with per-engine API key and base URL maps, legacy migration, and sanitization
  • Tool context consolidation (executors/mod.rs): introduces ToolContext struct to bundle workspace path, writable roots, thread ID, terminal manager, and DB pool — simplifies the execute_tool signature and passes the pool needed by web search
  • Conditional tool injection (agent_session_tools.rs): web_search tool is added to default and plan-read-only profiles only when the feature is enabled and an API key is configured; also wired into subagent helper profiles
  • Tool gateway (tool_gateway.rs): updated to pass ToolContext instead of individual parameters
  • Subagent orchestration (orchestrator.rs, runtime_orchestration.rs): propagates web search enabled state to helper agents

Frontend (TypeScript/React)

  • Settings types & store (types.ts, settings-store.ts, defaults.ts): adds WebSearchSettings type with engine, API key status, base URL, max results, and raw content toggle
  • Web search settings model (web-search-settings.ts): handles mapping between persisted and in-memory formats, per-engine API key/base URL normalization, and legacy field migration
  • IPC actions (settings-ipc-actions.ts, settings-ipc-actions.test.ts): adds updateWebSearchSettings with optimistic UI updates and backend sync; includes comprehensive unit tests for per-engine key/URL persistence and migration
  • Settings UI (settings-center-overlay.tsx): adds Web Search section with enable toggle, engine picker, API key input (masked), base URL, max results slider, and raw content switch
  • Profile library cards (settings-center-overlay.tsx): redesigns cards to show model brand icon and compact layout with hover-reveal actions
  • Agent access panel (profile-agent-access.tsx): replaces lock icon with checkbox for always-on agents, improves truncation and layout
  • Agents panel (agents-settings-panel.tsx): adds web_search to tool categories, uses shared error message helper, shows invocation description
  • i18n (en.ts, zh-CN.ts): adds translation keys for all web search settings labels and descriptions
  • Tool names (tool-names.ts): adds web_search to default-collapsed tools list

Test Plan

  • Enable web search in Settings → General, configure API key, verify tool appears in agent sessions
  • Switch engines (Tavily, Brave, Exa, Firecrawl) and confirm per-engine API key/base URL persistence
  • Test domain include/exclude and time range filters per engine
  • Verify web search is hidden when disabled or no API key configured
  • Confirm profile library card redesign renders correctly with model icons
  • Run npm run typecheck and npm run test:unit
  • Run cargo test --locked --manifest-path src-tauri/Cargo.toml

🤖 Generated with TiyCode

jorben added 5 commits May 22, 2026 16:42
…n filtering

Migrate API key and base URL storage from single fields to per-engine maps,
enabling independent configuration for each search engine. Remove legacy
`clearApiKey` action in favor of saving an empty string.

- Store `apiKeys` and `baseUrls` as engine-keyed maps in settings
- Automatically migrate existing single `apiKey`/`baseUrl` on read
- Update agent tool to remove `maxResults`/`includeRawContent` parameters (now exclusively controlled by app settings)
- Improve agent tool descriptions for query and timeRange parameters
- Add Brave domain filtering via `site:`/`-site:` query modifiers
- Add Exa published date range support for timeRange parameter
- Change default `includeRawContent` to `true`
- Remove unused translation keys `alwaysOn` and `off` in profile agent access
- UI: replace lock icon with read-only checkbox for built-in agents, clamp descriptions, clear API key input on engine change
… search

Introduce ToolContext to bundle workspace path, writable roots, thread id,
terminal manager, and database pool, simplifying function signatures across
executors and tool gateway.

Web search improvements:
- Use a static OnceLock HTTP client to avoid recreating per request.
- Remove providerResponse from tool output to reduce payload size.
- Adjust topic_from_time_range to only return "news" for day and week.

Subagent tool selection:
- Conditionally add web_search tool based on user settings.
- Pass a flag to helper_tools to control inclusion.

UI: refine profile library card styling (font sizes, spacing, layout)
and add debounced web search base URL update.
Add logic to transfer subagent access IDs from the source profile to the
new duplicate when cloning a profile. On failure, the created profile is
preserved and a warning is logged, ensuring robustness.

Also replace inline error message construction in agents settings panel
with a shared utility function for consistent invoke error handling.
@github-actions
Copy link
Copy Markdown

github-actions Bot commented May 22, 2026

AI Code Review Summary

PR: #197 (feat(web-search): ✨ add web search tool with multi-engine support)
Preferred language: English

Overall Assessment

Detected 10 actionable findings, prioritize CRITICAL/HIGH before merge.

Major Findings by Severity

  • HIGH (1)
    • src/modules/settings-center/model/settings-ipc-actions.ts:312 - Silent Failure of Web Search API Key Save on Backend Errors
  • MEDIUM (6)
    • src-tauri/src/core/executors/web_search.rs:354 - Memory Exhaustion Risk due to Unrestricted Response Body Buffering
    • src-tauri/src/core/executors/web_search.rs:354 - Unbounded response body buffering before size validation can lead to OOM / DoS
    • src-tauri/src/core/executors/web_search.rs:354 - Potential Memory Exhaustion via Unbounded HTTP Response Download
    • src/modules/settings-center/model/settings-ipc-actions.test.ts:1222 - Missing Test Coverage for Web Search Settings Rejection & Rollback
    • src/modules/settings-center/model/web-search-settings.ts:1 - Missing Unit Tests for Web Search Serialization & Parsing Utilities
    • src/modules/settings-center/ui/settings-center-overlay.tsx:1273 - Ambiguous "Save" button behavior for empty Web Search API keys
  • LOW (3)
    • src-tauri/src/core/executors/web_search.rs:371 - Unnecessary UTF-8 conversion and string allocation before JSON parsing
    • src-tauri/src/core/executors/web_search.rs:553 - Lack of Domain Validation in Query Construction
    • src/modules/settings-center/model/settings-ipc-actions.ts:289 - Optimistic hasApiKey state inconsistency on engine switch in non-Tauri environments

Actionable Suggestions

  • In web_search.rs, use response.chunk() or a similar streaming interface to enforce a strict limit on HTTP response body size during transit.
  • Validate domains in brave_query and other search providers to only permit standard hostname characters.
  • Check the Content-Length header in web_search.rs before calling response.bytes().await.
  • Alternatively, read the response body as a chunked stream up to a limit of 5 MB (using tokio::io::AsyncReadExt::take or equivalent stream helpers).
  • Refactor response_json in web_search.rs to parse using serde_json::from_slice instead of from_utf8_lossy.
  • Introduce a chunked reader or use reqwest stream limiters to avoid loading the entire payload when it exceeds MAX_RESPONSE_BYTES.
  • Buddy, update the Save button text dynamically to say 'Clear' when the field is empty and a key is currently configured.
  • Add a visual indicator in the UI showing exactly which engines currently have keys configured, rather than only the active one.
  • Buddy, please implement a simple URL scheme validator in src/modules/settings-center/model/web-search-settings.ts to ensure that custom base URLs start with http:// or https:// before saving.
  • Add a regex pattern check /^[a-z0-9-_]+$/ on the subagent slug editor text input in src/modules/settings-center/ui/agents-settings-panel.tsx to secure and standardize registration labels.
  • Buddy, propagate error rejections out of updateWebSearchSettings so the UI does not clear the API key field or hide save failures from the user.
  • Add a dedicated unit test suite (web-search-settings.test.ts) covering all edge-cases of data normalization and mapping helper functions.

Potential Risks

  • Memory starvation/OOM crash if web search providers or malicious custom endpoints serve gigantic payloads.
  • Bypassing intended domain filters if domain array values are manipulated with spaces or query operators.
  • Local or remote denial of service (DoS) in the Tauri application if a search engine endpoint serves an exceptionally large payload.
  • If an attacker can manipulate the database settings, they could theoretically configure the base URL to point to a malicious server, extracting the user's API keys when a search is run.
  • Out-of-memory (OOM) crashes if remote servers respond with very large payloads.
  • Unnecessary memory allocation spikes on high-frequency search requests.
  • Accidental deletion of saved API keys due to confusing 'Save' button behavior on empty inputs.
  • Malicious or malformed Base URLs may trick backend components into routing traffic to local addresses, posing a minor local SSRF or configuration bypass risk.
  • Unsanitized custom subagent slugs could conflict with standard tool-handling or directory structures if special traversal patterns are allowed.
  • Silent failed API key saves where the input box clears but the token never reaches the storage file, leading to developer frustration.
  • Race conditions causing temporary state flickers or older values overwriting newer settings during fast concurrent toggling.
  • App hydration freeze if the backend configuration database becomes temporarily corrupt or locked.

Test Suggestions

  • Test web_search behaviors under large simulated HTTP payloads.
  • Add more test assertions on query string validation and filtering.
  • Create a mock HTTP server test that sends an infinite stream of bytes to the search client to verify that your memory-limiting safeguard halts the connection promptly.
  • Write a test that uses a local mock server to stream an infinite body and assert that our client cuts it off at MAX_RESPONSE_BYTES without memory bloat.
  • Buddy, run the unit test suite locally to verify web search settings with 'npm run test:unit' to ensure no regressions.
  • Buddy, add a test case in src/modules/settings-center/model/web-search-settings.ts validating that malformed or non-http URLs are discarded or sanitized appropriately.
  • Include a validation test case in src/modules/settings-center/ui/agents-settings-panel.tsx ensuring that attempting to update a slug with special characters causes a validation warning on the UI.
  • Write a test mocking an API write rejection in updateWebSearchSettings and verify the UI keeps the typed value and raises an alert.
  • Add overlapping state update tests to assert consistent serial resolution of IPC settings updates.
  • Perform manual validation on unmount-on-blur flows for the custom Base URL and Result Count inputs.

File-Level Coverage Notes

  • src-tauri/src/core/agent_session.rs: Looks good. Loading web search settings before building the session spec is a fast database query, and there is no unnecessary lock contention.
  • src-tauri/src/core/agent_session_tools.rs: Well written. Checking for duplicate tools with .any() is perfectly efficient since the tool vectors are very small.
  • src-tauri/src/core/executors/mod.rs: The introduction of ToolContext is a great refactor that prevents parameter-bloat and avoids unnecessary clone operations by passing references.
  • src-tauri/src/core/executors/web_search.rs: Functional, but contains memory buffering risks and minor CPU inefficiencies on JSON parsing.
  • src-tauri/src/core/mod.rs: Pure module declaration change, no performance impact.
  • src-tauri/src/core/subagent/orchestrator.rs: Subagent orchestration loading settings is efficient and relies on quick persistence queries.
  • src-tauri/src/core/subagent/runtime_orchestration.rs: The changes to runtime orchestration and helper tool retrieval are low-overhead and look solid.
  • src-tauri/src/core/tool_gateway.rs: Passing ToolContext works seamlessly and has no performance regression.
  • src-tauri/src/core/web_search_settings.rs: Settings retrieval and sanitization are well optimized. BTreeMap sizes are minimal, so performance is excellent.
  • src-tauri/src/persistence/repo/custom_subagent_repo.rs: Removing the unused query binding result is a clean code hygiene change with no performance impact.
  • src/i18n/locales/en.ts: No major risks. Translation keys for the new Web Search settings and conflict errors were cleanly added. (Buddy, the cleanup of unused keys helps keep the dictionary size minimal.)
  • src/i18n/locales/zh-CN.ts: No major risks. Chinese translation strings align perfectly with the English equivalents. (Buddy, great work keeping the translations in sync with the core feature addition.)
  • src/modules/settings-center/model/defaults.ts: The default preferences now accurately integrate webSearch properties. No immediate risks here. (Buddy, because webSearch settings are stored under the backend key web_search.settings instead of the local UI key tiy-agent-local-ui-settings, there was no need to bump SETTINGS_STORAGE_SCHEMA_VERSION here, which is correct according to the schema rules.)
  • src/modules/settings-center/model/settings-hydration.ts: Hydration logic successfully added Web Search retrieval. However, adding it directly to the root Promise.all means a failure in retrieving web search settings could block full hydration. (Buddy, wrapping individual calls in the Promise.all with selective catch blocks or Promise.allSettled is a safer approach for non-critical settings like web search.)
  • src/modules/settings-center/model/settings-ipc-actions.test.ts: Solid coverage added for profile duplication access copies and happy path updateWebSearchSettings persistence. Missing error rollback coverage. (Buddy, adding those failure scenario tests will keep the codebase incredibly resilient!)
  • src/modules/settings-center/model/settings-ipc-actions.ts: Web Search updater and profile duplication subagent copy added. The error swallower causes silent save failures. (Buddy, propagating rejections or implementing sequential queuing of setting writes is highly recommended to mitigate these risks.)
  • src/modules/settings-center/model/settings-storage.test.ts: No risks. The new test properly ensures Web Search properties are not leaked into the local storage UI payload. (Buddy, this is an excellent check that enforces the separation of local UI state and backend persisted states.)
  • src/modules/settings-center/model/settings-store.ts: The global settingsStore correctly accommodates webSearch state. No risks identified. (Buddy, the store extension is clean and simple.)
  • src/modules/settings-center/model/types.ts: The TypeScript declarations cleanly outline the settings contracts and supported search engines. (Buddy, the explicit engine types prevent invalid engine names at compile-time.)
  • src/modules/settings-center/model/web-search-settings.ts: Solid utilities for mapping and patching settings, but lacking direct unit tests for edge cases. (Buddy, direct unit testing on these pure functions pays off heavily in speed and coverage completeness.)
  • ... and 6 more file-level entries.

Inline Downgraded Items (processed but not inline)

  • None

Coverage Status

  • Target files: 26
  • Covered files: 26
  • Uncovered files: 0
  • No-patch/binary covered as file-level: 0
  • Findings with unknown confidence (N/A): 0

Uncovered list:

  • None

No-patch covered list:

  • None

Runtime/Budget

  • Rounds used: 1/4
  • Planned batches: 2
  • Executed batches: 2
  • Sub-agent runs: 6
  • Planner calls: 1
  • Reviewer calls: 8
  • Model calls: 9/64
  • Structured-output summary-only degradation: NO

Copy link
Copy Markdown

@github-actions github-actions Bot left a comment

Choose a reason for hiding this comment

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

Automated PR review completed.

  • Findings kept: 13
  • Findings with unknown confidence: 0
  • Inline comments attempted: 13
  • Target files: 26
  • Covered files: 26
  • Uncovered files: 0
    See the summary comment for detailed analysis and coverage details.

insert_string(&mut body, "country", country);
}

let value = post_json(client.post(endpoint).bearer_auth(api_key).json(&body)).await?;

This comment was marked as outdated.

response_json(builder.send().await).await
}

async fn response_json(

This comment was marked as outdated.

)
})?;
let status = response.status();
let body = response.text().await.map_err(|error| {

This comment was marked as outdated.

}
}

fn map_tavily_result(value: &Value) -> StandardSearchResult {

This comment was marked as outdated.

}

fn parse_input(input: &Value, settings: &WebSearchSettings) -> Result<WebSearchInput, AppError> {
let query = input

This comment was marked as outdated.


const HTTP_TIMEOUT_SECS: u64 = 30;

static HTTP_CLIENT: OnceLock<reqwest::Client> = OnceLock::new();

This comment was marked as outdated.

}
}

pub async fn load_web_search_settings(pool: &SqlitePool) -> Result<WebSearchSettings, AppError> {

This comment was marked as outdated.

return optimistic;
}

try {

This comment was marked as outdated.

}));
}

export async function updateWebSearchSettings(patch: WebSearchSettingsPatch) {

This comment was marked as outdated.

</span>

<div
className={cn(

This comment was marked as outdated.

- Add MAX_QUERY_CHARS (500) and MAX_RESPONSE_BYTES (5MB) constants
- Validate web search query length before making request
- Check HTTP response size to prevent large payloads
- Move Tavily API key from bearer auth to request body
- Fix http_error function to accept &str instead of String
- Update settings overlay to debounce max results value with onBlur
- Add local state for max results to prevent premature saves
Copy link
Copy Markdown

@github-actions github-actions Bot left a comment

Choose a reason for hiding this comment

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

Automated PR review completed.

  • Findings kept: 14
  • Findings with unknown confidence: 0
  • Inline comments attempted: 14
  • Target files: 26
  • Covered files: 26
  • Uncovered files: 0
    See the summary comment for detailed analysis and coverage details.

profileList(),
promptCommandList(),
customSubagentList(),
settingsGet(WEB_SEARCH_SETTINGS_KEY),

This comment was marked as outdated.

)
})?;
let status = response.status();
let raw_body = response.bytes().await.map_err(|error| {

This comment was marked as outdated.

)
})?;
let status = response.status();
let raw_body = response.bytes().await.map_err(|error| {
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

[MEDIUM] Unbounded HTTP Response Buffering

The response bytes are fully downloaded and buffered into memory via response.bytes().await prior to checking if the size exceeds MAX_RESPONSE_BYTES (5 MB). This exposes the app to memory exhaustion (OOM) if a search engine endpoint returns a massive payload.

Suggestion: Read the response as a chunked stream or check the Content-Length header first (as a quick pre-check, though not entirely sufficient on its own). For robust streaming, use a bounded reader or read from the byte stream while tracking the count, breaking early if it crosses the limit.

Risk: High memory allocation spikes or process crashes (OOM) if querying a compromised or malfunctioning search API that streams infinite or extremely large responses.

Confidence: 0.95

[From SubAgent: performance]

)
})?;
let status = response.status();
let raw_body = response.bytes().await.map_err(|error| {

This comment was marked as outdated.

[],
);

const handleSaveWebSearchApiKey = async () => {

This comment was marked as outdated.

)
.then(async (profile) => {
const mapped = mapProfileDto(profile);
try {

This comment was marked as outdated.

}));
}

export async function updateWebSearchSettings(patch: WebSearchSettingsPatch) {

This comment was marked as outdated.

const optimistic: WebSearchSettings = {
...current,
...patch,
hasApiKey: Object.prototype.hasOwnProperty.call(patch, "apiKey")

This comment was marked as outdated.

}
};

useEffect(() => {

This comment was marked as outdated.

className="w-full md:w-[360px]"
onChange={(event) => setWebSearchBaseUrl(event.target.value)}
onBlur={() => {
const trimmed = webSearchBaseUrl.trim();

This comment was marked as outdated.

Add localized error handling for slug conflicts when creating or saving custom agents. The application now detects the `custom_subagent.slug_conflict` error code and displays a specific translation with the conflicting slug, improving user feedback.

Split error state into page-level (`pageErrorMessage`) and editor-level (`editorErrorMessage`) to show errors in their correct context. The editor now displays inline error messages near the header, with a dismiss button, while global errors like create/delete failures remain at the bottom. This provides clearer error attribution.
Copy link
Copy Markdown

@github-actions github-actions Bot left a comment

Choose a reason for hiding this comment

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

Automated PR review completed.

  • Findings kept: 10
  • Findings with unknown confidence: 0
  • Inline comments attempted: 10
  • Target files: 26
  • Covered files: 26
  • Uncovered files: 0
    See the summary comment for detailed analysis and coverage details.

const mapped = mapPersistedWebSearchSettings(persisted);
settingsStore.setState({ webSearch: mapped });
return mapped;
} catch (error) {
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

[HIGH] Silent Failure of Web Search API Key Save on Backend Errors

Buddy, the updateWebSearchSettings function catches backend storage write rejections internally, prints a console warning, and returns the previous state (current). Since it does not propagate/rethrow the error, calling UI handlers like handleSaveWebSearchApiKey in GeneralSettingsPanel assume success and immediately clear the input text (setWebSearchApiKey("")). This leads to a silent failure where the user's input is cleared and lost without any UI-level error notification.

Suggestion: Modify updateWebSearchSettings to either rethrow errors, return a success boolean/status, or dispatch a distinct UI error notification. Then, update handleSaveWebSearchApiKey to only clear the text input on successful persistence, and keep the user's typed key intact if saving fails so they can retry or see the error message.

Risk: Users can lose their typed API keys or custom base URLs during background save failures with no visual indication of failure, causing hard-to-debug tool authentication errors.

Confidence: 0.90

[From SubAgent: testing]

)
})?;
let status = response.status();
let raw_body = response.bytes().await.map_err(|error| {
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

[MEDIUM] Memory Exhaustion Risk due to Unrestricted Response Body Buffering

In web_search.rs, response.bytes().await is invoked to load the entire HTTP response body into memory before verifying its size against MAX_RESPONSE_BYTES. If a configured search engine endpoint returns an extremely large payload or an infinite stream, this can result in excessive memory consumption or an Out-Of-Memory (OOM) crash in the Tauri backend.

Suggestion: Buddy, consider checking the Content-Length header first (and rejecting the response if it exceeds MAX_RESPONSE_BYTES), or read the response body in chunks up to MAX_RESPONSE_BYTES using a limited stream reader to prevent downloading too much data into memory.

Risk: A malicious, compromised, or misconfigured search engine endpoint could return an extremely large payload (e.g. decompression bomb or endless stream), causing the Tauri application backend to crash due to memory exhaustion.

Confidence: 0.95

[From SubAgent: security]

)
})?;
let status = response.status();
let raw_body = response.bytes().await.map_err(|error| {
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

[MEDIUM] Potential Memory Exhaustion via Unbounded HTTP Response Download

The response.bytes().await call downloads the entire HTTP response body into memory before checking its length against MAX_RESPONSE_BYTES. If a custom or compromised endpoint returns an extremely large payload, this can lead to memory exhaustion.

Suggestion: Limit the body download size upfront by using a chunked reader or checking the Content-Length header, and short-circuit the request if the size exceeds MAX_RESPONSE_BYTES.

Risk: A massive payload from a customized base URL or faulty server could cause memory bloat or crash the Tauri app process.

Confidence: 0.90

[From SubAgent: general]

});
});

describe("updateWebSearchSettings", () => {
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

[MEDIUM] Missing Test Coverage for Web Search Settings Rejection & Rollback

Buddy, although there are several integration tests verifying happy-path persistence and key/URL switching for Web Search, there are zero tests verifying the behavior of updateWebSearchSettings when the backend storage settingsGet or settingsSet calls reject.

Suggestion: Add a test case in settings-ipc-actions.test.ts where mockSettingsSet or mockSettingsGet is mocked to reject with an Error, and assert that the store correctly rolls back to the pre-optimistic state.

Risk: Without test cases for rejections, regressions in state recovery/rollback logic could go undetected, causing the UI and store state to permanently drift from the backend state.

Confidence: 0.95

[From SubAgent: testing]

@@ -0,0 +1,165 @@
import type { WebSearchEngine, WebSearchSettings } from "@/modules/settings-center/model/types";
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

[MEDIUM] Missing Unit Tests for Web Search Serialization & Parsing Utilities

Buddy, the pure parsing, mapping, and patch-building utility functions in web-search-settings.ts contain extensive normalization logic (e.g., negative or floating-point maxResults, nulls, undefineds, custom engines) and migration code (moving a legacy flat apiKey/baseUrl into multi-engine map structures). However, these are only tested indirectly via the store, rather than using highly focused unit tests.

Suggestion: Create a web-search-settings.test.ts unit test file in the same directory. Write tests verifying correct output when passing malformed values, edge cases like NaN or invalid engines, and legacy migration configurations.

Risk: A subtle regression in parsing or migration utilities could corrupt the persistent settings file or cause settings to silently revert to default states upon hydration.

Confidence: 0.95

[From SubAgent: testing]

placeholder={webSearch.hasApiKey ? "••••••••••••" : t("settings.general.webSearchApiKeyPlaceholder")}
onChange={(event) => setWebSearchApiKey(event.target.value)}
/>
<Button
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

[MEDIUM] Ambiguous "Save" button behavior for empty Web Search API keys

When the API key input is cleared, the button remains enabled to allow the user to delete/clear the existing saved key. However, the button's label remains 'Save'. Clicking it deletes the key, which might surprise users expecting it to do nothing or save a blank key.

Suggestion: Buddy, let's dynamically set the button text to 'Clear' when the input is empty and a key exists, using the translation key t("settings.general.webSearchApiKeyClear").

Risk: Users could accidentally clear their API keys, or become confused on how to remove an API key because no explicit delete/clear button is shown.

Confidence: 0.95

[From SubAgent: general]

),
));
}
let body = String::from_utf8_lossy(&raw_body);
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

[LOW] Unnecessary UTF-8 conversion and string allocation before JSON parsing

On successful HTTP requests, the entire raw body (up to 5MB) is converted into a UTF-8 string before being parsed. This leads to redundant memory allocation and CPU cycles traversing the bytes.

Suggestion: Use serde_json::from_slice::(&raw_body) to parse the JSON directly from the raw byte buffer. Only construct the string lossy copy if the HTTP status code indicates an error and you need to construct an error message.

Risk: Increased memory churn and CPU overhead for larger search engine responses, especially when processing up to 5MB of search results.

Confidence: 0.90

[From SubAgent: performance]

}
}

fn brave_query(query: &str, include_domains: &[String], exclude_domains: &[String]) -> String {
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

[LOW] Lack of Domain Validation in Query Construction

Domain inputs for include_domains and exclude_domains are incorporated into the query string without validation. If they contain spaces or search operator syntax, they could mutate the query's behavior.

Suggestion: Sanitize domain strings to guarantee they only contain alphanumeric, dots, and hyphens, and strip out any spaces.

Risk: Unexpected search behavior or search engine filtering mutations if malformed or query-injected domains are passed.

Confidence: 0.85

[From SubAgent: general]

const optimistic: WebSearchSettings = {
...current,
...patch,
hasApiKey: Object.prototype.hasOwnProperty.call(patch, "apiKey")
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

[LOW] Optimistic hasApiKey state inconsistency on engine switch in non-Tauri environments

In non-Tauri web development environments, changing engines keeps the old engine's hasApiKey status because the optimistic state falls back to current.hasApiKey when apiKey is not in the patch.

Suggestion: Buddy, we could reset or compute hasApiKey on engine changes if we are in non-Tauri mode, or we can just accept this minor web dev inconsistency.

Risk: Minor UI discrepancy during web-only mock testing/debugging where switching engines incorrectly shows a key as configured.

Confidence: 0.85

[From SubAgent: general]

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