ClawBrainHub: only list brains that actually pull (filter HDF5/broken)
The picker was showing brains that fail to pull. Diagnosis: our parser reads the JSON .brain format; the 3 redclawsystems reference brains are JSON (work), but all of omar's brains are packed as HDF5 binary, and 2 reference brains 500 on pull. list_brains now concurrently pulls each candidate and keeps only those that parse into a usable card (JSON .brain with skills/tagline/capabilities), so the app shows only what works. Backend-only — no app rebuild needed. Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
This commit is contained in:
co-authored by
Claude Opus 4.8
parent
3b858db6ab
commit
b3d907c785
@@ -87,20 +87,7 @@ impl HttpBrainHubClient {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async fn get_json(&self, url: &str) -> Result<serde_json::Value, BrainHubError> {
|
async fn get_json(&self, url: &str) -> Result<serde_json::Value, BrainHubError> {
|
||||||
let mut req = self.http.get(url);
|
fetch_json(&self.http, &self.token, url).await
|
||||||
if let Some(t) = &self.token {
|
|
||||||
req = req.bearer_auth(t);
|
|
||||||
}
|
|
||||||
let resp = req
|
|
||||||
.send()
|
|
||||||
.await
|
|
||||||
.map_err(|e| BrainHubError::Http(e.to_string()))?;
|
|
||||||
if !resp.status().is_success() {
|
|
||||||
return Err(BrainHubError::Http(format!("status {}", resp.status())));
|
|
||||||
}
|
|
||||||
resp.json()
|
|
||||||
.await
|
|
||||||
.map_err(|e| BrainHubError::Parse(e.to_string()))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn list_owner(&self, owner: &str) -> Vec<BrainSummary> {
|
async fn list_owner(&self, owner: &str) -> Vec<BrainSummary> {
|
||||||
@@ -115,12 +102,51 @@ impl HttpBrainHubClient {
|
|||||||
#[async_trait]
|
#[async_trait]
|
||||||
impl BrainHubClient for HttpBrainHubClient {
|
impl BrainHubClient for HttpBrainHubClient {
|
||||||
async fn list_brains(&self) -> Result<Vec<BrainSummary>, BrainHubError> {
|
async fn list_brains(&self) -> Result<Vec<BrainSummary>, BrainHubError> {
|
||||||
let mut out = self.list_owner(&self.owner).await;
|
let mut summaries = self.list_owner(&self.owner).await;
|
||||||
for b in self.list_owner(REFERENCE_OWNER).await {
|
for b in self.list_owner(REFERENCE_OWNER).await {
|
||||||
if !out.iter().any(|x| x.owner == b.owner && x.name == b.name) {
|
if !summaries
|
||||||
out.push(b);
|
.iter()
|
||||||
|
.any(|x| x.owner == b.owner && x.name == b.name)
|
||||||
|
{
|
||||||
|
summaries.push(b);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Only surface brains we can actually turn into a card: pull each and
|
||||||
|
// keep the ones that parse from a JSON .brain with real content.
|
||||||
|
// HDF5-packed brains and server-erroring pulls are filtered out so the
|
||||||
|
// picker shows only what works. Pulls run concurrently.
|
||||||
|
let mut set = tokio::task::JoinSet::new();
|
||||||
|
for (i, s) in summaries.iter().enumerate() {
|
||||||
|
let http = self.http.clone();
|
||||||
|
let token = self.token.clone();
|
||||||
|
let url = format!(
|
||||||
|
"{}/brains/{}/{}/{}/pull",
|
||||||
|
self.base, s.owner, s.name, s.version
|
||||||
|
);
|
||||||
|
let (owner, name, version) = (s.owner.clone(), s.name.clone(), s.version.clone());
|
||||||
|
set.spawn(async move {
|
||||||
|
let usable = match fetch_json(&http, &token, &url).await {
|
||||||
|
Ok(json) => {
|
||||||
|
let b = parse_brain(&owner, &name, &version, &json);
|
||||||
|
!b.skills.is_empty() || !b.tagline.is_empty() || !b.capabilities.is_empty()
|
||||||
|
}
|
||||||
|
Err(_) => false,
|
||||||
|
};
|
||||||
|
(i, usable)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
let mut keep = vec![false; summaries.len()];
|
||||||
|
while let Some(res) = set.join_next().await {
|
||||||
|
if let Ok((i, usable)) = res {
|
||||||
|
keep[i] = usable;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let out = summaries
|
||||||
|
.into_iter()
|
||||||
|
.zip(keep)
|
||||||
|
.filter_map(|(s, k)| k.then_some(s))
|
||||||
|
.collect();
|
||||||
Ok(out)
|
Ok(out)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -136,6 +162,29 @@ impl BrainHubClient for HttpBrainHubClient {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// GET a URL as JSON, with the optional Bearer token. Free fn so it can run in
|
||||||
|
/// spawned (concurrent) tasks that can't borrow `&self`.
|
||||||
|
async fn fetch_json(
|
||||||
|
http: &reqwest::Client,
|
||||||
|
token: &Option<String>,
|
||||||
|
url: &str,
|
||||||
|
) -> Result<serde_json::Value, BrainHubError> {
|
||||||
|
let mut req = http.get(url);
|
||||||
|
if let Some(t) = token {
|
||||||
|
req = req.bearer_auth(t);
|
||||||
|
}
|
||||||
|
let resp = req
|
||||||
|
.send()
|
||||||
|
.await
|
||||||
|
.map_err(|e| BrainHubError::Http(e.to_string()))?;
|
||||||
|
if !resp.status().is_success() {
|
||||||
|
return Err(BrainHubError::Http(format!("status {}", resp.status())));
|
||||||
|
}
|
||||||
|
resp.json()
|
||||||
|
.await
|
||||||
|
.map_err(|e| BrainHubError::Parse(e.to_string()))
|
||||||
|
}
|
||||||
|
|
||||||
/// Parse a `/brains/{owner}` list response into summaries (latest version each).
|
/// Parse a `/brains/{owner}` list response into summaries (latest version each).
|
||||||
fn parse_summaries(json: &serde_json::Value) -> Vec<BrainSummary> {
|
fn parse_summaries(json: &serde_json::Value) -> Vec<BrainSummary> {
|
||||||
let arr = match json.as_array() {
|
let arr = match json.as_array() {
|
||||||
|
|||||||
Reference in New Issue
Block a user