mirror of
https://github.com/fawney19/Aether.git
synced 2026-10-03 16:07:46 +08:00
fix: extract nested provider response models
This commit is contained in:
@@ -1,3 +1,4 @@
|
|||||||
|
use aether_ai_formats::normalize_api_format_alias;
|
||||||
use async_trait::async_trait;
|
use async_trait::async_trait;
|
||||||
use chrono::{DateTime, Utc};
|
use chrono::{DateTime, Utc};
|
||||||
use serde_json::Value;
|
use serde_json::Value;
|
||||||
@@ -120,7 +121,7 @@ pub fn normalize_provider_service_tier(value: &str) -> Option<String> {
|
|||||||
Some(value.to_ascii_lowercase())
|
Some(value.to_ascii_lowercase())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 清洗响应体顶层 `model`,保留大小写,只去除首尾空白。
|
/// 清洗模型名称,保留大小写,只去除首尾空白。
|
||||||
pub fn normalize_provider_response_model(value: &str) -> Option<String> {
|
pub fn normalize_provider_response_model(value: &str) -> Option<String> {
|
||||||
let value = value.trim();
|
let value = value.trim();
|
||||||
if value.is_empty() || value.len() > 256 {
|
if value.is_empty() || value.len() > 256 {
|
||||||
@@ -129,12 +130,117 @@ pub fn normalize_provider_response_model(value: &str) -> Option<String> {
|
|||||||
Some(value.to_string())
|
Some(value.to_string())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn extract_model_at_paths(value: &Value, paths: &[&[&str]]) -> Option<String> {
|
||||||
|
paths.iter().find_map(|path| {
|
||||||
|
let value = path
|
||||||
|
.iter()
|
||||||
|
.try_fold(value, |current, key| current.as_object()?.get(*key))?;
|
||||||
|
value.as_str().and_then(normalize_provider_response_model)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn response_model_paths(provider_api_format: Option<&str>) -> &'static [&'static [&'static str]] {
|
||||||
|
match normalize_api_format_alias(provider_api_format.unwrap_or_default()).as_str() {
|
||||||
|
"gemini:generate_content" => {
|
||||||
|
// Gemini 原生响应使用 modelVersion;部分网关会改写为 model。
|
||||||
|
&[&["modelVersion"], &["model_version"], &["model"]]
|
||||||
|
}
|
||||||
|
"gemini:embedding" => {
|
||||||
|
// Gemini Embedding 可能返回 model、modelVersion 或 Vertex 的 deployedModelId。
|
||||||
|
&[
|
||||||
|
&["model"],
|
||||||
|
&["modelVersion"],
|
||||||
|
&["model_version"],
|
||||||
|
&["deployedModelId"],
|
||||||
|
&["deployed_model_id"],
|
||||||
|
]
|
||||||
|
}
|
||||||
|
"gemini:interactions" => {
|
||||||
|
// Interactions 请求既可能叫 model,也可能叫 agent;响应优先读取 model。
|
||||||
|
&[
|
||||||
|
&["model"],
|
||||||
|
&["modelVersion"],
|
||||||
|
&["model_version"],
|
||||||
|
&["agent"],
|
||||||
|
]
|
||||||
|
}
|
||||||
|
_ => &[&["model"]],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn extract_model_from_known_response_wrappers(
|
||||||
|
response_body: &Value,
|
||||||
|
paths: &[&[&str]],
|
||||||
|
) -> Option<String> {
|
||||||
|
// 只展开协议中已知的 response/chunks 包装,避免在候选内容、工具参数等任意嵌套
|
||||||
|
// 对象中搜索同名字段,误把 role="model" 一类内容当成响应模型。
|
||||||
|
extract_model_at_paths(response_body, paths)
|
||||||
|
.or_else(|| {
|
||||||
|
response_body
|
||||||
|
.get("response")
|
||||||
|
.and_then(|response| extract_model_at_paths(response, paths))
|
||||||
|
})
|
||||||
|
.or_else(|| {
|
||||||
|
response_body
|
||||||
|
.get("chunks")
|
||||||
|
.and_then(Value::as_array)
|
||||||
|
.and_then(|chunks| {
|
||||||
|
chunks.iter().rev().find_map(|chunk| {
|
||||||
|
extract_model_at_paths(chunk, paths).or_else(|| {
|
||||||
|
chunk
|
||||||
|
.get("response")
|
||||||
|
.and_then(|response| extract_model_at_paths(response, paths))
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.or_else(|| {
|
||||||
|
response_body
|
||||||
|
.get("response")
|
||||||
|
.and_then(|response| response.get("chunks"))
|
||||||
|
.and_then(Value::as_array)
|
||||||
|
.and_then(|chunks| {
|
||||||
|
chunks.iter().rev().find_map(|chunk| {
|
||||||
|
extract_model_at_paths(chunk, paths).or_else(|| {
|
||||||
|
chunk
|
||||||
|
.get("response")
|
||||||
|
.and_then(|response| extract_model_at_paths(response, paths))
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn extract_provider_model_from_response_body(
|
||||||
|
response_body: &Value,
|
||||||
|
provider_api_format: Option<&str>,
|
||||||
|
) -> Option<String> {
|
||||||
|
extract_model_from_known_response_wrappers(
|
||||||
|
response_body,
|
||||||
|
response_model_paths(provider_api_format),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn extract_provider_model_from_request_body(
|
||||||
|
request_body: &Value,
|
||||||
|
request_api_format: Option<&str>,
|
||||||
|
) -> Option<String> {
|
||||||
|
let paths: &[&[&str]] =
|
||||||
|
match normalize_api_format_alias(request_api_format.unwrap_or_default()).as_str() {
|
||||||
|
"gemini:interactions" => &[&["model"], &["agent"]],
|
||||||
|
_ => &[&["model"]],
|
||||||
|
};
|
||||||
|
extract_model_at_paths(request_body, paths)
|
||||||
|
}
|
||||||
|
|
||||||
/// 只有请求体和响应体都可作为完整事实时,才计算响应模型,避免用截断内容猜测。
|
/// 只有请求体和响应体都可作为完整事实时,才计算响应模型,避免用截断内容猜测。
|
||||||
pub fn extract_provider_response_model_from_bodies(
|
pub fn extract_provider_response_model_from_bodies(
|
||||||
request_body: Option<&Value>,
|
request_body: Option<&Value>,
|
||||||
request_body_state: Option<UsageBodyCaptureState>,
|
request_body_state: Option<UsageBodyCaptureState>,
|
||||||
|
request_api_format: Option<&str>,
|
||||||
response_body: Option<&Value>,
|
response_body: Option<&Value>,
|
||||||
response_body_state: Option<UsageBodyCaptureState>,
|
response_body_state: Option<UsageBodyCaptureState>,
|
||||||
|
provider_api_format: Option<&str>,
|
||||||
) -> Option<String> {
|
) -> Option<String> {
|
||||||
if !usage_body_capture_is_authoritative(request_body, request_body_state)
|
if !usage_body_capture_is_authoritative(request_body, request_body_state)
|
||||||
|| !usage_body_capture_is_authoritative(response_body, response_body_state)
|
|| !usage_body_capture_is_authoritative(response_body, response_body_state)
|
||||||
@@ -142,16 +248,10 @@ pub fn extract_provider_response_model_from_bodies(
|
|||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
|
|
||||||
let request_model = request_body
|
let request_model =
|
||||||
.and_then(Value::as_object)
|
extract_provider_model_from_request_body(request_body?, request_api_format)?;
|
||||||
.and_then(|body| body.get("model"))
|
let response_model =
|
||||||
.and_then(Value::as_str)
|
extract_provider_model_from_response_body(response_body?, provider_api_format)?;
|
||||||
.and_then(normalize_provider_response_model)?;
|
|
||||||
let response_model = response_body
|
|
||||||
.and_then(Value::as_object)
|
|
||||||
.and_then(|body| body.get("model"))
|
|
||||||
.and_then(Value::as_str)
|
|
||||||
.and_then(normalize_provider_response_model)?;
|
|
||||||
|
|
||||||
(request_model != response_model).then_some(response_model)
|
(request_model != response_model).then_some(response_model)
|
||||||
}
|
}
|
||||||
@@ -2723,8 +2823,10 @@ mod tests {
|
|||||||
extract_provider_response_model_from_bodies(
|
extract_provider_response_model_from_bodies(
|
||||||
Some(&request),
|
Some(&request),
|
||||||
Some(UsageBodyCaptureState::Inline),
|
Some(UsageBodyCaptureState::Inline),
|
||||||
|
Some("openai:chat"),
|
||||||
Some(&response),
|
Some(&response),
|
||||||
Some(UsageBodyCaptureState::Inline),
|
Some(UsageBodyCaptureState::Inline),
|
||||||
|
Some("openai:chat"),
|
||||||
),
|
),
|
||||||
Some("gpt-5.1".to_string())
|
Some("gpt-5.1".to_string())
|
||||||
);
|
);
|
||||||
@@ -2732,8 +2834,10 @@ mod tests {
|
|||||||
extract_provider_response_model_from_bodies(
|
extract_provider_response_model_from_bodies(
|
||||||
Some(&request),
|
Some(&request),
|
||||||
Some(UsageBodyCaptureState::Inline),
|
Some(UsageBodyCaptureState::Inline),
|
||||||
|
Some("openai:responses"),
|
||||||
Some(&json!({"model": "gpt-5"})),
|
Some(&json!({"model": "gpt-5"})),
|
||||||
Some(UsageBodyCaptureState::Inline),
|
Some(UsageBodyCaptureState::Inline),
|
||||||
|
Some("openai:responses"),
|
||||||
),
|
),
|
||||||
None
|
None
|
||||||
);
|
);
|
||||||
@@ -2741,8 +2845,10 @@ mod tests {
|
|||||||
extract_provider_response_model_from_bodies(
|
extract_provider_response_model_from_bodies(
|
||||||
Some(&request),
|
Some(&request),
|
||||||
Some(UsageBodyCaptureState::Truncated),
|
Some(UsageBodyCaptureState::Truncated),
|
||||||
|
Some("openai:chat"),
|
||||||
Some(&response),
|
Some(&response),
|
||||||
Some(UsageBodyCaptureState::Inline),
|
Some(UsageBodyCaptureState::Inline),
|
||||||
|
Some("openai:chat"),
|
||||||
),
|
),
|
||||||
None
|
None
|
||||||
);
|
);
|
||||||
@@ -2760,8 +2866,66 @@ mod tests {
|
|||||||
extract_provider_response_model_from_bodies(
|
extract_provider_response_model_from_bodies(
|
||||||
Some(&json!({"model": "gpt-5"})),
|
Some(&json!({"model": "gpt-5"})),
|
||||||
None,
|
None,
|
||||||
|
Some("openai:chat"),
|
||||||
Some(&json!({"model": 42})),
|
Some(&json!({"model": 42})),
|
||||||
None,
|
None,
|
||||||
|
Some("openai:chat"),
|
||||||
|
),
|
||||||
|
None
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn response_model_uses_provider_format_specific_nested_paths() {
|
||||||
|
let request = json!({"model": "gemini-2.5-flash"});
|
||||||
|
let response = json!({
|
||||||
|
"response": {
|
||||||
|
"modelVersion": "gemini-2.5-flash-001",
|
||||||
|
"candidates": [{"content": {"role": "model"}}]
|
||||||
|
}
|
||||||
|
});
|
||||||
|
assert_eq!(
|
||||||
|
extract_provider_response_model_from_bodies(
|
||||||
|
Some(&request),
|
||||||
|
Some(UsageBodyCaptureState::Inline),
|
||||||
|
Some("gemini:generate_content"),
|
||||||
|
Some(&response),
|
||||||
|
Some(UsageBodyCaptureState::Inline),
|
||||||
|
Some("gemini:generate_content"),
|
||||||
|
),
|
||||||
|
Some("gemini-2.5-flash-001".to_string())
|
||||||
|
);
|
||||||
|
|
||||||
|
let wrapped_chunks = json!({
|
||||||
|
"chunks": [
|
||||||
|
{"response": {"modelVersion": "gemini-old"}},
|
||||||
|
{"response": {"modelVersion": "gemini-final"}}
|
||||||
|
]
|
||||||
|
});
|
||||||
|
assert_eq!(
|
||||||
|
extract_provider_response_model_from_bodies(
|
||||||
|
Some(&request),
|
||||||
|
Some(UsageBodyCaptureState::Inline),
|
||||||
|
Some("gemini:generate_content"),
|
||||||
|
Some(&wrapped_chunks),
|
||||||
|
Some(UsageBodyCaptureState::Inline),
|
||||||
|
Some("gemini:generate_content"),
|
||||||
|
),
|
||||||
|
Some("gemini-final".to_string())
|
||||||
|
);
|
||||||
|
|
||||||
|
let ambiguous = json!({
|
||||||
|
"metadata": {"model": "do-not-use"},
|
||||||
|
"candidates": [{"content": {"role": "model"}}]
|
||||||
|
});
|
||||||
|
assert_eq!(
|
||||||
|
extract_provider_response_model_from_bodies(
|
||||||
|
Some(&request),
|
||||||
|
Some(UsageBodyCaptureState::Inline),
|
||||||
|
Some("gemini:generate_content"),
|
||||||
|
Some(&ambiguous),
|
||||||
|
Some(UsageBodyCaptureState::Inline),
|
||||||
|
Some("gemini:generate_content"),
|
||||||
),
|
),
|
||||||
None
|
None
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -242,8 +242,10 @@ impl WireOverrides {
|
|||||||
metadata,
|
metadata,
|
||||||
request_body,
|
request_body,
|
||||||
data.request_body_state,
|
data.request_body_state,
|
||||||
|
data.api_format.as_deref(),
|
||||||
data.response_body.as_ref(),
|
data.response_body.as_ref(),
|
||||||
data.response_body_state,
|
data.response_body_state,
|
||||||
|
data.endpoint_api_format.as_deref(),
|
||||||
);
|
);
|
||||||
// Billing reads raw-body TTL before metadata regardless of capture state.
|
// Billing reads raw-body TTL before metadata regardless of capture state.
|
||||||
// Preserve that precedence independently of reasoning and tier authority.
|
// Preserve that precedence independently of reasoning and tier authority.
|
||||||
|
|||||||
@@ -90,8 +90,10 @@ pub fn build_upsert_usage_record_from_event(
|
|||||||
data.request_metadata,
|
data.request_metadata,
|
||||||
data.request_body.as_ref(),
|
data.request_body.as_ref(),
|
||||||
data.request_body_state,
|
data.request_body_state,
|
||||||
|
data.api_format.as_deref(),
|
||||||
data.response_body.as_ref(),
|
data.response_body.as_ref(),
|
||||||
data.response_body_state,
|
data.response_body_state,
|
||||||
|
data.endpoint_api_format.as_deref(),
|
||||||
);
|
);
|
||||||
let now_unix_secs = event.timestamp_ms / 1_000;
|
let now_unix_secs = event.timestamp_ms / 1_000;
|
||||||
|
|
||||||
|
|||||||
@@ -255,8 +255,10 @@ pub(crate) fn attach_provider_response_model_metadata(
|
|||||||
metadata: Option<Value>,
|
metadata: Option<Value>,
|
||||||
request_body: Option<&Value>,
|
request_body: Option<&Value>,
|
||||||
request_body_state: Option<UsageBodyCaptureState>,
|
request_body_state: Option<UsageBodyCaptureState>,
|
||||||
|
request_api_format: Option<&str>,
|
||||||
response_body: Option<&Value>,
|
response_body: Option<&Value>,
|
||||||
response_body_state: Option<UsageBodyCaptureState>,
|
response_body_state: Option<UsageBodyCaptureState>,
|
||||||
|
provider_api_format: Option<&str>,
|
||||||
) -> Option<Value> {
|
) -> Option<Value> {
|
||||||
let both_bodies_are_authoritative =
|
let both_bodies_are_authoritative =
|
||||||
usage_body_capture_is_authoritative(request_body, request_body_state)
|
usage_body_capture_is_authoritative(request_body, request_body_state)
|
||||||
@@ -264,8 +266,10 @@ pub(crate) fn attach_provider_response_model_metadata(
|
|||||||
let response_model = extract_provider_response_model_from_bodies(
|
let response_model = extract_provider_response_model_from_bodies(
|
||||||
request_body,
|
request_body,
|
||||||
request_body_state,
|
request_body_state,
|
||||||
|
request_api_format,
|
||||||
response_body,
|
response_body,
|
||||||
response_body_state,
|
response_body_state,
|
||||||
|
provider_api_format,
|
||||||
);
|
);
|
||||||
if !both_bodies_are_authoritative && response_model.is_none() {
|
if !both_bodies_are_authoritative && response_model.is_none() {
|
||||||
return metadata;
|
return metadata;
|
||||||
@@ -293,8 +297,10 @@ pub(crate) fn refresh_provider_response_model_metadata(
|
|||||||
metadata: Option<Value>,
|
metadata: Option<Value>,
|
||||||
request_body: Option<&Value>,
|
request_body: Option<&Value>,
|
||||||
request_body_state: Option<UsageBodyCaptureState>,
|
request_body_state: Option<UsageBodyCaptureState>,
|
||||||
|
request_api_format: Option<&str>,
|
||||||
response_body: Option<&Value>,
|
response_body: Option<&Value>,
|
||||||
response_body_state: Option<UsageBodyCaptureState>,
|
response_body_state: Option<UsageBodyCaptureState>,
|
||||||
|
provider_api_format: Option<&str>,
|
||||||
) -> Option<Value> {
|
) -> Option<Value> {
|
||||||
let mut object = match metadata {
|
let mut object = match metadata {
|
||||||
Some(Value::Object(object)) => object,
|
Some(Value::Object(object)) => object,
|
||||||
@@ -308,8 +314,10 @@ pub(crate) fn refresh_provider_response_model_metadata(
|
|||||||
if let Some(response_model) = extract_provider_response_model_from_bodies(
|
if let Some(response_model) = extract_provider_response_model_from_bodies(
|
||||||
request_body,
|
request_body,
|
||||||
request_body_state,
|
request_body_state,
|
||||||
|
request_api_format,
|
||||||
response_body,
|
response_body,
|
||||||
response_body_state,
|
response_body_state,
|
||||||
|
provider_api_format,
|
||||||
) {
|
) {
|
||||||
object.insert(
|
object.insert(
|
||||||
PROVIDER_RESPONSE_MODEL_METADATA_KEY.to_string(),
|
PROVIDER_RESPONSE_MODEL_METADATA_KEY.to_string(),
|
||||||
@@ -881,8 +889,10 @@ mod tests {
|
|||||||
Some(json!({"provider_response_model": "old-model", "trace_id": "trace-1"})),
|
Some(json!({"provider_response_model": "old-model", "trace_id": "trace-1"})),
|
||||||
Some(&json!({"model": "gpt-5"})),
|
Some(&json!({"model": "gpt-5"})),
|
||||||
Some(UsageBodyCaptureState::Inline),
|
Some(UsageBodyCaptureState::Inline),
|
||||||
|
Some("openai:chat"),
|
||||||
Some(&json!({"model": "gpt-5.1"})),
|
Some(&json!({"model": "gpt-5.1"})),
|
||||||
Some(UsageBodyCaptureState::Inline),
|
Some(UsageBodyCaptureState::Inline),
|
||||||
|
Some("openai:chat"),
|
||||||
)
|
)
|
||||||
.expect("response model should be attached");
|
.expect("response model should be attached");
|
||||||
assert_eq!(metadata["provider_response_model"], "gpt-5.1");
|
assert_eq!(metadata["provider_response_model"], "gpt-5.1");
|
||||||
@@ -892,8 +902,10 @@ mod tests {
|
|||||||
Some(json!({"provider_response_model": "gpt-5.1"})),
|
Some(json!({"provider_response_model": "gpt-5.1"})),
|
||||||
Some(&json!({"model": "gpt-5"})),
|
Some(&json!({"model": "gpt-5"})),
|
||||||
Some(UsageBodyCaptureState::Inline),
|
Some(UsageBodyCaptureState::Inline),
|
||||||
|
Some("openai:chat"),
|
||||||
Some(&json!({"model": "gpt-5"})),
|
Some(&json!({"model": "gpt-5"})),
|
||||||
Some(UsageBodyCaptureState::Inline),
|
Some(UsageBodyCaptureState::Inline),
|
||||||
|
Some("openai:chat"),
|
||||||
);
|
);
|
||||||
assert!(metadata.is_none());
|
assert!(metadata.is_none());
|
||||||
|
|
||||||
@@ -901,8 +913,10 @@ mod tests {
|
|||||||
Some(json!({"provider_response_model": "gpt-5.1"})),
|
Some(json!({"provider_response_model": "gpt-5.1"})),
|
||||||
None,
|
None,
|
||||||
Some(UsageBodyCaptureState::Disabled),
|
Some(UsageBodyCaptureState::Disabled),
|
||||||
|
Some("openai:chat"),
|
||||||
Some(&json!({"model": "gpt-5.2"})),
|
Some(&json!({"model": "gpt-5.2"})),
|
||||||
Some(UsageBodyCaptureState::Inline),
|
Some(UsageBodyCaptureState::Inline),
|
||||||
|
Some("openai:chat"),
|
||||||
);
|
);
|
||||||
assert!(metadata.is_none());
|
assert!(metadata.is_none());
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5304,8 +5304,10 @@ fn preserve_provider_response_facts(event: &mut UsageEvent) {
|
|||||||
metadata,
|
metadata,
|
||||||
event.data.request_body.as_ref(),
|
event.data.request_body.as_ref(),
|
||||||
event.data.request_body_state,
|
event.data.request_body_state,
|
||||||
|
event.data.api_format.as_deref(),
|
||||||
event.data.response_body.as_ref(),
|
event.data.response_body.as_ref(),
|
||||||
event.data.response_body_state,
|
event.data.response_body_state,
|
||||||
|
event.data.endpoint_api_format.as_deref(),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -729,8 +729,10 @@ fn build_terminal_usage_event_from_seed_impl(
|
|||||||
request_metadata,
|
request_metadata,
|
||||||
request_body.as_ref(),
|
request_body.as_ref(),
|
||||||
body_states.request_body_state,
|
body_states.request_body_state,
|
||||||
|
Some(client_contract.as_str()),
|
||||||
provider_response.as_ref(),
|
provider_response.as_ref(),
|
||||||
body_states.response_body_state,
|
body_states.response_body_state,
|
||||||
|
Some(provider_contract.as_str()),
|
||||||
);
|
);
|
||||||
|
|
||||||
let mut data = UsageEventData {
|
let mut data = UsageEventData {
|
||||||
@@ -1059,8 +1061,10 @@ pub fn build_sync_terminal_usage_seed(
|
|||||||
request_metadata,
|
request_metadata,
|
||||||
context_seed.request_body.as_ref(),
|
context_seed.request_body.as_ref(),
|
||||||
context_seed.body_states.request_body_state,
|
context_seed.body_states.request_body_state,
|
||||||
|
Some(context_seed.client_contract.as_str()),
|
||||||
provider_response_full.as_ref(),
|
provider_response_full.as_ref(),
|
||||||
provider_response_body_state,
|
provider_response_body_state,
|
||||||
|
Some(context_seed.provider_contract.as_str()),
|
||||||
);
|
);
|
||||||
|
|
||||||
TerminalUsageSeed {
|
TerminalUsageSeed {
|
||||||
@@ -1244,8 +1248,10 @@ pub fn build_stream_terminal_usage_seed(
|
|||||||
request_metadata,
|
request_metadata,
|
||||||
context_seed.request_body.as_ref(),
|
context_seed.request_body.as_ref(),
|
||||||
context_seed.body_states.request_body_state,
|
context_seed.body_states.request_body_state,
|
||||||
|
Some(context_seed.client_contract.as_str()),
|
||||||
provider_response_full.as_ref(),
|
provider_response_full.as_ref(),
|
||||||
provider_response_body_state,
|
provider_response_body_state,
|
||||||
|
Some(context_seed.provider_contract.as_str()),
|
||||||
);
|
);
|
||||||
// The parser's terminal summary is authoritative when a response body is truncated or the
|
// The parser's terminal summary is authoritative when a response body is truncated or the
|
||||||
// body and summary disagree; attach it after the body refresh so it wins.
|
// body and summary disagree; attach it after the body refresh so it wins.
|
||||||
|
|||||||
Reference in New Issue
Block a user