fix(providers): use Bearer auth for Gemini CLI OAuth tokens
* fix(providers): use Bearer auth for Gemini CLI OAuth tokens When credentials come from ~/.gemini/oauth_creds.json (Gemini CLI), send them as Authorization: Bearer header instead of ?key= query parameter. API keys from env vars or config continue using ?key=. Fixes #194 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * refactor(gemini): harden OAuth bearer auth flow and tests * fix(gemini): granular auth source tracking and review fixes Build on chumyin's auth model refactor with: - Expand GeminiAuth to 4 variants (ExplicitKey/EnvGeminiKey/EnvGoogleKey/ OAuthToken) so auth_source() uses stored discriminant without re-reading env vars at call time - Add is_api_key()/credential() helpers on the enum - Upgrade expired OAuth token log from debug to warn - Add tests: provider_rejects_empty_key, auth_source_explicit_key, auth_source_none_without_credentials Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * style: apply rustfmt to fix CI lint failures Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com> Co-authored-by: root <root@instance-20220913-1738.vcn09131738.oraclevcn.com> Co-authored-by: argenis de la rosa <theonlyhennygod@gmail.com>
This commit is contained in:
parent
e057bf4128
commit
49bb20f961
15 changed files with 358 additions and 148 deletions
|
|
@ -74,11 +74,10 @@ const BAD_PATTERNS: &[&str] = &[
|
|||
/// Check if `haystack` contains `word` as a whole word (bounded by non-alphanumeric chars).
|
||||
fn contains_word(haystack: &str, word: &str) -> bool {
|
||||
for (i, _) in haystack.match_indices(word) {
|
||||
let before_ok = i == 0
|
||||
|| !haystack.as_bytes()[i - 1].is_ascii_alphanumeric();
|
||||
let before_ok = i == 0 || !haystack.as_bytes()[i - 1].is_ascii_alphanumeric();
|
||||
let after = i + word.len();
|
||||
let after_ok = after >= haystack.len()
|
||||
|| !haystack.as_bytes()[after].is_ascii_alphanumeric();
|
||||
let after_ok =
|
||||
after >= haystack.len() || !haystack.as_bytes()[after].is_ascii_alphanumeric();
|
||||
if before_ok && after_ok {
|
||||
return true;
|
||||
}
|
||||
|
|
@ -217,7 +216,11 @@ mod tests {
|
|||
c.name = "malware-skill".into();
|
||||
let res = eval.evaluate(c);
|
||||
// 0.5 base + 0.3 license - 0.5 bad_pattern + 0.2 recency = 0.5
|
||||
assert!(res.scores.security <= 0.5, "security: {}", res.scores.security);
|
||||
assert!(
|
||||
res.scores.security <= 0.5,
|
||||
"security: {}",
|
||||
res.scores.security
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
|
@ -245,7 +248,11 @@ mod tests {
|
|||
c.description = "Tools for hackathons and lifehacks".into();
|
||||
let res = eval.evaluate(c);
|
||||
// "hack" should NOT match "hackathon" or "lifehacks"
|
||||
assert!(res.scores.security >= 0.5, "security: {}", res.scores.security);
|
||||
assert!(
|
||||
res.scores.security >= 0.5,
|
||||
"security: {}",
|
||||
res.scores.security
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
|
@ -256,6 +263,10 @@ mod tests {
|
|||
c.updated_at = None;
|
||||
let res = eval.evaluate(c);
|
||||
// 0.5 base + 0.0 license - 0.5 bad_pattern + 0.0 recency = 0.0
|
||||
assert!(res.scores.security < 0.5, "security: {}", res.scores.security);
|
||||
assert!(
|
||||
res.scores.security < 0.5,
|
||||
"security: {}",
|
||||
res.scores.security
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -78,10 +78,7 @@ impl std::fmt::Debug for SkillForgeConfig {
|
|||
.field("sources", &self.sources)
|
||||
.field("scan_interval_hours", &self.scan_interval_hours)
|
||||
.field("min_score", &self.min_score)
|
||||
.field(
|
||||
"github_token",
|
||||
&self.github_token.as_ref().map(|_| "***"),
|
||||
)
|
||||
.field("github_token", &self.github_token.as_ref().map(|_| "***"))
|
||||
.field("output_dir", &self.output_dir)
|
||||
.finish()
|
||||
}
|
||||
|
|
@ -155,7 +152,10 @@ impl SkillForge {
|
|||
}
|
||||
}
|
||||
ScoutSource::ClawHub | ScoutSource::HuggingFace => {
|
||||
info!(source = src.as_str(), "Source not yet implemented — skipping");
|
||||
info!(
|
||||
source = src.as_str(),
|
||||
"Source not yet implemented — skipping"
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -79,9 +79,7 @@ impl GitHubScout {
|
|||
let mut headers = reqwest::header::HeaderMap::new();
|
||||
headers.insert(
|
||||
reqwest::header::ACCEPT,
|
||||
"application/vnd.github+json"
|
||||
.parse()
|
||||
.expect("valid header"),
|
||||
"application/vnd.github+json".parse().expect("valid header"),
|
||||
);
|
||||
headers.insert(
|
||||
reqwest::header::USER_AGENT,
|
||||
|
|
@ -101,10 +99,7 @@ impl GitHubScout {
|
|||
|
||||
Self {
|
||||
client,
|
||||
queries: vec![
|
||||
"zeroclaw skill".into(),
|
||||
"ai agent skill".into(),
|
||||
],
|
||||
queries: vec!["zeroclaw skill".into(), "ai agent skill".into()],
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -143,10 +138,7 @@ impl GitHubScout {
|
|||
.and_then(|v| v.as_str())
|
||||
.unwrap_or("unknown")
|
||||
.to_string();
|
||||
let has_license = item
|
||||
.get("license")
|
||||
.map(|v| !v.is_null())
|
||||
.unwrap_or(false);
|
||||
let has_license = item.get("license").map(|v| !v.is_null()).unwrap_or(false);
|
||||
|
||||
Some(ScoutResult {
|
||||
name,
|
||||
|
|
@ -225,9 +217,7 @@ impl Scout for GitHubScout {
|
|||
|
||||
/// Minimal percent-encoding for query strings (space → +).
|
||||
fn urlencoding(s: &str) -> String {
|
||||
s.replace(' ', "+")
|
||||
.replace('&', "%26")
|
||||
.replace('#', "%23")
|
||||
s.replace(' ', "+").replace('&', "%26").replace('#', "%23")
|
||||
}
|
||||
|
||||
/// Deduplicate scout results by URL (keeps first occurrence).
|
||||
|
|
@ -246,13 +236,31 @@ mod tests {
|
|||
|
||||
#[test]
|
||||
fn scout_source_from_str() {
|
||||
assert_eq!("github".parse::<ScoutSource>().unwrap(), ScoutSource::GitHub);
|
||||
assert_eq!("GitHub".parse::<ScoutSource>().unwrap(), ScoutSource::GitHub);
|
||||
assert_eq!("clawhub".parse::<ScoutSource>().unwrap(), ScoutSource::ClawHub);
|
||||
assert_eq!("huggingface".parse::<ScoutSource>().unwrap(), ScoutSource::HuggingFace);
|
||||
assert_eq!("hf".parse::<ScoutSource>().unwrap(), ScoutSource::HuggingFace);
|
||||
assert_eq!(
|
||||
"github".parse::<ScoutSource>().unwrap(),
|
||||
ScoutSource::GitHub
|
||||
);
|
||||
assert_eq!(
|
||||
"GitHub".parse::<ScoutSource>().unwrap(),
|
||||
ScoutSource::GitHub
|
||||
);
|
||||
assert_eq!(
|
||||
"clawhub".parse::<ScoutSource>().unwrap(),
|
||||
ScoutSource::ClawHub
|
||||
);
|
||||
assert_eq!(
|
||||
"huggingface".parse::<ScoutSource>().unwrap(),
|
||||
ScoutSource::HuggingFace
|
||||
);
|
||||
assert_eq!(
|
||||
"hf".parse::<ScoutSource>().unwrap(),
|
||||
ScoutSource::HuggingFace
|
||||
);
|
||||
// unknown falls back to GitHub
|
||||
assert_eq!("unknown".parse::<ScoutSource>().unwrap(), ScoutSource::GitHub);
|
||||
assert_eq!(
|
||||
"unknown".parse::<ScoutSource>().unwrap(),
|
||||
ScoutSource::GitHub
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue