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    /// Model configurations.
104    #[serde(default)]
105    pub models: ModelConfig,
106}
107
108/// A structured section appended to system instructions.
109#[derive(Debug, Clone, Serialize, Deserialize)]
110pub struct SystemInstructionSection {
111    /// Main markdown or text body of the section.
112    pub content: String,
113    /// Described title of the section.
114    pub title: String,
115}
116
117/// Directly supplied system instruction instructions text override.
118#[derive(Debug, Clone, Serialize, Deserialize)]
119pub struct CustomSystemInstructions {
120    /// Custom raw instructions text.
121    pub text: String,
122}
123
124/// Appended instructions format, maintaining identity overrides and section segments.
125#[derive(Debug, Clone, Serialize, Deserialize)]
126pub struct AppendedSystemInstructions {
127    /// Optional override for the agent's custom identity block.
128    #[serde(skip_serializing_if = "Option::is_none")]
129    pub custom_identity: Option<String>,
130    /// Sections to be appended to the standard system instructions.
131    #[serde(default)]
132    pub appended_sections: Vec<SystemInstructionSection>,
133}
134
135/// Represents the style or content source for system instructions.
136#[derive(Debug, Clone, Serialize, Deserialize)]
137#[serde(untagged)]
138pub enum SystemInstructions {
139    /// Completely custom text override.
140    Custom(CustomSystemInstructions),
141    /// Standard structured segments appended to the system identity.
142    Appended(AppendedSystemInstructions),
143}
144
145/// Enumeration of built-in tools supported by the agent system.
146#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq, Hash)]
147pub enum BuiltinTools {
148    /// Tool to create a new file.
149    #[serde(rename = "CREATE_FILE")]
150    CreateFile,
151    /// Tool to edit an existing file.
152    #[serde(rename = "EDIT_FILE")]
153    EditFile,
154    /// Tool to query/find files in a directory.
155    #[serde(rename = "FIND_FILE")]
156    FindFile,
157    /// Tool to list files inside a directory.
158    #[serde(rename = "LIST_DIR")]
159    ListDir,
160    /// Tool to execute a shell command.
161    #[serde(rename = "RUN_COMMAND")]
162    RunCommand,
163    /// Tool to perform ripgrep searches.
164    #[serde(rename = "SEARCH_DIR")]
165    SearchDir,
166    /// Tool to view a file's content.
167    #[serde(rename = "VIEW_FILE")]
168    ViewFile,
169    /// Tool to instantiate a subagent.
170    #[serde(rename = "START_SUBAGENT")]
171    StartSubagent,
172    /// Tool to generate images from descriptions.
173    #[serde(rename = "GENERATE_IMAGE")]
174    GenerateImage,
175    /// Terminating signal indicating the task is completed.
176    #[serde(rename = "FINISH")]
177    Finish,
178}
179
180impl BuiltinTools {
181    /// Returns the static string slice mapping to the tool name.
182    pub const fn as_str(&self) -> &'static str {
183        match self {
184            Self::CreateFile => "CREATE_FILE",
185            Self::EditFile => "EDIT_FILE",
186            Self::FindFile => "FIND_FILE",
187            Self::ListDir => "LIST_DIR",
188            Self::RunCommand => "RUN_COMMAND",
189            Self::SearchDir => "SEARCH_DIR",
190            Self::ViewFile => "VIEW_FILE",
191            Self::StartSubagent => "START_SUBAGENT",
192            Self::GenerateImage => "GENERATE_IMAGE",
193            Self::Finish => "FINISH",
194        }
195    }
196
197    /// Returns a list of all safe, read-only tools.
198    pub fn read_only() -> Vec<Self> {
199        vec![
200            Self::FindFile,
201            Self::ListDir,
202            Self::ViewFile,
203            Self::SearchDir,
204        ]
205    }
206}
207
208/// Agent capabilities and tool restrictions configuration.
209#[derive(Debug, Clone, Serialize, Deserialize, Default)]
210pub struct CapabilitiesConfig {
211    /// List of explicitly enabled tools.
212    #[serde(skip_serializing_if = "Option::is_none")]
213    pub enabled_tools: Option<Vec<BuiltinTools>>,
214    /// List of explicitly disabled tools.
215    #[serde(skip_serializing_if = "Option::is_none")]
216    pub disabled_tools: Option<Vec<BuiltinTools>>,
217    /// Threshold at which the message history is compacted/summarized.
218    #[serde(skip_serializing_if = "Option::is_none")]
219    pub compaction_threshold: Option<u32>,
220    /// Custom schema override for the finish tool schema.
221    #[serde(skip_serializing_if = "Option::is_none")]
222    pub finish_tool_schema_json: Option<String>,
223    /// Model designated for processing images.
224    #[serde(skip_serializing_if = "Option::is_none")]
225    pub image_model: Option<String>,
226}
227
228/// Configuration settings for Model Context Protocol (MCP) servers.
229#[derive(Debug, Clone, Serialize, Deserialize)]
230#[serde(tag = "type")]
231pub enum McpServerConfig {
232    /// Launch the MCP server as a local stdio process.
233    #[serde(rename = "stdio")]
234    Stdio {
235        /// command binary.
236        command: String,
237        /// execution arguments.
238        args: Vec<String>,
239    },
240    /// Connect to the MCP server via Server-Sent Events (SSE).
241    #[serde(rename = "sse")]
242    Sse {
243        /// HTTP URL endpoint.
244        url: String,
245        /// Additional HTTP headers.
246        #[serde(skip_serializing_if = "Option::is_none")]
247        headers: Option<HashMap<String, String>>,
248    },
249    /// Connect to the MCP server via standard HTTP.
250    #[serde(rename = "http")]
251    Http {
252        /// HTTP URL endpoint.
253        url: String,
254        /// Additional HTTP headers.
255        #[serde(skip_serializing_if = "Option::is_none")]
256        headers: Option<HashMap<String, String>>,
257        /// General connection timeout in seconds.
258        #[serde(default = "default_mcp_timeout")]
259        timeout: f64,
260        /// Reading timeout for the SSE listener.
261        #[serde(default = "default_mcp_sse_timeout")]
262        sse_read_timeout: f64,
263        /// Flag whether to terminate the channel connection when closed.
264        #[serde(default = "default_true")]
265        terminate_on_close: bool,
266    },
267}
268
269const fn default_mcp_timeout() -> f64 {
270    30.0
271}
272const fn default_mcp_sse_timeout() -> f64 {
273    300.0
274}
275const fn default_true() -> bool {
276    true
277}
278
279/// Describes a model's request to execute a registered tool.
280#[derive(Debug, Clone, Serialize, Deserialize)]
281pub struct ToolCall {
282    /// Unique call ID generated for correlation.
283    pub id: String,
284    /// Name of the target tool.
285    pub name: String,
286    /// Arguments payload parsed as JSON.
287    pub args: Value,
288    /// Canonical file system path (if the tool targets a file/directory).
289    #[serde(skip_serializing_if = "Option::is_none")]
290    pub canonical_path: Option<String>,
291}
292
293/// The response outcome of executing a client-side tool.
294#[derive(Debug, Clone, Serialize, Deserialize)]
295pub struct ToolResult {
296    /// Name of the executed tool.
297    pub name: String,
298    /// Optional matching tool call ID.
299    #[serde(skip_serializing_if = "Option::is_none")]
300    pub id: Option<String>,
301    /// Output result of successful tool execution.
302    #[serde(skip_serializing_if = "Option::is_none")]
303    pub result: Option<Value>,
304    /// Error message string if tool execution failed.
305    #[serde(skip_serializing_if = "Option::is_none")]
306    pub error: Option<String>,
307}
308
309/// Consumption stats for API usage tracking.
310#[derive(Debug, Clone, Serialize, Deserialize, Default)]
311pub struct UsageMetadata {
312    /// Tokens included in the request prompt.
313    pub prompt_token_count: i32,
314    /// Tokens generated in candidates.
315    pub candidates_token_count: i32,
316    /// Total combined tokens.
317    pub total_token_count: i32,
318    /// Cache hit content tokens.
319    #[serde(default)]
320    pub cached_content_token_count: i32,
321    /// Tokens consumed during inner thinking/reasoning.
322    #[serde(default)]
323    pub thoughts_token_count: i32,
324}
325
326/// The classification type of a step in the trajectory.
327#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
328pub enum StepType {
329    /// Raw text content returned by the model.
330    #[serde(rename = "TEXT_RESPONSE")]
331    TextResponse,
332    /// Execution of a tool call.
333    #[serde(rename = "TOOL_CALL")]
334    ToolCall,
335    /// Logging or notification events from the system.
336    #[serde(rename = "SYSTEM_MESSAGE")]
337    SystemMessage,
338    /// A history compaction step summarizing context.
339    #[serde(rename = "COMPACTION")]
340    Compaction,
341    /// Terminating milestone indicator.
342    #[serde(rename = "FINISH")]
343    Finish,
344    /// Catch-all variant for unrecognized steps.
345    #[serde(rename = "UNKNOWN")]
346    Unknown,
347}
348
349/// The originating source component of a trajectory step.
350#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
351pub enum StepSource {
352    /// Internal orchestration environment.
353    #[serde(rename = "SYSTEM")]
354    System,
355    /// End-user input.
356    #[serde(rename = "USER")]
357    User,
358    /// Generative model prediction.
359    #[serde(rename = "MODEL")]
360    Model,
361    /// Unknown author.
362    #[serde(rename = "UNKNOWN")]
363    Unknown,
364}
365
366/// The target destination of a step event.
367#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
368pub enum StepTarget {
369    /// Event addressed to the user.
370    #[serde(rename = "TARGET_USER")]
371    User,
372    /// Event executing in the sandbox environment.
373    #[serde(rename = "TARGET_ENVIRONMENT")]
374    Environment,
375    /// Unspecified destination.
376    #[serde(rename = "TARGET_UNSPECIFIED")]
377    Unspecified,
378    /// Unknown destination.
379    #[serde(rename = "UNKNOWN")]
380    Unknown,
381}
382
383/// Lifecycle execution status of a trajectory step.
384#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
385pub enum StepStatus {
386    /// Active/running state.
387    #[serde(rename = "ACTIVE")]
388    Active,
389    /// Completed successfully.
390    #[serde(rename = "DONE")]
391    Done,
392    /// Waiting for user response/confirmation.
393    #[serde(rename = "WAITING_FOR_USER")]
394    WaitingForUser,
395    /// Finished with a fatal error.
396    #[serde(rename = "ERROR")]
397    Error,
398    /// Execution was canceled.
399    #[serde(rename = "CANCELED")]
400    Canceled,
401    /// Unknown status.
402    #[serde(rename = "UNKNOWN")]
403    Unknown,
404}
405
406/// Individual step event recording an action in the agent's history trajectory.
407#[derive(Debug, Clone, Serialize, Deserialize)]
408pub struct Step {
409    /// Unique identifier for this step.
410    pub id: String,
411    /// Positional index in the trajectory sequence.
412    pub step_index: u32,
413    /// Functional type of the step.
414    pub r#type: StepType,
415    /// Originating author.
416    pub source: StepSource,
417    /// Destination target.
418    pub target: StepTarget,
419    /// Execution status.
420    pub status: StepStatus,
421    /// Main text/markdown content associated with the step.
422    pub content: String,
423    /// Text difference delta relative to previous steps.
424    pub content_delta: String,
425    /// Reasoning thoughts generated for this step.
426    pub thinking: String,
427    /// Thinking reasoning difference delta relative to previous steps.
428    pub thinking_delta: String,
429    /// Custom tool executions registered in this step.
430    pub tool_calls: Vec<ToolCall>,
431    /// Captured execution errors.
432    pub error: String,
433    /// True if this represents the final response segment from the model.
434    pub is_complete_response: Option<bool>,
435    /// Parsed structured JSON output.
436    pub structured_output: Option<Value>,
437    /// Token usage details.
438    pub usage_metadata: Option<UsageMetadata>,
439    /// Unique identifier of the execution cascade grouping subagents.
440    #[serde(default)]
441    pub cascade_id: String,
442    /// Sub-agent trajectory grouping identifier.
443    #[serde(default)]
444    pub trajectory_id: String,
445    /// HTTP status code (if from a network action).
446    #[serde(default)]
447    pub http_code: u32,
448}
449
450impl Default for Step {
451    fn default() -> Self {
452        Self {
453            id: String::new(),
454            step_index: 0,
455            r#type: StepType::Unknown,
456            source: StepSource::Unknown,
457            target: StepTarget::Unknown,
458            status: StepStatus::Unknown,
459            content: String::new(),
460            content_delta: String::new(),
461            thinking: String::new(),
462            thinking_delta: String::new(),
463            tool_calls: Vec::new(),
464            error: String::new(),
465            is_complete_response: None,
466            structured_output: None,
467            usage_metadata: None,
468            cascade_id: String::new(),
469            trajectory_id: String::new(),
470            http_code: 0,
471        }
472    }
473}
474
475/// The result returned by a middleware hook.
476#[derive(Debug, Clone, Serialize, Deserialize, Default)]
477pub struct HookResult {
478    /// True if the operation is allowed to proceed.
479    pub allow: bool,
480    /// Diagnostic or error message details.
481    #[serde(default)]
482    pub message: String,
483}
484
485/// Individual multiple-choice or freeform answer to an interactive user question.
486#[derive(Debug, Clone, Serialize, Deserialize)]
487pub struct QuestionResponse {
488    /// Selected index choices (if multiple-choice).
489    pub selected_option_ids: Option<Vec<String>>,
490    /// Freeform response text.
491    #[serde(default)]
492    pub freeform_response: String,
493    /// True if the question was skipped.
494    #[serde(default)]
495    pub skipped: bool,
496}
497
498/// Complete collection of responses answered to a set of interactive questions.
499#[derive(Debug, Clone, Serialize, Deserialize)]
500pub struct QuestionHookResult {
501    /// List of user responses.
502    pub responses: Vec<QuestionResponse>,
503    /// True if the question panel dialogue was canceled.
504    #[serde(default)]
505    pub cancelled: bool,
506}
507
508/// Single choice option in a multiple-choice question.
509#[derive(Debug, Clone, Serialize, Deserialize)]
510pub struct AskQuestionOption {
511    /// Unique identifier for this choice option.
512    pub id: String,
513    /// Visual label text for the choice.
514    pub text: String,
515}
516
517/// Interactive user question entry structure.
518#[derive(Debug, Clone, Serialize, Deserialize)]
519pub struct AskQuestionEntry {
520    /// Main question prompt text.
521    pub question: String,
522    /// List of multiple-choice options.
523    pub options: Vec<AskQuestionOption>,
524    /// True if multiple selections are supported.
525    #[serde(default)]
526    pub is_multi_select: bool,
527}
528
529/// Final summary outcome of a chat interaction.
530#[derive(Debug, Clone, Serialize, Deserialize)]
531pub struct ChatResponse {
532    /// Combined text output returned.
533    pub text: String,
534    /// Combined reasoning thoughts.
535    pub thinking: String,
536    /// Sequence of intermediate execution steps.
537    pub steps: Vec<Step>,
538    /// Token usage metrics.
539    pub usage_metadata: UsageMetadata,
540}
541
542/// Streaming fragment sent over chunk-based event listeners.
543#[derive(Debug, Clone, Serialize, Deserialize)]
544#[serde(tag = "chunk_type")]
545pub enum StreamChunk {
546    /// Streaming thinking fragment.
547    Thought {
548        /// Step index identifier.
549        step_index: u32,
550        /// Thinking segment.
551        text: String,
552    },
553    /// Streaming text response fragment.
554    Text {
555        /// Step index identifier.
556        step_index: u32,
557        /// Text segment.
558        text: String,
559    },
560    /// Complete parsed tool call definition.
561    ToolCall(ToolCall),
562}
563
564#[cfg(test)]
565mod tests {
566    #![allow(
567        clippy::unwrap_used,
568        clippy::expect_used,
569        clippy::panic,
570        clippy::field_reassign_with_default
571    )]
572    use super::*;
573    use serde_json::json;
574
575    #[test]
576    fn test_tool_call_construction() {
577        let tc = ToolCall {
578            id: "call_1".to_string(),
579            name: "read_file".to_string(),
580            args: json!({"path": "/tmp/foo"}),
581            canonical_path: None,
582        };
583        assert_eq!(tc.name, "read_file");
584        assert_eq!(tc.args["path"], "/tmp/foo");
585        assert_eq!(tc.id, "call_1");
586        assert_eq!(tc.canonical_path, None);
587    }
588
589    #[test]
590    fn test_tool_call_serialization() {
591        let json_data = r#"{"id":"call_1","name":"read_file","args":{"path":"/tmp/foo"}}"#;
592        let tc: ToolCall = serde_json::from_str(json_data).unwrap();
593        assert_eq!(tc.name, "read_file");
594        assert_eq!(tc.args["path"], "/tmp/foo");
595        assert_eq!(tc.id, "call_1");
596        assert_eq!(tc.canonical_path, None);
597    }
598
599    #[test]
600    fn test_tool_result_success() {
601        let tr = ToolResult {
602            name: "sum_tool".to_string(),
603            id: Some("call_1".to_string()),
604            result: Some(json!(42)),
605            error: None,
606        };
607        assert_eq!(tr.name, "sum_tool");
608        assert_eq!(tr.result.unwrap(), 42);
609        assert!(tr.error.is_none());
610        assert_eq!(tr.id.unwrap(), "call_1");
611    }
612
613    #[test]
614    fn test_tool_result_error() {
615        let tr = ToolResult {
616            name: "bad_tool".to_string(),
617            id: None,
618            result: None,
619            error: Some("kaboom".to_string()),
620        };
621        assert_eq!(tr.name, "bad_tool");
622        assert!(tr.result.is_none());
623        assert_eq!(tr.error.unwrap(), "kaboom");
624        assert!(tr.id.is_none());
625    }
626
627    #[test]
628    fn test_tool_result_mutability() {
629        let mut tr = ToolResult {
630            name: "tool".to_string(),
631            id: None,
632            result: None,
633            error: None,
634        };
635        tr.result = Some(json!("updated"));
636        assert_eq!(tr.result.unwrap(), "updated");
637    }
638
639    #[test]
640    fn test_step_defaults() {
641        let step = Step::default();
642        assert_eq!(step.id, "");
643        assert_eq!(step.step_index, 0);
644        assert!(matches!(step.r#type, StepType::Unknown));
645        assert!(matches!(step.status, StepStatus::Unknown));
646        assert!(matches!(step.source, StepSource::Unknown));
647        assert_eq!(step.content, "");
648        assert!(step.tool_calls.is_empty());
649        assert_eq!(step.error, "");
650    }
651
652    #[test]
653    fn test_step_mutability() {
654        let mut step = Step::default();
655        step.content = "goodbye".to_string();
656        assert_eq!(step.content, "goodbye");
657    }
658
659    #[test]
660    fn test_hook_result_defaults() {
661        let hr = HookResult::default();
662        assert!(!hr.allow); // derived default for bool in Rust is false
663        assert_eq!(hr.message, "");
664    }
665
666    #[test]
667    fn test_question_response_defaults() {
668        let qr = QuestionResponse {
669            selected_option_ids: None,
670            freeform_response: String::new(),
671            skipped: false,
672        };
673        assert!(qr.selected_option_ids.is_none());
674        assert_eq!(qr.freeform_response, "");
675        assert!(!qr.skipped);
676    }
677
678    #[test]
679    fn test_question_response_skipped() {
680        let qr = QuestionResponse {
681            selected_option_ids: None,
682            freeform_response: String::new(),
683            skipped: true,
684        };
685        assert!(qr.skipped);
686    }
687
688    #[test]
689    fn test_gemini_config_defaults() {
690        let config = GeminiConfig::default();
691        assert!(config.api_key.is_none());
692        assert_eq!(config.models.default.name, DEFAULT_MODEL);
693        assert!(config.models.default.generation.thinking_level.is_none());
694    }
695
696    #[test]
697    fn test_thinking_level_serialization() {
698        let level = ThinkingLevel::Low;
699        let json_str = serde_json::to_string(&level).unwrap();
700        assert_eq!(json_str, "\"low\"");
701    }
702
703    #[test]
704    fn test_capabilities_config_defaults() {
705        let config = CapabilitiesConfig::default();
706        assert!(config.enabled_tools.is_none());
707        assert!(config.disabled_tools.is_none());
708        assert!(config.compaction_threshold.is_none());
709    }
710}