mirror of
https://github.com/instructkr/claw-code.git
synced 2026-04-08 17:14:49 +08:00
fix(api): enrich JSON parse errors with response body, provider, and model
Raw 'json_error: no field X' now includes truncated response body, provider name, and model ID for debugging context.
This commit is contained in:
@@ -131,7 +131,15 @@ impl OpenAiCompatClient {
|
||||
preflight_message_request(&request)?;
|
||||
let response = self.send_with_retry(&request).await?;
|
||||
let request_id = request_id_from_headers(response.headers());
|
||||
let payload = response.json::<ChatCompletionResponse>().await?;
|
||||
let body = response.text().await.map_err(ApiError::from)?;
|
||||
let payload = serde_json::from_str::<ChatCompletionResponse>(&body).map_err(|error| {
|
||||
ApiError::json_deserialize(
|
||||
self.config.provider_name,
|
||||
&request.model,
|
||||
&body,
|
||||
error,
|
||||
)
|
||||
})?;
|
||||
let mut normalized = normalize_response(&request.model, payload)?;
|
||||
if normalized.request_id.is_none() {
|
||||
normalized.request_id = request_id;
|
||||
@@ -150,7 +158,10 @@ impl OpenAiCompatClient {
|
||||
Ok(MessageStream {
|
||||
request_id: request_id_from_headers(response.headers()),
|
||||
response,
|
||||
parser: OpenAiSseParser::new(),
|
||||
parser: OpenAiSseParser::with_context(
|
||||
self.config.provider_name,
|
||||
request.model.clone(),
|
||||
),
|
||||
pending: VecDeque::new(),
|
||||
done: false,
|
||||
state: StreamState::new(request.model.clone()),
|
||||
@@ -282,11 +293,17 @@ impl MessageStream {
|
||||
#[derive(Debug, Default)]
|
||||
struct OpenAiSseParser {
|
||||
buffer: Vec<u8>,
|
||||
provider: String,
|
||||
model: String,
|
||||
}
|
||||
|
||||
impl OpenAiSseParser {
|
||||
fn new() -> Self {
|
||||
Self::default()
|
||||
fn with_context(provider: impl Into<String>, model: impl Into<String>) -> Self {
|
||||
Self {
|
||||
buffer: Vec::new(),
|
||||
provider: provider.into(),
|
||||
model: model.into(),
|
||||
}
|
||||
}
|
||||
|
||||
fn push(&mut self, chunk: &[u8]) -> Result<Vec<ChatCompletionChunk>, ApiError> {
|
||||
@@ -294,7 +311,7 @@ impl OpenAiSseParser {
|
||||
let mut events = Vec::new();
|
||||
|
||||
while let Some(frame) = next_sse_frame(&mut self.buffer) {
|
||||
if let Some(event) = parse_sse_frame(&frame)? {
|
||||
if let Some(event) = parse_sse_frame(&frame, &self.provider, &self.model)? {
|
||||
events.push(event);
|
||||
}
|
||||
}
|
||||
@@ -835,7 +852,11 @@ fn next_sse_frame(buffer: &mut Vec<u8>) -> Option<String> {
|
||||
Some(String::from_utf8_lossy(&frame[..frame_len]).into_owned())
|
||||
}
|
||||
|
||||
fn parse_sse_frame(frame: &str) -> Result<Option<ChatCompletionChunk>, ApiError> {
|
||||
fn parse_sse_frame(
|
||||
frame: &str,
|
||||
provider: &str,
|
||||
model: &str,
|
||||
) -> Result<Option<ChatCompletionChunk>, ApiError> {
|
||||
let trimmed = frame.trim();
|
||||
if trimmed.is_empty() {
|
||||
return Ok(None);
|
||||
@@ -857,9 +878,9 @@ fn parse_sse_frame(frame: &str) -> Result<Option<ChatCompletionChunk>, ApiError>
|
||||
if payload == "[DONE]" {
|
||||
return Ok(None);
|
||||
}
|
||||
serde_json::from_str(&payload)
|
||||
serde_json::from_str::<ChatCompletionChunk>(&payload)
|
||||
.map(Some)
|
||||
.map_err(ApiError::from)
|
||||
.map_err(|error| ApiError::json_deserialize(provider, model, &payload, error))
|
||||
}
|
||||
|
||||
fn read_env_non_empty(key: &str) -> Result<Option<String>, ApiError> {
|
||||
|
||||
Reference in New Issue
Block a user