mirror of
https://github.com/pchuan98/codex.git
synced 2026-07-01 00:31:56 +08:00
Support end_turn in response.completed (#19610)
Some providers of Responses API forward a model-defined `end_turn` boolean indicating explicitly the model's indication of whether it would like to end the turn or to be inferenced again. In this PR, we update the sampling loop to use this field correctly if it's set. If the field is not set by the provider, we fall back to the existing sampling logic.
This commit is contained in:
committed by
GitHub
Unverified
parent
5591912f0b
commit
355c40ad7e
Generated
-1
@@ -2870,7 +2870,6 @@ dependencies = [
|
||||
"codex-plugin",
|
||||
"codex-protocol",
|
||||
"codex-rmcp-client",
|
||||
"codex-utils-absolute-path",
|
||||
"codex-utils-plugins",
|
||||
"futures",
|
||||
"pretty_assertions",
|
||||
|
||||
@@ -78,8 +78,9 @@ fn response_event_to_json(event: codex_api::ResponseEvent) -> serde_json::Value
|
||||
codex_api::ResponseEvent::Completed {
|
||||
response_id,
|
||||
token_usage,
|
||||
end_turn,
|
||||
} => {
|
||||
let response = match token_usage {
|
||||
let mut response = match token_usage {
|
||||
Some(token_usage) => json!({
|
||||
"id": response_id,
|
||||
"usage": {
|
||||
@@ -96,6 +97,9 @@ fn response_event_to_json(event: codex_api::ResponseEvent) -> serde_json::Value
|
||||
}),
|
||||
None => json!({ "id": response_id }),
|
||||
};
|
||||
if let Some(end_turn) = end_turn {
|
||||
response["end_turn"] = json!(end_turn);
|
||||
}
|
||||
json!({ "type": "response.completed", "response": response })
|
||||
}
|
||||
codex_api::ResponseEvent::OutputTextDelta(delta) => {
|
||||
@@ -165,6 +169,7 @@ mod tests {
|
||||
reasoning_output_tokens: 3,
|
||||
total_tokens: 17,
|
||||
}),
|
||||
end_turn: Some(true),
|
||||
});
|
||||
assert_eq!(
|
||||
completed,
|
||||
@@ -183,6 +188,7 @@ mod tests {
|
||||
},
|
||||
"total_tokens": 17,
|
||||
},
|
||||
"end_turn": true,
|
||||
},
|
||||
})
|
||||
);
|
||||
@@ -190,10 +196,22 @@ mod tests {
|
||||
let completed_without_usage = response_event_to_json(codex_api::ResponseEvent::Completed {
|
||||
response_id: "resp-2".to_string(),
|
||||
token_usage: None,
|
||||
end_turn: Some(false),
|
||||
});
|
||||
assert_eq!(
|
||||
completed_without_usage,
|
||||
json!({"type": "response.completed", "response": {"id": "resp-2"}})
|
||||
json!({"type": "response.completed", "response": {"id": "resp-2", "end_turn": false}})
|
||||
);
|
||||
|
||||
let completed_without_usage_or_end_turn =
|
||||
response_event_to_json(codex_api::ResponseEvent::Completed {
|
||||
response_id: "resp-3".to_string(),
|
||||
token_usage: None,
|
||||
end_turn: None,
|
||||
});
|
||||
assert_eq!(
|
||||
completed_without_usage_or_end_turn,
|
||||
json!({"type": "response.completed", "response": {"id": "resp-3"}})
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -81,6 +81,9 @@ pub enum ResponseEvent {
|
||||
Completed {
|
||||
response_id: String,
|
||||
token_usage: Option<TokenUsage>,
|
||||
/// Did the model affirmatively end its turn? Some providers do not set this,
|
||||
/// so we rely on fallback logic when this is `None`.
|
||||
end_turn: Option<bool>,
|
||||
},
|
||||
OutputTextDelta(String),
|
||||
ToolCallInputDelta {
|
||||
|
||||
@@ -123,6 +123,8 @@ struct ResponseCompleted {
|
||||
id: String,
|
||||
#[serde(default)]
|
||||
usage: Option<ResponseCompletedUsage>,
|
||||
#[serde(default)]
|
||||
end_turn: Option<bool>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
@@ -382,6 +384,7 @@ pub fn process_responses_event(
|
||||
return Ok(Some(ResponseEvent::Completed {
|
||||
response_id: resp.id,
|
||||
token_usage: resp.usage.map(Into::into),
|
||||
end_turn: resp.end_turn,
|
||||
}));
|
||||
}
|
||||
Err(err) => {
|
||||
@@ -704,9 +707,11 @@ mod tests {
|
||||
Ok(ResponseEvent::Completed {
|
||||
response_id,
|
||||
token_usage,
|
||||
end_turn,
|
||||
}) => {
|
||||
assert_eq!(response_id, "resp1");
|
||||
assert!(token_usage.is_none());
|
||||
assert!(end_turn.is_none());
|
||||
}
|
||||
other => panic!("unexpected third event: {other:?}"),
|
||||
}
|
||||
@@ -843,9 +848,11 @@ mod tests {
|
||||
Ok(ResponseEvent::Completed {
|
||||
response_id,
|
||||
token_usage,
|
||||
end_turn,
|
||||
}) => {
|
||||
assert_eq!(response_id, "resp1");
|
||||
assert!(token_usage.is_none());
|
||||
assert!(end_turn.is_none());
|
||||
}
|
||||
other => panic!("unexpected event: {other:?}"),
|
||||
}
|
||||
@@ -1148,7 +1155,8 @@ mod tests {
|
||||
&events[1],
|
||||
ResponseEvent::Completed {
|
||||
response_id,
|
||||
token_usage: None
|
||||
token_usage: None,
|
||||
end_turn: None,
|
||||
} if response_id == "resp-1"
|
||||
);
|
||||
}
|
||||
@@ -1184,7 +1192,8 @@ mod tests {
|
||||
&events[2],
|
||||
ResponseEvent::Completed {
|
||||
response_id,
|
||||
token_usage: None
|
||||
token_usage: None,
|
||||
end_turn: None,
|
||||
} if response_id == "resp-1"
|
||||
);
|
||||
}
|
||||
@@ -1218,7 +1227,8 @@ mod tests {
|
||||
&events[1],
|
||||
ResponseEvent::Completed {
|
||||
response_id,
|
||||
token_usage: None
|
||||
token_usage: None,
|
||||
end_turn: None,
|
||||
} if response_id == "resp-1"
|
||||
);
|
||||
}
|
||||
|
||||
@@ -158,9 +158,11 @@ async fn responses_stream_parses_items_and_completed_end_to_end() -> Result<()>
|
||||
ResponseEvent::Completed {
|
||||
response_id,
|
||||
token_usage,
|
||||
end_turn,
|
||||
} => {
|
||||
assert_eq!(response_id, "resp1");
|
||||
assert!(token_usage.is_none());
|
||||
assert!(end_turn.is_none());
|
||||
}
|
||||
other => panic!("unexpected third event: {other:?}"),
|
||||
}
|
||||
|
||||
@@ -1655,6 +1655,7 @@ where
|
||||
Ok(ResponseEvent::Completed {
|
||||
response_id,
|
||||
token_usage,
|
||||
end_turn,
|
||||
}) => {
|
||||
if let Some(usage) = &token_usage {
|
||||
session_telemetry.sse_event_completed(
|
||||
@@ -1680,6 +1681,7 @@ where
|
||||
.send(Ok(ResponseEvent::Completed {
|
||||
response_id,
|
||||
token_usage,
|
||||
end_turn,
|
||||
}))
|
||||
.await
|
||||
.is_err()
|
||||
|
||||
@@ -2132,6 +2132,7 @@ async fn try_run_sampling_request(
|
||||
ResponseEvent::Completed {
|
||||
response_id: _,
|
||||
token_usage,
|
||||
end_turn,
|
||||
} => {
|
||||
flush_assistant_text_segments_all(
|
||||
&sess,
|
||||
@@ -2143,7 +2144,9 @@ async fn try_run_sampling_request(
|
||||
sess.update_token_usage_info(&turn_context, token_usage.as_ref())
|
||||
.await;
|
||||
should_emit_turn_diff = true;
|
||||
|
||||
if let Some(false) = end_turn {
|
||||
needs_follow_up = true;
|
||||
}
|
||||
break Ok(SamplingRequestResult {
|
||||
needs_follow_up,
|
||||
last_agent_message,
|
||||
|
||||
Reference in New Issue
Block a user