@@ -116,6 +116,193 @@ pub(crate) struct DeliveryAckPayload {
116116 pub ( super ) event_id : EventId ,
117117}
118118
119+ /// Classify delivery ids that are meaningful Relaycast message ids for
120+ /// read-ack purposes. A read-ack means "delivered to the recipient location",
121+ /// not proof that a model turn cognitively processed the message.
122+ pub ( crate ) fn synthetic_delivery_read_ack_reason ( event_id : & EventId ) -> Option < & ' static str > {
123+ let event_id = event_id. as_str ( ) . trim ( ) ;
124+ if event_id. is_empty ( ) {
125+ return Some ( "blank_event_id" ) ;
126+ }
127+ if event_id. starts_with ( "http_" ) {
128+ return Some ( "http_api_synthetic_event_id" ) ;
129+ }
130+ if event_id. starts_with ( "init_" ) {
131+ return Some ( "initial_task_synthetic_event_id" ) ;
132+ }
133+ if event_id. starts_with ( "cont_load_" ) {
134+ return Some ( "continuity_synthetic_event_id" ) ;
135+ }
136+ None
137+ }
138+
139+ #[ cfg( test) ]
140+ pub ( crate ) fn delivery_read_ack_is_relaycast_message ( event_id : & EventId ) -> bool {
141+ synthetic_delivery_read_ack_reason ( event_id) . is_none ( )
142+ }
143+
144+ pub ( crate ) fn seed_supplied_agent_token (
145+ relaycast_http : & RelaycastHttpClient ,
146+ agent_name : & str ,
147+ token : & str ,
148+ ) {
149+ relaycast_http. seed_agent_token ( agent_name, token) ;
150+ }
151+
152+ const DELIVERY_READ_ACK_TIMEOUT : Duration = Duration :: from_secs ( 2 ) ;
153+
154+ pub ( crate ) fn mark_delivery_read_ack (
155+ relaycast_http : & RelaycastHttpClient ,
156+ sdk_out_tx : & mpsc:: Sender < ProtocolEnvelope < Value > > ,
157+ dedup : & mut DedupCache ,
158+ worker_name : & WorkerName ,
159+ cli_hint : Option < & str > ,
160+ delivery_id : & DeliveryId ,
161+ event_id : & EventId ,
162+ ) {
163+ mark_delivery_read_ack_with_timeout (
164+ relaycast_http,
165+ sdk_out_tx,
166+ dedup,
167+ worker_name,
168+ cli_hint,
169+ delivery_id,
170+ event_id,
171+ DELIVERY_READ_ACK_TIMEOUT ,
172+ ) ;
173+ }
174+
175+ pub ( crate ) fn mark_delivery_read_ack_with_timeout (
176+ relaycast_http : & RelaycastHttpClient ,
177+ sdk_out_tx : & mpsc:: Sender < ProtocolEnvelope < Value > > ,
178+ dedup : & mut DedupCache ,
179+ worker_name : & WorkerName ,
180+ cli_hint : Option < & str > ,
181+ delivery_id : & DeliveryId ,
182+ event_id : & EventId ,
183+ timeout_window : Duration ,
184+ ) {
185+ let dedup_key = format ! ( "delivery_read_ack:{worker_name}:{event_id}" ) ;
186+ if !dedup. insert_if_new ( & dedup_key, Instant :: now ( ) ) {
187+ emit_delivery_read_ack_telemetry (
188+ sdk_out_tx. clone ( ) ,
189+ BrokerEvent :: DeliveryReadAck {
190+ name : worker_name. clone ( ) ,
191+ delivery_id : delivery_id. clone ( ) ,
192+ event_id : event_id. clone ( ) ,
193+ status : DeliveryReadAckStatus :: SuppressedDuplicate ,
194+ reason : Some ( "duplicate_delivery_read_ack" . to_string ( ) ) ,
195+ } ,
196+ ) ;
197+ return ;
198+ }
199+
200+ if let Some ( reason) = synthetic_delivery_read_ack_reason ( event_id) {
201+ emit_delivery_read_ack_telemetry (
202+ sdk_out_tx. clone ( ) ,
203+ BrokerEvent :: DeliveryReadAck {
204+ name : worker_name. clone ( ) ,
205+ delivery_id : delivery_id. clone ( ) ,
206+ event_id : event_id. clone ( ) ,
207+ status : DeliveryReadAckStatus :: SkippedSynthetic ,
208+ reason : Some ( reason. to_string ( ) ) ,
209+ } ,
210+ ) ;
211+ return ;
212+ }
213+
214+ let relaycast_http = relaycast_http. clone ( ) ;
215+ let sdk_out_tx = sdk_out_tx. clone ( ) ;
216+ let worker_name = worker_name. clone ( ) ;
217+ let cli_hint = cli_hint. map ( str:: to_string) ;
218+ let delivery_id = delivery_id. clone ( ) ;
219+ let event_id = event_id. clone ( ) ;
220+
221+ tokio:: spawn ( async move {
222+ let result = timeout (
223+ timeout_window,
224+ relaycast_http. mark_read_as_agent (
225+ worker_name. as_str ( ) ,
226+ cli_hint. as_deref ( ) ,
227+ event_id. as_str ( ) ,
228+ ) ,
229+ )
230+ . await ;
231+
232+ match result {
233+ Ok ( Ok ( _) ) => {
234+ let _ = send_broker_event (
235+ & sdk_out_tx,
236+ BrokerEvent :: DeliveryReadAck {
237+ name : worker_name,
238+ delivery_id,
239+ event_id,
240+ status : DeliveryReadAckStatus :: Marked ,
241+ reason : None ,
242+ } ,
243+ )
244+ . await ;
245+ }
246+ Ok ( Err ( error) ) => {
247+ let reason = error. to_string ( ) ;
248+ tracing:: warn!(
249+ target = "agent_relay::broker" ,
250+ worker = %worker_name,
251+ delivery_id = %delivery_id,
252+ event_id = %event_id,
253+ error = %reason,
254+ "failed to mark relaycast message read after delivery_ack"
255+ ) ;
256+ let _ = send_broker_event (
257+ & sdk_out_tx,
258+ BrokerEvent :: DeliveryReadAck {
259+ name : worker_name,
260+ delivery_id,
261+ event_id,
262+ status : DeliveryReadAckStatus :: Failed ,
263+ reason : Some ( reason) ,
264+ } ,
265+ )
266+ . await ;
267+ }
268+ Err ( _) => {
269+ let reason = format ! (
270+ "relaycast mark_read timed out after {}ms" ,
271+ timeout_window. as_millis( )
272+ ) ;
273+ tracing:: warn!(
274+ target = "agent_relay::broker" ,
275+ worker = %worker_name,
276+ delivery_id = %delivery_id,
277+ event_id = %event_id,
278+ timeout_ms = %timeout_window. as_millis( ) ,
279+ "timed out marking relaycast message read after delivery_ack"
280+ ) ;
281+ let _ = send_broker_event (
282+ & sdk_out_tx,
283+ BrokerEvent :: DeliveryReadAck {
284+ name : worker_name,
285+ delivery_id,
286+ event_id,
287+ status : DeliveryReadAckStatus :: Failed ,
288+ reason : Some ( reason) ,
289+ } ,
290+ )
291+ . await ;
292+ }
293+ }
294+ } ) ;
295+ }
296+
297+ fn emit_delivery_read_ack_telemetry (
298+ sdk_out_tx : mpsc:: Sender < ProtocolEnvelope < Value > > ,
299+ event : BrokerEvent ,
300+ ) {
301+ tokio:: spawn ( async move {
302+ let _ = send_broker_event ( & sdk_out_tx, event) . await ;
303+ } ) ;
304+ }
305+
119306/// Outcome of [`queue_inbound_for_delivery_mode`]. Distinguishes the
120307/// three cases broker call sites care about: the message is queued and
121308/// should wait for an explicit flush, the queue should be drained now,
0 commit comments