mirror of
https://github.com/hanzoai/dev.git
synced 2026-10-25 03:32:32 +00:00
feat(core): route FedRAMP auth and model metadata
Propagate FedRAMP account routing headers through ChatGPT, cloud tasks, and model provider HTTP clients. Also retain newer protocol/model metadata fields such as custom tool output names, search support, and extra speed tiers.
This commit is contained in:
@@ -96,6 +96,7 @@ pub struct Client {
|
||||
bearer_token: Option<String>,
|
||||
user_agent: Option<HeaderValue>,
|
||||
chatgpt_account_id: Option<String>,
|
||||
chatgpt_account_is_fedramp: bool,
|
||||
path_style: PathStyle,
|
||||
}
|
||||
|
||||
@@ -121,6 +122,7 @@ impl Client {
|
||||
bearer_token: None,
|
||||
user_agent: None,
|
||||
chatgpt_account_id: None,
|
||||
chatgpt_account_is_fedramp: false,
|
||||
path_style,
|
||||
})
|
||||
}
|
||||
@@ -142,6 +144,11 @@ impl Client {
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_fedramp_routing_header(mut self) -> Self {
|
||||
self.chatgpt_account_is_fedramp = true;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_path_style(mut self, style: PathStyle) -> Self {
|
||||
self.path_style = style;
|
||||
self
|
||||
@@ -166,6 +173,11 @@ impl Client {
|
||||
{
|
||||
h.insert(name, hv);
|
||||
}
|
||||
if self.chatgpt_account_is_fedramp
|
||||
&& let Ok(name) = HeaderName::from_bytes(b"X-OpenAI-Fedramp")
|
||||
{
|
||||
h.insert(name, HeaderValue::from_static("true"));
|
||||
}
|
||||
h
|
||||
}
|
||||
|
||||
|
||||
@@ -25,14 +25,16 @@ pub(crate) async fn chatgpt_get_request<T: DeserializeOwned>(
|
||||
anyhow::anyhow!("ChatGPT account ID not available, please re-run `code login`")
|
||||
});
|
||||
|
||||
let response = client
|
||||
let mut request = client
|
||||
.get(&url)
|
||||
.bearer_auth(&token.access_token)
|
||||
.header("chatgpt-account-id", account_id?)
|
||||
.header("Content-Type", "application/json")
|
||||
.send()
|
||||
.await
|
||||
.context("Failed to send request")?;
|
||||
.header("Content-Type", "application/json");
|
||||
if token.id_token.is_fedramp_account() {
|
||||
request = request.header("X-OpenAI-Fedramp", "true");
|
||||
}
|
||||
|
||||
let response = request.send().await.context("Failed to send request")?;
|
||||
|
||||
if response.status().is_success() {
|
||||
let result: T = response
|
||||
|
||||
@@ -47,6 +47,11 @@ impl HttpClient {
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_fedramp_routing_header(mut self) -> Self {
|
||||
self.backend = self.backend.clone().with_fedramp_routing_header();
|
||||
self
|
||||
}
|
||||
|
||||
fn tasks_api(&self) -> api::Tasks<'_> {
|
||||
api::Tasks::new(self)
|
||||
}
|
||||
|
||||
@@ -225,6 +225,10 @@ pub async fn run_main(cli: Cli, _code_linux_sandbox_exe: Option<PathBuf>) -> any
|
||||
append_info_log(format!("auth: set ChatGPT-Account-Id header: {acc}"));
|
||||
http = http.with_chatgpt_account_id(acc);
|
||||
}
|
||||
if auth.is_fedramp_account() {
|
||||
append_info_log("auth: set X-OpenAI-Fedramp header: true".to_string());
|
||||
http = http.with_fedramp_routing_header();
|
||||
}
|
||||
t
|
||||
}
|
||||
_ => {
|
||||
|
||||
@@ -265,6 +265,7 @@ mod tests {
|
||||
id_token: IdTokenInfo {
|
||||
email: Some(email.to_string()),
|
||||
chatgpt_plan_type: None,
|
||||
chatgpt_account_is_fedramp: false,
|
||||
raw_jwt: fake_jwt(email, "pro"),
|
||||
},
|
||||
access_token: "access".to_string(),
|
||||
|
||||
@@ -249,6 +249,11 @@ impl CodexAuth {
|
||||
.and_then(|t| t.id_token.chatgpt_plan_type.as_ref().map(|p| p.as_string()))
|
||||
}
|
||||
|
||||
pub fn is_fedramp_account(&self) -> bool {
|
||||
self.get_current_token_data()
|
||||
.is_some_and(|t| t.id_token.is_fedramp_account())
|
||||
}
|
||||
|
||||
pub fn supports_pro_only_models(&self) -> bool {
|
||||
self.mode.is_chatgpt()
|
||||
&& self
|
||||
@@ -1084,6 +1089,7 @@ mod tests {
|
||||
id_token: IdTokenInfo {
|
||||
email: Some("[email protected]".to_string()),
|
||||
chatgpt_plan_type: Some(PlanType::Known(KnownPlan::Pro)),
|
||||
chatgpt_account_is_fedramp: false,
|
||||
raw_jwt: fake_jwt,
|
||||
},
|
||||
access_token: "test-access-token".to_string(),
|
||||
@@ -1137,6 +1143,7 @@ mod tests {
|
||||
id_token: IdTokenInfo {
|
||||
email: Some("[email protected]".to_string()),
|
||||
chatgpt_plan_type: Some(PlanType::Known(KnownPlan::Pro)),
|
||||
chatgpt_account_is_fedramp: false,
|
||||
raw_jwt: fake_jwt,
|
||||
},
|
||||
access_token: "test-access-token".to_string(),
|
||||
|
||||
@@ -380,6 +380,7 @@ mod tests {
|
||||
id_token: IdTokenInfo {
|
||||
email: email.map(|s| s.to_string()),
|
||||
chatgpt_plan_type: None,
|
||||
chatgpt_account_is_fedramp: false,
|
||||
raw_jwt: fake_jwt(account_id, email, "pro"),
|
||||
},
|
||||
access_token: "access".to_string(),
|
||||
|
||||
@@ -318,7 +318,11 @@ pub(crate) async fn stream_chat_completions(
|
||||
});
|
||||
push_tool_call_message(&mut messages, tool_call, reasoning);
|
||||
}
|
||||
ResponseItem::CustomToolCallOutput { call_id, output } => {
|
||||
ResponseItem::CustomToolCallOutput {
|
||||
call_id,
|
||||
output,
|
||||
..
|
||||
} => {
|
||||
messages.push(json!({
|
||||
"role": "tool",
|
||||
"tool_call_id": call_id,
|
||||
|
||||
@@ -729,10 +729,18 @@ pub fn sanitize_items_for_compact(items: Vec<ResponseItem>) -> Vec<ResponseItem>
|
||||
input,
|
||||
})
|
||||
}
|
||||
ResponseItem::CustomToolCallOutput { call_id, output } => {
|
||||
ResponseItem::CustomToolCallOutput {
|
||||
call_id,
|
||||
name,
|
||||
output,
|
||||
} => {
|
||||
let output = truncate_for_compact(output.to_string(), COMPACT_TOOL_OUTPUT_MAX_BYTES);
|
||||
let output = FunctionCallOutputPayload::from_text(output);
|
||||
Some(ResponseItem::CustomToolCallOutput { call_id, output })
|
||||
Some(ResponseItem::CustomToolCallOutput {
|
||||
call_id,
|
||||
name,
|
||||
output,
|
||||
})
|
||||
}
|
||||
ResponseItem::Reasoning { id, summary, .. } => Some(ResponseItem::Reasoning {
|
||||
id,
|
||||
|
||||
@@ -2567,12 +2567,17 @@ async fn run_agent(sess: Arc<Session>, turn_context: Arc<TurnContext>, sub_id: S
|
||||
}
|
||||
(
|
||||
ResponseItem::CustomToolCall { .. },
|
||||
Some(ResponseInputItem::CustomToolCallOutput { call_id, output }),
|
||||
Some(ResponseInputItem::CustomToolCallOutput {
|
||||
call_id,
|
||||
name,
|
||||
output,
|
||||
}),
|
||||
) => {
|
||||
items_to_record_in_conversation_history.push(item.clone());
|
||||
items_to_record_in_conversation_history.push(
|
||||
ResponseItem::CustomToolCallOutput {
|
||||
call_id: call_id.clone(),
|
||||
name: name.clone(),
|
||||
output: output.clone(),
|
||||
},
|
||||
);
|
||||
@@ -3897,6 +3902,7 @@ async fn try_run_turn(
|
||||
})
|
||||
.map(|call_id| ResponseItem::CustomToolCallOutput {
|
||||
call_id: call_id.clone(),
|
||||
name: None,
|
||||
output: FunctionCallOutputPayload::from_text("aborted".to_string()),
|
||||
})
|
||||
.collect::<Vec<_>>()
|
||||
|
||||
@@ -103,6 +103,12 @@ pub struct ModelFamily {
|
||||
/// Responses API.
|
||||
pub supports_parallel_tool_calls: bool,
|
||||
|
||||
/// Additional speed tiers advertised by the backend for this model.
|
||||
pub additional_speed_tiers: Vec<String>,
|
||||
|
||||
/// Whether the backend says this model supports the native search tool.
|
||||
pub supports_search_tool: bool,
|
||||
|
||||
/// Prefer websocket transport for this model when supported by the provider.
|
||||
pub prefer_websockets: bool,
|
||||
|
||||
@@ -175,6 +181,8 @@ macro_rules! model_family {
|
||||
default_reasoning_effort: None,
|
||||
default_reasoning_summary: ReasoningSummary::Auto,
|
||||
supports_parallel_tool_calls: false,
|
||||
additional_speed_tiers: Vec::new(),
|
||||
supports_search_tool: false,
|
||||
prefer_websockets: false,
|
||||
uses_local_shell_tool: false,
|
||||
apply_patch_tool_type: None,
|
||||
@@ -460,6 +468,8 @@ pub fn derive_default_model_family(model: &str) -> ModelFamily {
|
||||
default_reasoning_effort: None,
|
||||
default_reasoning_summary: ReasoningSummary::Auto,
|
||||
supports_parallel_tool_calls: false,
|
||||
additional_speed_tiers: Vec::new(),
|
||||
supports_search_tool: false,
|
||||
prefer_websockets: false,
|
||||
uses_local_shell_tool: false,
|
||||
apply_patch_tool_type: None,
|
||||
|
||||
@@ -307,6 +307,9 @@ impl ModelProviderInfo {
|
||||
|
||||
if let Some(auth) = auth.as_ref() {
|
||||
builder = builder.bearer_auth(auth.get_token().await?);
|
||||
if auth.is_fedramp_account() {
|
||||
builder = builder.header("X-OpenAI-Fedramp", "true");
|
||||
}
|
||||
}
|
||||
|
||||
Ok(self.apply_http_headers(builder))
|
||||
@@ -339,6 +342,9 @@ impl ModelProviderInfo {
|
||||
|
||||
if let Some(auth) = auth.as_ref() {
|
||||
builder = builder.bearer_auth(auth.get_token().await?);
|
||||
if auth.is_fedramp_account() {
|
||||
builder = builder.header("X-OpenAI-Fedramp", "true");
|
||||
}
|
||||
}
|
||||
|
||||
Ok(self.apply_http_headers(builder))
|
||||
@@ -373,6 +379,9 @@ impl ModelProviderInfo {
|
||||
let mut builder = client.post(url);
|
||||
if let Some(auth) = auth.as_ref() {
|
||||
builder = builder.bearer_auth(auth.get_token().await?);
|
||||
if auth.is_fedramp_account() {
|
||||
builder = builder.header("X-OpenAI-Fedramp", "true");
|
||||
}
|
||||
}
|
||||
|
||||
Ok(self.apply_http_headers(builder))
|
||||
|
||||
@@ -424,6 +424,8 @@ fn apply_model_info_overrides_with_personality(
|
||||
.experimental_supported_tools
|
||||
.iter()
|
||||
.any(|tool| tool == IMAGE_GENERATION_TOOL);
|
||||
family.additional_speed_tiers = info.additional_speed_tiers.clone();
|
||||
family.supports_search_tool = info.supports_search_tool;
|
||||
|
||||
if let Some(limit) = info.auto_compact_token_limit() {
|
||||
family.set_auto_compact_token_limit(Some(limit));
|
||||
@@ -463,6 +465,7 @@ mod tests {
|
||||
visibility: ModelVisibility::None,
|
||||
supported_in_api: true,
|
||||
priority: 0,
|
||||
additional_speed_tiers: Vec::new(),
|
||||
availability_nux: None,
|
||||
upgrade: None,
|
||||
base_instructions: String::new(),
|
||||
@@ -481,6 +484,7 @@ mod tests {
|
||||
effective_context_window_percent: 95,
|
||||
experimental_supported_tools: Vec::new(),
|
||||
input_modalities: default_input_modalities(),
|
||||
supports_search_tool: false,
|
||||
prefer_websockets: false,
|
||||
used_fallback_model_metadata: false,
|
||||
}
|
||||
|
||||
@@ -42,6 +42,7 @@ pub struct IdTokenInfo {
|
||||
/// (e.g., "free", "plus", "pro", "business", "enterprise", "edu").
|
||||
/// (Note: values may vary by backend.)
|
||||
pub(crate) chatgpt_plan_type: Option<PlanType>,
|
||||
pub(crate) chatgpt_account_is_fedramp: bool,
|
||||
pub raw_jwt: String,
|
||||
}
|
||||
|
||||
@@ -52,6 +53,10 @@ impl IdTokenInfo {
|
||||
PlanType::Unknown(s) => s.clone(),
|
||||
})
|
||||
}
|
||||
|
||||
pub fn is_fedramp_account(&self) -> bool {
|
||||
self.chatgpt_account_is_fedramp
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||
@@ -94,6 +99,8 @@ struct IdClaims {
|
||||
struct AuthClaims {
|
||||
#[serde(default)]
|
||||
chatgpt_plan_type: Option<PlanType>,
|
||||
#[serde(default)]
|
||||
chatgpt_account_is_fedramp: bool,
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
@@ -143,6 +150,11 @@ pub fn parse_id_token(id_token: &str) -> Result<IdTokenInfo, IdTokenInfoError> {
|
||||
tracing::debug!(
|
||||
email = claims.email.as_deref().unwrap_or("<missing>"),
|
||||
chatgpt_plan_type = plan.as_deref().unwrap_or("unknown"),
|
||||
chatgpt_account_is_fedramp = claims
|
||||
.auth
|
||||
.as_ref()
|
||||
.map(|auth| auth.chatgpt_account_is_fedramp)
|
||||
.unwrap_or(false),
|
||||
"decoded ChatGPT id_token claims"
|
||||
);
|
||||
}
|
||||
@@ -150,6 +162,10 @@ pub fn parse_id_token(id_token: &str) -> Result<IdTokenInfo, IdTokenInfoError> {
|
||||
|
||||
Ok(IdTokenInfo {
|
||||
email,
|
||||
chatgpt_account_is_fedramp: auth
|
||||
.as_ref()
|
||||
.map(|claims| claims.chatgpt_account_is_fedramp)
|
||||
.unwrap_or(false),
|
||||
chatgpt_plan_type: auth.and_then(|a| a.chatgpt_plan_type),
|
||||
raw_jwt: id_token.to_string(),
|
||||
})
|
||||
@@ -205,6 +221,7 @@ mod tests {
|
||||
let info = parse_id_token(&fake_jwt).expect("should parse");
|
||||
assert_eq!(info.email.as_deref(), Some("[email protected]"));
|
||||
assert_eq!(info.get_chatgpt_plan_type().as_deref(), Some("Pro"));
|
||||
assert!(!info.is_fedramp_account());
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -232,6 +249,38 @@ mod tests {
|
||||
let info = parse_id_token(&fake_jwt).expect("should parse");
|
||||
assert!(info.email.is_none());
|
||||
assert!(info.get_chatgpt_plan_type().is_none());
|
||||
assert!(!info.is_fedramp_account());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn id_token_info_parses_fedramp_flag() {
|
||||
#[derive(Serialize)]
|
||||
struct Header {
|
||||
alg: &'static str,
|
||||
typ: &'static str,
|
||||
}
|
||||
let header = Header {
|
||||
alg: "none",
|
||||
typ: "JWT",
|
||||
};
|
||||
let payload = serde_json::json!({
|
||||
"https://api.openai.com/auth": {
|
||||
"chatgpt_account_is_fedramp": true
|
||||
}
|
||||
});
|
||||
|
||||
fn b64url_no_pad(bytes: &[u8]) -> String {
|
||||
base64::engine::general_purpose::URL_SAFE_NO_PAD.encode(bytes)
|
||||
}
|
||||
|
||||
let header_b64 = b64url_no_pad(&serde_json::to_vec(&header).unwrap());
|
||||
let payload_b64 = b64url_no_pad(&serde_json::to_vec(&payload).unwrap());
|
||||
let signature_b64 = b64url_no_pad(b"sig");
|
||||
let fake_jwt = format!("{header_b64}.{payload_b64}.{signature_b64}");
|
||||
|
||||
let info = parse_id_token(&fake_jwt).expect("should parse");
|
||||
|
||||
assert!(info.is_fedramp_account());
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
@@ -209,6 +209,9 @@ pub enum ResponseInputItem {
|
||||
},
|
||||
CustomToolCallOutput {
|
||||
call_id: String,
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
#[ts(optional)]
|
||||
name: Option<String>,
|
||||
output: FunctionCallOutputPayload,
|
||||
},
|
||||
ToolSearchOutput {
|
||||
@@ -342,6 +345,9 @@ pub enum ResponseItem {
|
||||
},
|
||||
CustomToolCallOutput {
|
||||
call_id: String,
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
#[ts(optional)]
|
||||
name: Option<String>,
|
||||
output: FunctionCallOutputPayload,
|
||||
},
|
||||
ToolSearchOutput {
|
||||
@@ -854,9 +860,15 @@ impl From<ResponseInputItem> for ResponseItem {
|
||||
};
|
||||
Self::FunctionCallOutput { call_id, output }
|
||||
}
|
||||
ResponseInputItem::CustomToolCallOutput { call_id, output } => {
|
||||
Self::CustomToolCallOutput { call_id, output }
|
||||
}
|
||||
ResponseInputItem::CustomToolCallOutput {
|
||||
call_id,
|
||||
name,
|
||||
output,
|
||||
} => Self::CustomToolCallOutput {
|
||||
call_id,
|
||||
name,
|
||||
output,
|
||||
},
|
||||
ResponseInputItem::ToolSearchOutput {
|
||||
call_id,
|
||||
status,
|
||||
@@ -1677,6 +1689,7 @@ mod tests {
|
||||
fn serializes_custom_tool_image_outputs_as_array() -> Result<()> {
|
||||
let item = ResponseInputItem::CustomToolCallOutput {
|
||||
call_id: "call1".into(),
|
||||
name: None,
|
||||
output: FunctionCallOutputPayload::from_content_items(vec![
|
||||
FunctionCallOutputContentItem::InputImage {
|
||||
image_url: "data:image/png;base64,BASE64".into(),
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
//! are used to preserve compatibility when older payloads omit newly introduced attributes.
|
||||
|
||||
use std::collections::HashMap;
|
||||
use std::str::FromStr;
|
||||
|
||||
use schemars::JsonSchema;
|
||||
use serde::Deserialize;
|
||||
@@ -19,6 +20,7 @@ use crate::config_types::ReasoningSummary;
|
||||
use crate::config_types::Verbosity;
|
||||
|
||||
const PERSONALITY_PLACEHOLDER: &str = "{{ personality }}";
|
||||
pub const SPEED_TIER_FAST: &str = "fast";
|
||||
|
||||
/// See https://platform.openai.com/docs/guides/reasoning?api-mode=responses#get-started-with-reasoning
|
||||
#[derive(
|
||||
@@ -48,6 +50,15 @@ pub enum ReasoningEffort {
|
||||
XHigh,
|
||||
}
|
||||
|
||||
impl FromStr for ReasoningEffort {
|
||||
type Err = String;
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
serde_json::from_value(serde_json::Value::String(s.to_string()))
|
||||
.map_err(|_| format!("invalid reasoning_effort: {s}"))
|
||||
}
|
||||
}
|
||||
|
||||
/// Canonical user-input modality tags advertised by a model.
|
||||
#[derive(
|
||||
Debug,
|
||||
@@ -122,6 +133,9 @@ pub struct ModelPreset {
|
||||
/// Whether this model supports personality-specific instructions.
|
||||
#[serde(default)]
|
||||
pub supports_personality: bool,
|
||||
/// Additional speed tiers this model can run with beyond the standard path.
|
||||
#[serde(default)]
|
||||
pub additional_speed_tiers: Vec<String>,
|
||||
/// Whether this is the default model for new users.
|
||||
pub is_default: bool,
|
||||
/// recommended upgrade model
|
||||
@@ -242,6 +256,8 @@ pub struct ModelInfo {
|
||||
pub visibility: ModelVisibility,
|
||||
pub supported_in_api: bool,
|
||||
pub priority: i32,
|
||||
#[serde(default)]
|
||||
pub additional_speed_tiers: Vec<String>,
|
||||
pub availability_nux: Option<ModelAvailabilityNux>,
|
||||
pub upgrade: Option<ModelInfoUpgrade>,
|
||||
pub base_instructions: String,
|
||||
@@ -274,6 +290,8 @@ pub struct ModelInfo {
|
||||
/// Input modalities accepted by the backend for this model.
|
||||
#[serde(default = "default_input_modalities")]
|
||||
pub input_modalities: Vec<InputModality>,
|
||||
#[serde(default)]
|
||||
pub supports_search_tool: bool,
|
||||
/// When true, this model should use websocket transport even when websocket features are off.
|
||||
#[serde(default)]
|
||||
pub prefer_websockets: bool,
|
||||
@@ -419,6 +437,7 @@ impl From<ModelInfo> for ModelPreset {
|
||||
.unwrap_or(ReasoningEffort::None),
|
||||
supported_reasoning_efforts: info.supported_reasoning_levels.clone(),
|
||||
supports_personality,
|
||||
additional_speed_tiers: info.additional_speed_tiers,
|
||||
is_default: false, // default is the highest priority available model
|
||||
upgrade: info.upgrade.as_ref().map(|upgrade| ModelUpgrade {
|
||||
id: upgrade.model.clone(),
|
||||
@@ -440,6 +459,12 @@ impl From<ModelInfo> for ModelPreset {
|
||||
}
|
||||
|
||||
impl ModelPreset {
|
||||
pub fn supports_fast_mode(&self) -> bool {
|
||||
self.additional_speed_tiers
|
||||
.iter()
|
||||
.any(|tier| tier == SPEED_TIER_FAST)
|
||||
}
|
||||
|
||||
/// Filter models based on authentication mode.
|
||||
///
|
||||
/// In ChatGPT mode, all models are visible. Otherwise, only API-supported models are shown.
|
||||
@@ -518,6 +543,7 @@ mod tests {
|
||||
visibility: ModelVisibility::List,
|
||||
supported_in_api: true,
|
||||
priority: 1,
|
||||
additional_speed_tiers: Vec::new(),
|
||||
availability_nux: None,
|
||||
upgrade: None,
|
||||
base_instructions: "base".to_string(),
|
||||
@@ -536,6 +562,7 @@ mod tests {
|
||||
effective_context_window_percent: 95,
|
||||
experimental_supported_tools: vec![],
|
||||
input_modalities: default_input_modalities(),
|
||||
supports_search_tool: false,
|
||||
prefer_websockets: false,
|
||||
used_fallback_model_metadata: false,
|
||||
}
|
||||
@@ -743,12 +770,14 @@ mod tests {
|
||||
"effective_context_window_percent": 95,
|
||||
"experimental_supported_tools": [],
|
||||
"input_modalities": ["text", "image"],
|
||||
"supports_search_tool": false,
|
||||
"prefer_websockets": false
|
||||
}))
|
||||
.expect("deserialize model info");
|
||||
|
||||
assert_eq!(model.availability_nux, None);
|
||||
assert!(!model.supports_image_detail_original);
|
||||
assert!(!model.supports_search_tool);
|
||||
assert_eq!(model.web_search_tool_type, WebSearchToolType::Text);
|
||||
}
|
||||
|
||||
@@ -758,6 +787,7 @@ mod tests {
|
||||
availability_nux: Some(ModelAvailabilityNux {
|
||||
message: "Try Spark.".to_string(),
|
||||
}),
|
||||
additional_speed_tiers: vec![SPEED_TIER_FAST.to_string()],
|
||||
..test_model(None)
|
||||
});
|
||||
|
||||
@@ -767,5 +797,20 @@ mod tests {
|
||||
message: "Try Spark.".to_string(),
|
||||
})
|
||||
);
|
||||
assert!(preset.supports_fast_mode());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn reasoning_effort_from_str_accepts_known_values() {
|
||||
assert_eq!("high".parse(), Ok(ReasoningEffort::High));
|
||||
assert_eq!("minimal".parse(), Ok(ReasoningEffort::Minimal));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn reasoning_effort_from_str_rejects_unknown_values() {
|
||||
assert_eq!(
|
||||
"unsupported".parse::<ReasoningEffort>(),
|
||||
Err("invalid reasoning_effort: unsupported".to_string())
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -32,6 +32,7 @@ struct CloudTasksConfig {
|
||||
base_url: String,
|
||||
token: Option<String>,
|
||||
account_id: Option<String>,
|
||||
is_fedramp_account: bool,
|
||||
use_mock: bool,
|
||||
}
|
||||
|
||||
@@ -196,6 +197,9 @@ fn build_backend(config: &CloudTasksConfig) -> Result<Arc<dyn CloudBackend>> {
|
||||
if let Some(account) = &config.account_id {
|
||||
client = client.with_chatgpt_account_id(account.clone());
|
||||
}
|
||||
if config.is_fedramp_account {
|
||||
client = client.with_fedramp_routing_header();
|
||||
}
|
||||
Ok(Arc::new(client))
|
||||
}
|
||||
|
||||
@@ -212,6 +216,7 @@ async fn load_config() -> Result<CloudTasksConfig> {
|
||||
base_url,
|
||||
token: None,
|
||||
account_id: None,
|
||||
is_fedramp_account: false,
|
||||
use_mock,
|
||||
});
|
||||
}
|
||||
@@ -240,6 +245,7 @@ async fn load_config() -> Result<CloudTasksConfig> {
|
||||
base_url,
|
||||
token: Some(token),
|
||||
account_id,
|
||||
is_fedramp_account: auth.is_fedramp_account(),
|
||||
use_mock: false,
|
||||
})
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user