fix(usage): treat usage_script credentials as explicit overrides (#4654)

* fix(usage): treat usage_script credentials as explicit overrides

* fix: treat usage script credentials as explicit overrides
This commit is contained in:
yyhhyyyyyy
2026-06-28 17:37:58 +08:00
committed by GitHub
parent eb847642d0
commit d1f6c74be8
6 changed files with 711 additions and 17 deletions
+44 -14
View File
@@ -185,6 +185,48 @@ fn get_primary_endpoint(request: &DeepLinkImportRequest) -> String {
.unwrap_or_default()
}
fn normalize_deeplink_api_key(api_key: &str) -> String {
api_key.trim().to_string()
}
fn normalize_deeplink_base_url(base_url: &str) -> String {
base_url.trim().trim_end_matches('/').to_string()
}
fn usage_api_key_override(request: &DeepLinkImportRequest) -> Option<String> {
let usage_api_key = normalize_deeplink_api_key(request.usage_api_key.as_deref()?);
if usage_api_key.is_empty() {
return None;
}
let provider_api_key = request
.api_key
.as_deref()
.map(normalize_deeplink_api_key)
.unwrap_or_default();
if !provider_api_key.is_empty() && usage_api_key == provider_api_key {
None
} else {
Some(usage_api_key)
}
}
fn usage_base_url_override(request: &DeepLinkImportRequest) -> Option<String> {
let usage_base_url = normalize_deeplink_base_url(request.usage_base_url.as_deref()?);
if usage_base_url.is_empty() {
return None;
}
let provider_base_url = normalize_deeplink_base_url(&get_primary_endpoint(request));
if !provider_base_url.is_empty() && usage_base_url == provider_base_url {
None
} else {
Some(usage_base_url)
}
}
/// Build provider meta with usage script configuration
fn build_provider_meta(request: &DeepLinkImportRequest) -> Result<Option<ProviderMeta>, AppError> {
// Check if any usage script fields are provided
@@ -211,25 +253,13 @@ fn build_provider_meta(request: &DeepLinkImportRequest) -> Result<Option<Provide
// Determine enabled state: explicit param > has code > false
let enabled = request.usage_enabled.unwrap_or(!code.is_empty());
// Build UsageScript - use provider's API key and endpoint as defaults
// Note: use primary endpoint only (first one if comma-separated)
let usage_script = UsageScript {
enabled,
language: "javascript".to_string(),
code,
timeout: Some(10),
api_key: request
.usage_api_key
.clone()
.or_else(|| request.api_key.clone()),
base_url: request.usage_base_url.clone().or_else(|| {
let primary = get_primary_endpoint(request);
if primary.is_empty() {
None
} else {
Some(primary)
}
}),
api_key: usage_api_key_override(request),
base_url: usage_base_url_override(request),
access_token: request.usage_access_token.clone(),
user_id: request.usage_user_id.clone(),
template_type: None, // Deeplink providers don't specify template type (will use backward compatibility logic)
+148
View File
@@ -260,6 +260,154 @@ fn test_build_gemini_provider_without_model() {
assert!(env.get("GEMINI_MODEL").is_none());
}
#[test]
fn test_deeplink_usage_script_does_not_copy_provider_credentials() {
use super::provider::build_provider_from_request;
let request = DeepLinkImportRequest {
version: "v1".to_string(),
resource: "provider".to_string(),
app: Some("claude".to_string()),
name: Some("Test Claude".to_string()),
homepage: Some("https://example.com".to_string()),
endpoint: Some("https://api.example.com/v1/".to_string()),
api_key: Some("sk-main".to_string()),
icon: None,
model: None,
notes: None,
haiku_model: None,
sonnet_model: None,
opus_model: None,
config: None,
config_format: None,
config_url: None,
apps: None,
repo: None,
directory: None,
branch: None,
content: None,
description: None,
enabled: None,
usage_enabled: Some(true),
usage_script: None,
usage_api_key: None,
usage_base_url: None,
usage_access_token: None,
usage_user_id: None,
usage_auto_interval: None,
};
let provider = build_provider_from_request(&AppType::Claude, &request).unwrap();
let script = provider
.meta
.as_ref()
.and_then(|meta| meta.usage_script.as_ref())
.expect("usage script should be created");
assert!(script.enabled);
assert_eq!(script.api_key, None);
assert_eq!(script.base_url, None);
}
#[test]
fn test_deeplink_usage_script_omits_explicit_credentials_that_match_provider() {
use super::provider::build_provider_from_request;
let request = DeepLinkImportRequest {
version: "v1".to_string(),
resource: "provider".to_string(),
app: Some("claude".to_string()),
name: Some("Test Claude".to_string()),
homepage: Some("https://example.com".to_string()),
endpoint: Some("https://api.example.com/v1/".to_string()),
api_key: Some("sk-main".to_string()),
icon: None,
model: None,
notes: None,
haiku_model: None,
sonnet_model: None,
opus_model: None,
config: None,
config_format: None,
config_url: None,
apps: None,
repo: None,
directory: None,
branch: None,
content: None,
description: None,
enabled: None,
usage_enabled: Some(true),
usage_script: None,
usage_api_key: Some(" sk-main ".to_string()),
usage_base_url: Some(" https://api.example.com/v1/ ".to_string()),
usage_access_token: None,
usage_user_id: None,
usage_auto_interval: None,
};
let provider = build_provider_from_request(&AppType::Claude, &request).unwrap();
let script = provider
.meta
.as_ref()
.and_then(|meta| meta.usage_script.as_ref())
.expect("usage script should be created");
assert_eq!(script.api_key, None);
assert_eq!(script.base_url, None);
}
#[test]
fn test_deeplink_usage_script_preserves_distinct_usage_credentials() {
use super::provider::build_provider_from_request;
let request = DeepLinkImportRequest {
version: "v1".to_string(),
resource: "provider".to_string(),
app: Some("claude".to_string()),
name: Some("Test Claude".to_string()),
homepage: Some("https://example.com".to_string()),
endpoint: Some("https://api.example.com/v1".to_string()),
api_key: Some("sk-main".to_string()),
icon: None,
model: None,
notes: None,
haiku_model: None,
sonnet_model: None,
opus_model: None,
config: None,
config_format: None,
config_url: None,
apps: None,
repo: None,
directory: None,
branch: None,
content: None,
description: None,
enabled: None,
usage_enabled: Some(true),
usage_script: None,
usage_api_key: Some(" sk-usage ".to_string()),
usage_base_url: Some(" https://usage.example/api/ ".to_string()),
usage_access_token: None,
usage_user_id: None,
usage_auto_interval: None,
};
let provider = build_provider_from_request(&AppType::Claude, &request).unwrap();
let script = provider
.meta
.as_ref()
.and_then(|meta| meta.usage_script.as_ref())
.expect("usage script should be created");
assert_eq!(script.api_key.as_deref(), Some("sk-usage"));
assert_eq!(
script.base_url.as_deref(),
Some("https://usage.example/api")
);
}
#[test]
fn test_parse_and_merge_config_claude() {
// Prepare Base64 encoded Claude config