Skip to main content

hanzo_client/apis/
websearch_api.rs

1/*
2 * Hanzo Cloud API
3 *
4 * The Hanzo Cloud API as a customer calls it: every operation under /v1/ except the operator's admin product, relay routes, legacy spellings and capabilities still reached by flag. Tagged by product: the first path segment after /v1/.
5 *
6 * The version of the OpenAPI document: v1
7 * 
8 * Generated by: https://openapi-generator.tech
9 */
10
11
12use reqwest;
13use serde::{Deserialize, Serialize, de::Error as _};
14use crate::{apis::ResponseContent, models};
15use super::{Error, configuration, ContentType};
16
17
18/// struct for typed errors of method [`delete_websearch_search`]
19#[derive(Debug, Clone, Serialize, Deserialize)]
20#[serde(untagged)]
21pub enum DeleteWebsearchSearchError {
22    UnknownValue(serde_json::Value),
23}
24
25/// struct for typed errors of method [`get_websearch_search`]
26#[derive(Debug, Clone, Serialize, Deserialize)]
27#[serde(untagged)]
28pub enum GetWebsearchSearchError {
29    UnknownValue(serde_json::Value),
30}
31
32/// struct for typed errors of method [`patch_websearch_search`]
33#[derive(Debug, Clone, Serialize, Deserialize)]
34#[serde(untagged)]
35pub enum PatchWebsearchSearchError {
36    UnknownValue(serde_json::Value),
37}
38
39/// struct for typed errors of method [`post_websearch_scrape`]
40#[derive(Debug, Clone, Serialize, Deserialize)]
41#[serde(untagged)]
42pub enum PostWebsearchScrapeError {
43    UnknownValue(serde_json::Value),
44}
45
46/// struct for typed errors of method [`post_websearch_search`]
47#[derive(Debug, Clone, Serialize, Deserialize)]
48#[serde(untagged)]
49pub enum PostWebsearchSearchError {
50    UnknownValue(serde_json::Value),
51}
52
53/// struct for typed errors of method [`put_websearch_search`]
54#[derive(Debug, Clone, Serialize, Deserialize)]
55#[serde(untagged)]
56pub enum PutWebsearchSearchError {
57    UnknownValue(serde_json::Value),
58}
59
60/// struct for typed errors of method [`search_web`]
61#[derive(Debug, Clone, Serialize, Deserialize)]
62#[serde(untagged)]
63pub enum SearchWebError {
64    UnknownValue(serde_json::Value),
65}
66
67
68/// Answers {query, number_of_results, results:[{url, title, content, engine}]} — the exact /search?format=json contract a SearXNG client decodes, so an agent tool configured against SearXNG reaches this with no change. `q` is the query and `language` narrows it; both are read from the QUERY STRING.  Served in-process by a Go meta-search over keyless public engines, never a third-party search API and never a search key. The enabled engines run concurrently and their hits are merged, deduplicated by normalised URL (host and path, trailing slash and fragment dropped, query kept — distinct queries are distinct results) and capped at 30. Ranking is deterministic rather than scored: the first configured engine's hits lead.  TWO WAYS IN, one-way equivalent, and no third: a validated principal — the same gate the whole data plane uses — passes straight through, and a caller without one must present the shared service key as X-API-Key, compared in constant time. A deployment with no key configured answers 503 rather than opening the surface to everyone, and a missing or wrong key is 401. It is never an open proxy. There is no tenant scoping beyond that gate, and there is nothing to scope: the results are public web pages, identical for every caller.  It fails SOFT on the engines and closed only on the gate. An engine that errors or is served a bot-challenge page contributes zero results and never fails the request, so an empty `results` is a real answer — nothing was found — and not an outage. The array is always present, never null.  The one thing to get right: every method answers identically. This is one handler registered for all of them, and it reads only the query string, so a body sent on the write verbs is ignored rather than refused.
69pub async fn delete_websearch_search(configuration: &configuration::Configuration, ) -> Result<(), Error<DeleteWebsearchSearchError>> {
70
71    let uri_str = format!("{}/v1/websearch/search", configuration.base_path);
72    let mut req_builder = configuration.client.request(reqwest::Method::DELETE, &uri_str);
73
74    if let Some(ref user_agent) = configuration.user_agent {
75        req_builder = req_builder.header(reqwest::header::USER_AGENT, user_agent.clone());
76    }
77    if let Some(ref token) = configuration.bearer_access_token {
78        req_builder = req_builder.bearer_auth(token.to_owned());
79    };
80
81    let req = req_builder.build()?;
82    let resp = configuration.client.execute(req).await?;
83
84    let status = resp.status();
85
86    if !status.is_client_error() && !status.is_server_error() {
87        Ok(())
88    } else {
89        let content = resp.text().await?;
90        let entity: Option<DeleteWebsearchSearchError> = serde_json::from_str(&content).ok();
91        Err(Error::ResponseError(ResponseContent { status, content, entity }))
92    }
93}
94
95/// Answers {query, number_of_results, results:[{url, title, content, engine}]} — the exact /search?format=json contract a SearXNG client decodes, so an agent tool configured against SearXNG reaches this with no change. `q` is the query and `language` narrows it; both are read from the QUERY STRING.  Served in-process by a Go meta-search over keyless public engines, never a third-party search API and never a search key. The enabled engines run concurrently and their hits are merged, deduplicated by normalised URL (host and path, trailing slash and fragment dropped, query kept — distinct queries are distinct results) and capped at 30. Ranking is deterministic rather than scored: the first configured engine's hits lead.  TWO WAYS IN, one-way equivalent, and no third: a validated principal — the same gate the whole data plane uses — passes straight through, and a caller without one must present the shared service key as X-API-Key, compared in constant time. A deployment with no key configured answers 503 rather than opening the surface to everyone, and a missing or wrong key is 401. It is never an open proxy. There is no tenant scoping beyond that gate, and there is nothing to scope: the results are public web pages, identical for every caller.  It fails SOFT on the engines and closed only on the gate. An engine that errors or is served a bot-challenge page contributes zero results and never fails the request, so an empty `results` is a real answer — nothing was found — and not an outage. The array is always present, never null.  The one thing to get right: every method answers identically. This is one handler registered for all of them, and it reads only the query string, so a body sent on the write verbs is ignored rather than refused.
96pub async fn get_websearch_search(configuration: &configuration::Configuration, ) -> Result<(), Error<GetWebsearchSearchError>> {
97
98    let uri_str = format!("{}/v1/websearch/search", configuration.base_path);
99    let mut req_builder = configuration.client.request(reqwest::Method::GET, &uri_str);
100
101    if let Some(ref user_agent) = configuration.user_agent {
102        req_builder = req_builder.header(reqwest::header::USER_AGENT, user_agent.clone());
103    }
104    if let Some(ref token) = configuration.bearer_access_token {
105        req_builder = req_builder.bearer_auth(token.to_owned());
106    };
107
108    let req = req_builder.build()?;
109    let resp = configuration.client.execute(req).await?;
110
111    let status = resp.status();
112
113    if !status.is_client_error() && !status.is_server_error() {
114        Ok(())
115    } else {
116        let content = resp.text().await?;
117        let entity: Option<GetWebsearchSearchError> = serde_json::from_str(&content).ok();
118        Err(Error::ResponseError(ResponseContent { status, content, entity }))
119    }
120}
121
122/// Answers {query, number_of_results, results:[{url, title, content, engine}]} — the exact /search?format=json contract a SearXNG client decodes, so an agent tool configured against SearXNG reaches this with no change. `q` is the query and `language` narrows it; both are read from the QUERY STRING.  Served in-process by a Go meta-search over keyless public engines, never a third-party search API and never a search key. The enabled engines run concurrently and their hits are merged, deduplicated by normalised URL (host and path, trailing slash and fragment dropped, query kept — distinct queries are distinct results) and capped at 30. Ranking is deterministic rather than scored: the first configured engine's hits lead.  TWO WAYS IN, one-way equivalent, and no third: a validated principal — the same gate the whole data plane uses — passes straight through, and a caller without one must present the shared service key as X-API-Key, compared in constant time. A deployment with no key configured answers 503 rather than opening the surface to everyone, and a missing or wrong key is 401. It is never an open proxy. There is no tenant scoping beyond that gate, and there is nothing to scope: the results are public web pages, identical for every caller.  It fails SOFT on the engines and closed only on the gate. An engine that errors or is served a bot-challenge page contributes zero results and never fails the request, so an empty `results` is a real answer — nothing was found — and not an outage. The array is always present, never null.  The one thing to get right: every method answers identically. This is one handler registered for all of them, and it reads only the query string, so a body sent on the write verbs is ignored rather than refused.
123pub async fn patch_websearch_search(configuration: &configuration::Configuration, ) -> Result<(), Error<PatchWebsearchSearchError>> {
124
125    let uri_str = format!("{}/v1/websearch/search", configuration.base_path);
126    let mut req_builder = configuration.client.request(reqwest::Method::PATCH, &uri_str);
127
128    if let Some(ref user_agent) = configuration.user_agent {
129        req_builder = req_builder.header(reqwest::header::USER_AGENT, user_agent.clone());
130    }
131    if let Some(ref token) = configuration.bearer_access_token {
132        req_builder = req_builder.bearer_auth(token.to_owned());
133    };
134
135    let req = req_builder.build()?;
136    let resp = configuration.client.execute(req).await?;
137
138    let status = resp.status();
139
140    if !status.is_client_error() && !status.is_server_error() {
141        Ok(())
142    } else {
143        let content = resp.text().await?;
144        let entity: Option<PatchWebsearchSearchError> = serde_json::from_str(&content).ok();
145        Err(Error::ResponseError(ResponseContent { status, content, entity }))
146    }
147}
148
149/// Takes {url} and answers {success, data:{markdown, metadata}} — the exact contract a firecrawl client decodes. The fetch, extraction and optional browser render run in-process; there is no crawler pod to be down.  The shared service key is required as an Authorization Bearer, compared in constant time: unset on the deployment is 503, missing or wrong is 401. Unlike search, a validated principal does NOT substitute for it — this is the service-to-service endpoint.  A page is archived under the caller's own org and project, taken from the verified principal when there is one, so a scrape lands in the same corpus /v1/crawl fills and a URL already read under that scope is answered from the archive without touching the network. A service caller carrying no principal shares the unscoped prefix.  The URL is caller-supplied and fetched from INSIDE the cluster, which makes this a request-forgery primitive by construction: in-namespace service DNS and a cloud metadata endpoint that hands credentials to anyone who asks are both a resolution away. Only http and https are accepted, and every address actually dialled must be public unicast — loopback, link-local, private and multicast are refused. The check lives in the DIALER rather than on the hostname, because resolving a name to validate it and then letting the transport resolve it again is a gap DNS rebinding walks straight through; redirects re-enter the same dialer, so a public URL that bounces to the metadata address is refused at the hop that matters.  The one thing to get right: FAILURE IS 200. A missing or unparseable url, a body over the 1 MiB read cap, and a fetch that could not be completed all answer HTTP 200 with success:false and a reason — a firecrawl client reads data.success, not the status line. Only the two auth refusals use a status code, so a caller that branches on HTTP status alone will read every failed scrape as a success.
150pub async fn post_websearch_scrape(configuration: &configuration::Configuration, ) -> Result<(), Error<PostWebsearchScrapeError>> {
151
152    let uri_str = format!("{}/v1/websearch/scrape", configuration.base_path);
153    let mut req_builder = configuration.client.request(reqwest::Method::POST, &uri_str);
154
155    if let Some(ref user_agent) = configuration.user_agent {
156        req_builder = req_builder.header(reqwest::header::USER_AGENT, user_agent.clone());
157    }
158    if let Some(ref token) = configuration.bearer_access_token {
159        req_builder = req_builder.bearer_auth(token.to_owned());
160    };
161
162    let req = req_builder.build()?;
163    let resp = configuration.client.execute(req).await?;
164
165    let status = resp.status();
166
167    if !status.is_client_error() && !status.is_server_error() {
168        Ok(())
169    } else {
170        let content = resp.text().await?;
171        let entity: Option<PostWebsearchScrapeError> = serde_json::from_str(&content).ok();
172        Err(Error::ResponseError(ResponseContent { status, content, entity }))
173    }
174}
175
176/// Answers {query, number_of_results, results:[{url, title, content, engine}]} — the exact /search?format=json contract a SearXNG client decodes, so an agent tool configured against SearXNG reaches this with no change. `q` is the query and `language` narrows it; both are read from the QUERY STRING.  Served in-process by a Go meta-search over keyless public engines, never a third-party search API and never a search key. The enabled engines run concurrently and their hits are merged, deduplicated by normalised URL (host and path, trailing slash and fragment dropped, query kept — distinct queries are distinct results) and capped at 30. Ranking is deterministic rather than scored: the first configured engine's hits lead.  TWO WAYS IN, one-way equivalent, and no third: a validated principal — the same gate the whole data plane uses — passes straight through, and a caller without one must present the shared service key as X-API-Key, compared in constant time. A deployment with no key configured answers 503 rather than opening the surface to everyone, and a missing or wrong key is 401. It is never an open proxy. There is no tenant scoping beyond that gate, and there is nothing to scope: the results are public web pages, identical for every caller.  It fails SOFT on the engines and closed only on the gate. An engine that errors or is served a bot-challenge page contributes zero results and never fails the request, so an empty `results` is a real answer — nothing was found — and not an outage. The array is always present, never null.  The one thing to get right: every method answers identically. This is one handler registered for all of them, and it reads only the query string, so a body sent on the write verbs is ignored rather than refused.
177pub async fn post_websearch_search(configuration: &configuration::Configuration, ) -> Result<(), Error<PostWebsearchSearchError>> {
178
179    let uri_str = format!("{}/v1/websearch/search", configuration.base_path);
180    let mut req_builder = configuration.client.request(reqwest::Method::POST, &uri_str);
181
182    if let Some(ref user_agent) = configuration.user_agent {
183        req_builder = req_builder.header(reqwest::header::USER_AGENT, user_agent.clone());
184    }
185    if let Some(ref token) = configuration.bearer_access_token {
186        req_builder = req_builder.bearer_auth(token.to_owned());
187    };
188
189    let req = req_builder.build()?;
190    let resp = configuration.client.execute(req).await?;
191
192    let status = resp.status();
193
194    if !status.is_client_error() && !status.is_server_error() {
195        Ok(())
196    } else {
197        let content = resp.text().await?;
198        let entity: Option<PostWebsearchSearchError> = serde_json::from_str(&content).ok();
199        Err(Error::ResponseError(ResponseContent { status, content, entity }))
200    }
201}
202
203/// Answers {query, number_of_results, results:[{url, title, content, engine}]} — the exact /search?format=json contract a SearXNG client decodes, so an agent tool configured against SearXNG reaches this with no change. `q` is the query and `language` narrows it; both are read from the QUERY STRING.  Served in-process by a Go meta-search over keyless public engines, never a third-party search API and never a search key. The enabled engines run concurrently and their hits are merged, deduplicated by normalised URL (host and path, trailing slash and fragment dropped, query kept — distinct queries are distinct results) and capped at 30. Ranking is deterministic rather than scored: the first configured engine's hits lead.  TWO WAYS IN, one-way equivalent, and no third: a validated principal — the same gate the whole data plane uses — passes straight through, and a caller without one must present the shared service key as X-API-Key, compared in constant time. A deployment with no key configured answers 503 rather than opening the surface to everyone, and a missing or wrong key is 401. It is never an open proxy. There is no tenant scoping beyond that gate, and there is nothing to scope: the results are public web pages, identical for every caller.  It fails SOFT on the engines and closed only on the gate. An engine that errors or is served a bot-challenge page contributes zero results and never fails the request, so an empty `results` is a real answer — nothing was found — and not an outage. The array is always present, never null.  The one thing to get right: every method answers identically. This is one handler registered for all of them, and it reads only the query string, so a body sent on the write verbs is ignored rather than refused.
204pub async fn put_websearch_search(configuration: &configuration::Configuration, ) -> Result<(), Error<PutWebsearchSearchError>> {
205
206    let uri_str = format!("{}/v1/websearch/search", configuration.base_path);
207    let mut req_builder = configuration.client.request(reqwest::Method::PUT, &uri_str);
208
209    if let Some(ref user_agent) = configuration.user_agent {
210        req_builder = req_builder.header(reqwest::header::USER_AGENT, user_agent.clone());
211    }
212    if let Some(ref token) = configuration.bearer_access_token {
213        req_builder = req_builder.bearer_auth(token.to_owned());
214    };
215
216    let req = req_builder.build()?;
217    let resp = configuration.client.execute(req).await?;
218
219    let status = resp.status();
220
221    if !status.is_client_error() && !status.is_server_error() {
222        Ok(())
223    } else {
224        let content = resp.text().await?;
225        let entity: Option<PutWebsearchSearchError> = serde_json::from_str(&content).ok();
226        Err(Error::ResponseError(ResponseContent { status, content, entity }))
227    }
228}
229
230/// Searches the live web and answers with ranked results.  This is the fleet's path to what is happening RIGHT NOW — today's weather, an outage, a release that postdates any model's training. `q` is the query and `language` narrows it to a locale. The answer is `{query, number_of_results, results:[{url, title, content, engine}]}`, where `content` is the ENGINE's snippet and not the page: read a page with POST /v1/crawl.  It is served in-process by a Go meta-search over keyless public engines — never a third-party search API and never a search key. The enabled engines run concurrently and their hits are merged, deduplicated by normalised URL (host and path, trailing slash and fragment dropped, query kept, so distinct queries stay distinct results) and capped at 30. Ranking is deterministic rather than scored: the first configured engine's hits lead.  It fails SOFT on the engines. One that errors, times out or is served a bot-challenge page contributes zero results and never fails the call, so an empty `results` is a real answer — nothing was found — and not an outage. The array is always present, never null.  Two refusals in the order they have to be asked, both in the PREAMBLE. A typed op is also an MCP tool, a call-plane operation, a graph field and a CLI command, and every one of those invokes it with no route and therefore no middleware — so what admits a caller here is asked where every caller reaches it rather than in a middleware only one of them passes through.  A VALIDATED PRINCIPAL IS REQUIRED, and there is no tenant beyond that: the results are public web pages, identical for every caller, so nothing here is scoped and nothing here can leak across orgs.  THEN THE ANTI-FORGERY TOKEN, immediately before the money, because that is what it is about. This search is the SAME bought meta-search the compat endpoint runs — the engines cost, and account.Shared/meter.go bills the caller's ledger for the answer — so a page the caller never visited must not be able to spend for them by sending their browser here with a cookie they already hold. Nothing leaks; the answer is unreadable cross-origin. What moves is money.  It is account's control, the one every operation in this estate asks, and it is a no-op the moment a caller PRESENTS a credential (Bearer, gateway, API key) — which is every service and console caller here — so it costs a CLI, an agent and an API client nothing. Only the ambient-cookie path is asked for the echoed token. The raw /v1/websearch/search route asks the same control on its group (see Mount), so the two addresses of one search are admitted alike.
231pub async fn search_web(configuration: &configuration::Configuration, web_search_query: models::WebSearchQuery) -> Result<models::WebSearchResults, Error<SearchWebError>> {
232    // add a prefix to parameters to efficiently prevent name collisions
233    let p_web_search_query = web_search_query;
234
235    let uri_str = format!("{}/v1/websearch", configuration.base_path);
236    let mut req_builder = configuration.client.request(reqwest::Method::POST, &uri_str);
237
238    if let Some(ref user_agent) = configuration.user_agent {
239        req_builder = req_builder.header(reqwest::header::USER_AGENT, user_agent.clone());
240    }
241    if let Some(ref token) = configuration.bearer_access_token {
242        req_builder = req_builder.bearer_auth(token.to_owned());
243    };
244    req_builder = req_builder.json(&p_web_search_query);
245
246    let req = req_builder.build()?;
247    let resp = configuration.client.execute(req).await?;
248
249    let status = resp.status();
250    let content_type = resp
251        .headers()
252        .get("content-type")
253        .and_then(|v| v.to_str().ok())
254        .unwrap_or("application/octet-stream");
255    let content_type = super::ContentType::from(content_type);
256
257    if !status.is_client_error() && !status.is_server_error() {
258        let content = resp.text().await?;
259        match content_type {
260            ContentType::Json => serde_json::from_str(&content).map_err(Error::from),
261            ContentType::Text => return Err(Error::from(serde_json::Error::custom("Received `text/plain` content type response that cannot be converted to `models::WebSearchResults`"))),
262            ContentType::Unsupported(unknown_type) => return Err(Error::from(serde_json::Error::custom(format!("Received `{unknown_type}` content type response that cannot be converted to `models::WebSearchResults`")))),
263        }
264    } else {
265        let content = resp.text().await?;
266        let entity: Option<SearchWebError> = serde_json::from_str(&content).ok();
267        Err(Error::ResponseError(ResponseContent { status, content, entity }))
268    }
269}
270