Skip to main content

antigravity_sdk_rust/
types.rs

1//! Common configuration structures, enums, and SDK data models.
2//!
3//! This module houses all the data types shared across the SDK, including Gemini configuration
4//! parameters, system instructions, capability filters, built-in tools list, and step execution progress structs.
5
6use serde::{Deserialize, Serialize};
7use serde_json::Value;
8use std::collections::HashMap;
9
10/// The default model name used when none is specified.
11pub const DEFAULT_MODEL: &str = "gemini-3.5-flash";
12
13/// The default image generation model name used.
14pub const DEFAULT_IMAGE_GENERATION_MODEL: &str = "gemini-3.1-flash-image-preview";
15
16/// Configures the intensity of the reasoning/thinking process for models that support it.
17#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
18#[serde(rename_all = "lowercase")]
19pub enum ThinkingLevel {
20    /// Minimal reasoning overhead.
21    Minimal,
22    /// Low reasoning.
23    Low,
24    /// Medium reasoning.
25    Medium,
26    /// High reasoning.
27    High,
28}
29
30/// Generation configuration parameters.
31#[derive(Debug, Clone, Serialize, Deserialize, Default)]
32pub struct GenerationConfig {
33    /// Desired thinking level for reasoning-based models.
34    #[serde(skip_serializing_if = "Option::is_none")]
35    pub thinking_level: Option<ThinkingLevel>,
36}
37
38/// Specific model entry defining the model name, key, and generation settings.
39#[derive(Debug, Clone, Serialize, Deserialize)]
40pub struct ModelEntry {
41    /// The name/identifier of the model.
42    pub name: String,
43    /// Model-specific API key (if overriding the global key).
44    #[serde(skip_serializing_if = "Option::is_none")]
45    pub api_key: Option<String>,
46    /// Generation settings (e.g. thinking configurations).
47    #[serde(default)]
48    pub generation: GenerationConfig,
49}
50
51impl Default for ModelEntry {
52    fn default() -> Self {
53        Self {
54            name: DEFAULT_MODEL.to_string(),
55            api_key: None,
56            generation: GenerationConfig::default(),
57        }
58    }
59}
60
61/// Mapping of models configured for different tasks in the agent's session.
62#[derive(Debug, Clone, Serialize, Deserialize)]
63pub struct ModelConfig {
64    /// The primary text/chat model.
65    #[serde(default = "default_model_entry")]
66    pub default: ModelEntry,
67    /// The model used for image generation tasks.
68    #[serde(default = "default_image_generation_entry")]
69    pub image_generation: ModelEntry,
70}
71
72fn default_model_entry() -> ModelEntry {
73    ModelEntry {
74        name: DEFAULT_MODEL.to_string(),
75        api_key: None,
76        generation: GenerationConfig::default(),
77    }
78}
79
80fn default_image_generation_entry() -> ModelEntry {
81    ModelEntry {
82        name: DEFAULT_IMAGE_GENERATION_MODEL.to_string(),
83        api_key: None,
84        generation: GenerationConfig::default(),
85    }
86}
87
88impl Default for ModelConfig {
89    fn default() -> Self {
90        Self {
91            default: default_model_entry(),
92            image_generation: default_image_generation_entry(),
93        }
94    }
95}
96
97/// Root configurations for the Gemini AI model endpoints.
98#[derive(Debug, Clone, Serialize, Deserialize, Default)]
99pub struct GeminiConfig {
100    /// Global API key for Gemini endpoints.
101    #[serde(skip_serializing_if = "Option::is_none")]
102    pub api_key: Option<String>,
103    /// If true, uses the Vertex AI backend instead of Gemini Developer API.
104    #[serde(default)]
105    pub vertex: bool,
106    /// GCP Project ID for Vertex AI.
107    #[serde(skip_serializing_if = "Option::is_none")]
108    pub project: Option<String>,
109    /// GCP Location/Region for Vertex AI (e.g., "us-central1").
110    #[serde(skip_serializing_if = "Option::is_none")]
111    pub location: Option<String>,
112    /// Model configurations.
113    #[serde(default)]
114    pub models: ModelConfig,
115    /// Option to enable Google Search grounding tool.
116    #[serde(skip_serializing_if = "Option::is_none")]
117    pub enable_google_search: Option<bool>,
118    /// Option to enable URL context resolution.
119    #[serde(skip_serializing_if = "Option::is_none")]
120    pub enable_url_context: Option<bool>,
121}
122
123/// A structured section appended to system instructions.
124#[derive(Debug, Clone, Serialize, Deserialize)]
125pub struct SystemInstructionSection {
126    /// Main markdown or text body of the section.
127    pub content: String,
128    /// Described title of the section.
129    pub title: String,
130}
131
132/// Directly supplied system instruction instructions text override.
133#[derive(Debug, Clone, Serialize, Deserialize)]
134pub struct CustomSystemInstructions {
135    /// Custom raw instructions text.
136    pub text: String,
137}
138
139/// Appended instructions format, maintaining identity overrides and section segments.
140#[derive(Debug, Clone, Serialize, Deserialize)]
141pub struct AppendedSystemInstructions {
142    /// Optional override for the agent's custom identity block.
143    #[serde(skip_serializing_if = "Option::is_none")]
144    pub custom_identity: Option<String>,
145    /// Sections to be appended to the standard system instructions.
146    #[serde(default)]
147    pub appended_sections: Vec<SystemInstructionSection>,
148}
149
150/// Represents the style or content source for system instructions.
151#[derive(Debug, Clone, Serialize, Deserialize)]
152#[serde(untagged)]
153pub enum SystemInstructions {
154    /// Completely custom text override.
155    Custom(CustomSystemInstructions),
156    /// Standard structured segments appended to the system identity.
157    Appended(AppendedSystemInstructions),
158}
159
160/// Enumeration of built-in tools supported by the agent system.
161#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq, Hash)]
162pub enum BuiltinTools {
163    /// Tool to create a new file.
164    #[serde(rename = "CREATE_FILE")]
165    CreateFile,
166    /// Tool to edit an existing file.
167    #[serde(rename = "EDIT_FILE")]
168    EditFile,
169    /// Tool to query/find files in a directory.
170    #[serde(rename = "FIND_FILE")]
171    FindFile,
172    /// Tool to list files inside a directory.
173    #[serde(rename = "LIST_DIR")]
174    ListDir,
175    /// Tool to execute a shell command.
176    #[serde(rename = "RUN_COMMAND")]
177    RunCommand,
178    /// Tool to perform ripgrep searches.
179    #[serde(rename = "SEARCH_DIR")]
180    SearchDir,
181    /// Tool to view a file's content.
182    #[serde(rename = "VIEW_FILE")]
183    ViewFile,
184    /// Tool to instantiate a subagent.
185    #[serde(rename = "START_SUBAGENT")]
186    StartSubagent,
187    /// Tool to generate images from descriptions.
188    #[serde(rename = "GENERATE_IMAGE")]
189    GenerateImage,
190    /// Terminating signal indicating the task is completed.
191    #[serde(rename = "FINISH")]
192    Finish,
193}
194
195impl BuiltinTools {
196    /// Returns the static string slice mapping to the tool name.
197    pub const fn as_str(&self) -> &'static str {
198        match self {
199            Self::CreateFile => "CREATE_FILE",
200            Self::EditFile => "EDIT_FILE",
201            Self::FindFile => "FIND_FILE",
202            Self::ListDir => "LIST_DIR",
203            Self::RunCommand => "RUN_COMMAND",
204            Self::SearchDir => "SEARCH_DIR",
205            Self::ViewFile => "VIEW_FILE",
206            Self::StartSubagent => "START_SUBAGENT",
207            Self::GenerateImage => "GENERATE_IMAGE",
208            Self::Finish => "FINISH",
209        }
210    }
211
212    /// Returns a list of all safe, read-only tools.
213    pub fn read_only() -> Vec<Self> {
214        vec![
215            Self::FindFile,
216            Self::ListDir,
217            Self::ViewFile,
218            Self::SearchDir,
219        ]
220    }
221}
222
223/// Agent capabilities and tool restrictions configuration.
224#[derive(Debug, Clone, Serialize, Deserialize, Default)]
225pub struct CapabilitiesConfig {
226    /// List of explicitly enabled tools.
227    #[serde(skip_serializing_if = "Option::is_none")]
228    pub enabled_tools: Option<Vec<BuiltinTools>>,
229    /// List of explicitly disabled tools.
230    #[serde(skip_serializing_if = "Option::is_none")]
231    pub disabled_tools: Option<Vec<BuiltinTools>>,
232    /// Threshold at which the message history is compacted/summarized.
233    #[serde(skip_serializing_if = "Option::is_none")]
234    pub compaction_threshold: Option<u32>,
235    /// Custom schema override for the finish tool schema.
236    #[serde(skip_serializing_if = "Option::is_none")]
237    pub finish_tool_schema_json: Option<String>,
238    /// Model designated for processing images.
239    #[serde(skip_serializing_if = "Option::is_none")]
240    pub image_model: Option<String>,
241}
242
243/// Configuration settings for Model Context Protocol (MCP) servers.
244#[derive(Debug, Clone, Serialize, Deserialize)]
245#[serde(tag = "type")]
246pub enum McpServerConfig {
247    /// Launch the MCP server as a local stdio process.
248    #[serde(rename = "stdio")]
249    Stdio {
250        /// Unique identifier for this MCP server.
251        name: String,
252        /// command binary.
253        command: String,
254        /// execution arguments.
255        args: Vec<String>,
256        /// Explicit allowlist of tools to enable. Mutually exclusive with `disabled_tools`.
257        #[serde(skip_serializing_if = "Option::is_none")]
258        enabled_tools: Option<Vec<String>>,
259        /// Explicit denylist of tools to disable. Mutually exclusive with `enabled_tools`.
260        #[serde(skip_serializing_if = "Option::is_none")]
261        disabled_tools: Option<Vec<String>>,
262    },
263    /// Connect to the MCP server via Server-Sent Events (SSE).
264    #[serde(rename = "sse")]
265    Sse {
266        /// Unique identifier for this MCP server.
267        name: String,
268        /// HTTP URL endpoint.
269        url: String,
270        /// Additional HTTP headers.
271        #[serde(skip_serializing_if = "Option::is_none")]
272        headers: Option<HashMap<String, String>>,
273        /// Explicit allowlist of tools to enable.
274        #[serde(skip_serializing_if = "Option::is_none")]
275        enabled_tools: Option<Vec<String>>,
276        /// Explicit denylist of tools to disable.
277        #[serde(skip_serializing_if = "Option::is_none")]
278        disabled_tools: Option<Vec<String>>,
279    },
280    /// Connect to the MCP server via standard HTTP.
281    #[serde(rename = "http")]
282    Http {
283        /// Unique identifier for this MCP server.
284        name: String,
285        /// HTTP URL endpoint.
286        url: String,
287        /// Additional HTTP headers.
288        #[serde(skip_serializing_if = "Option::is_none")]
289        headers: Option<HashMap<String, String>>,
290        /// General connection timeout in seconds.
291        #[serde(default = "default_mcp_timeout")]
292        timeout: f64,
293        /// Reading timeout for the SSE listener.
294        #[serde(default = "default_mcp_sse_timeout")]
295        sse_read_timeout: f64,
296        /// Flag whether to terminate the channel connection when closed.
297        #[serde(default = "default_true")]
298        terminate_on_close: bool,
299        /// Explicit allowlist of tools to enable.
300        #[serde(skip_serializing_if = "Option::is_none")]
301        enabled_tools: Option<Vec<String>>,
302        /// Explicit denylist of tools to disable.
303        #[serde(skip_serializing_if = "Option::is_none")]
304        disabled_tools: Option<Vec<String>>,
305    },
306}
307
308impl McpServerConfig {
309    /// Returns the unique name identifier of this MCP server.
310    pub fn name(&self) -> &str {
311        match self {
312            Self::Stdio { name, .. } | Self::Sse { name, .. } | Self::Http { name, .. } => name,
313        }
314    }
315}
316
317/// Error raised when the agent execution encounters a terminal (non-recoverable) error.
318///
319/// This indicates that the agent loop has terminated due to a fatal error
320/// (e.g. model call failure, system constraint violation) and cannot continue.
321#[derive(Debug, Clone)]
322pub struct AntigravityExecutionError {
323    /// The error message describing the terminal failure.
324    pub message: String,
325}
326
327impl std::fmt::Display for AntigravityExecutionError {
328    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
329        write!(f, "Terminal execution error: {}", self.message)
330    }
331}
332
333impl std::error::Error for AntigravityExecutionError {}
334
335const fn default_mcp_timeout() -> f64 {
336    30.0
337}
338const fn default_mcp_sse_timeout() -> f64 {
339    300.0
340}
341const fn default_true() -> bool {
342    true
343}
344
345/// Describes a model's request to execute a registered tool.
346#[derive(Debug, Clone, Serialize, Deserialize)]
347pub struct ToolCall {
348    /// Unique call ID generated for correlation.
349    pub id: String,
350    /// Name of the target tool.
351    pub name: String,
352    /// Arguments payload parsed as JSON.
353    pub args: Value,
354    /// Canonical file system path (if the tool targets a file/directory).
355    #[serde(skip_serializing_if = "Option::is_none")]
356    pub canonical_path: Option<String>,
357}
358
359/// The response outcome of executing a client-side tool.
360#[derive(Debug, Clone, Serialize, Deserialize)]
361pub struct ToolResult {
362    /// Name of the executed tool.
363    pub name: String,
364    /// Optional matching tool call ID.
365    #[serde(skip_serializing_if = "Option::is_none")]
366    pub id: Option<String>,
367    /// Output result of successful tool execution.
368    #[serde(skip_serializing_if = "Option::is_none")]
369    pub result: Option<Value>,
370    /// Error message string if tool execution failed.
371    #[serde(skip_serializing_if = "Option::is_none")]
372    pub error: Option<String>,
373}
374
375/// Consumption stats for API usage tracking.
376#[derive(Debug, Clone, Serialize, Deserialize, Default)]
377pub struct UsageMetadata {
378    /// Tokens included in the request prompt.
379    pub prompt_token_count: i32,
380    /// Tokens generated in candidates.
381    pub candidates_token_count: i32,
382    /// Total combined tokens.
383    pub total_token_count: i32,
384    /// Cache hit content tokens.
385    #[serde(default)]
386    pub cached_content_token_count: i32,
387    /// Tokens consumed during inner thinking/reasoning.
388    #[serde(default)]
389    pub thoughts_token_count: i32,
390}
391
392/// The classification type of a step in the trajectory.
393#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
394pub enum StepType {
395    /// Raw text content returned by the model.
396    #[serde(rename = "TEXT_RESPONSE")]
397    TextResponse,
398    /// Execution of a tool call.
399    #[serde(rename = "TOOL_CALL")]
400    ToolCall,
401    /// Logging or notification events from the system.
402    #[serde(rename = "SYSTEM_MESSAGE")]
403    SystemMessage,
404    /// A history compaction step summarizing context.
405    #[serde(rename = "COMPACTION")]
406    Compaction,
407    /// Terminating milestone indicator.
408    #[serde(rename = "FINISH")]
409    Finish,
410    /// Catch-all variant for unrecognized steps.
411    #[serde(rename = "UNKNOWN")]
412    Unknown,
413}
414
415/// The originating source component of a trajectory step.
416#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
417pub enum StepSource {
418    /// Internal orchestration environment.
419    #[serde(rename = "SYSTEM")]
420    System,
421    /// End-user input.
422    #[serde(rename = "USER")]
423    User,
424    /// Generative model prediction.
425    #[serde(rename = "MODEL")]
426    Model,
427    /// Unknown author.
428    #[serde(rename = "UNKNOWN")]
429    Unknown,
430}
431
432/// The target destination of a step event.
433#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
434pub enum StepTarget {
435    /// Event addressed to the user.
436    #[serde(rename = "TARGET_USER")]
437    User,
438    /// Event executing in the sandbox environment.
439    #[serde(rename = "TARGET_ENVIRONMENT")]
440    Environment,
441    /// Unspecified destination.
442    #[serde(rename = "TARGET_UNSPECIFIED")]
443    Unspecified,
444    /// Unknown destination.
445    #[serde(rename = "UNKNOWN")]
446    Unknown,
447}
448
449/// Lifecycle execution status of a trajectory step.
450#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
451pub enum StepStatus {
452    /// Active/running state.
453    #[serde(rename = "ACTIVE")]
454    Active,
455    /// Completed successfully.
456    #[serde(rename = "DONE")]
457    Done,
458    /// Waiting for user response/confirmation.
459    #[serde(rename = "WAITING_FOR_USER")]
460    WaitingForUser,
461    /// Finished with a fatal error.
462    #[serde(rename = "ERROR")]
463    Error,
464    /// Execution was canceled.
465    #[serde(rename = "CANCELED")]
466    Canceled,
467    /// A fatal, non-recoverable error occurred during execution.
468    #[serde(rename = "TERMINAL_ERROR")]
469    TerminalError,
470    /// Unknown status.
471    #[serde(rename = "UNKNOWN")]
472    Unknown,
473}
474
475/// Individual step event recording an action in the agent's history trajectory.
476#[derive(Debug, Clone, Serialize, Deserialize)]
477pub struct Step {
478    /// Unique identifier for this step.
479    pub id: String,
480    /// Positional index in the trajectory sequence.
481    pub step_index: u32,
482    /// Functional type of the step.
483    pub r#type: StepType,
484    /// Originating author.
485    pub source: StepSource,
486    /// Destination target.
487    pub target: StepTarget,
488    /// Execution status.
489    pub status: StepStatus,
490    /// Main text/markdown content associated with the step.
491    pub content: String,
492    /// Text difference delta relative to previous steps.
493    pub content_delta: String,
494    /// Reasoning thoughts generated for this step.
495    pub thinking: String,
496    /// Thinking reasoning difference delta relative to previous steps.
497    pub thinking_delta: String,
498    /// Custom tool executions registered in this step.
499    pub tool_calls: Vec<ToolCall>,
500    /// Captured execution errors.
501    pub error: String,
502    /// True if this represents the final response segment from the model.
503    pub is_complete_response: Option<bool>,
504    /// Parsed structured JSON output.
505    pub structured_output: Option<Value>,
506    /// Token usage details.
507    pub usage_metadata: Option<UsageMetadata>,
508    /// Unique identifier of the execution cascade grouping subagents.
509    #[serde(default)]
510    pub cascade_id: String,
511    /// Sub-agent trajectory grouping identifier.
512    #[serde(default)]
513    pub trajectory_id: String,
514    /// HTTP status code (if from a network action).
515    #[serde(default)]
516    pub http_code: u32,
517}
518
519impl Default for Step {
520    fn default() -> Self {
521        Self {
522            id: String::new(),
523            step_index: 0,
524            r#type: StepType::Unknown,
525            source: StepSource::Unknown,
526            target: StepTarget::Unknown,
527            status: StepStatus::Unknown,
528            content: String::new(),
529            content_delta: String::new(),
530            thinking: String::new(),
531            thinking_delta: String::new(),
532            tool_calls: Vec::new(),
533            error: String::new(),
534            is_complete_response: None,
535            structured_output: None,
536            usage_metadata: None,
537            cascade_id: String::new(),
538            trajectory_id: String::new(),
539            http_code: 0,
540        }
541    }
542}
543
544/// The result returned by a middleware hook.
545#[derive(Debug, Clone, Serialize, Deserialize, Default)]
546pub struct HookResult {
547    /// True if the operation is allowed to proceed.
548    pub allow: bool,
549    /// Diagnostic or error message details.
550    #[serde(default)]
551    pub message: String,
552}
553
554/// Individual multiple-choice or freeform answer to an interactive user question.
555#[derive(Debug, Clone, Serialize, Deserialize)]
556pub struct QuestionResponse {
557    /// Selected index choices (if multiple-choice).
558    pub selected_option_ids: Option<Vec<String>>,
559    /// Freeform response text.
560    #[serde(default)]
561    pub freeform_response: String,
562    /// True if the question was skipped.
563    #[serde(default)]
564    pub skipped: bool,
565}
566
567/// Complete collection of responses answered to a set of interactive questions.
568#[derive(Debug, Clone, Serialize, Deserialize)]
569pub struct QuestionHookResult {
570    /// List of user responses.
571    pub responses: Vec<QuestionResponse>,
572    /// True if the question panel dialogue was canceled.
573    #[serde(default)]
574    pub cancelled: bool,
575}
576
577/// Single choice option in a multiple-choice question.
578#[derive(Debug, Clone, Serialize, Deserialize)]
579pub struct AskQuestionOption {
580    /// Unique identifier for this choice option.
581    pub id: String,
582    /// Visual label text for the choice.
583    pub text: String,
584}
585
586/// Interactive user question entry structure.
587#[derive(Debug, Clone, Serialize, Deserialize)]
588pub struct AskQuestionEntry {
589    /// Main question prompt text.
590    pub question: String,
591    /// List of multiple-choice options.
592    pub options: Vec<AskQuestionOption>,
593    /// True if multiple selections are supported.
594    #[serde(default)]
595    pub is_multi_select: bool,
596}
597
598/// Final summary outcome of a chat interaction.
599#[derive(Debug, Clone, Serialize, Deserialize)]
600pub struct ChatResponse {
601    /// Combined text output returned.
602    pub text: String,
603    /// Combined reasoning thoughts.
604    pub thinking: String,
605    /// Sequence of intermediate execution steps.
606    pub steps: Vec<Step>,
607    /// Token usage metrics.
608    pub usage_metadata: UsageMetadata,
609}
610
611/// Streaming fragment sent over chunk-based event listeners.
612#[derive(Debug, Clone, Serialize, Deserialize)]
613#[serde(tag = "chunk_type")]
614pub enum StreamChunk {
615    /// Streaming thinking fragment.
616    Thought {
617        /// Step index identifier.
618        step_index: u32,
619        /// Thinking segment.
620        text: String,
621    },
622    /// Streaming text response fragment.
623    Text {
624        /// Step index identifier.
625        step_index: u32,
626        /// Text segment.
627        text: String,
628    },
629    /// Complete parsed tool call definition.
630    ToolCall(ToolCall),
631}
632
633#[cfg(test)]
634mod tests {
635    #![allow(
636        clippy::unwrap_used,
637        clippy::expect_used,
638        clippy::panic,
639        clippy::field_reassign_with_default
640    )]
641    use super::*;
642    use serde_json::json;
643
644    #[test]
645    fn test_tool_call_construction() {
646        let tc = ToolCall {
647            id: "call_1".to_string(),
648            name: "read_file".to_string(),
649            args: json!({"path": "/tmp/foo"}),
650            canonical_path: None,
651        };
652        assert_eq!(tc.name, "read_file");
653        assert_eq!(tc.args["path"], "/tmp/foo");
654        assert_eq!(tc.id, "call_1");
655        assert_eq!(tc.canonical_path, None);
656    }
657
658    #[test]
659    fn test_tool_call_serialization() {
660        let json_data = r#"{"id":"call_1","name":"read_file","args":{"path":"/tmp/foo"}}"#;
661        let tc: ToolCall = serde_json::from_str(json_data).unwrap();
662        assert_eq!(tc.name, "read_file");
663        assert_eq!(tc.args["path"], "/tmp/foo");
664        assert_eq!(tc.id, "call_1");
665        assert_eq!(tc.canonical_path, None);
666    }
667
668    #[test]
669    fn test_tool_result_success() {
670        let tr = ToolResult {
671            name: "sum_tool".to_string(),
672            id: Some("call_1".to_string()),
673            result: Some(json!(42)),
674            error: None,
675        };
676        assert_eq!(tr.name, "sum_tool");
677        assert_eq!(tr.result.unwrap(), 42);
678        assert!(tr.error.is_none());
679        assert_eq!(tr.id.unwrap(), "call_1");
680    }
681
682    #[test]
683    fn test_tool_result_error() {
684        let tr = ToolResult {
685            name: "bad_tool".to_string(),
686            id: None,
687            result: None,
688            error: Some("kaboom".to_string()),
689        };
690        assert_eq!(tr.name, "bad_tool");
691        assert!(tr.result.is_none());
692        assert_eq!(tr.error.unwrap(), "kaboom");
693        assert!(tr.id.is_none());
694    }
695
696    #[test]
697    fn test_tool_result_mutability() {
698        let mut tr = ToolResult {
699            name: "tool".to_string(),
700            id: None,
701            result: None,
702            error: None,
703        };
704        tr.result = Some(json!("updated"));
705        assert_eq!(tr.result.unwrap(), "updated");
706    }
707
708    #[test]
709    fn test_step_defaults() {
710        let step = Step::default();
711        assert_eq!(step.id, "");
712        assert_eq!(step.step_index, 0);
713        assert!(matches!(step.r#type, StepType::Unknown));
714        assert!(matches!(step.status, StepStatus::Unknown));
715        assert!(matches!(step.source, StepSource::Unknown));
716        assert_eq!(step.content, "");
717        assert!(step.tool_calls.is_empty());
718        assert_eq!(step.error, "");
719    }
720
721    #[test]
722    fn test_step_mutability() {
723        let mut step = Step::default();
724        step.content = "goodbye".to_string();
725        assert_eq!(step.content, "goodbye");
726    }
727
728    #[test]
729    fn test_hook_result_defaults() {
730        let hr = HookResult::default();
731        assert!(!hr.allow); // derived default for bool in Rust is false
732        assert_eq!(hr.message, "");
733    }
734
735    #[test]
736    fn test_question_response_defaults() {
737        let qr = QuestionResponse {
738            selected_option_ids: None,
739            freeform_response: String::new(),
740            skipped: false,
741        };
742        assert!(qr.selected_option_ids.is_none());
743        assert_eq!(qr.freeform_response, "");
744        assert!(!qr.skipped);
745    }
746
747    #[test]
748    fn test_question_response_skipped() {
749        let qr = QuestionResponse {
750            selected_option_ids: None,
751            freeform_response: String::new(),
752            skipped: true,
753        };
754        assert!(qr.skipped);
755    }
756
757    #[test]
758    fn test_gemini_config_defaults() {
759        let config = GeminiConfig::default();
760        assert!(config.api_key.is_none());
761        assert!(!config.vertex);
762        assert!(config.project.is_none());
763        assert!(config.location.is_none());
764        assert_eq!(config.models.default.name, DEFAULT_MODEL);
765        assert!(config.models.default.generation.thinking_level.is_none());
766        assert!(config.enable_google_search.is_none());
767        assert!(config.enable_url_context.is_none());
768    }
769
770    #[test]
771    fn test_thinking_level_serialization() {
772        let level = ThinkingLevel::Low;
773        let json_str = serde_json::to_string(&level).unwrap();
774        assert_eq!(json_str, "\"low\"");
775    }
776
777    #[test]
778    fn test_capabilities_config_defaults() {
779        let config = CapabilitiesConfig::default();
780        assert!(config.enabled_tools.is_none());
781        assert!(config.disabled_tools.is_none());
782        assert!(config.compaction_threshold.is_none());
783    }
784}