Extract ContentPart::OPENAI_REASONING and OPENAI_MESSAGE constants

Replace 16 raw string literal usages of "openai_reasoning" and
"openai_message" across openai.rs and history.rs with constants
defined on ContentPart, eliminating typo risk and centralizing
the kind identifiers.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Bryan Helmkamp 2026-03-07 18:43:39 -05:00
parent ca71b2895d
commit e73803039c
3 changed files with 22 additions and 17 deletions

View file

@ -39,7 +39,7 @@ impl History {
for turn in &mut self.turns {
if let Turn::Assistant { provider_parts, .. } = turn {
provider_parts.retain(|p| {
!matches!(p, ContentPart::Other { kind, .. } if kind == "openai_reasoning" || kind == "openai_message")
!matches!(p, ContentPart::Other { kind, .. } if kind == ContentPart::OPENAI_REASONING || kind == ContentPart::OPENAI_MESSAGE)
});
}
}
@ -285,7 +285,7 @@ mod tests {
fn assistant_turn_preserves_provider_parts() {
let mut history = History::default();
let reasoning_item = ContentPart::Other {
kind: "openai_reasoning".to_string(),
kind: ContentPart::OPENAI_REASONING.to_string(),
data: serde_json::json!({"type": "reasoning", "id": "rs_abc"}),
};
let tc = ToolCall::new("call_1", "search", serde_json::json!({}));
@ -301,7 +301,7 @@ mod tests {
assert_eq!(messages.len(), 1);
// Provider parts come first, then tool calls
assert!(
matches!(&messages[0].content[0], ContentPart::Other { kind, .. } if kind == "openai_reasoning")
matches!(&messages[0].content[0], ContentPart::Other { kind, .. } if kind == ContentPart::OPENAI_REASONING)
);
assert!(matches!(&messages[0].content[1], ContentPart::ToolCall(_)));
}
@ -421,7 +421,7 @@ mod tests {
timestamp: SystemTime::now(),
});
let reasoning = ContentPart::Other {
kind: "openai_reasoning".into(),
kind: ContentPart::OPENAI_REASONING.into(),
data: serde_json::json!({"type": "reasoning", "id": "rs_abc"}),
};
let tc = ToolCall::new("call_1", "search", serde_json::json!({}));
@ -501,7 +501,7 @@ mod tests {
content: format!("response {i}"),
tool_calls: vec![],
provider_parts: vec![ContentPart::Other {
kind: "openai_reasoning".into(),
kind: ContentPart::OPENAI_REASONING.into(),
data: serde_json::json!({"type": "reasoning", "id": format!("rs_{i}")}),
}],
usage: Usage::default(),

View file

@ -224,7 +224,7 @@ fn translate_input(messages: &[Message]) -> (Option<String>, Vec<serde_json::Val
// required so that reasoning items can find their "required following
// item" during Responses API round-tripping.
let has_opaque_message = msg.content.iter().any(|p| {
matches!(p, ContentPart::Other { kind, .. } if kind == "openai_message")
matches!(p, ContentPart::Other { kind, .. } if kind == ContentPart::OPENAI_MESSAGE)
});
for part in &msg.content {
match part {
@ -261,7 +261,7 @@ fn translate_input(messages: &[Message]) -> (Option<String>, Vec<serde_json::Val
}
// Round-trip opaque items back to the API
ContentPart::Other { kind, data }
if kind == "openai_reasoning" || kind == "openai_message" =>
if kind == ContentPart::OPENAI_REASONING || kind == ContentPart::OPENAI_MESSAGE =>
{
input.push(data.clone());
}
@ -414,7 +414,7 @@ fn parse_output(output: &[serde_json::Value]) -> (Vec<ContentPart>, bool) {
// The item's `id` and `status` fields are required so that reasoning
// items preceding it can find their "required following item."
parts.push(ContentPart::Other {
kind: "openai_message".to_string(),
kind: ContentPart::OPENAI_MESSAGE.to_string(),
data: item.clone(),
});
if let Some(content) = item.get("content").and_then(|c| c.as_array()) {
@ -433,7 +433,7 @@ fn parse_output(output: &[serde_json::Value]) -> (Vec<ContentPart>, bool) {
}
Some("reasoning") => {
parts.push(ContentPart::Other {
kind: "openai_reasoning".to_string(),
kind: ContentPart::OPENAI_REASONING.to_string(),
data: item.clone(),
});
}
@ -827,14 +827,14 @@ fn handle_response_completed(
// Reasoning items must precede function calls for Responses API round-trip
for item in &state.reasoning_items {
content_parts.push(ContentPart::Other {
kind: "openai_reasoning".to_string(),
kind: ContentPart::OPENAI_REASONING.to_string(),
data: item.clone(),
});
}
// Preserve full message output items for Responses API round-tripping
for item in &state.message_items {
content_parts.push(ContentPart::Other {
kind: "openai_message".to_string(),
kind: ContentPart::OPENAI_MESSAGE.to_string(),
data: item.clone(),
});
}
@ -1330,7 +1330,7 @@ mod tests {
// First part is the reasoning item
match &parts[0] {
ContentPart::Other { kind, data } => {
assert_eq!(kind, "openai_reasoning");
assert_eq!(kind, ContentPart::OPENAI_REASONING);
assert_eq!(data["type"], "reasoning");
assert_eq!(data["id"], "rs_abc123");
}
@ -1367,8 +1367,8 @@ mod tests {
assert!(has_tool_calls);
// reasoning + openai_message + text + function_call
assert_eq!(parts.len(), 4);
assert!(matches!(&parts[0], ContentPart::Other { kind, .. } if kind == "openai_reasoning"));
assert!(matches!(&parts[1], ContentPart::Other { kind, data } if kind == "openai_message" && data["id"] == "msg_xyz"));
assert!(matches!(&parts[0], ContentPart::Other { kind, .. } if kind == ContentPart::OPENAI_REASONING));
assert!(matches!(&parts[1], ContentPart::Other { kind, data } if kind == ContentPart::OPENAI_MESSAGE && data["id"] == "msg_xyz"));
assert!(matches!(&parts[2], ContentPart::Text(t) if t == "Hello"));
assert!(matches!(&parts[3], ContentPart::ToolCall(_)));
}
@ -1387,7 +1387,7 @@ mod tests {
role: Role::Assistant,
content: vec![
ContentPart::Other {
kind: "openai_reasoning".to_string(),
kind: ContentPart::OPENAI_REASONING.to_string(),
data: reasoning,
},
ContentPart::ToolCall(tc),
@ -1431,11 +1431,11 @@ mod tests {
role: Role::Assistant,
content: vec![
ContentPart::Other {
kind: "openai_reasoning".to_string(),
kind: ContentPart::OPENAI_REASONING.to_string(),
data: reasoning,
},
ContentPart::Other {
kind: "openai_message".to_string(),
kind: ContentPart::OPENAI_MESSAGE.to_string(),
data: opaque_message,
},
ContentPart::text("Checking now."),

View file

@ -223,6 +223,11 @@ impl<'de> Deserialize<'de> for ContentPart {
}
impl ContentPart {
/// Kind string for opaque OpenAI reasoning output items.
pub const OPENAI_REASONING: &str = "openai_reasoning";
/// Kind string for opaque OpenAI message output items.
pub const OPENAI_MESSAGE: &str = "openai_message";
pub fn text(text: impl Into<String>) -> Self {
Self::Text(text.into())
}