Skip to main content

gproxy_protocol/adapt/generate/image_resources/
stream.rs

1use super::*;
2use crate::{capability::ResourceAccess, transform::identity::IdNamespace};
3
4/// Resource progress for a single incremental invocation. Native events remain
5/// in the invocation while these operations await; receipts survive
6/// cancellation of an individual `next_with_image_resources` future.
7pub struct ImageStreamProgress<H> {
8    namespace: Option<IdNamespace>,
9    pub(crate) revision: u64,
10    source_index: u64,
11    client_index: u64,
12    native_parts: usize,
13    source: BTreeMap<u64, GeminiImageReads>,
14    client: BTreeMap<u64, GeminiImagePublications<H>>,
15    pub(crate) reads: GeminiImageReads,
16}
17
18impl<H> Default for ImageStreamProgress<H> {
19    fn default() -> Self {
20        Self {
21            namespace: None,
22            revision: 0,
23            source_index: 0,
24            client_index: 0,
25            native_parts: 0,
26            source: BTreeMap::new(),
27            client: BTreeMap::new(),
28            reads: Default::default(),
29        }
30    }
31}
32
33impl<H> ImageStreamProgress<H> {
34    pub(crate) fn failed(&self) -> bool {
35        self.source.values().any(|event| event.failed)
36            || self.client.values().any(|event| event.failed)
37    }
38    pub(crate) fn committed<T>(&mut self, value: T) -> Result<T, TransformError> {
39        self.revision = self.revision.checked_add(1).ok_or_else(limit)?;
40        Ok(value)
41    }
42    pub(crate) fn bind(&mut self, namespace: IdNamespace) -> Result<(), TransformError> {
43        if self.namespace.is_some_and(|old| old != namespace) {
44            return Err(conflict(
45                "image stream progress belongs to another invocation",
46            ));
47        }
48        self.namespace = Some(namespace);
49        Ok(())
50    }
51    /// Includes uncertain operations whose body must not be submitted again.
52    pub fn publication_budget(&self) -> Result<(usize, u64), TransformError> {
53        let mut count = 0usize;
54        let mut bytes = 0u64;
55        for image in self
56            .client
57            .values()
58            .flat_map(|event| event.publications.values())
59        {
60            let padding = if image.source.data.ends_with("==") {
61                2
62            } else {
63                usize::from(image.source.data.ends_with('='))
64            };
65            let size = (image.source.data.len() / 4 * 3).saturating_sub(padding) as u64;
66            count = count.checked_add(1).ok_or_else(limit)?;
67            bytes = bytes.checked_add(size).ok_or_else(limit)?;
68        }
69        Ok((count, bytes))
70    }
71    pub fn publication_receipts(&self) -> impl Iterator<Item = &PublishedResource<H>> {
72        self.client.values().flat_map(|event| event.receipts())
73    }
74    pub fn publication_ids(&self) -> impl Iterator<Item = &str> {
75        self.client.values().flat_map(|event| event.operation_ids())
76    }
77    pub(crate) async fn source<R: ResourceAccess<PublishedHandle = H>>(
78        &mut self,
79        input: &g::GenerateContentResponseBody,
80        resources: &super::super::GenerationResources<'_, R>,
81    ) -> Result<g::GenerateContentResponseBody, TransformError> {
82        let parts: usize = input
83            .candidates
84            .iter()
85            .flatten()
86            .flat_map(|candidate| candidate.content.iter())
87            .map(|content| content.parts.as_ref().map_or(0, Vec::len))
88            .sum();
89        let has_files = input
90            .candidates
91            .iter()
92            .flatten()
93            .flat_map(|candidate| candidate.content.iter())
94            .flat_map(|content| content.parts.iter().flatten())
95            .any(|part| part.file_data.is_some());
96        let value = if has_files {
97            let used_bytes: u64 = self
98                .source
99                .iter()
100                .filter(|(index, _)| **index != self.source_index)
101                .map(|(_, event)| event.bytes)
102                .sum();
103            let used_reads: usize = self
104                .source
105                .iter()
106                .filter(|(index, _)| **index != self.source_index)
107                .map(|(_, event)| event.attempted)
108                .sum();
109            let mut limits = resources.limits;
110            limits.max_body_bytes = limits.max_body_bytes.saturating_sub(used_bytes);
111            let bound = super::super::GenerationResources {
112                access: resources.access,
113                scope: resources.scope,
114                limits,
115                max_references: resources.max_references.saturating_sub(used_reads),
116                now: resources.now,
117            };
118            let event = self.source.entry(self.source_index).or_default();
119            let value = event.materialize(input, &bound).await?;
120            for image in event.images() {
121                let mut image = image.clone();
122                image.part = image
123                    .part
124                    .checked_add(self.native_parts)
125                    .ok_or_else(limit)?;
126                self.reads
127                    .images
128                    .insert((image.candidate, image.part), image);
129            }
130            value
131        } else {
132            input.clone()
133        };
134        self.native_parts = self.native_parts.checked_add(parts).ok_or_else(limit)?;
135        self.source_index = self.source_index.checked_add(1).ok_or_else(limit)?;
136        Ok(value)
137    }
138    pub(crate) async fn client<R: ResourceAccess<PublishedHandle = H>>(
139        &mut self,
140        input: &[g::GenerateContentResponseBody],
141        expires_at: std::time::SystemTime,
142        resources: &super::super::GenerationResources<'_, R>,
143    ) -> Result<Vec<g::GenerateContentResponseBody>, TransformError> {
144        let mut output = Vec::with_capacity(input.len());
145        // Advance only after the complete converted batch succeeds. An awaited
146        // publication may be canceled after earlier events in this batch finish.
147        for (offset, event) in input.iter().enumerate() {
148            let index = self
149                .client_index
150                .checked_add(offset as u64)
151                .ok_or_else(limit)?;
152            let has_images = event
153                .candidates
154                .iter()
155                .flatten()
156                .flat_map(|candidate| candidate.content.iter())
157                .flat_map(|content| content.parts.iter().flatten())
158                .any(|part| part.inline_data.is_some() || part.file_data.is_some());
159            if !has_images {
160                output.push(event.clone());
161                continue;
162            }
163            let used_bytes: u64 = self
164                .client
165                .iter()
166                .filter(|(old, _)| **old != index)
167                .flat_map(|(_, event)| event.publications.values())
168                .map(|image| {
169                    let padding = if image.source.data.ends_with("==") {
170                        2
171                    } else {
172                        usize::from(image.source.data.ends_with('='))
173                    };
174                    (image.source.data.len() / 4 * 3).saturating_sub(padding) as u64
175                })
176                .sum();
177            let used_count: usize = self
178                .client
179                .iter()
180                .filter(|(old, _)| **old != index)
181                .map(|(_, event)| event.publications.len())
182                .sum();
183            let mut limits = resources.limits;
184            limits.max_body_bytes = limits.max_body_bytes.saturating_sub(used_bytes);
185            let bound = super::super::GenerationResources {
186                access: resources.access,
187                scope: resources.scope,
188                limits,
189                max_references: resources.max_references.saturating_sub(used_count),
190                now: resources.now,
191            };
192            let value = self
193                .client
194                .entry(index)
195                .or_default()
196                .publish_event(
197                    event,
198                    self.namespace
199                        .ok_or_else(|| missing("unbound image stream progress"))?,
200                    index,
201                    expires_at,
202                    &bound,
203                )
204                .await?;
205            output.push(value);
206        }
207        self.client_index = self
208            .client_index
209            .checked_add(input.len() as u64)
210            .ok_or_else(limit)?;
211        Ok(output)
212    }
213}