Files
Aether/crates/aether-provider-transport/src/kiro/request.rs
fawney19 b0b40c16ff feat: 全栈功能增强 - 扩展 provider/pool 管理、完善调度与数据层、重构前端 Pool 页面
后端:
- 扩展 pool_admin payloads 和 provider query models,增强 endpoint key 管理
- 完善 scheduler-core 候选排序与请求候选逻辑
- 增强 usage-runtime 写入、provider-transport 网络层与 OAuth 刷新
- 改进 AI pipeline 响应转换与流式处理
- 扩展 global_models/provider_catalog 数据层查询能力
- 增强 video-tasks-core 多 provider 支持
- 新增大量集成测试覆盖 pool/keys/provider_query/frontdoor

前端:
- 重构 PoolManagement 页面,拆分状态管理/对话框逻辑到独立模块
- 新增 poolAdvancedDialog/poolSchedulingDialog/poolManagementState/poolMobilePresentation 工具函数及测试
- 改进 Dialog 组件与 provider tabs 显示

部署:
- 更新 Rust CI workflow 和 Dockerfile 构建配置

Closes #275
Co-authored-by: AAEE86 <ppk0227@hotmail.com>
2026-04-09 13:51:50 +08:00

301 lines
9.3 KiB
Rust

use std::collections::BTreeMap;
use serde_json::{json, Value};
pub use super::super::rules::{
apply_local_body_rules, apply_local_header_rules, body_rules_are_locally_supported,
header_rules_are_locally_supported,
};
use super::super::should_skip_upstream_passthrough_header;
use super::converter::convert_claude_messages_to_conversation_state;
use super::credentials::KiroAuthConfig;
use super::headers::build_generate_assistant_headers;
pub fn supports_local_kiro_request_shape(
header_rules: Option<&Value>,
body_rules: Option<&Value>,
) -> bool {
header_rules_are_locally_supported(header_rules) && body_rules_are_locally_supported(body_rules)
}
pub fn build_kiro_provider_request_body(
body_json: &Value,
mapped_model: &str,
auth_config: &KiroAuthConfig,
body_rules: Option<&Value>,
) -> Option<Value> {
let conversation_state =
convert_claude_messages_to_conversation_state(body_json, mapped_model)?;
let mut provider_request_body = json!({
"conversationState": conversation_state
});
let mut inference_config = serde_json::Map::new();
if let Some(max_tokens) = body_json
.get("max_tokens")
.and_then(|value| {
value
.as_i64()
.or_else(|| value.as_u64().map(|value| value as i64))
})
.filter(|value| *value > 0)
{
inference_config.insert("maxTokens".to_string(), Value::from(max_tokens));
}
if let Some(temperature) = body_json
.get("temperature")
.and_then(Value::as_f64)
.filter(|value| *value >= 0.0)
{
inference_config.insert("temperature".to_string(), Value::from(temperature));
}
if let Some(top_p) = body_json
.get("top_p")
.and_then(Value::as_f64)
.filter(|value| *value > 0.0)
{
inference_config.insert("topP".to_string(), Value::from(top_p));
}
if !inference_config.is_empty() {
provider_request_body.as_object_mut()?.insert(
"inferenceConfig".to_string(),
Value::Object(inference_config),
);
}
if let Some(profile_arn) = auth_config.profile_arn_for_payload() {
provider_request_body.as_object_mut()?.insert(
"profileArn".to_string(),
Value::String(profile_arn.to_string()),
);
}
if !apply_local_body_rules(&mut provider_request_body, body_rules, Some(body_json)) {
return None;
}
Some(provider_request_body)
}
#[derive(Clone, Copy, Debug)]
pub struct KiroProviderHeadersInput<'a> {
pub headers: &'a http::HeaderMap,
pub provider_request_body: &'a Value,
pub original_request_body: &'a Value,
pub header_rules: Option<&'a Value>,
pub auth_header: &'a str,
pub auth_value: &'a str,
pub auth_config: &'a KiroAuthConfig,
pub machine_id: &'a str,
}
pub fn build_kiro_provider_headers(
input: KiroProviderHeadersInput<'_>,
) -> Option<BTreeMap<String, String>> {
let KiroProviderHeadersInput {
headers,
provider_request_body,
original_request_body,
header_rules,
auth_header,
auth_value,
auth_config,
machine_id,
} = input;
let mut out = BTreeMap::new();
for (name, value) in headers {
let Ok(value) = value.to_str() else {
continue;
};
let key = name.as_str().to_ascii_lowercase();
if should_skip_upstream_passthrough_header(&key) {
continue;
}
let value = value.trim();
if value.is_empty() {
continue;
}
out.insert(key, value.to_string());
}
if !apply_local_header_rules(
&mut out,
header_rules,
&[auth_header, "content-type"],
provider_request_body,
Some(original_request_body),
) {
return None;
}
for (key, value) in build_generate_assistant_headers(auth_config, machine_id) {
out.insert(key, value);
}
out.insert(
auth_header.trim().to_ascii_lowercase(),
auth_value.trim().to_string(),
);
out.entry("content-type".to_string())
.or_insert_with(|| "application/json".to_string());
out.remove("content-length");
Some(out)
}
#[cfg(test)]
mod tests {
use serde_json::json;
use super::super::credentials::KiroAuthConfig;
use super::{
build_kiro_provider_headers, build_kiro_provider_request_body,
supports_local_kiro_request_shape, KiroProviderHeadersInput,
};
#[test]
fn supports_empty_local_request_shape() {
assert!(supports_local_kiro_request_shape(None, None));
}
#[test]
fn rejects_unsupported_rule_shape() {
assert!(!supports_local_kiro_request_shape(
Some(&json!({"action":"set"})),
None
));
}
#[test]
fn supports_simple_header_and_body_rules() {
assert!(supports_local_kiro_request_shape(
Some(&json!([{"action":"set","key":"x-provider-extra","value":"1"}])),
Some(&json!([{"action":"set","path":"debugTag","value":true}]))
));
}
#[test]
fn wraps_claude_request_into_kiro_payload_before_body_rules() {
let auth_config = KiroAuthConfig {
auth_method: None,
refresh_token: Some("r".repeat(128)),
expires_at: None,
profile_arn: Some("arn:aws:bedrock:demo".to_string()),
region: None,
auth_region: None,
api_region: Some("us-east-1".to_string()),
client_id: None,
client_secret: None,
machine_id: Some("123e4567-e89b-12d3-a456-426614174000".to_string()),
kiro_version: None,
system_version: None,
node_version: None,
access_token: Some("cached-token".to_string()),
};
let payload = build_kiro_provider_request_body(
&json!({
"messages": [{"role":"user","content":"hello"}],
"max_tokens": 64
}),
"claude-sonnet-4-upstream",
&auth_config,
Some(&json!([
{"action":"set","path":"debugTag","value":"kiro-local"}
])),
)
.expect("payload should build");
assert!(payload.get("conversationState").is_some());
assert_eq!(
payload
.get("inferenceConfig")
.and_then(|value| value.get("maxTokens")),
Some(&json!(64))
);
assert_eq!(
payload.get("profileArn"),
Some(&json!("arn:aws:bedrock:demo"))
);
assert_eq!(payload.get("debugTag"), Some(&json!("kiro-local")));
}
#[test]
fn applies_header_rules_before_kiro_extra_headers() {
let auth_config = KiroAuthConfig {
auth_method: None,
refresh_token: Some("r".repeat(128)),
expires_at: None,
profile_arn: None,
region: None,
auth_region: None,
api_region: Some("us-east-1".to_string()),
client_id: None,
client_secret: None,
machine_id: None,
kiro_version: None,
system_version: None,
node_version: None,
access_token: Some("cached-token".to_string()),
};
let headers = build_kiro_provider_headers(KiroProviderHeadersInput {
headers: &http::HeaderMap::new(),
provider_request_body: &json!({"conversationState": {}}),
original_request_body: &json!({"messages": []}),
header_rules: Some(&json!([
{"action":"set","key":"accept","value":"text/plain"},
{"action":"set","key":"x-endpoint-tag","value":"kiro-local"}
])),
auth_header: "authorization",
auth_value: "Bearer cached-token",
auth_config: &auth_config,
machine_id: "machine-123",
})
.expect("headers should build");
assert_eq!(
headers.get("accept").map(String::as_str),
Some("application/vnd.amazon.eventstream")
);
assert_eq!(
headers.get("authorization").map(String::as_str),
Some("Bearer cached-token")
);
assert_eq!(
headers.get("x-endpoint-tag").map(String::as_str),
Some("kiro-local")
);
}
#[test]
fn omits_profile_arn_for_idc_auth() {
let auth_config = KiroAuthConfig {
auth_method: Some("identity_center".to_string()),
refresh_token: Some("r".repeat(128)),
expires_at: None,
profile_arn: Some("arn:aws:bedrock:demo".to_string()),
region: None,
auth_region: None,
api_region: Some("us-east-1".to_string()),
client_id: Some("cid".to_string()),
client_secret: Some("secret".to_string()),
machine_id: None,
kiro_version: None,
system_version: None,
node_version: None,
access_token: Some("cached-token".to_string()),
};
let payload = build_kiro_provider_request_body(
&json!({
"messages": [{"role":"user","content":"hello"}]
}),
"claude-sonnet-4-upstream",
&auth_config,
None,
)
.expect("payload should build");
assert!(payload.get("profileArn").is_none());
}
}