diff --git a/docs/internal/server-secrets-strategy.md b/docs/internal/server-secrets-strategy.md index 839f2bcec..df4160886 100644 --- a/docs/internal/server-secrets-strategy.md +++ b/docs/internal/server-secrets-strategy.md @@ -39,6 +39,7 @@ the vault: - `FABRO_SLACK_BOT_TOKEN` - `DAYTONA_API_KEY` - `BRAVE_SEARCH_API_KEY` +- `VENICE_API_KEY` `FABRO_JWT_PRIVATE_KEY` and `FABRO_JWT_PUBLIC_KEY` are removed. `SESSION_SECRET` is the single auth root. diff --git a/docs/public/administration/deploy-railway.mdx b/docs/public/administration/deploy-railway.mdx index 9a52facc9..c26c845d0 100644 --- a/docs/public/administration/deploy-railway.mdx +++ b/docs/public/administration/deploy-railway.mdx @@ -41,7 +41,7 @@ Add variables in **Service → Variables** as needed. The [Server Configuration] | `SESSION_SECRET` | 64-character hex string; required when the web UI is enabled | | `AWS_ACCESS_KEY_ID`, `AWS_SECRET_ACCESS_KEY`, `AWS_SESSION_TOKEN` | Optional static S3 object-store credentials | -Do not put optional integration secrets in Railway variables for server runtime. After the server is running, add LLM provider keys, Slack, Daytona, Brave Search, `GITHUB_TOKEN`, and GitHub App secrets to the server vault with `fabro secret set`, `fabro provider login`, or `fabro install`. +Do not put optional integration secrets in Railway variables for server runtime. After the server is running, add LLM provider keys, Slack, Daytona, Brave Search, Venice Search, `GITHUB_TOKEN`, and GitHub App secrets to the server vault with `fabro secret set`, `fabro provider login`, or `fabro install`. No `.env` file is auto-loaded inside the container; bootstrap variables come from Railway's environment. diff --git a/docs/public/administration/security.mdx b/docs/public/administration/security.mdx index 27e1aeea9..b27fada61 100644 --- a/docs/public/administration/security.mdx +++ b/docs/public/administration/security.mdx @@ -38,7 +38,7 @@ Fabro is single-tenant software designed for small, trusted teams. The following ### Secrets - **Keep API keys out of sandboxes.** The local sandbox strips environment variables ending in `_API_KEY`, `_SECRET`, `_TOKEN`, `_PASSWORD`, or `_CREDENTIAL`, but Docker and Daytona sandboxes provide stronger isolation — only explicitly configured variables are passed through. -- **Use the server vault for optional integration credentials.** For server-backed workflows, persist LLM provider keys, Slack, Daytona, Brave Search, GitHub token, and GitHub App secrets with `fabro provider login`, `fabro secret set`, or `fabro install`. Process env and `server.env` are reserved for bootstrap secrets such as `SESSION_SECRET`, `FABRO_DEV_TOKEN`, and object-store credentials. Do not commit secrets to version control. +- **Use the server vault for optional integration credentials.** For server-backed workflows, persist LLM provider keys, Slack, Daytona, Brave Search, Venice Search, GitHub token, and GitHub App secrets with `fabro provider login`, `fabro secret set`, or `fabro install`. Process env and `server.env` are reserved for bootstrap secrets such as `SESSION_SECRET`, `FABRO_DEV_TOKEN`, and object-store credentials. Do not commit secrets to version control. - **Rotate the session secret.** The `SESSION_SECRET` environment variable encrypts web app sessions. Rotate it periodically and use a strong random value. ### Execution diff --git a/docs/public/administration/self-host-docker.mdx b/docs/public/administration/self-host-docker.mdx index c896e29e6..ab0228149 100644 --- a/docs/public/administration/self-host-docker.mdx +++ b/docs/public/administration/self-host-docker.mdx @@ -108,7 +108,7 @@ Generate one with `openssl rand -hex 32`. | `FABRO_DEV_TOKEN` | Optional — pre-set the dev token instead of reading the one written to `/storage` on first boot | | `AWS_ACCESS_KEY_ID`, `AWS_SECRET_ACCESS_KEY`, `AWS_SESSION_TOKEN` | Optional static S3 object-store credentials | -Do not put optional integration secrets in `.env` for server runtime. Configure LLM provider keys, Slack, Daytona, Brave Search, `GITHUB_TOKEN`, and GitHub App secrets in the vault with `fabro secret set`, `fabro provider login`, or `fabro install`. +Do not put optional integration secrets in `.env` for server runtime. Configure LLM provider keys, Slack, Daytona, Brave Search, Venice Search, `GITHUB_TOKEN`, and GitHub App secrets in the vault with `fabro secret set`, `fabro provider login`, or `fabro install`. Optional: diff --git a/docs/public/administration/server-configuration.mdx b/docs/public/administration/server-configuration.mdx index 5ed65df8c..6dd8f1e67 100644 --- a/docs/public/administration/server-configuration.mdx +++ b/docs/public/administration/server-configuration.mdx @@ -334,22 +334,6 @@ Tailscale Services and Tailscale Funnel are different ingress features. Services Incoming webhooks are authenticated only by GitHub's `X-Hub-Signature-256` HMAC signature, not by Fabro's bearer/session auth. -### `[server.integrations.search]` section - -Select the HTTP backend for the built-in [`web_search`](/agents/tools#web_search) tool. Brave remains the default when this table is absent. - -```toml title="settings.toml" -[server.integrations.search] -provider = "brave" # "brave" (default) | "venice" -venice_engine = "brave" # venice-only: "brave" | "google" -``` - -- `provider = "brave"`: direct Brave Search. Requires vault `BRAVE_SEARCH_API_KEY`. See [Brave Search](/integrations/brave-search). -- `provider = "venice"`: Venice `POST /api/v1/augment/search`. Requires vault `VENICE_API_KEY` (the same key as the Venice LLM provider). See [Venice Search](/integrations/venice-search). -- `venice_engine = "brave"` is Brave **through Venice** (Firecrawl ZDR, billed as Venice credits). Direct Brave remains `provider = "brave"`. - -The tool is registered only when the selected provider is configured. Failed calls do not fall back between backends. - ### `[run.checkpoint]` section Configure checkpoint behavior for all runs. @@ -421,11 +405,13 @@ fabro secret set BRAVE_SEARCH_API_KEY BSA... fabro secret set VENICE_API_KEY venice-... ``` +The built-in [`web_search`](/agents/tools#web_search) tool selects its backend from these credentials. It uses direct Brave Search when `BRAVE_SEARCH_API_KEY` exists. Otherwise it uses Venice Search when `VENICE_API_KEY` exists. When neither exists, the tool is not registered. + | Variable | Description | |---|---| | `DAYTONA_API_KEY` | Daytona cloud sandbox API key | -| `BRAVE_SEARCH_API_KEY` | Brave Search API key (`web_search` when `provider = "brave"`) | -| `VENICE_API_KEY` | Venice API key (LLM provider, and `web_search` when `provider = "venice"`) | +| `BRAVE_SEARCH_API_KEY` | Brave Search API key; the preferred `web_search` backend when present | +| `VENICE_API_KEY` | Venice API key; used by the Venice LLM provider and by `web_search` when no Brave key exists | ### Server authentication diff --git a/docs/public/agents/prompts.mdx b/docs/public/agents/prompts.mdx index 50abbe3d1..ff2c2fd4c 100644 --- a/docs/public/agents/prompts.mdx +++ b/docs/public/agents/prompts.mdx @@ -145,7 +145,7 @@ The system prompt varies by LLM provider. Each provider has its own identity tex This is the full system prompt sent to Claude as the LLM system message. The `` block is filled in at runtime. -Tool guidance tracks the tools actually registered for the session. The `web_search` section shown below is present only when a [Brave Search API key](/integrations/brave-search) is configured; without one, both the tool and its guidance are omitted. +Tool guidance tracks the tools actually registered for the session. The `web_search` section shown below is present when a [Brave Search API key](/integrations/brave-search) or [Venice API key](/integrations/venice-search) is configured. Without either key, both the tool and its guidance are omitted. ``` You are Claude, an AI coding assistant made by Anthropic. You help users with @@ -232,7 +232,7 @@ first). Use this for finding files rather than using shell find or ls commands. ## web_search -Search the web using Brave Search. Returns titles, URLs, and descriptions. +Search the web. Returns titles, URLs, and descriptions. ## web_fetch Fetch content from a URL and optionally summarize it. Pass a prompt to diff --git a/docs/public/agents/tools.mdx b/docs/public/agents/tools.mdx index 9b17b8957..d6df674ed 100644 --- a/docs/public/agents/tools.mdx +++ b/docs/public/agents/tools.mdx @@ -123,28 +123,18 @@ Patterns are case-sensitive and relative to `path`: `*` and `?` stay within one ### web_search -Searches the web using Brave Search (default) or Venice Search. +Searches the web using Brave Search or Venice Search. Fabro selects the backend automatically from the available credentials. | Parameter | Type | Required | Description | |---|---|---|---| | `query` | string | yes | Search query. Venice rejects queries longer than 400 characters before the HTTP call. | | `max_results` | integer | no | Maximum results (default: 5, max: 20) | -| `engine` | `"brave"` \| `"google"` | no | Venice backend only. Overrides `[server.integrations.search].venice_engine`. Brave ignores this parameter. | -Select the backend in server settings. Brave remains the default when the table is absent: +Fabro uses direct [Brave Search](/integrations/brave-search) when `BRAVE_SEARCH_API_KEY` is present. Otherwise it uses [Venice Search](/integrations/venice-search) when `VENICE_API_KEY` is present. If both credentials are present, Brave wins. Venice always uses its Brave search engine. -```toml -[server.integrations.search] -provider = "venice" # "brave" (default) | "venice" -venice_engine = "brave" # venice-only: "brave" (ZDR) | "google" (anon proxy) -``` +Runs read both keys from the server vault. Workers start from a cleared environment, so exporting a key in the server's shell has no effect. The standalone agent CLI reads the keys from the invoking shell instead. -Runs read the selected provider's key from the server vault — workers start from a cleared environment, so exporting the key in the server's shell has no effect. The standalone agent CLI reads keys from the invoking shell instead. - -- Brave (`provider = "brave"`): `fabro secret set BRAVE_SEARCH_API_KEY `. See [Brave Search](/integrations/brave-search). -- Venice (`provider = "venice"`): reuse `VENICE_API_KEY` (`fabro provider login --provider venice` or `fabro secret set VENICE_API_KEY `). See [Venice Search](/integrations/venice-search). - -The tool is registered only when the **selected** provider is configured. Fabro does not fall back Brave ↔ Venice on a failed call. Returns numbered results with title, URL, and description; Venice includes `date` on a fourth line when present. +The tool is registered when either credential is available. Once Fabro selects a backend, a failed call returns an error; it does not retry through the other backend. Results contain numbered titles, URLs, and descriptions. Venice includes `date` on a fourth line when present. ### web_fetch diff --git a/docs/public/api-reference/fabro-api.yaml b/docs/public/api-reference/fabro-api.yaml index e9b019329..546b9d106 100644 --- a/docs/public/api-reference/fabro-api.yaml +++ b/docs/public/api-reference/fabro-api.yaml @@ -14231,14 +14231,12 @@ components: ServerIntegrationsSettings: type: object - required: [github, slack, search] + required: [github, slack] properties: github: $ref: "#/components/schemas/GithubIntegrationSettings" slack: $ref: "#/components/schemas/SlackIntegrationSettings" - search: - $ref: "#/components/schemas/SearchIntegrationSettings" GithubIntegrationSettings: type: object @@ -14278,23 +14276,6 @@ components: default_channel: type: ["string", "null"] - SearchIntegrationSettings: - type: object - required: [provider, venice_engine] - properties: - provider: - $ref: "#/components/schemas/SearchProvider" - venice_engine: - $ref: "#/components/schemas/VeniceSearchEngine" - - SearchProvider: - type: string - enum: [brave, venice] - - VeniceSearchEngine: - type: string - enum: [brave, google] - IntegrationWebhooksSettings: type: object required: [strategy] diff --git a/docs/public/changelog/2026-08-21.mdx b/docs/public/changelog/2026-08-21.mdx index d264b5aad..71308d8f3 100644 --- a/docs/public/changelog/2026-08-21.mdx +++ b/docs/public/changelog/2026-08-21.mdx @@ -5,6 +5,6 @@ date: "2026-08-21" ## Venice search backend for `web_search` -The built-in `web_search` tool now has a second HTTP backend. Brave Search remains the default. Set `[server.integrations.search].provider = "venice"` to send queries to Venice `POST /api/v1/augment/search`, reusing vault `VENICE_API_KEY`. Failed calls do not fall back between providers. +The built-in `web_search` tool now supports Venice as an automatic alternative to direct Brave Search. Fabro uses `BRAVE_SEARCH_API_KEY` when present. Otherwise it uses `VENICE_API_KEY` with Venice's Brave search engine. If neither key is present, the tool is not registered. Failed calls do not fall back between providers. See [Venice Search](/integrations/venice-search) and [Brave Search](/integrations/brave-search). diff --git a/docs/public/core-concepts/models.mdx b/docs/public/core-concepts/models.mdx index df78987d7..9bdeadce1 100644 --- a/docs/public/core-concepts/models.mdx +++ b/docs/public/core-concepts/models.mdx @@ -161,7 +161,7 @@ Workflow runs also add `x-session-id: ` to every LLM request so compatib Provider `agent_profile` defaults from `adapter` and controls profile-specific behavior such as which tools the agent registers, project-memory filenames, CLI/ACP command selection, and native session routing. Valid values are `anthropic`, `openai`, `gemini`, `kimi`, and `gpt56`; model-level values override provider-level values. -Two profiles are selected per model rather than per provider, because they follow the model wherever it is served: `kimi` for Kimi models, and `gpt56` for the GPT-5.6 models (Sol, Terra, Luna). The `gpt56` profile uses Codex's narrow core surface — `shell_command`, `apply_patch`, and `update_plan`, plus optional Brave-backed `web_search` — instead of fabro's dedicated file-read, discovery, and `web_fetch` tools. On OpenAI-compatible routes that cannot carry the freeform `apply_patch` grammar, it substitutes the JSON-schema `edit_file` tool. Session features may add their own question, skill, or subagent tools separately. +Two profiles are selected per model rather than per provider, because they follow the model wherever it is served: `kimi` for Kimi models, and `gpt56` for the GPT-5.6 models (Sol, Terra, Luna). The `gpt56` profile uses Codex's narrow core surface — `shell_command`, `apply_patch`, and `update_plan`, plus optional credential-backed `web_search` — instead of fabro's dedicated file-read, discovery, and `web_fetch` tools. On OpenAI-compatible routes that cannot carry the freeform `apply_patch` grammar, it substitutes the JSON-schema `edit_file` tool. Session features may add their own question, skill, or subagent tools separately. Provider `billing_policy` defaults from `adapter` and controls usage-cost estimation. Use `openai`, `anthropic`, `gemini`, or `none`. Model rows may override it for models whose billing family differs from their provider's — for example, Claude models served through OpenRouter set `billing_policy = "anthropic"` so cache reads and writes price correctly. diff --git a/docs/public/integrations/brave-search.mdx b/docs/public/integrations/brave-search.mdx index 2c46f25dc..5b6ee2475 100644 --- a/docs/public/integrations/brave-search.mdx +++ b/docs/public/integrations/brave-search.mdx @@ -3,9 +3,9 @@ title: "Brave Search" description: "Give Fabro agents web search capabilities via the Brave Search API" --- -Fabro's [`web_search`](/agents/tools#web_search) tool lets agents search the web during workflow execution. It uses the [Brave Search API](https://brave.com/search/api/) to return titles, URLs, and descriptions for any query. Setting the API key is the only configuration needed — the tool is then registered for all provider profiles (Anthropic, OpenAI, Gemini). Without a key the tool is not registered at all, so agents are never offered a search tool they cannot use. +Fabro's [`web_search`](/agents/tools#web_search) tool lets agents search the web during workflow execution. It uses the [Brave Search API](https://brave.com/search/api/) to return titles, URLs, and descriptions for any query. -Brave is the default `web_search` backend. To use Venice instead, see [Venice Search](/integrations/venice-search). +Fabro selects the backend from the credentials in its vault. Direct Brave Search is preferred whenever `BRAVE_SEARCH_API_KEY` is present. When that key is absent, Fabro can use [Venice Search](/integrations/venice-search) with `VENICE_API_KEY` instead. ## Setup @@ -23,7 +23,7 @@ fabro secret set BRAVE_SEARCH_API_KEY BSA... fabro doctor ``` -The doctor output should show **Web Search** as `brave: configured and reachable`. If the key is missing, web search is reported as a warning — workflows still run, but the `web_search` tool is omitted from the agent's tool set and its system prompt, so agents fall back to other tools. +The doctor output should show **Web Search** as `brave: configured and reachable`. If the Brave key is missing but a Venice key exists, Fabro checks Venice instead. If neither key exists, web search is reported as a warning. Workflows still run, but the `web_search` tool is omitted from the agent's tool set and its system prompt. The Fabro server reads this key from the vault only. It does not read `BRAVE_SEARCH_API_KEY` from process env or `server.env`. @@ -41,7 +41,7 @@ Agents call the `web_search` tool with a query string. Fabro sends the query to The Rust book ``` -If `BRAVE_SEARCH_API_KEY` is not configured in the vault, the tool returns an error explaining that the key is required. The agent can then fall back to other approaches. +If `BRAVE_SEARCH_API_KEY` is not configured, Fabro uses Venice when `VENICE_API_KEY` is available. If neither key is configured, the tool is not registered. See the [`web_search` tool reference](/agents/tools#web_search) for parameters and details. diff --git a/docs/public/integrations/venice-search.mdx b/docs/public/integrations/venice-search.mdx index 2e23cbad0..ac37cafb6 100644 --- a/docs/public/integrations/venice-search.mdx +++ b/docs/public/integrations/venice-search.mdx @@ -3,9 +3,9 @@ title: "Venice Search" description: "Give Fabro agents web search capabilities via Venice's augment/search API" --- -Fabro's [`web_search`](/agents/tools#web_search) tool lets agents search the web during workflow execution. The default backend is [Brave Search](/integrations/brave-search). Set `[server.integrations.search].provider = "venice"` to use [Venice Search](https://docs.venice.ai/api-reference/endpoint/augment/search) instead, reusing the same `VENICE_API_KEY` already used for the Venice LLM provider. +Fabro's [`web_search`](/agents/tools#web_search) tool lets agents search the web during workflow execution. Fabro uses [Venice Search](https://docs.venice.ai/api-reference/endpoint/augment/search) automatically when `VENICE_API_KEY` is available and a direct [Brave Search](/integrations/brave-search) key is not. -Agents keep calling `web_search`. Only the HTTP backend changes. +Venice Search reuses the same `VENICE_API_KEY` as the Venice LLM provider. Agents keep calling `web_search`; only the HTTP backend changes. ## Setup @@ -17,30 +17,25 @@ fabro provider login --provider venice fabro secret set VENICE_API_KEY venice-... ``` -2. Select Venice as the search backend: +Fabro prefers direct Brave Search whenever `BRAVE_SEARCH_API_KEY` is also present. To select Venice, leave that key unset or remove it: -```toml title="settings.toml" -[server.integrations.search] -provider = "venice" -# venice_engine = "brave" # default: Firecrawl ZDR, billed as Venice credits -# venice_engine = "google" # anonymized proxy +```bash +fabro secret rm BRAVE_SEARCH_API_KEY ``` -`provider = "brave"` (the default) talks to Brave Search directly and still needs `BRAVE_SEARCH_API_KEY`. `venice_engine = "brave"` is Brave **through Venice**, not the direct Brave backend. - -3. Verify the key is working: +2. Verify the key is working: ```bash fabro doctor ``` -The doctor output should show **Web Search** as `venice: configured and reachable`. If `VENICE_API_KEY` is missing, web search is reported as a warning — workflows still run, but the `web_search` tool is omitted from the agent's tool set. +The doctor output should show **Web Search** as `venice: configured and reachable`. If neither Venice nor Brave is configured, web search is reported as a warning. Workflows still run, but the `web_search` tool is omitted from the agent's tool set. The Fabro server reads this key from the vault only. It does not read `VENICE_API_KEY` from process env or `server.env`. ## How it works -Agents call the `web_search` tool with a query string. Fabro `POST`s to Venice `https://api.venice.ai/api/v1/augment/search` and returns numbered results with title, URL, description, and date when Venice includes one: +Agents call the `web_search` tool with a query string. Fabro `POST`s to Venice `https://api.venice.ai/api/v1/augment/search` with the Brave search engine and returns numbered results with title, URL, description, and date when Venice includes one: ``` 1. Rust Lang @@ -51,7 +46,7 @@ Agents call the `web_search` tool with a query string. Fabro `POST`s to Venice ` Venice Search is billed by Venice at $0.01 per request and is rate-limited to 20 requests per minute on the Venice side. Queries longer than 400 characters are rejected before the HTTP call. -If `VENICE_API_KEY` is not configured in the vault, the tool is not registered. Failed calls return an error; Fabro does not fall back to Brave. +If `VENICE_API_KEY` is absent but `BRAVE_SEARCH_API_KEY` exists, Fabro uses direct Brave Search. If neither key exists, the tool is not registered. After selecting Venice, a failed call returns an error; Fabro does not retry through direct Brave Search. See the [`web_search` tool reference](/agents/tools#web_search) for parameters and details. @@ -79,6 +74,6 @@ See the [`web_search` tool reference](/agents/tools#web_search) for parameters a Full `web_search` tool reference — parameters, output format, and error handling. - Direct Brave Search backend (the default when `provider` is unset). + Direct Brave Search backend, preferred whenever its key is configured. diff --git a/lib/apps/fabro-server/src/demo/mod.rs b/lib/apps/fabro-server/src/demo/mod.rs index 97c29e039..18c51e8a4 100644 --- a/lib/apps/fabro-server/src/demo/mod.rs +++ b/lib/apps/fabro-server/src/demo/mod.rs @@ -672,7 +672,7 @@ pub(crate) async fn run_diagnostics( { "name": "GitHub App", "status": "pass", "summary": "demo configured", "details": [], "remediation": null }, { "name": "Docker Sandbox", "status": "pass", "summary": "disabled", "details": [{ "text": "server.sandbox.providers.docker.enabled = false", "warn": false }], "remediation": null }, { "name": "Cloud Sandbox", "status": "warning", "summary": "not configured", "details": [], "remediation": "Set DAYTONA_API_KEY to enable cloud sandbox execution" }, - { "name": "Brave Search", "status": "warning", "summary": "not configured", "details": [], "remediation": "Set BRAVE_SEARCH_API_KEY to enable web search" } + { "name": "Web Search", "status": "warning", "summary": "optional, not configured", "details": [], "remediation": "Set BRAVE_SEARCH_API_KEY or VENICE_API_KEY to enable web search" } ] }, { diff --git a/lib/apps/fabro-server/src/diagnostics.rs b/lib/apps/fabro-server/src/diagnostics.rs index ab1a87bd0..297ee3347 100644 --- a/lib/apps/fabro-server/src/diagnostics.rs +++ b/lib/apps/fabro-server/src/diagnostics.rs @@ -12,8 +12,8 @@ use fabro_model::{Catalog, ProviderId}; use fabro_redact::redact_string; use fabro_sandbox::{DockerSandboxProvider, daytona}; use fabro_static::EnvVars; +use fabro_types::settings::ServerAuthMethod; use fabro_types::settings::server::GithubIntegrationStrategy; -use fabro_types::settings::{SearchProvider, ServerAuthMethod}; use fabro_util::check_report::{CheckDetail, CheckResult, CheckSection, CheckStatus}; use fabro_util::dev_token::validate_dev_token_format; use fabro_util::session_secret; @@ -758,17 +758,7 @@ fn check_storage_dir_path(path: &std::path::Path) -> CheckResult { } async fn check_web_search(state: &AppState) -> CheckResult { - let search = state.server_settings().server.integrations.search; - match search.provider { - SearchProvider::Brave => check_brave_search(state).await, - SearchProvider::Venice => check_venice_search(state).await, - } -} - -const WEB_SEARCH_CHECK_NAME: &str = "Web Search"; - -async fn check_brave_search(state: &AppState) -> CheckResult { - let api_key = match diagnostic_secret( + let brave_api_key = match diagnostic_secret( state, WEB_SEARCH_CHECK_NAME, EnvVars::BRAVE_SEARCH_API_KEY, @@ -778,18 +768,33 @@ async fn check_brave_search(state: &AppState) -> CheckResult { Ok(value) => value, Err(result) => return result, }; - let Some(api_key) = api_key else { - return CheckResult { - name: WEB_SEARCH_CHECK_NAME.to_string(), - status: CheckStatus::Warning, - summary: "brave: optional, not configured".to_string(), - details: Vec::new(), - remediation: Some( - "Run `fabro secret set BRAVE_SEARCH_API_KEY` to enable web search".to_string(), - ), - }; - }; + if let Some(api_key) = brave_api_key { + return check_brave_search(api_key).await; + } + let venice_api_key = + match diagnostic_secret(state, WEB_SEARCH_CHECK_NAME, EnvVars::VENICE_API_KEY).await { + Ok(value) => value, + Err(result) => return result, + }; + if let Some(api_key) = venice_api_key { + return check_venice_search(api_key).await; + } + + CheckResult { + name: WEB_SEARCH_CHECK_NAME.to_string(), + status: CheckStatus::Warning, + summary: "optional, not configured".to_string(), + details: Vec::new(), + remediation: Some( + "Run `fabro secret set BRAVE_SEARCH_API_KEY` or `fabro secret set VENICE_API_KEY` to enable web search".to_string(), + ), + } +} + +const WEB_SEARCH_CHECK_NAME: &str = "Web Search"; + +async fn check_brave_search(api_key: String) -> CheckResult { let http = match http_client_or_check(WEB_SEARCH_CHECK_NAME, CheckStatus::Warning) { Ok(http) => http, Err(result) => return result, @@ -807,24 +812,7 @@ async fn check_brave_search(state: &AppState) -> CheckResult { match_web_search_probe(probe, "brave", "BRAVE_SEARCH_API_KEY") } -async fn check_venice_search(state: &AppState) -> CheckResult { - let api_key = - match diagnostic_secret(state, WEB_SEARCH_CHECK_NAME, EnvVars::VENICE_API_KEY).await { - Ok(value) => value, - Err(result) => return result, - }; - let Some(api_key) = api_key else { - return CheckResult { - name: WEB_SEARCH_CHECK_NAME.to_string(), - status: CheckStatus::Warning, - summary: "venice: optional, not configured".to_string(), - details: Vec::new(), - remediation: Some( - "Run `fabro secret set VENICE_API_KEY` to enable web search".to_string(), - ), - }; - }; - +async fn check_venice_search(api_key: String) -> CheckResult { let http = match http_client_or_check(WEB_SEARCH_CHECK_NAME, CheckStatus::Warning) { Ok(http) => http, Err(result) => return result, @@ -833,7 +821,11 @@ async fn check_venice_search(state: &AppState) -> CheckResult { let probe = timeout(EXTERNAL_SERVICE_PROBE_TIMEOUT, async move { http.post("https://api.venice.ai/api/v1/augment/search") .bearer_auth(api_key) - .json(&serde_json::json!({ "query": "test", "limit": 1 })) + .json(&serde_json::json!({ + "query": "test", + "limit": 1, + "search_provider": "brave", + })) .send() .await .map_err(anyhow::Error::new) @@ -1256,7 +1248,7 @@ enabled = false } #[tokio::test] - async fn check_web_search_ignores_env_backed_api_key() { + async fn check_web_search_ignores_env_backed_brave_api_key() { let state = TestAppStateBuilder::new() .env_lookup(|name| { (name == EnvVars::BRAVE_SEARCH_API_KEY).then(|| "brave-from-env".to_string()) @@ -1267,29 +1259,18 @@ enabled = false assert_eq!(result.name, "Web Search"); assert_eq!(result.status, CheckStatus::Warning); - assert_eq!(result.summary, "brave: optional, not configured"); + assert_eq!(result.summary, "optional, not configured"); assert_eq!( result.remediation.as_deref(), - Some("Run `fabro secret set BRAVE_SEARCH_API_KEY` to enable web search") + Some( + "Run `fabro secret set BRAVE_SEARCH_API_KEY` or `fabro secret set VENICE_API_KEY` to enable web search" + ) ); } #[tokio::test] - async fn check_web_search_venice_ignores_env_backed_api_key() { - let settings = fabro_config::ServerSettingsBuilder::from_toml( - r#" -_version = 1 - -[server.auth] -methods = ["dev-token"] - -[server.integrations.search] -provider = "venice" -"#, - ) - .expect("venice search settings should parse"); + async fn check_web_search_ignores_env_backed_venice_api_key() { let state = TestAppStateBuilder::new() - .runtime_settings(settings, RunLayer::default()) .env_lookup(|name| { (name == EnvVars::VENICE_API_KEY).then(|| "venice-from-env".to_string()) }) @@ -1299,13 +1280,44 @@ provider = "venice" assert_eq!(result.name, "Web Search"); assert_eq!(result.status, CheckStatus::Warning); - assert_eq!(result.summary, "venice: optional, not configured"); + assert_eq!(result.summary, "optional, not configured"); assert_eq!( result.remediation.as_deref(), - Some("Run `fabro secret set VENICE_API_KEY` to enable web search") + Some( + "Run `fabro secret set BRAVE_SEARCH_API_KEY` or `fabro secret set VENICE_API_KEY` to enable web search" + ) ); } + #[tokio::test] + async fn check_web_search_prefers_brave_when_both_vault_keys_exist() { + let state = TestAppStateBuilder::new() + .vault_entries([ + (EnvVars::BRAVE_SEARCH_API_KEY, "invalid\n"), + (EnvVars::VENICE_API_KEY, "invalid\n"), + ]) + .build(); + + let result = check_web_search(&state).await; + + assert_eq!(result.name, "Web Search"); + assert_eq!(result.status, CheckStatus::Warning); + assert_eq!(result.summary, "brave: connectivity error"); + } + + #[tokio::test] + async fn check_web_search_uses_venice_when_brave_vault_key_is_absent() { + let state = TestAppStateBuilder::new() + .vault_entries([(EnvVars::VENICE_API_KEY, "invalid\n")]) + .build(); + + let result = check_web_search(&state).await; + + assert_eq!(result.name, "Web Search"); + assert_eq!(result.status, CheckStatus::Warning); + assert_eq!(result.summary, "venice: connectivity error"); + } + #[tokio::test] async fn check_crypto_requires_github_client_secret_from_vault() { let settings = fabro_config::ServerSettingsBuilder::from_toml( diff --git a/lib/components/fabro-agent/src/cli.rs b/lib/components/fabro-agent/src/cli.rs index 63c485f20..f7cfdd7f9 100644 --- a/lib/components/fabro-agent/src/cli.rs +++ b/lib/components/fabro-agent/src/cli.rs @@ -34,7 +34,7 @@ use crate::tool_permissions::{is_auto_approved, tool_category}; use crate::tools::WebFetchSummarizer; use crate::{ AgentEvent, AgentProfile, AgentProfileBuilder, LocalSandbox, Message, Sandbox, Session, - SessionOptions, SessionShutdownReason, search_settings_from_disk, + SessionOptions, SessionShutdownReason, }; #[expect( @@ -45,7 +45,6 @@ fn cli_tool_secrets() -> ToolSecrets { ToolSecrets { brave_search_api_key: std::env::var(EnvVars::BRAVE_SEARCH_API_KEY).ok(), venice_api_key: std::env::var(EnvVars::VENICE_API_KEY).ok(), - search: search_settings_from_disk(), } } diff --git a/lib/components/fabro-agent/src/config.rs b/lib/components/fabro-agent/src/config.rs index 44a9db968..cc207c3f4 100644 --- a/lib/components/fabro-agent/src/config.rs +++ b/lib/components/fabro-agent/src/config.rs @@ -6,7 +6,6 @@ use fabro_llm::types::{ReasoningEffort, Speed}; use fabro_mcp::config::McpServerSettings; use fabro_model::AgentProfileKind; use fabro_types::PermissionLevel; -use fabro_types::settings::SearchIntegrationSettings; /// Callback invoked before each tool execution. Return `Ok(())` to allow, /// `Err(message)` to deny with the given message. @@ -105,7 +104,6 @@ impl ToolHookCallback for ToolApprovalAdapter { pub struct ToolSecrets { pub brave_search_api_key: Option, pub venice_api_key: Option, - pub search: SearchIntegrationSettings, } impl std::fmt::Debug for ToolSecrets { @@ -116,7 +114,6 @@ impl std::fmt::Debug for ToolSecrets { &self.brave_search_api_key.is_some(), ) .field("venice_search_configured", &self.venice_api_key.is_some()) - .field("search_provider", &self.search.provider.as_str()) .finish() } } @@ -355,15 +352,13 @@ mod tests { fn tool_secrets_debug_redacts_values() { let secrets = ToolSecrets { brave_search_api_key: Some("brave-secret-value".to_string()), - venice_api_key: Some("venice-secret-value".to_string()), - ..ToolSecrets::default() + venice_api_key: Some("venice-secret-value".to_string()), }; let debug = format!("{secrets:?}"); assert!(debug.contains("brave_search_configured: true")); assert!(debug.contains("venice_search_configured: true")); - assert!(debug.contains("search_provider: \"brave\"")); assert!(!debug.contains("brave-secret-value")); assert!(!debug.contains("venice-secret-value")); } diff --git a/lib/components/fabro-agent/src/lib.rs b/lib/components/fabro-agent/src/lib.rs index f7600a7f6..9f982f47a 100644 --- a/lib/components/fabro-agent/src/lib.rs +++ b/lib/components/fabro-agent/src/lib.rs @@ -86,7 +86,6 @@ pub use types::{ AgentEvent, McpToolSummary, MemoryFileSummary, Message, SessionEvent, SessionState, SkillActivationSource, SkillSummary, }; -pub use web_search::search_settings_from_disk; #[cfg(test)] #[allow( diff --git a/lib/components/fabro-agent/src/profiles/claude5_tools.rs b/lib/components/fabro-agent/src/profiles/claude5_tools.rs index 140e8502a..6f53622b0 100644 --- a/lib/components/fabro-agent/src/profiles/claude5_tools.rs +++ b/lib/components/fabro-agent/src/profiles/claude5_tools.rs @@ -116,28 +116,19 @@ pub(crate) fn make_bash_tool(options: &NativeToolOptions) -> RegisteredTool { #[must_use] pub(crate) fn make_web_search_tool(backend: SearchBackend) -> RegisteredTool { - let includes_engine = backend.includes_engine_param(); let mut tool = web_search::make_web_search_tool(backend); - let mut properties = serde_json::json!({ - "query": { - "type": "string", - "description": "The web search query." - } - }); - if includes_engine { - properties["engine"] = serde_json::json!({ - "type": "string", - "enum": ["brave", "google"], - "description": "Venice search engine. brave is ZDR (default); google is an anonymized proxy." - }); - } tool.definition = definition( NativeTool::WebSearch, "Search the web when current external information is needed. Returns result titles, URLs, \ and descriptions; use WebFetch to inspect a specific URL.", serde_json::json!({ "type": "object", - "properties": properties, + "properties": { + "query": { + "type": "string", + "description": "The web search query." + } + }, "required": ["query"], "additionalProperties": false }), @@ -468,7 +459,6 @@ mod tests { use std::collections::BTreeSet; use std::sync::Mutex; - use fabro_types::settings::VeniceSearchEngine; use serde_json::json; use tokio_util::sync::CancellationToken; @@ -553,11 +543,8 @@ mod tests { &["query"], ); assert_schema( - &make_web_search_tool(SearchBackend::venice( - "key".to_string(), - VeniceSearchEngine::Brave, - )), - &["engine", "query"], + &make_web_search_tool(SearchBackend::venice("key".to_string())), + &["query"], &["query"], ); diff --git a/lib/components/fabro-agent/src/profiles/prompts/openai.md.j2 b/lib/components/fabro-agent/src/profiles/prompts/openai.md.j2 index db180ee8c..2bf337ea9 100644 --- a/lib/components/fabro-agent/src/profiles/prompts/openai.md.j2 +++ b/lib/components/fabro-agent/src/profiles/prompts/openai.md.j2 @@ -70,7 +70,7 @@ Search file contents with regex. Use glob_filter to narrow results. Find files by name pattern. {% if inputs.has_web_search %}## web_search -Search the web using Brave Search. Returns titles, URLs, and descriptions. +Search the web. Returns titles, URLs, and descriptions. {% endif %}## web_fetch Fetch content from a URL and optionally summarize it. Pass a prompt to extract specific information instead of returning the full page. URLs must start with http:// or https://. diff --git a/lib/components/fabro-agent/src/profiles/snapshots/fabro_agent__profiles__tests__openai_apply_patch_and_web_search_prompt_snapshot.snap b/lib/components/fabro-agent/src/profiles/snapshots/fabro_agent__profiles__tests__openai_apply_patch_and_web_search_prompt_snapshot.snap index 14b7c7cd0..bd48ea598 100644 --- a/lib/components/fabro-agent/src/profiles/snapshots/fabro_agent__profiles__tests__openai_apply_patch_and_web_search_prompt_snapshot.snap +++ b/lib/components/fabro-agent/src/profiles/snapshots/fabro_agent__profiles__tests__openai_apply_patch_and_web_search_prompt_snapshot.snap @@ -78,7 +78,7 @@ Search file contents with regex. Use glob_filter to narrow results. Find files by name pattern. ## web_search -Search the web using Brave Search. Returns titles, URLs, and descriptions. +Search the web. Returns titles, URLs, and descriptions. ## web_fetch Fetch content from a URL and optionally summarize it. Pass a prompt to extract specific information instead of returning the full page. URLs must start with http:// or https://. diff --git a/lib/components/fabro-agent/src/profiles/snapshots/fabro_agent__profiles__tests__openai_edit_file_and_web_search_prompt_snapshot.snap b/lib/components/fabro-agent/src/profiles/snapshots/fabro_agent__profiles__tests__openai_edit_file_and_web_search_prompt_snapshot.snap index 43e52f68b..0cd769362 100644 --- a/lib/components/fabro-agent/src/profiles/snapshots/fabro_agent__profiles__tests__openai_edit_file_and_web_search_prompt_snapshot.snap +++ b/lib/components/fabro-agent/src/profiles/snapshots/fabro_agent__profiles__tests__openai_edit_file_and_web_search_prompt_snapshot.snap @@ -68,7 +68,7 @@ Search file contents with regex. Use glob_filter to narrow results. Find files by name pattern. ## web_search -Search the web using Brave Search. Returns titles, URLs, and descriptions. +Search the web. Returns titles, URLs, and descriptions. ## web_fetch Fetch content from a URL and optionally summarize it. Pass a prompt to extract specific information instead of returning the full page. URLs must start with http:// or https://. diff --git a/lib/components/fabro-agent/src/tools.rs b/lib/components/fabro-agent/src/tools.rs index 9cbeb571b..6d9f673e1 100644 --- a/lib/components/fabro-agent/src/tools.rs +++ b/lib/components/fabro-agent/src/tools.rs @@ -50,14 +50,14 @@ fn html_to_markdown(text: &str) -> String { converter.convert(text).unwrap_or_else(|_| text.to_string()) } -/// Name of the Brave-backed web search tool. Profiles look this up in their own -/// registry to decide whether to advertise web search in the system prompt, so -/// availability and prompt guidance cannot drift apart. +/// Name of the credential-backed web search tool. Profiles look this up in +/// their own registry to decide whether to advertise web search in the system +/// prompt, so availability and prompt guidance cannot drift apart. pub const WEB_SEARCH_TOOL_NAME: &str = "web_search"; /// Registers the core tools shared by all provider profiles: `read_file`, /// `write_file`, `shell`, `grep`, `glob`, and `web_fetch`. `web_search` is -/// included when a Brave Search API key is configured. +/// included when a Brave or Venice Search API key is configured. /// /// The shell tool captures its default and max timeouts from `options`. pub fn register_core_tools( @@ -83,7 +83,7 @@ pub(crate) fn register_discovery_and_web_tools( registry.register(make_web_fetch_tool(summarizer)); } -/// Register `web_search` when the selected search provider is configured. +/// Register `web_search` when a search provider credential is configured. /// /// Separate from [`register_discovery_and_web_tools`] for profiles that offer /// search without fabro's discovery tools. diff --git a/lib/components/fabro-agent/src/web_search.rs b/lib/components/fabro-agent/src/web_search.rs index 11fe4c7bf..08c23863a 100644 --- a/lib/components/fabro-agent/src/web_search.rs +++ b/lib/components/fabro-agent/src/web_search.rs @@ -1,14 +1,13 @@ //! Built-in `web_search` backends. //! -//! Agents always call the same tool. The HTTP backend is selected by -//! `[server.integrations.search].provider`. +//! Agents always call the same tool. Brave is preferred when its credential +//! is present; otherwise Venice is used when its credential is present. use std::fmt::Write; use std::sync::OnceLock; use std::time::Duration; use fabro_llm::types::ToolDefinition; -use fabro_types::settings::{SearchIntegrationSettings, SearchProvider, VeniceSearchEngine}; use crate::config::ToolSecrets; use crate::tool_registry::{RegisteredTool, ToolSource}; @@ -29,7 +28,6 @@ pub(crate) enum SearchBackend { }, Venice { api_key: String, - engine: VeniceSearchEngine, search_url: String, }, } @@ -37,15 +35,13 @@ pub(crate) enum SearchBackend { impl SearchBackend { #[must_use] pub(crate) fn from_secrets(secrets: &ToolSecrets) -> Option { - match secrets.search.provider { - SearchProvider::Brave => secrets - .brave_search_api_key - .as_ref() - .map(|api_key| Self::brave(api_key.clone())), - SearchProvider::Venice => secrets - .venice_api_key - .as_ref() - .map(|api_key| Self::venice(api_key.clone(), secrets.search.venice_engine)), + match ( + secrets.brave_search_api_key.as_ref(), + secrets.venice_api_key.as_ref(), + ) { + (Some(api_key), _) => Some(Self::brave(api_key.clone())), + (None, Some(api_key)) => Some(Self::venice(api_key.clone())), + (None, None) => None, } } @@ -58,25 +54,14 @@ impl SearchBackend { } #[must_use] - pub(crate) fn venice(api_key: String, engine: VeniceSearchEngine) -> Self { + pub(crate) fn venice(api_key: String) -> Self { Self::Venice { api_key, - engine, search_url: VENICE_SEARCH_URL.to_string(), } } - #[must_use] - pub(crate) fn includes_engine_param(&self) -> bool { - matches!(self, Self::Venice { .. }) - } - - async fn search( - &self, - query: &str, - max_results: u64, - engine_override: Option, - ) -> Result { + async fn search(&self, query: &str, max_results: u64) -> Result { match self { Self::Brave { api_key, @@ -84,7 +69,6 @@ impl SearchBackend { } => search_brave(api_key, search_url, query, max_results).await, Self::Venice { api_key, - engine, search_url, } => { if query.chars().count() > VENICE_QUERY_MAX_CHARS { @@ -92,8 +76,7 @@ impl SearchBackend { "query exceeds Venice Search maximum of {VENICE_QUERY_MAX_CHARS} characters" )); } - let engine = engine_override.unwrap_or(*engine); - search_venice(api_key, search_url, query, max_results, engine).await + search_venice(api_key, search_url, query, max_results).await } } } @@ -150,7 +133,6 @@ async fn search_venice( search_url: &str, query: &str, max_results: u64, - engine: VeniceSearchEngine, ) -> Result { let limit = max_results.clamp(1, MAX_RESULTS); let resp = search_http_client() @@ -161,7 +143,7 @@ async fn search_venice( .json(&serde_json::json!({ "query": query, "limit": limit, - "search_provider": engine.as_str(), + "search_provider": "brave", })) .send() .await @@ -277,16 +259,6 @@ fn optional_json_str(value: &serde_json::Value, key: &str) -> Option { .map(str::to_owned) } -fn parse_engine_arg(args: &serde_json::Value) -> Result, String> { - let Some(value) = args.get("engine").and_then(serde_json::Value::as_str) else { - return Ok(None); - }; - value - .parse() - .map(Some) - .map_err(|_| format!("Invalid engine `{value}`; expected `brave` or `google`")) -} - fn max_results_arg(args: &serde_json::Value) -> u64 { args.get("max_results") .and_then(serde_json::Value::as_u64) @@ -296,25 +268,16 @@ fn max_results_arg(args: &serde_json::Value) -> u64 { #[must_use] pub(crate) fn make_web_search_tool(backend: SearchBackend) -> RegisteredTool { - let mut properties = serde_json::json!({ - "query": {"type": "string", "description": "Search query"}, - "max_results": {"type": "integer", "description": "Maximum number of results (default 5, max 20)"} - }); - if backend.includes_engine_param() { - properties["engine"] = serde_json::json!({ - "type": "string", - "enum": ["brave", "google"], - "description": "Venice search engine. `brave` is ZDR (default); `google` is an anonymized proxy." - }); - } - RegisteredTool { definition: ToolDefinition { name: WEB_SEARCH_TOOL_NAME.into(), description: "Search the web when current external information is needed. Returns result titles, URLs, and descriptions; use web_fetch for a specific URL.".into(), parameters: serde_json::json!({ "type": "object", - "properties": properties, + "properties": { + "query": {"type": "string", "description": "Search query"}, + "max_results": {"type": "integer", "description": "Maximum number of results (default 5, max 20)"} + }, "required": ["query"] }), }, @@ -322,10 +285,7 @@ pub(crate) fn make_web_search_tool(backend: SearchBackend) -> RegisteredTool { let backend = backend.clone(); Box::pin(async move { let query = required_str(&args, "query")?; - let engine = parse_engine_arg(&args)?; - backend - .search(query, max_results_arg(&args), engine) - .await + backend.search(query, max_results_arg(&args)).await }) }), source: ToolSource::Native, @@ -338,19 +298,10 @@ pub(crate) fn make_web_search_tool_with_api_key(api_key: String) -> RegisteredTo make_web_search_tool(SearchBackend::brave(api_key)) } -#[must_use] -pub fn search_settings_from_disk() -> SearchIntegrationSettings { - fabro_config::ServerSettingsBuilder::load_default() - .ok() - .map(|settings| settings.server.integrations.search) - .unwrap_or_default() -} - #[cfg(test)] mod tests { use std::sync::Arc; - use fabro_types::settings::{SearchIntegrationSettings, SearchProvider, VeniceSearchEngine}; use httpmock::Method::{GET, POST}; use httpmock::MockServer; use tokio_util::sync::CancellationToken; @@ -361,14 +312,10 @@ mod tests { use crate::test_support::MockSandbox; use crate::tool_registry::ToolContext; - fn secrets(brave: Option<&str>, venice: Option<&str>, provider: SearchProvider) -> ToolSecrets { + fn secrets(brave: Option<&str>, venice: Option<&str>) -> ToolSecrets { ToolSecrets { brave_search_api_key: brave.map(str::to_string), venice_api_key: venice.map(str::to_string), - search: SearchIntegrationSettings { - provider, - venice_engine: VeniceSearchEngine::Brave, - }, } } @@ -387,39 +334,26 @@ mod tests { } #[test] - fn from_secrets_registers_brave_by_default_when_brave_key_is_present() { - let backend = SearchBackend::from_secrets(&secrets( - Some("brave-key"), - Some("venice-key"), - SearchProvider::Brave, - )); + fn from_secrets_prefers_brave_when_both_keys_are_present() { + let backend = SearchBackend::from_secrets(&secrets(Some("brave-key"), Some("venice-key"))); assert!(matches!(backend, Some(SearchBackend::Brave { .. }))); } #[test] - fn from_secrets_omits_brave_when_key_missing() { - assert!( - SearchBackend::from_secrets(&secrets(None, Some("venice-key"), SearchProvider::Brave)) - .is_none() - ); + fn from_secrets_registers_brave_when_only_brave_key_is_present() { + let backend = SearchBackend::from_secrets(&secrets(Some("brave-key"), None)); + assert!(matches!(backend, Some(SearchBackend::Brave { .. }))); } #[test] - fn from_secrets_registers_venice_when_selected_and_key_present() { - let backend = SearchBackend::from_secrets(&secrets( - Some("brave-key"), - Some("venice-key"), - SearchProvider::Venice, - )); + fn from_secrets_registers_venice_when_only_venice_key_is_present() { + let backend = SearchBackend::from_secrets(&secrets(None, Some("venice-key"))); assert!(matches!(backend, Some(SearchBackend::Venice { .. }))); } #[test] - fn from_secrets_omits_venice_when_key_missing() { - assert!( - SearchBackend::from_secrets(&secrets(Some("brave-key"), None, SearchProvider::Venice)) - .is_none() - ); + fn from_secrets_omits_search_when_both_keys_are_missing() { + assert!(SearchBackend::from_secrets(&secrets(None, None)).is_none()); } #[test] @@ -466,26 +400,14 @@ mod tests { } #[test] - fn venice_schema_includes_engine_and_brave_schema_does_not() { + fn brave_and_venice_use_the_same_tool_schema() { let brave = make_web_search_tool(SearchBackend::brave("key".into())); - let venice = make_web_search_tool(SearchBackend::venice( - "key".into(), - VeniceSearchEngine::Brave, - )); - assert!( - brave.definition.parameters["properties"] - .get("engine") - .is_none() - ); - assert!( - venice.definition.parameters["properties"] - .get("engine") - .is_some() - ); + let venice = make_web_search_tool(SearchBackend::venice("key".into())); + assert_eq!(brave.definition.parameters, venice.definition.parameters); } #[tokio::test] - async fn venice_search_posts_augment_search_and_maps_engine() { + async fn venice_search_posts_augment_search_with_brave_engine() { let server = MockServer::start(); let mock = server.mock(|when, then| { when.method(POST) @@ -494,7 +416,7 @@ mod tests { .json_body(serde_json::json!({ "query": "fabro", "limit": 3, - "search_provider": "google" + "search_provider": "brave" })); then.status(200).json_body(serde_json::json!({ "query": "fabro", @@ -507,7 +429,7 @@ mod tests { })); }); - let mut backend = SearchBackend::venice("venice-key".into(), VeniceSearchEngine::Brave); + let mut backend = SearchBackend::venice("venice-key".into()); if let SearchBackend::Venice { search_url, .. } = &mut backend { *search_url = format!("{}/api/v1/augment/search", server.base_url()); } @@ -516,8 +438,7 @@ mod tests { &tool, serde_json::json!({ "query": "fabro", - "max_results": 3, - "engine": "google" + "max_results": 3 }), ) .await @@ -539,7 +460,7 @@ mod tests { .json_body(serde_json::json!({"results": []})); }); - let mut backend = SearchBackend::venice("venice-key".into(), VeniceSearchEngine::Brave); + let mut backend = SearchBackend::venice("venice-key".into()); if let SearchBackend::Venice { search_url, .. } = &mut backend { *search_url = format!("{}/api/v1/augment/search", server.base_url()); } @@ -567,7 +488,7 @@ mod tests { then.status(status).body("error"); }), }; - let mut backend = SearchBackend::venice("venice-key".into(), VeniceSearchEngine::Brave); + let mut backend = SearchBackend::venice("venice-key".into()); if let SearchBackend::Venice { search_url, .. } = &mut backend { *search_url = format!("{}/api/v1/augment/search", server.base_url()); } diff --git a/lib/components/fabro-workflow/src/pipeline/initialize.rs b/lib/components/fabro-workflow/src/pipeline/initialize.rs index 275e20527..c692b9627 100644 --- a/lib/components/fabro-workflow/src/pipeline/initialize.rs +++ b/lib/components/fabro-workflow/src/pipeline/initialize.rs @@ -239,7 +239,6 @@ async fn tool_secrets_from_configured_sources(vault: &Arc>) - ToolSecrets { brave_search_api_key: vault.get(EnvVars::BRAVE_SEARCH_API_KEY).map(str::to_string), venice_api_key: vault.get(EnvVars::VENICE_API_KEY).map(str::to_string), - search: fabro_agent::search_settings_from_disk(), } } diff --git a/lib/foundation/fabro-api/build.rs b/lib/foundation/fabro-api/build.rs index 577746fa4..0fb3cccd0 100644 --- a/lib/foundation/fabro-api/build.rs +++ b/lib/foundation/fabro-api/build.rs @@ -324,21 +324,6 @@ fn main() { "fabro_types::settings::server::SlackIntegrationSettings", &[], ), - ( - "SearchIntegrationSettings", - "fabro_types::settings::server::SearchIntegrationSettings", - &[], - ), - ( - "SearchProvider", - "fabro_types::settings::server::SearchProvider", - &[], - ), - ( - "VeniceSearchEngine", - "fabro_types::settings::server::VeniceSearchEngine", - &[], - ), ( "IntegrationWebhooksSettings", "fabro_types::settings::server::IntegrationWebhooksSettings", diff --git a/lib/foundation/fabro-api/src/lib.rs b/lib/foundation/fabro-api/src/lib.rs index 97eb3c35b..c53e682c7 100644 --- a/lib/foundation/fabro-api/src/lib.rs +++ b/lib/foundation/fabro-api/src/lib.rs @@ -28,12 +28,11 @@ pub mod types { pub use fabro_types::settings::run::{McpHttpProtocol, RunModelControls, RunModelSettings}; pub use fabro_types::settings::server::{ GithubIntegrationSettings, GithubIntegrationStrategy, IntegrationWebhooksSettings, - LogDestination, ObjectStoreSettings, SearchIntegrationSettings, SearchProvider, - ServerApiSettings, ServerArtifactsSettings, ServerAuthGithubSettings, ServerAuthMethod, - ServerAuthSettings, ServerIntegrationsSettings, ServerListenSettings, - ServerLoggingSettings, ServerSandboxProviderSettings, ServerSandboxProvidersSettings, - ServerSandboxSettings, ServerSchedulerSettings, ServerSlateDbSettings, - ServerStorageSettings, ServerWebSettings, SlackIntegrationSettings, VeniceSearchEngine, + LogDestination, ObjectStoreSettings, ServerApiSettings, ServerArtifactsSettings, + ServerAuthGithubSettings, ServerAuthMethod, ServerAuthSettings, ServerIntegrationsSettings, + ServerListenSettings, ServerLoggingSettings, ServerSandboxProviderSettings, + ServerSandboxProvidersSettings, ServerSandboxSettings, ServerSchedulerSettings, + ServerSlateDbSettings, ServerStorageSettings, ServerWebSettings, SlackIntegrationSettings, WebhookStrategy, }; pub use fabro_types::settings::{McpTransport, ServerNamespace}; diff --git a/lib/foundation/fabro-config/src/layers/combine.rs b/lib/foundation/fabro-config/src/layers/combine.rs index f3ef27232..aec393777 100644 --- a/lib/foundation/fabro-config/src/layers/combine.rs +++ b/lib/foundation/fabro-config/src/layers/combine.rs @@ -7,8 +7,8 @@ use fabro_types::settings::run::{ ApprovalMode, EnvironmentNetworkMode, EnvironmentProvider, MergeStrategy, RunMode, }; use fabro_types::settings::server::{ - GithubIntegrationStrategy, LogDestination, ObjectStoreProvider, SearchProvider, - ServerAuthMethod, VeniceSearchEngine, WebhookStrategy, + GithubIntegrationStrategy, LogDestination, ObjectStoreProvider, ServerAuthMethod, + WebhookStrategy, }; use fabro_types::settings::{Duration, InterpString, Size}; @@ -82,9 +82,7 @@ impl_combine_or_option!( GithubIntegrationStrategy, LogDestination, ObjectStoreProvider, - SearchProvider, ServerAuthMethod, - VeniceSearchEngine, WebhookStrategy, LogFilter, AgentProfileKind, diff --git a/lib/foundation/fabro-config/src/layers/mod.rs b/lib/foundation/fabro-config/src/layers/mod.rs index 253345227..c3fa1632c 100644 --- a/lib/foundation/fabro-config/src/layers/mod.rs +++ b/lib/foundation/fabro-config/src/layers/mod.rs @@ -39,8 +39,8 @@ pub use run::{ }; pub use server::{ GithubIntegrationLayer, IntegrationWebhooksLayer, ObjectStoreLocalLayer, ObjectStoreS3Layer, - SearchIntegrationLayer, ServerApiLayer, ServerArtifactsLayer, ServerAuthGithubLayer, - ServerAuthLayer, ServerIntegrationsLayer, ServerLayer, ServerListenLayer, ServerLoggingLayer, + ServerApiLayer, ServerArtifactsLayer, ServerAuthGithubLayer, ServerAuthLayer, + ServerIntegrationsLayer, ServerLayer, ServerListenLayer, ServerLoggingLayer, ServerSandboxLayer, ServerSandboxProviderLayer, ServerSandboxProvidersLayer, ServerSchedulerLayer, ServerSlateDbLayer, ServerStorageLayer, ServerWebLayer, SlackIntegrationLayer, diff --git a/lib/foundation/fabro-config/src/layers/server.rs b/lib/foundation/fabro-config/src/layers/server.rs index 867a8da22..351d4da68 100644 --- a/lib/foundation/fabro-config/src/layers/server.rs +++ b/lib/foundation/fabro-config/src/layers/server.rs @@ -1,8 +1,8 @@ //! Sparse `[server]` settings layer definitions. use fabro_types::settings::server::{ - GithubIntegrationStrategy, LogDestination, ObjectStoreProvider, SearchProvider, - ServerAuthMethod, VeniceSearchEngine, WebhookStrategy, + GithubIntegrationStrategy, LogDestination, ObjectStoreProvider, ServerAuthMethod, + WebhookStrategy, }; use fabro_types::settings::{Duration, InterpString}; use serde::{Deserialize, Serialize}; @@ -206,8 +206,6 @@ pub struct ServerIntegrationsLayer { pub github: Option, #[serde(default, skip_serializing_if = "Option::is_none")] pub slack: Option, - #[serde(default, skip_serializing_if = "Option::is_none")] - pub search: Option, } /// `[server.integrations.github]` — GitHub App, credentials, and inbound @@ -239,16 +237,6 @@ pub struct SlackIntegrationLayer { pub default_channel: Option, } -/// `[server.integrations.search]` — backend for the built-in `web_search` tool. -#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize, fabro_macros::Combine)] -#[serde(deny_unknown_fields)] -pub struct SearchIntegrationLayer { - #[serde(default, skip_serializing_if = "Option::is_none")] - pub provider: Option, - #[serde(default, skip_serializing_if = "Option::is_none")] - pub venice_engine: Option, -} - #[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize, fabro_macros::Combine)] #[serde(deny_unknown_fields)] pub struct IntegrationWebhooksLayer { diff --git a/lib/foundation/fabro-config/src/lib.rs b/lib/foundation/fabro-config/src/lib.rs index 43bc1c5ab..f6097b9be 100644 --- a/lib/foundation/fabro-config/src/lib.rs +++ b/lib/foundation/fabro-config/src/lib.rs @@ -53,12 +53,11 @@ pub use layers::{ RunArtifactsLayer, RunCheckpointLayer, RunCloneLayer, RunEnvironmentLayer, RunExecutionLayer, RunGitLayer, RunGoalLayer, RunIntegrationsGithubLayer, RunIntegrationsLayer, RunLayer, RunMetaBranchLayer, RunModelControlsLayer, RunModelLayer, RunPrepareLayer, RunPullRequestLayer, - RunRunBranchLayer, RunScmLayer, ScmGitHubLayer, SearchIntegrationLayer, ServerApiLayer, - ServerArtifactsLayer, ServerAuthGithubLayer, ServerAuthLayer, ServerIntegrationsLayer, - ServerLayer, ServerListenLayer, ServerLoggingLayer, ServerSandboxLayer, - ServerSandboxProviderLayer, ServerSandboxProvidersLayer, ServerSchedulerLayer, - ServerSlateDbLayer, ServerStorageLayer, ServerWebLayer, SettingsLayer, SlackIntegrationLayer, - StickyMap, StringOrSplice, WorkflowLayer, + RunRunBranchLayer, RunScmLayer, ScmGitHubLayer, ServerApiLayer, ServerArtifactsLayer, + ServerAuthGithubLayer, ServerAuthLayer, ServerIntegrationsLayer, ServerLayer, + ServerListenLayer, ServerLoggingLayer, ServerSandboxLayer, ServerSandboxProviderLayer, + ServerSandboxProvidersLayer, ServerSchedulerLayer, ServerSlateDbLayer, ServerStorageLayer, + ServerWebLayer, SettingsLayer, SlackIntegrationLayer, StickyMap, StringOrSplice, WorkflowLayer, }; pub use logging::{resolve_log_destination, resolve_log_destination_with_env}; pub use parse::ParseError; diff --git a/lib/foundation/fabro-config/src/resolve/server.rs b/lib/foundation/fabro-config/src/resolve/server.rs index 08a5900db..39b41eaf9 100644 --- a/lib/foundation/fabro-config/src/resolve/server.rs +++ b/lib/foundation/fabro-config/src/resolve/server.rs @@ -2,12 +2,12 @@ use std::path::Path; use fabro_types::settings::server::{ GithubIntegrationSettings, GithubIntegrationStrategy, IntegrationWebhooksSettings, - ObjectStoreProvider, ObjectStoreSettings, SearchIntegrationSettings, ServerApiSettings, - ServerArtifactsSettings, ServerAuthGithubSettings, ServerAuthMethod, ServerAuthSettings, - ServerIntegrationsSettings, ServerListenSettings, ServerLoggingSettings, ServerNamespace, - ServerSandboxProviderSettings, ServerSandboxProvidersSettings, ServerSandboxSettings, - ServerSchedulerSettings, ServerSlateDbSettings, ServerStorageSettings, ServerWebSettings, - SlackIntegrationSettings, WebhookStrategy, + ObjectStoreProvider, ObjectStoreSettings, ServerApiSettings, ServerArtifactsSettings, + ServerAuthGithubSettings, ServerAuthMethod, ServerAuthSettings, ServerIntegrationsSettings, + ServerListenSettings, ServerLoggingSettings, ServerNamespace, ServerSandboxProviderSettings, + ServerSandboxProvidersSettings, ServerSandboxSettings, ServerSchedulerSettings, + ServerSlateDbSettings, ServerStorageSettings, ServerWebSettings, SlackIntegrationSettings, + WebhookStrategy, }; use fabro_util::Home; @@ -375,13 +375,6 @@ fn resolve_integrations(layer: Option<&ServerIntegrationsLayer>) -> ServerIntegr } }, ), - search: layer - .and_then(|integrations| integrations.search.as_ref()) - .map(|search| SearchIntegrationSettings { - provider: search.provider.unwrap_or_default(), - venice_engine: search.venice_engine.unwrap_or_default(), - }) - .unwrap_or_default(), } } diff --git a/lib/foundation/fabro-config/src/tests/resolve_server.rs b/lib/foundation/fabro-config/src/tests/resolve_server.rs index 0e058dd12..e4eecb353 100644 --- a/lib/foundation/fabro-config/src/tests/resolve_server.rs +++ b/lib/foundation/fabro-config/src/tests/resolve_server.rs @@ -4,8 +4,8 @@ )] use fabro_types::settings::server::{ - GithubIntegrationStrategy, LogDestination, ObjectStoreSettings, SearchProvider, - ServerAuthMethod, ServerListenSettings, ServerNamespace, VeniceSearchEngine, + GithubIntegrationStrategy, LogDestination, ObjectStoreSettings, ServerAuthMethod, + ServerListenSettings, ServerNamespace, }; use fabro_util::Home; use temp_env::with_var; @@ -127,10 +127,6 @@ fn resolved_server_integrations_disable_slack_when_config_is_absent() { "enabled": false, "default_channel": null, }, - "search": { - "provider": "brave", - "venice_engine": "brave", - }, }) ); } @@ -177,43 +173,6 @@ default_channel = "#releases" ); } -#[test] -fn resolve_search_defaults_to_brave_when_config_is_absent() { - let settings = resolve_server(&parse( - r" -_version = 1 -", - )); - - assert_eq!(settings.integrations.search.provider, SearchProvider::Brave); - assert_eq!( - settings.integrations.search.venice_engine, - VeniceSearchEngine::Brave - ); -} - -#[test] -fn resolve_search_provider_venice_and_google_engine() { - let settings = resolve_server(&parse( - r#" -_version = 1 - -[server.integrations.search] -provider = "venice" -venice_engine = "google" -"#, - )); - - assert_eq!( - settings.integrations.search.provider, - SearchProvider::Venice - ); - assert_eq!( - settings.integrations.search.venice_engine, - VeniceSearchEngine::Google - ); -} - #[test] fn resolve_slack_default_channel_keeps_template_token_literal() { // `server.integrations.slack.default_channel` is a plain literal now: a diff --git a/lib/foundation/fabro-types/src/settings/mod.rs b/lib/foundation/fabro-types/src/settings/mod.rs index d7694c6fa..5bf91e4d9 100644 --- a/lib/foundation/fabro-types/src/settings/mod.rs +++ b/lib/foundation/fabro-types/src/settings/mod.rs @@ -46,11 +46,10 @@ pub use run::{ }; pub use server::{ GithubIntegrationSettings, IntegrationWebhooksSettings, LogDestination, ObjectStoreSettings, - SearchIntegrationSettings, SearchProvider, ServerApiSettings, ServerArtifactsSettings, - ServerAuthGithubSettings, ServerAuthMethod, ServerAuthSettings, ServerIntegrationsSettings, - ServerListenSettings, ServerLoggingSettings, ServerNamespace, ServerSchedulerSettings, - ServerSlateDbSettings, ServerStorageSettings, ServerWebSettings, SlackIntegrationSettings, - VeniceSearchEngine, + ServerApiSettings, ServerArtifactsSettings, ServerAuthGithubSettings, ServerAuthMethod, + ServerAuthSettings, ServerIntegrationsSettings, ServerListenSettings, ServerLoggingSettings, + ServerNamespace, ServerSchedulerSettings, ServerSlateDbSettings, ServerStorageSettings, + ServerWebSettings, SlackIntegrationSettings, }; pub use size::{ParseSizeError, Size}; pub use workflow::WorkflowNamespace; diff --git a/lib/foundation/fabro-types/src/settings/server.rs b/lib/foundation/fabro-types/src/settings/server.rs index 4af44d9f1..ef8da68b8 100644 --- a/lib/foundation/fabro-types/src/settings/server.rs +++ b/lib/foundation/fabro-types/src/settings/server.rs @@ -242,7 +242,6 @@ pub struct ServerLoggingSettings { pub struct ServerIntegrationsSettings { pub github: GithubIntegrationSettings, pub slack: SlackIntegrationSettings, - pub search: SearchIntegrationSettings, } #[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] @@ -270,75 +269,6 @@ impl Default for SlackIntegrationSettings { } } -/// Backend used by the built-in `web_search` tool. -#[derive( - Debug, - Clone, - Copy, - Default, - PartialEq, - Eq, - Serialize, - Deserialize, - strum::EnumString, - strum::IntoStaticStr, -)] -#[serde(rename_all = "lowercase")] -#[strum(serialize_all = "lowercase")] -pub enum SearchProvider { - #[default] - Brave, - Venice, -} - -impl SearchProvider { - #[must_use] - pub const fn as_str(self) -> &'static str { - match self { - Self::Brave => "brave", - Self::Venice => "venice", - } - } -} - -/// Venice `/augment/search` engine. `brave` is Firecrawl ZDR; `google` is an -/// anonymized proxy. Direct Brave Search remains [`SearchProvider::Brave`]. -#[derive( - Debug, - Clone, - Copy, - Default, - PartialEq, - Eq, - Serialize, - Deserialize, - strum::EnumString, - strum::IntoStaticStr, -)] -#[serde(rename_all = "lowercase")] -#[strum(serialize_all = "lowercase")] -pub enum VeniceSearchEngine { - #[default] - Brave, - Google, -} - -impl VeniceSearchEngine { - #[must_use] - pub const fn as_str(self) -> &'static str { - match self { - Self::Brave => "brave", - Self::Google => "google", - } - } -} - -#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize, Deserialize)] -pub struct SearchIntegrationSettings { - pub provider: SearchProvider, - pub venice_engine: VeniceSearchEngine, -} - #[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] pub struct IntegrationWebhooksSettings { pub strategy: Option, diff --git a/lib/packages/fabro-api-client/src/.openapi-generator/FILES b/lib/packages/fabro-api-client/src/.openapi-generator/FILES index 42ca0bf8f..4f8d5f871 100644 --- a/lib/packages/fabro-api-client/src/.openapi-generator/FILES +++ b/lib/packages/fabro-api-client/src/.openapi-generator/FILES @@ -441,8 +441,6 @@ models/saved-query.ts models/secret-list-response.ts models/secret-metadata.ts models/secret-type.ts -models/search-integration-settings.ts -models/search-provider.ts models/server-api-settings.ts models/server-artifacts-settings.ts models/server-auth-github-settings.ts @@ -534,7 +532,6 @@ models/user-response.ts models/validate-response.ts models/variable-list-response.ts models/variable.ts -models/venice-search-engine.ts models/vnc-preview-response.ts models/webhook-strategy.ts models/workflow-detail-response.ts diff --git a/lib/packages/fabro-api-client/src/models/index.ts b/lib/packages/fabro-api-client/src/models/index.ts index 12d3fa017..04df237be 100644 --- a/lib/packages/fabro-api-client/src/models/index.ts +++ b/lib/packages/fabro-api-client/src/models/index.ts @@ -410,8 +410,6 @@ export * from './saved-query'; export * from './secret-list-response'; export * from './secret-metadata'; export * from './secret-type'; -export * from './search-integration-settings'; -export * from './search-provider'; export * from './server-api-settings'; export * from './server-artifacts-settings'; export * from './server-auth-github-settings'; @@ -503,7 +501,6 @@ export * from './user-response'; export * from './validate-response'; export * from './variable'; export * from './variable-list-response'; -export * from './venice-search-engine'; export * from './vnc-preview-response'; export * from './webhook-strategy'; export * from './workflow-detail-response'; diff --git a/lib/packages/fabro-api-client/src/models/search-integration-settings.ts b/lib/packages/fabro-api-client/src/models/search-integration-settings.ts deleted file mode 100644 index 65a6902fe..000000000 --- a/lib/packages/fabro-api-client/src/models/search-integration-settings.ts +++ /dev/null @@ -1,26 +0,0 @@ -/* tslint:disable */ -/* eslint-disable */ -/** - * Fabro Run API - * HTTP API for managing Fabro workflow run executions. - * - * The version of the OpenAPI document: 0.2.0 - * - * - * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). - * https://openapi-generator.tech - * Do not edit the class manually. - */ - - -// May contain unused imports in some cases -// @ts-ignore -import type { SearchProvider } from './search-provider'; -// May contain unused imports in some cases -// @ts-ignore -import type { VeniceSearchEngine } from './venice-search-engine'; - -export interface SearchIntegrationSettings { - 'provider': SearchProvider; - 'venice_engine': VeniceSearchEngine; -} diff --git a/lib/packages/fabro-api-client/src/models/search-provider.ts b/lib/packages/fabro-api-client/src/models/search-provider.ts deleted file mode 100644 index 552ccbe54..000000000 --- a/lib/packages/fabro-api-client/src/models/search-provider.ts +++ /dev/null @@ -1,23 +0,0 @@ -/* tslint:disable */ -/* eslint-disable */ -/** - * Fabro Run API - * HTTP API for managing Fabro workflow run executions. - * - * The version of the OpenAPI document: 0.2.0 - * - * - * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). - * https://openapi-generator.tech - * Do not edit the class manually. - */ - - - - -export const SearchProvider = { - BRAVE: 'brave', - VENICE: 'venice' -} as const; - -export type SearchProvider = typeof SearchProvider[keyof typeof SearchProvider]; diff --git a/lib/packages/fabro-api-client/src/models/server-integrations-settings.ts b/lib/packages/fabro-api-client/src/models/server-integrations-settings.ts index 3525f9c5a..88b8fdd8d 100644 --- a/lib/packages/fabro-api-client/src/models/server-integrations-settings.ts +++ b/lib/packages/fabro-api-client/src/models/server-integrations-settings.ts @@ -18,13 +18,9 @@ import type { GithubIntegrationSettings } from './github-integration-settings'; // May contain unused imports in some cases // @ts-ignore -import type { SearchIntegrationSettings } from './search-integration-settings'; -// May contain unused imports in some cases -// @ts-ignore import type { SlackIntegrationSettings } from './slack-integration-settings'; export interface ServerIntegrationsSettings { 'github': GithubIntegrationSettings; 'slack': SlackIntegrationSettings; - 'search': SearchIntegrationSettings; } diff --git a/lib/packages/fabro-api-client/src/models/venice-search-engine.ts b/lib/packages/fabro-api-client/src/models/venice-search-engine.ts deleted file mode 100644 index 29e7494cf..000000000 --- a/lib/packages/fabro-api-client/src/models/venice-search-engine.ts +++ /dev/null @@ -1,23 +0,0 @@ -/* tslint:disable */ -/* eslint-disable */ -/** - * Fabro Run API - * HTTP API for managing Fabro workflow run executions. - * - * The version of the OpenAPI document: 0.2.0 - * - * - * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). - * https://openapi-generator.tech - * Do not edit the class manually. - */ - - - - -export const VeniceSearchEngine = { - BRAVE: 'brave', - GOOGLE: 'google' -} as const; - -export type VeniceSearchEngine = typeof VeniceSearchEngine[keyof typeof VeniceSearchEngine];