mirror of
https://github.com/openai/codex.git
synced 2026-11-14 21:40:39 +00:00
## Why Uploaded audio always reserved 12,000 tokens per file, even for short clips. This could discard recordings during compaction that would fit within the retained-message budget. ## What changed - Add optional audio duration metadata to attachments and cache measured durations per session, resolving metadata for new input and replayed history. - Use measured durations for history estimates and compaction budgets at 10 tokens per second, rounded up and capped at 12,000 tokens. Keep the full budget for missing or unusable metadata. - Bound metadata lookups to 250 milliseconds per batch with up to eight concurrent requests, and record accepted input before lookup so interruption preserves the batch. ## Testing Add coverage for duration rounding and fallback estimates, mixed measured and unknown audio, retention through compaction and cold resume, and interruption during metadata lookup. GitOrigin-RevId: 26fe320c7024944d9d4c371d5fdcda84442b5cc5
101 lines
3.3 KiB
Rust
101 lines
3.3 KiB
Rust
//! Caches trusted file-audio durations for one session. Metadata lookup is best effort
|
|
//! and time-bounded. Accepted input is recorded before lookup, and unmeasured
|
|
//! files reserve the full audio token budget.
|
|
|
|
use std::collections::BTreeMap;
|
|
use std::collections::BTreeSet;
|
|
use std::collections::HashMap;
|
|
use std::sync::Arc;
|
|
use std::time::Duration;
|
|
|
|
use codex_attachment_store::FormatSpecificMetadata;
|
|
use codex_attachment_store::ResolveRequest;
|
|
use codex_history::ResponseItemEnvelope;
|
|
use codex_protocol::models::AudioReference;
|
|
use codex_protocol::models::ContentItem;
|
|
use codex_protocol::models::ResponseItem;
|
|
use codex_utils_audio::estimate_file_audio_tokens;
|
|
use futures::StreamExt;
|
|
use futures::stream;
|
|
|
|
use crate::session::Session;
|
|
|
|
/// Thread-local metadata. It is never serialized into a rollout or a provider request.
|
|
#[derive(Clone, Debug, Default)]
|
|
pub(crate) struct FileAudioDurations(BTreeMap<String, Duration>);
|
|
|
|
impl FileAudioDurations {
|
|
pub(crate) fn tokens(&self, file_id: &str) -> usize {
|
|
estimate_file_audio_tokens(self.0.get(file_id).copied())
|
|
}
|
|
}
|
|
|
|
pub(crate) async fn snapshot(session: &Session) -> Arc<FileAudioDurations> {
|
|
Arc::clone(&session.state.lock().await.history.file_audio_durations)
|
|
}
|
|
|
|
pub(super) fn file_ids(items: &[ResponseItemEnvelope]) -> impl Iterator<Item = &String> {
|
|
items
|
|
.iter()
|
|
.filter_map(|envelope| match &envelope.item {
|
|
ResponseItem::Message { content, .. } => Some(content),
|
|
_ => None,
|
|
})
|
|
.flatten()
|
|
.filter_map(|part| match part {
|
|
ContentItem::InputAudio {
|
|
audio: AudioReference::File { file_id },
|
|
} => Some(file_id),
|
|
_ => None,
|
|
})
|
|
}
|
|
|
|
pub(super) async fn resolve(session: &Session, mut file_ids: BTreeSet<String>) {
|
|
if file_ids.is_empty() {
|
|
return;
|
|
}
|
|
{
|
|
let state = session.state.lock().await;
|
|
file_ids.retain(|id| !state.history.file_audio_durations.0.contains_key(id));
|
|
}
|
|
let mut lookups = stream::iter(file_ids)
|
|
.map(|id| async move {
|
|
let result = session
|
|
.services
|
|
.image_store
|
|
.resolve(ResolveRequest {
|
|
file_id: &id,
|
|
tags: HashMap::new(),
|
|
download_url_ttl: None,
|
|
})
|
|
.await;
|
|
(id, result)
|
|
})
|
|
.buffer_unordered(8);
|
|
let mut resolved = BTreeMap::new();
|
|
// Metadata is optional. Bound the entire batch, keep completed lookups on timeout,
|
|
// and allow future attachments to retry a missing value.
|
|
let _ = tokio::time::timeout(Duration::from_millis(250), async {
|
|
while let Some((id, result)) = lookups.next().await {
|
|
if let Ok(metadata) = result
|
|
&& let Some(FormatSpecificMetadata::Audio(audio)) = metadata.format_specific
|
|
&& let Some(duration_ms) = audio.duration_ms
|
|
&& duration_ms > 0
|
|
{
|
|
resolved.insert(id, Duration::from_millis(duration_ms));
|
|
}
|
|
}
|
|
})
|
|
.await;
|
|
if !resolved.is_empty() {
|
|
let mut state = session.state.lock().await;
|
|
Arc::make_mut(&mut state.history.file_audio_durations)
|
|
.0
|
|
.extend(resolved);
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
#[path = "file_audio_tests.rs"]
|
|
mod tests;
|