refactor(search): select backend from available credentials

This commit is contained in:
Bryan Helmkamp 2026-08-21 20:09:52 -04:00
parent cfa8ae92c0
commit 88ed2ac9a3
No known key found for this signature in database
40 changed files with 188 additions and 555 deletions

View file

@ -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.

View file

@ -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.

View file

@ -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

View file

@ -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:

View file

@ -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

View file

@ -145,7 +145,7 @@ The system prompt varies by LLM provider. Each provider has its own identity tex
<Accordion title="Example system prompt (Anthropic provider)">
This is the full system prompt sent to Claude as the LLM system message. The `<environment>` 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

View file

@ -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 <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 <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

View file

@ -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]

View file

@ -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).

View file

@ -161,7 +161,7 @@ Workflow runs also add `x-session-id: <run-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.

View file

@ -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.

View file

@ -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.
</Card>
<Card title="Brave Search" icon="globe" href="/integrations/brave-search">
Direct Brave Search backend (the default when `provider` is unset).
Direct Brave Search backend, preferred whenever its key is configured.
</Card>
</Columns>

View file

@ -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" }
]
},
{

View file

@ -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(

View file

@ -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(),
}
}

View file

@ -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<String>,
pub venice_api_key: Option<String>,
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"));
}

View file

@ -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(

View file

@ -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"],
);

View file

@ -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://.

View file

@ -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://.

View file

@ -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://.

View file

@ -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.

View file

@ -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<Self> {
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<VeniceSearchEngine>,
) -> Result<String, String> {
async fn search(&self, query: &str, max_results: u64) -> Result<String, String> {
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<String, String> {
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<String> {
.map(str::to_owned)
}
fn parse_engine_arg(args: &serde_json::Value) -> Result<Option<VeniceSearchEngine>, 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());
}

View file

@ -239,7 +239,6 @@ async fn tool_secrets_from_configured_sources(vault: &Arc<AsyncRwLock<Vault>>) -
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(),
}
}

View file

@ -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",

View file

@ -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};

View file

@ -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,

View file

@ -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,

View file

@ -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<GithubIntegrationLayer>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub slack: Option<SlackIntegrationLayer>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub search: Option<SearchIntegrationLayer>,
}
/// `[server.integrations.github]` — GitHub App, credentials, and inbound
@ -239,16 +237,6 @@ pub struct SlackIntegrationLayer {
pub default_channel: Option<String>,
}
/// `[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<SearchProvider>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub venice_engine: Option<VeniceSearchEngine>,
}
#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize, fabro_macros::Combine)]
#[serde(deny_unknown_fields)]
pub struct IntegrationWebhooksLayer {

View file

@ -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;

View file

@ -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(),
}
}

View file

@ -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

View file

@ -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;

View file

@ -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<WebhookStrategy>,

View file

@ -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

View file

@ -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';

View file

@ -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;
}

View file

@ -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];

View file

@ -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;
}

View file

@ -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];