Skip to content

Commit f0ac2ba

Browse files
slin1237key4ng
authored andcommitted
[model-gateway] move oai header util to router header util (sgl-project#14441)
Co-authored-by: key4ng <rukeyang@gmail.com>
1 parent fd791dd commit f0ac2ba

File tree

4 files changed

+92
-102
lines changed

4 files changed

+92
-102
lines changed

sgl-router/src/routers/header_utils.rs

Lines changed: 91 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,8 @@
1-
use axum::{body::Body, extract::Request, http::HeaderMap};
1+
use axum::{
2+
body::Body,
3+
extract::Request,
4+
http::{HeaderMap, HeaderValue},
5+
};
26

37
/// Copy request headers to a Vec of name-value string pairs
48
/// Used for forwarding headers to backend workers
@@ -92,3 +96,89 @@ pub fn apply_request_headers(
9296

9397
request_builder
9498
}
99+
100+
/// API provider types for provider-specific header handling
101+
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
102+
pub enum ApiProvider {
103+
Anthropic,
104+
Xai,
105+
OpenAi,
106+
Gemini,
107+
Generic,
108+
}
109+
110+
impl ApiProvider {
111+
/// Detect provider type from URL
112+
pub fn from_url(url: &str) -> Self {
113+
if url.contains("anthropic") {
114+
ApiProvider::Anthropic
115+
} else if url.contains("x.ai") {
116+
ApiProvider::Xai
117+
} else if url.contains("openai.com") {
118+
ApiProvider::OpenAi
119+
} else if url.contains("googleapis.com") {
120+
ApiProvider::Gemini
121+
} else {
122+
ApiProvider::Generic
123+
}
124+
}
125+
}
126+
127+
/// Apply provider-specific headers to request
128+
pub fn apply_provider_headers(
129+
mut req: reqwest::RequestBuilder,
130+
url: &str,
131+
auth_header: Option<&HeaderValue>,
132+
) -> reqwest::RequestBuilder {
133+
let provider = ApiProvider::from_url(url);
134+
135+
match provider {
136+
ApiProvider::Anthropic => {
137+
// Anthropic requires x-api-key instead of Authorization
138+
// Extract Bearer token and use as x-api-key
139+
if let Some(auth) = auth_header {
140+
if let Ok(auth_str) = auth.to_str() {
141+
let api_key = auth_str.strip_prefix("Bearer ").unwrap_or(auth_str);
142+
req = req
143+
.header("x-api-key", api_key)
144+
.header("anthropic-version", "2023-06-01");
145+
}
146+
}
147+
}
148+
ApiProvider::Gemini | ApiProvider::Xai | ApiProvider::OpenAi | ApiProvider::Generic => {
149+
// Standard OpenAI-compatible: use Authorization header as-is
150+
if let Some(auth) = auth_header {
151+
req = req.header("Authorization", auth);
152+
}
153+
}
154+
}
155+
156+
req
157+
}
158+
159+
/// Extract auth header with passthrough semantics.
160+
///
161+
/// Passthrough mode: User's Authorization header takes priority.
162+
/// Fallback: Worker's API key is used only if user didn't provide auth.
163+
///
164+
/// This enables use cases where:
165+
/// 1. Users send their own API keys (multi-tenant, BYOK)
166+
/// 2. Router has a default key for users who don't provide one
167+
pub fn extract_auth_header(
168+
headers: Option<&HeaderMap>,
169+
worker_api_key: &Option<String>,
170+
) -> Option<HeaderValue> {
171+
// Passthrough: Try user's auth header first
172+
let user_auth = headers.and_then(|h| {
173+
h.get("authorization")
174+
.or_else(|| h.get("Authorization"))
175+
.cloned()
176+
});
177+
178+
// Return user's auth if provided, otherwise use worker's API key
179+
user_auth.or_else(|| {
180+
worker_api_key
181+
.as_ref()
182+
.and_then(|k| HeaderValue::from_str(&format!("Bearer {}", k)).ok())
183+
})
184+
}

sgl-router/src/routers/openai/conversations.rs

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -22,10 +22,6 @@ use crate::{
2222
protocols::responses::{generate_id, ResponseInput, ResponsesRequest},
2323
};
2424

25-
// ============================================================================
26-
// Persistence Operations (OpenAI-specific)
27-
// ============================================================================
28-
2925
/// Persist conversation items to storage
3026
///
3127
/// This function:

sgl-router/src/routers/openai/router.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,6 @@ use super::{
3030
provider::ProviderRegistry,
3131
responses::{mask_tools_as_mcp, patch_streaming_response_json},
3232
streaming::handle_streaming_response,
33-
utils::{apply_provider_headers, extract_auth_header},
3433
};
3534
use crate::{
3635
app_context::AppContext,
@@ -48,6 +47,7 @@ use crate::{
4847
ResponsesGetParams, ResponsesRequest,
4948
},
5049
},
50+
routers::header_utils::{apply_provider_headers, extract_auth_header},
5151
};
5252

