1use std::collections::{BTreeMap, HashMap};
5
6use schemars::JsonSchema;
7use serde::{Deserialize, Serialize};
8
9use crate::ensure_protocol_version;
10use crate::registry_errors::{RemoteProtocolError, require_non_empty};
11use crate::usage_activity::RemoteUsage;
12
13#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
14pub struct RemoteSchemaContract {
15 pub canonical: serde_json::Value,
16 #[serde(
17 default,
18 skip_serializing_if = "RemoteSchemaProjectionPolicy::is_default"
19 )]
20 pub projection: RemoteSchemaProjectionPolicy,
21}
22
23impl RemoteSchemaContract {
24 fn new(canonical: serde_json::Value) -> Self {
25 Self {
26 canonical,
27 projection: RemoteSchemaProjectionPolicy::default(),
28 }
29 }
30}
31
32impl Default for RemoteSchemaContract {
33 fn default() -> Self {
34 Self::new(serde_json::Value::Null)
35 }
36}
37
38#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
39pub struct RemoteSchemaProjectionPolicy {
40 #[serde(default, skip_serializing_if = "RemoteProjectionMode::is_auto")]
41 pub mode: RemoteProjectionMode,
42 #[serde(default, skip_serializing_if = "Vec::is_empty")]
43 pub overrides: Vec<RemoteSchemaProjectionOverride>,
44}
45
46impl RemoteSchemaProjectionPolicy {
47 fn is_default(&self) -> bool {
48 self.mode == RemoteProjectionMode::Auto && self.overrides.is_empty()
49 }
50}
51
52impl Default for RemoteSchemaProjectionPolicy {
53 fn default() -> Self {
54 Self {
55 mode: RemoteProjectionMode::Auto,
56 overrides: Vec::new(),
57 }
58 }
59}
60
61#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
62#[serde(rename_all = "snake_case")]
63pub enum RemoteProjectionMode {
64 #[default]
65 Auto,
66 ExplicitOnly,
67 Exact,
68}
69
70impl RemoteProjectionMode {
71 fn is_auto(&self) -> bool {
72 *self == Self::Auto
73 }
74}
75
76#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
77pub struct RemoteSchemaProjectionOverride {
78 pub dialect: String,
79 pub schema: serde_json::Value,
80}
81
82pub(crate) fn default_remote_input_schema() -> RemoteSchemaContract {
83 RemoteSchemaContract::new(serde_json::json!({
84 "type": "object",
85 "properties": {},
86 "additionalProperties": true
87 }))
88}
89
90#[derive(Clone, Debug, PartialEq, Serialize, Deserialize, JsonSchema)]
91pub struct RemoteLlmRequest {
92 pub protocol_version: u32,
93 pub request_id: String,
94 pub scope: RemoteLlmRequestScope,
95 pub model_intent: RemoteModelIntent,
96 #[serde(default, skip_serializing_if = "Vec::is_empty")]
97 pub messages: Vec<RemoteLlmMessage>,
98 #[serde(default, skip_serializing_if = "Vec::is_empty")]
99 pub attachments: Vec<RemoteAttachmentSource>,
100 #[serde(default, skip_serializing_if = "Vec::is_empty")]
101 pub tools: Vec<RemoteLlmToolSpec>,
102 #[serde(default)]
103 pub tool_choice: RemoteLlmToolChoice,
104 #[serde(default, skip_serializing_if = "Option::is_none")]
105 pub output_spec: Option<RemoteLlmOutputSpec>,
106 #[serde(default, skip_serializing_if = "RemoteGenerationOptions::is_empty")]
107 pub generation: RemoteGenerationOptions,
108 #[serde(default, skip_serializing_if = "HashMap::is_empty")]
109 pub metadata: HashMap<String, serde_json::Value>,
110}
111
112impl RemoteLlmRequest {
113 pub fn validate(&self) -> Result<(), RemoteProtocolError> {
114 ensure_protocol_version(self.protocol_version)?;
115 require_non_empty("RemoteLlmRequest", "request_id", &self.request_id)?;
116 self.scope.validate()?;
117 self.model_intent.validate()?;
118 self.generation.validate("RemoteLlmRequest")?;
119 for (index, message) in self.messages.iter().enumerate() {
120 message.validate(index)?;
121 }
122 for (index, attachment) in self.attachments.iter().enumerate() {
123 attachment.validate(index)?;
124 }
125 for tool in &self.tools {
126 tool.validate()?;
127 }
128 if let Some(output_spec) = &self.output_spec {
129 output_spec.validate()?;
130 }
131 Ok(())
132 }
133}
134
135#[derive(Clone, Debug, PartialEq, Serialize, Deserialize, JsonSchema)]
136pub struct RemoteLlmResponse {
137 pub protocol_version: u32,
138 pub request_id: String,
139 #[serde(default)]
140 pub full_text: String,
141 #[serde(default, skip_serializing_if = "Vec::is_empty")]
142 pub output_parts: Vec<RemoteLlmOutputPart>,
143 #[serde(default)]
144 pub usage: RemoteUsage,
145 #[serde(default)]
146 pub terminal_reason: RemoteLlmTerminalReason,
147 #[serde(default, skip_serializing_if = "Vec::is_empty")]
148 pub diagnostics: Vec<RemoteDiagnostic>,
149 #[serde(default, skip_serializing_if = "RemoteProviderMetadata::is_empty")]
150 pub provider_metadata: RemoteProviderMetadata,
151 #[serde(default, skip_serializing_if = "Option::is_none")]
152 pub execution_evidence: Option<RemoteExecutionEvidence>,
153}
154
155#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
156pub struct RemoteExecutionEvidence {
157 #[serde(default, skip_serializing_if = "Option::is_none")]
158 pub served_model: Option<String>,
159 #[serde(default, skip_serializing_if = "Option::is_none")]
160 pub provider_response_id: Option<String>,
161 #[serde(default, skip_serializing_if = "Option::is_none")]
162 pub provider_request_id: Option<String>,
163 #[serde(default, skip_serializing_if = "Option::is_none")]
164 pub reasoning_output_tokens: Option<u64>,
165 #[serde(default, skip_serializing_if = "Option::is_none")]
166 pub provider_finish_reason: Option<String>,
167}
168
169impl RemoteLlmResponse {
170 pub fn validate(&self) -> Result<(), RemoteProtocolError> {
171 ensure_protocol_version(self.protocol_version)?;
172 require_non_empty("RemoteLlmResponse", "request_id", &self.request_id)?;
173 Ok(())
174 }
175}
176
177#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
178pub struct RemoteModelIntent {
179 pub model: String,
180 #[serde(default)]
181 pub variant: RemoteReasoningSelection,
182 #[serde(default, skip_serializing_if = "RemoteModelCapability::is_empty")]
185 pub capability: RemoteModelCapability,
186 #[serde(default, skip_serializing_if = "Option::is_none")]
187 pub provider: Option<String>,
188 #[serde(default, skip_serializing_if = "HashMap::is_empty")]
189 pub metadata: HashMap<String, String>,
190}
191
192#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
196pub struct RemoteModelCapability {
197 #[serde(default, skip_serializing_if = "Option::is_none")]
198 pub reasoning: Option<RemoteReasoningCapability>,
199 #[serde(default, skip_serializing_if = "Option::is_none")]
200 pub cache_control: Option<RemoteCacheControlDialect>,
201 #[serde(default, skip_serializing_if = "Option::is_none")]
202 pub stream_termination: Option<RemoteStreamTermination>,
203}
204
205impl RemoteModelCapability {
206 pub fn is_empty(&self) -> bool {
207 self.reasoning.is_none()
208 && self.cache_control.is_none()
209 && self.stream_termination.is_none()
210 }
211}
212
213#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
215#[serde(rename_all = "snake_case")]
216pub enum RemoteCacheControlDialect {
217 Anthropic,
218 Gemini,
219}
220
221#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
223#[serde(rename_all = "snake_case")]
224pub enum RemoteStreamTermination {
225 RequireTerminalEvidence,
226 EofTolerated,
227}
228
229#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
231pub struct RemoteReasoningCapability {
232 #[serde(default)]
233 pub efforts: Vec<String>,
234 #[serde(default, skip_serializing_if = "Option::is_none")]
235 pub default_effort: Option<String>,
236 #[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
237 pub aliases: BTreeMap<String, String>,
238 #[serde(default)]
239 pub encoding: RemoteReasoningEncoding,
240 #[serde(default, skip_serializing_if = "Option::is_none")]
241 pub disable: Option<RemoteReasoningDisableEncoding>,
242 #[serde(default)]
243 pub mandatory: bool,
244}
245
246#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
247#[serde(rename_all = "snake_case")]
248pub enum RemoteReasoningSelection {
249 #[default]
250 ProviderDefault,
251 Disabled,
252 Effort(String),
253}
254
255#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
256#[serde(rename_all = "snake_case")]
257pub enum RemoteReasoningDisableEncoding {
258 Native,
259 Omit,
260 Effort(String),
261 Budget(u32),
262 ToggleFalse,
263}
264
265#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
267#[serde(rename_all = "snake_case")]
268pub enum RemoteReasoningEncoding {
269 #[default]
270 Effort,
271 Budget(BTreeMap<String, u32>),
272}
273
274impl RemoteModelIntent {
275 pub fn new(model: impl Into<String>) -> Self {
276 Self {
277 model: model.into(),
278 variant: RemoteReasoningSelection::ProviderDefault,
279 capability: RemoteModelCapability::default(),
280 provider: None,
281 metadata: HashMap::new(),
282 }
283 }
284
285 pub(crate) fn validate(&self) -> Result<(), RemoteProtocolError> {
286 require_non_empty("RemoteModelIntent", "model", &self.model)
287 }
288}
289
290#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
291pub struct RemoteGenerationOptions {
292 #[serde(default, skip_serializing_if = "Option::is_none")]
293 pub output_token_cap: Option<u64>,
294 #[serde(default, skip_serializing_if = "Option::is_none")]
295 pub temperature: Option<String>,
296 #[serde(default, skip_serializing_if = "Option::is_none")]
297 pub top_p: Option<String>,
298 #[serde(default, skip_serializing_if = "Vec::is_empty")]
299 pub stop: Vec<String>,
300 #[serde(default, skip_serializing_if = "HashMap::is_empty")]
301 pub provider_options: HashMap<String, String>,
302}
303
304impl RemoteGenerationOptions {
305 pub fn is_empty(&self) -> bool {
306 self.output_token_cap.is_none()
307 && self.temperature.is_none()
308 && self.top_p.is_none()
309 && self.stop.is_empty()
310 && self.provider_options.is_empty()
311 }
312
313 pub(crate) fn validate(&self, type_name: &'static str) -> Result<(), RemoteProtocolError> {
314 if self.output_token_cap == Some(0) {
315 return Err(RemoteProtocolError::InvalidEnvelope {
316 type_name,
317 message: "generation.output_token_cap must be greater than zero".to_string(),
318 });
319 }
320 Ok(())
321 }
322}
323
324#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
325pub struct RemoteLlmRequestScope {
326 pub session_id: String,
327 pub agent_frame_id: String,
328 pub request_id: String,
329}
330
331impl RemoteLlmRequestScope {
332 pub fn new(
333 session_id: impl Into<String>,
334 agent_frame_id: impl Into<String>,
335 request_id: impl Into<String>,
336 ) -> Self {
337 Self {
338 session_id: session_id.into(),
339 agent_frame_id: agent_frame_id.into(),
340 request_id: request_id.into(),
341 }
342 }
343
344 fn validate(&self) -> Result<(), RemoteProtocolError> {
345 require_non_empty("RemoteLlmRequestScope", "session_id", &self.session_id)?;
346 require_non_empty(
347 "RemoteLlmRequestScope",
348 "agent_frame_id",
349 &self.agent_frame_id,
350 )?;
351 require_non_empty("RemoteLlmRequestScope", "request_id", &self.request_id)?;
352 Ok(())
353 }
354}
355
356#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
357#[serde(rename_all = "snake_case")]
358pub enum RemoteLlmRole {
359 #[default]
360 User,
361 Assistant,
362 System,
363}
364
365#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
366pub struct RemoteLlmMessage {
367 pub role: RemoteLlmRole,
368 #[serde(default, skip_serializing_if = "Vec::is_empty")]
369 pub content: Vec<RemoteLlmContentBlock>,
370}
371
372impl RemoteLlmMessage {
373 fn validate(&self, index: usize) -> Result<(), RemoteProtocolError> {
374 if self.content.is_empty() {
375 return Err(RemoteProtocolError::InvalidEnvelope {
376 type_name: "RemoteLlmMessage",
377 message: format!("message at index {index} must contain at least one block"),
378 });
379 }
380 for block in &self.content {
381 block.validate()?;
382 }
383 Ok(())
384 }
385}
386
387#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
388#[serde(tag = "type", rename_all = "snake_case")]
389pub enum RemoteLlmContentBlock {
390 Text {
391 text: String,
392 #[serde(default, skip_serializing_if = "Option::is_none")]
393 response_meta: Option<RemoteResponseTextMeta>,
394 #[serde(default, skip_serializing_if = "std::ops::Not::not")]
395 cache_breakpoint: bool,
396 },
397 Attachment {
398 attachment_index: usize,
399 },
400 ToolCall {
401 call_id: String,
402 tool_name: String,
403 input_json: String,
404 #[serde(default, skip_serializing_if = "Option::is_none")]
405 replay: Option<RemoteProviderReplayMeta>,
406 },
407 ToolResult {
408 call_id: String,
409 content: String,
410 #[serde(default, skip_serializing_if = "Option::is_none")]
411 tool_name: Option<String>,
412 },
413 Reasoning {
414 text: String,
415 #[serde(default, skip_serializing_if = "Option::is_none")]
416 replay: Option<RemoteProviderReasoningReplay>,
417 },
418}
419
420impl RemoteLlmContentBlock {
421 fn validate(&self) -> Result<(), RemoteProtocolError> {
422 match self {
423 Self::ToolCall {
424 call_id, tool_name, ..
425 } => {
426 require_non_empty("RemoteLlmContentBlock::ToolCall", "call_id", call_id)?;
427 require_non_empty("RemoteLlmContentBlock::ToolCall", "tool_name", tool_name)
428 }
429 Self::ToolResult { call_id, .. } => {
430 require_non_empty("RemoteLlmContentBlock::ToolResult", "call_id", call_id)
431 }
432 Self::Text { .. } | Self::Attachment { .. } | Self::Reasoning { .. } => Ok(()),
433 }
434 }
435}
436
437#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
438pub struct RemoteResponseTextMeta {
439 #[serde(default, skip_serializing_if = "Option::is_none")]
440 pub id: Option<String>,
441 #[serde(default, skip_serializing_if = "Option::is_none")]
442 pub status: Option<String>,
443 #[serde(default, skip_serializing_if = "Option::is_none")]
444 pub phase: Option<String>,
445 #[serde(default, skip_serializing_if = "Option::is_none")]
446 pub provider_payload: Option<String>,
447 #[serde(default, skip_serializing_if = "Option::is_none")]
448 pub origin_provider: Option<String>,
449 #[serde(default, skip_serializing_if = "Option::is_none")]
450 pub origin_model: Option<String>,
451}
452
453#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
454pub struct RemoteProviderReplayMeta {
455 #[serde(default, skip_serializing_if = "Option::is_none")]
456 pub item_id: Option<String>,
457 #[serde(default, skip_serializing_if = "Option::is_none")]
458 pub opaque: Option<String>,
459}
460
461#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
462pub struct RemoteProviderReasoningReplay {
463 #[serde(default, skip_serializing_if = "Option::is_none")]
464 pub item_id: Option<String>,
465 #[serde(default, skip_serializing_if = "Option::is_none")]
466 pub encrypted_content: Option<String>,
467 #[serde(default, skip_serializing_if = "Option::is_none")]
468 pub signature: Option<String>,
469 #[serde(default, skip_serializing_if = "std::ops::Not::not")]
470 pub redacted: bool,
471 #[serde(default, skip_serializing_if = "Vec::is_empty")]
472 pub summary: Vec<String>,
473}
474
475#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
476#[serde(tag = "source", rename_all = "snake_case", deny_unknown_fields)]
477pub enum RemoteAttachmentSource {
478 Inline {
479 media_type: String,
480 data_base64: String,
481 },
482 Stored {
483 attachment_ref: RemoteAttachmentRef,
484 },
485 ExternalUrl {
486 media_type: String,
487 url: String,
488 },
489 ProviderFile {
490 provider_scope: RemoteProviderFileScope,
491 id: String,
492 },
493}
494
495impl RemoteAttachmentSource {
496 pub(crate) fn validate(&self, _index: usize) -> Result<(), RemoteProtocolError> {
497 match self {
498 Self::Inline {
499 media_type,
500 data_base64,
501 } => {
502 require_non_empty("RemoteAttachmentSource::Inline", "media_type", media_type)?;
503 validate_media_type("RemoteAttachmentSource::Inline", media_type)?;
504 require_non_empty("RemoteAttachmentSource::Inline", "data_base64", data_base64)
505 }
506 Self::Stored { attachment_ref } => attachment_ref.validate(),
507 Self::ExternalUrl { media_type, url } => {
508 require_non_empty(
509 "RemoteAttachmentSource::ExternalUrl",
510 "media_type",
511 media_type,
512 )?;
513 validate_media_type("RemoteAttachmentSource::ExternalUrl", media_type)?;
514 require_non_empty("RemoteAttachmentSource::ExternalUrl", "url", url)
515 }
516 Self::ProviderFile { provider_scope, id } => {
517 provider_scope.validate()?;
518 require_non_empty("RemoteAttachmentSource::ProviderFile", "id", id)
519 }
520 }
521 }
522}
523
524#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
525pub struct RemoteProviderFileScope {
526 pub provider: String,
527 pub credential_scope: String,
528}
529
530impl RemoteProviderFileScope {
531 fn validate(&self) -> Result<(), RemoteProtocolError> {
532 require_non_empty("RemoteProviderFileScope", "provider", &self.provider)?;
533 require_non_empty(
534 "RemoteProviderFileScope",
535 "credential_scope",
536 &self.credential_scope,
537 )
538 }
539}
540
541#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
542pub struct RemoteAttachmentRef {
543 pub id: String,
544 pub media_type: String,
545 pub byte_len: u64,
546 #[serde(default, skip_serializing_if = "Option::is_none")]
547 pub type_metadata: Option<RemoteAttachmentTypeMetadata>,
548 #[serde(default, skip_serializing_if = "Option::is_none")]
549 pub label: Option<String>,
550}
551
552impl RemoteAttachmentRef {
553 pub(crate) fn validate(&self) -> Result<(), RemoteProtocolError> {
554 require_non_empty("RemoteAttachmentRef", "id", &self.id)?;
555 require_non_empty("RemoteAttachmentRef", "media_type", &self.media_type)?;
556 validate_media_type("RemoteAttachmentRef", &self.media_type)
557 }
558}
559
560fn validate_media_type(type_name: &'static str, value: &str) -> Result<(), RemoteProtocolError> {
561 let mut pieces = value.split('/');
562 let type_token = pieces.next().unwrap_or_default();
563 let subtype_token = pieces.next().unwrap_or_default();
564 if pieces.next().is_some()
565 || !is_media_type_token(type_token)
566 || !is_media_type_token(subtype_token)
567 {
568 return Err(RemoteProtocolError::InvalidEnvelope {
569 type_name,
570 message: format!("media_type `{value}` must be a syntactically valid type/subtype"),
571 });
572 }
573 Ok(())
574}
575
576fn is_media_type_token(value: &str) -> bool {
577 !value.is_empty()
578 && value.bytes().all(|byte| {
579 byte.is_ascii_alphanumeric()
580 || matches!(
581 byte,
582 b'!' | b'#'
583 | b'$'
584 | b'%'
585 | b'&'
586 | b'\''
587 | b'*'
588 | b'+'
589 | b'-'
590 | b'.'
591 | b'^'
592 | b'_'
593 | b'`'
594 | b'|'
595 | b'~'
596 )
597 })
598}
599
600#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
601#[serde(tag = "type", rename_all = "snake_case", deny_unknown_fields)]
602pub enum RemoteAttachmentTypeMetadata {
603 Image {
604 #[serde(default, skip_serializing_if = "Option::is_none")]
605 width: Option<u32>,
606 #[serde(default, skip_serializing_if = "Option::is_none")]
607 height: Option<u32>,
608 },
609}
610
611#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
612pub struct RemoteLlmToolSpec {
613 pub name: String,
614 #[serde(default)]
615 pub description: String,
616 #[serde(default = "default_remote_input_schema")]
617 pub input_schema: RemoteSchemaContract,
618 #[serde(default)]
619 pub output_schema: RemoteSchemaContract,
620}
621
622impl RemoteLlmToolSpec {
623 pub(crate) fn validate(&self) -> Result<(), RemoteProtocolError> {
624 require_non_empty("RemoteLlmToolSpec", "name", &self.name)
625 }
626}
627
628#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
629#[serde(rename_all = "snake_case")]
630pub enum RemoteLlmToolChoice {
631 #[default]
632 Auto,
633 None,
634 Required,
635}
636
637#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
638#[serde(tag = "type", rename_all = "snake_case")]
639pub enum RemoteLlmOutputSpec {
640 JsonObject,
641 JsonSchema {
642 name: String,
643 schema: RemoteSchemaContract,
644 strict: bool,
645 },
646}
647
648impl RemoteLlmOutputSpec {
649 fn validate(&self) -> Result<(), RemoteProtocolError> {
650 match self {
651 Self::JsonObject => Ok(()),
652 Self::JsonSchema { name, .. } => {
653 require_non_empty("RemoteLlmOutputSpec::JsonSchema", "name", name)
654 }
655 }
656 }
657}
658
659#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
660#[serde(tag = "type", rename_all = "snake_case")]
661pub enum RemoteLlmOutputPart {
662 Text {
663 text: String,
664 #[serde(default, skip_serializing_if = "Option::is_none")]
665 response_meta: Option<RemoteResponseTextMeta>,
666 },
667 Reasoning {
668 text: String,
669 #[serde(default, skip_serializing_if = "Option::is_none")]
670 replay: Option<RemoteProviderReasoningReplay>,
671 },
672 ToolCall {
673 call_id: String,
674 tool_name: String,
675 input_json: String,
676 #[serde(default, skip_serializing_if = "Option::is_none")]
677 replay: Option<RemoteProviderReplayMeta>,
678 },
679}
680
681#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
682#[serde(rename_all = "snake_case")]
683pub enum RemoteLlmTerminalReason {
684 Stop,
685 ToolUse,
686 OutputLimit,
687 ContextOverflow,
688 ContentFilter,
689 ProviderError,
690 Cancelled,
691 #[default]
692 Unknown,
693}
694
695#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
697#[serde(rename_all = "snake_case")]
698pub enum RemoteProviderFailureKind {
699 Transport,
700 Timeout,
701 Http,
702 Stream,
703 Auth,
704 Validation,
705 Quota,
706 Unsupported,
707 #[default]
708 #[serde(other)]
709 Unknown,
710}
711
712#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
713pub struct RemoteProviderMetadata {
714 #[serde(default, skip_serializing_if = "Option::is_none")]
715 pub usage: Option<serde_json::Value>,
716 #[serde(default, skip_serializing_if = "Option::is_none")]
717 pub request_body: Option<String>,
718 #[serde(default, skip_serializing_if = "Option::is_none")]
719 pub http_summary: Option<String>,
720 #[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
721 pub data: BTreeMap<String, serde_json::Value>,
722}
723
724impl RemoteProviderMetadata {
725 pub fn is_empty(&self) -> bool {
726 self.usage.is_none()
727 && self.request_body.is_none()
728 && self.http_summary.is_none()
729 && self.data.is_empty()
730 }
731}
732
733#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
734pub struct RemoteDiagnostic {
735 pub kind: String,
736 #[serde(default, skip_serializing_if = "Option::is_none")]
737 pub code: Option<String>,
738 pub message: String,
739 #[serde(default, skip_serializing_if = "Option::is_none")]
740 pub data: Option<serde_json::Value>,
741}