This introduces an experimental `--output-last-message` flag that can be used to identify a file where the final message from the agent will be written. Two use cases: - Ultimately, we will likely add a `--quiet` option to `exec`, but even if the user does not want any output written to the terminal, they probably want to know what the agent did. Writing the output to a file makes it possible to get that information in a clean way. - Relatedly, when using `exec` in CI, it is easier to review the transcript written "normally," (i.e., not as JSON or something with extra escapes), but getting programmatic access to the last message is likely helpful, so writing the last message to a file gets the best of both worlds. I am calling this "experimental" because it is possible that we are overfitting and will want a more general solution to this problem that would justify removing this flag.
51 lines
1.7 KiB
Rust
51 lines
1.7 KiB
Rust
use crate::models::ResponseItem;
|
|
|
|
/// Transcript of conversation history that is needed:
|
|
/// - for ZDR clients for which previous_response_id is not available, so we
|
|
/// must include the transcript with every API call. This must include each
|
|
/// `function_call` and its corresponding `function_call_output`.
|
|
/// - for clients using the "chat completions" API as opposed to the
|
|
/// "responses" API.
|
|
#[derive(Debug, Clone)]
|
|
pub(crate) struct ConversationHistory {
|
|
/// The oldest items are at the beginning of the vector.
|
|
items: Vec<ResponseItem>,
|
|
}
|
|
|
|
impl ConversationHistory {
|
|
pub(crate) fn new() -> Self {
|
|
Self { items: Vec::new() }
|
|
}
|
|
|
|
/// Returns a clone of the contents in the transcript.
|
|
pub(crate) fn contents(&self) -> Vec<ResponseItem> {
|
|
self.items.clone()
|
|
}
|
|
|
|
/// `items` is ordered from oldest to newest.
|
|
pub(crate) fn record_items<I>(&mut self, items: I)
|
|
where
|
|
I: IntoIterator,
|
|
I::Item: std::ops::Deref<Target = ResponseItem>,
|
|
{
|
|
for item in items {
|
|
if is_api_message(&item) {
|
|
// Note agent-loop.ts also does filtering on some of the fields.
|
|
self.items.push(item.clone());
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Anything that is not a system message or "reasoning" message is considered
|
|
/// an API message.
|
|
fn is_api_message(message: &ResponseItem) -> bool {
|
|
match message {
|
|
ResponseItem::Message { role, .. } => role.as_str() != "system",
|
|
ResponseItem::FunctionCallOutput { .. }
|
|
| ResponseItem::FunctionCall { .. }
|
|
| ResponseItem::LocalShellCall { .. } => true,
|
|
ResponseItem::Reasoning { .. } | ResponseItem::Other => false,
|
|
}
|
|
}
|