AGC 官方 LLM Router 账号链路与流式联网输出 #242
@@ -1922,19 +1922,25 @@ impl CodexAppServerConnection {
|
||||
"创建 Codex app-server 临时目录失败:{error}"
|
||||
))
|
||||
})?;
|
||||
let direct_provider_route = match credential {
|
||||
let (direct_provider_route, main_site_upstream) = match credential {
|
||||
CodexAppServerCredential::PlatformSession {
|
||||
api_base_url,
|
||||
access_token,
|
||||
..
|
||||
} => Some((
|
||||
format!("{}/api/llm", api_base_url.trim_end_matches('/')),
|
||||
access_token.clone(),
|
||||
)),
|
||||
_ => (workspace_mode == CodexAppServerWorkspaceMode::DirectProject)
|
||||
.then(|| credential.direct_provider_route(llm))
|
||||
.flatten()
|
||||
.map(|(base_url, api_key)| (base_url.to_string(), api_key.to_string())),
|
||||
} => (
|
||||
Some((
|
||||
format!("{}/api/llm", api_base_url.trim_end_matches('/')),
|
||||
access_token.clone(),
|
||||
)),
|
||||
true,
|
||||
),
|
||||
_ => (
|
||||
(workspace_mode == CodexAppServerWorkspaceMode::DirectProject)
|
||||
.then(|| credential.direct_provider_route(llm))
|
||||
.flatten()
|
||||
.map(|(base_url, api_key)| (base_url.to_string(), api_key.to_string())),
|
||||
false,
|
||||
),
|
||||
};
|
||||
let remote_control_disable_reason =
|
||||
credential.remote_control_disable_reason(direct_provider_route.is_some());
|
||||
@@ -2024,7 +2030,7 @@ impl CodexAppServerConnection {
|
||||
};
|
||||
let provider_proxy = if let Some((base_url, api_key)) = direct_provider_route.as_ref() {
|
||||
Some(
|
||||
start_codex_provider_proxy(base_url, api_key)
|
||||
start_codex_provider_proxy(base_url, api_key, main_site_upstream)
|
||||
.await
|
||||
.map_err(platform_llm::LlmError::InvalidConfig)?,
|
||||
)
|
||||
@@ -4966,7 +4972,7 @@ mod tests {
|
||||
#[tokio::test]
|
||||
async fn direct_project_command_receives_only_provider_proxy_session_token() {
|
||||
let provider_key = "fixture-upstream-provider-secret";
|
||||
let proxy = start_codex_provider_proxy("http://127.0.0.1:9", provider_key)
|
||||
let proxy = start_codex_provider_proxy("http://127.0.0.1:9", provider_key, false)
|
||||
.await
|
||||
.expect("start credential broker");
|
||||
let mut command = tokio::process::Command::new("fixture");
|
||||
|
||||
@@ -9,12 +9,15 @@ use std::sync::Arc;
|
||||
pub(crate) const CODEX_PROVIDER_PROXY_PROTOCOL: &str = "genarrative-codex-provider-proxy.v1";
|
||||
|
||||
const CODEX_PROVIDER_PROXY_MAX_REQUEST_BYTES: usize = 32 * 1024 * 1024;
|
||||
const AGC_CLIENT_MARKER_HEADER: &str = "x-genarrative-client";
|
||||
const AGC_CLIENT_MARKER_VALUE: &str = "agc";
|
||||
|
||||
#[derive(Clone)]
|
||||
struct CodexProviderProxyState {
|
||||
upstream_base_url: String,
|
||||
upstream_bearer_token: String,
|
||||
downstream_bearer_token: String,
|
||||
main_site_upstream: bool,
|
||||
client: reqwest::Client,
|
||||
}
|
||||
|
||||
@@ -137,10 +140,14 @@ async fn proxy_codex_provider_request(
|
||||
headers.append(name.clone(), value.clone());
|
||||
}
|
||||
}
|
||||
headers.insert(
|
||||
axum::http::HeaderName::from_static("x-genarrative-client"),
|
||||
axum::http::HeaderValue::from_static("agc"),
|
||||
);
|
||||
// 仅 AGC 主站 `/api/llm` 路由需要携带保留的客户端标记,供服务端校验模型
|
||||
// 方案;通用 Provider/凭据桥接不得把该标记外发给第三方上游。
|
||||
if state.main_site_upstream {
|
||||
headers.insert(
|
||||
axum::http::HeaderName::from_static(AGC_CLIENT_MARKER_HEADER),
|
||||
axum::http::HeaderValue::from_static(AGC_CLIENT_MARKER_VALUE),
|
||||
);
|
||||
}
|
||||
let upstream_authorization = match format!("Bearer {}", state.upstream_bearer_token).parse() {
|
||||
Ok(value) => value,
|
||||
Err(_) => {
|
||||
@@ -193,6 +200,7 @@ async fn proxy_codex_provider_request(
|
||||
pub(crate) async fn start_codex_provider_proxy(
|
||||
upstream_base_url: &str,
|
||||
upstream_bearer_token: &str,
|
||||
main_site_upstream: bool,
|
||||
) -> Result<CodexProviderProxy, String> {
|
||||
let upstream_base_url = normalize_codex_provider_upstream(upstream_base_url)?;
|
||||
let upstream_bearer_token = upstream_bearer_token.trim();
|
||||
@@ -223,6 +231,7 @@ pub(crate) async fn start_codex_provider_proxy(
|
||||
upstream_base_url,
|
||||
upstream_bearer_token: upstream_bearer_token.to_string(),
|
||||
downstream_bearer_token: downstream_bearer_token.clone(),
|
||||
main_site_upstream,
|
||||
client,
|
||||
});
|
||||
let app = Router::new()
|
||||
@@ -269,6 +278,25 @@ mod tests {
|
||||
.expect("fake response")
|
||||
}
|
||||
|
||||
async fn fake_main_site_upstream(
|
||||
State(calls): State<Arc<AtomicUsize>>,
|
||||
headers: HeaderMap,
|
||||
body: axum::body::Bytes,
|
||||
) -> Response<Body> {
|
||||
calls.fetch_add(1, Ordering::SeqCst);
|
||||
assert_eq!(
|
||||
headers
|
||||
.get(AGC_CLIENT_MARKER_HEADER)
|
||||
.and_then(|value| value.to_str().ok()),
|
||||
Some(AGC_CLIENT_MARKER_VALUE)
|
||||
);
|
||||
Response::builder()
|
||||
.status(StatusCode::OK)
|
||||
.header("content-type", "application/json")
|
||||
.body(Body::from(body))
|
||||
.expect("fake main-site response")
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn loopback_proxy_strips_false_codex_limit_headers_and_requires_bearer() {
|
||||
let calls = Arc::new(AtomicUsize::new(0));
|
||||
@@ -285,6 +313,7 @@ mod tests {
|
||||
let proxy = start_codex_provider_proxy(
|
||||
&format!("http://127.0.0.1:{}", address.port()),
|
||||
"fixture-provider-key",
|
||||
false,
|
||||
)
|
||||
.await
|
||||
.expect("start provider proxy");
|
||||
@@ -328,4 +357,37 @@ mod tests {
|
||||
assert_eq!(calls.load(Ordering::SeqCst), 1);
|
||||
upstream_task.abort();
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn loopback_proxy_adds_main_site_marker_only_when_bridging_the_agc_route() {
|
||||
let calls = Arc::new(AtomicUsize::new(0));
|
||||
let listener = tokio::net::TcpListener::bind((std::net::Ipv4Addr::LOCALHOST, 0))
|
||||
.await
|
||||
.expect("bind upstream");
|
||||
let address = listener.local_addr().expect("upstream address");
|
||||
let app = Router::new()
|
||||
.route("/responses", post(fake_main_site_upstream))
|
||||
.with_state(Arc::clone(&calls));
|
||||
let upstream_task = tokio::spawn(async move {
|
||||
let _ = axum::serve(listener, app).await;
|
||||
});
|
||||
let proxy = start_codex_provider_proxy(
|
||||
&format!("http://127.0.0.1:{}", address.port()),
|
||||
"fixture-provider-key",
|
||||
true,
|
||||
)
|
||||
.await
|
||||
.expect("start main-site provider proxy");
|
||||
|
||||
let accepted = reqwest::Client::new()
|
||||
.post(format!("{}/responses", proxy.base_url()))
|
||||
.bearer_auth(proxy.downstream_bearer_token())
|
||||
.body("{\"input\":\"ok\"}")
|
||||
.send()
|
||||
.await
|
||||
.expect("accepted main-site response");
|
||||
assert_eq!(accepted.status(), StatusCode::OK);
|
||||
assert_eq!(calls.load(Ordering::SeqCst), 1);
|
||||
upstream_task.abort();
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user