5353
pub struct OpenAIRouter {

sgl-router/src/routers/openai/utils.rs

Lines changed: 0 additions & 96 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,6 @@
22
33
use std::collections::HashMap;
44

5-
use axum::http::HeaderValue;
6-
75
// ============================================================================
86
// SSE Event Type Constants
97
// ============================================================================
@@ -95,100 +93,6 @@ impl OutputIndexMapper {
9593
}
9694
}
9795

98-
// ============================================================================
99-
// Provider Detection and Header Handling
100-
// ============================================================================
101-
102-
/// API provider types
103-
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
104-
pub enum ApiProvider {
105-
Anthropic,
106-
Xai,
107-
OpenAi,
108-
Gemini,
109-
Generic,
110-
}
111-
112-
impl ApiProvider {
113-
/// Detect provider type from URL
114-
pub fn from_url(url: &str) -> Self {
115-
if url.contains("anthropic") {
116-
ApiProvider::Anthropic
117-
} else if url.contains("x.ai") {
118-
ApiProvider::Xai
119-
} else if url.contains("openai.com") {
120-
ApiProvider::OpenAi
121-
} else if url.contains("googleapis.com") {
122-
ApiProvider::Gemini
123-
} else {
124-
ApiProvider::Generic
125-
}
126-
}
127-
}
128-
129-
/// Apply provider-specific headers to request
130-
pub fn apply_provider_headers(
131-
mut req: reqwest::RequestBuilder,
132-
url: &str,
133-
auth_header: Option<&HeaderValue>,
134-
) -> reqwest::RequestBuilder {
135-
let provider = ApiProvider::from_url(url);
136-
137-
match provider {
138-
ApiProvider::Anthropic => {
139-
// Anthropic requires x-api-key instead of Authorization
140-
// Extract Bearer token and use as x-api-key
141-
if let Some(auth) = auth_header {
142-
if let Ok(auth_str) = auth.to_str() {
143-
let api_key = auth_str.strip_prefix("Bearer ").unwrap_or(auth_str);
144-
req = req
145-
.header("x-api-key", api_key)
146-
.header("anthropic-version", "2023-06-01");
147-
}
148-
}
149-
}
150-
ApiProvider::Gemini | ApiProvider::Xai | ApiProvider::OpenAi | ApiProvider::Generic => {
151-
// Standard OpenAI-compatible: use Authorization header as-is
152-
if let Some(auth) = auth_header {
153-
req = req.header("Authorization", auth);
154-
}
155-
}
156-
}
157-
158-
req
159-
}
160-
161-
// ============================================================================
162-
// Auth Header Resolution
163-
// ============================================================================
164-
165-
/// Extract auth header with passthrough semantics.
166-
///
167-
/// Passthrough mode: User's Authorization header takes priority.
168-
/// Fallback: Worker's API key is used only if user didn't provide auth.
169-
///
170-
/// This enables use cases where:
171-
/// 1. Users send their own API keys (multi-tenant, BYOK)
172-
/// 2. Router has a default key for users who don't provide one
173-
pub fn extract_auth_header(
174-
headers: Option<&http::HeaderMap>,
175-
worker_api_key: &Option<String>,
176-
) -> Option<HeaderValue> {
177-
// Passthrough: Try user's auth header first
178-
let user_auth = headers.and_then(|h| {
179-
h.get("authorization")
180-
.or_else(|| h.get("Authorization"))
181-
.cloned()
182-
});
183-
184-
// Return user's auth if provided, otherwise use worker's API key
185-
user_auth.or_else(|| {
186-
worker_api_key
187-
.as_ref()
188-
.and_then(|k| HeaderValue::from_str(&format!("Bearer {}", k)).ok())
189-
})
190-
}
191-
19296
// ============================================================================
19397
// Re-export FunctionCallInProgress from mcp module
19498
// ============================================================================

0 commit comments

Comments
 (0)