1use serde::{Deserialize, Serialize};
7use serde_json::Value;
8use std::collections::HashMap;
9
10pub const DEFAULT_MODEL: &str = "gemini-3.5-flash";
12
13pub const DEFAULT_IMAGE_GENERATION_MODEL: &str = "gemini-3.1-flash-image-preview";
15
16#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
18#[serde(rename_all = "lowercase")]
19pub enum ThinkingLevel {
20 Minimal,
22 Low,
24 Medium,
26 High,
28}
29
30#[derive(Debug, Clone, Serialize, Deserialize, Default)]
32pub struct GenerationConfig {
33 #[serde(skip_serializing_if = "Option::is_none")]
35 pub thinking_level: Option<ThinkingLevel>,
36}
37
38#[derive(Debug, Clone, Serialize, Deserialize)]
40pub struct ModelEntry {
41 pub name: String,
43 #[serde(skip_serializing_if = "Option::is_none")]
45 pub api_key: Option<String>,
46 #[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#[derive(Debug, Clone, Serialize, Deserialize)]
63pub struct ModelConfig {
64 #[serde(default = "default_model_entry")]
66 pub default: ModelEntry,
67 #[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#[derive(Debug, Clone, Serialize, Deserialize, Default)]
99pub struct GeminiConfig {
100 #[serde(skip_serializing_if = "Option::is_none")]
102 pub api_key: Option<String>,
103 #[serde(default)]
105 pub vertex: bool,
106 #[serde(skip_serializing_if = "Option::is_none")]
108 pub project: Option<String>,
109 #[serde(skip_serializing_if = "Option::is_none")]
111 pub location: Option<String>,
112 #[serde(default)]
114 pub models: ModelConfig,
115 #[serde(skip_serializing_if = "Option::is_none")]
117 pub enable_google_search: Option<bool>,
118 #[serde(skip_serializing_if = "Option::is_none")]
120 pub enable_url_context: Option<bool>,
121}
122
123#[derive(Debug, Clone, Serialize, Deserialize)]
125pub struct SystemInstructionSection {
126 pub content: String,
128 pub title: String,
130}
131
132#[derive(Debug, Clone, Serialize, Deserialize)]
134pub struct CustomSystemInstructions {
135 pub text: String,
137}
138
139#[derive(Debug, Clone, Serialize, Deserialize)]
141pub struct AppendedSystemInstructions {
142 #[serde(skip_serializing_if = "Option::is_none")]
144 pub custom_identity: Option<String>,
145 #[serde(default)]
147 pub appended_sections: Vec<SystemInstructionSection>,
148}
149
150#[derive(Debug, Clone, Serialize, Deserialize)]
152#[serde(untagged)]
153pub enum SystemInstructions {
154 Custom(CustomSystemInstructions),
156 Appended(AppendedSystemInstructions),
158}
159
160#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq, Hash)]
162pub enum BuiltinTools {
163 #[serde(rename = "CREATE_FILE")]
165 CreateFile,
166 #[serde(rename = "EDIT_FILE")]
168 EditFile,
169 #[serde(rename = "FIND_FILE")]
171 FindFile,
172 #[serde(rename = "LIST_DIR")]
174 ListDir,
175 #[serde(rename = "RUN_COMMAND")]
177 RunCommand,
178 #[serde(rename = "SEARCH_DIR")]
180 SearchDir,
181 #[serde(rename = "VIEW_FILE")]
183 ViewFile,
184 #[serde(rename = "START_SUBAGENT")]
186 StartSubagent,
187 #[serde(rename = "GENERATE_IMAGE")]
189 GenerateImage,
190 #[serde(rename = "FINISH")]
192 Finish,
193}
194
195impl BuiltinTools {
196 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 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#[derive(Debug, Clone, Serialize, Deserialize, Default)]
225pub struct CapabilitiesConfig {
226 #[serde(skip_serializing_if = "Option::is_none")]
228 pub enabled_tools: Option<Vec<BuiltinTools>>,
229 #[serde(skip_serializing_if = "Option::is_none")]
231 pub disabled_tools: Option<Vec<BuiltinTools>>,
232 #[serde(skip_serializing_if = "Option::is_none")]
234 pub compaction_threshold: Option<u32>,
235 #[serde(skip_serializing_if = "Option::is_none")]
237 pub finish_tool_schema_json: Option<String>,
238 #[serde(skip_serializing_if = "Option::is_none")]
240 pub image_model: Option<String>,
241}
242
243#[derive(Debug, Clone, Serialize, Deserialize)]
245#[serde(tag = "type")]
246pub enum McpServerConfig {
247 #[serde(rename = "stdio")]
249 Stdio {
250 name: String,
252 command: String,
254 args: Vec<String>,
256 #[serde(skip_serializing_if = "Option::is_none")]
258 enabled_tools: Option<Vec<String>>,
259 #[serde(skip_serializing_if = "Option::is_none")]
261 disabled_tools: Option<Vec<String>>,
262 },
263 #[serde(rename = "sse")]
265 Sse {
266 name: String,
268 url: String,
270 #[serde(skip_serializing_if = "Option::is_none")]
272 headers: Option<HashMap<String, String>>,
273 #[serde(skip_serializing_if = "Option::is_none")]
275 enabled_tools: Option<Vec<String>>,
276 #[serde(skip_serializing_if = "Option::is_none")]
278 disabled_tools: Option<Vec<String>>,
279 },
280 #[serde(rename = "http")]
282 Http {
283 name: String,
285 url: String,
287 #[serde(skip_serializing_if = "Option::is_none")]
289 headers: Option<HashMap<String, String>>,
290 #[serde(default = "default_mcp_timeout")]
292 timeout: f64,
293 #[serde(default = "default_mcp_sse_timeout")]
295 sse_read_timeout: f64,
296 #[serde(default = "default_true")]
298 terminate_on_close: bool,
299 #[serde(skip_serializing_if = "Option::is_none")]
301 enabled_tools: Option<Vec<String>>,
302 #[serde(skip_serializing_if = "Option::is_none")]
304 disabled_tools: Option<Vec<String>>,
305 },
306}
307
308impl McpServerConfig {
309 pub fn name(&self) -> &str {
311 match self {
312 Self::Stdio { name, .. } | Self::Sse { name, .. } | Self::Http { name, .. } => name,
313 }
314 }
315}
316
317#[derive(Debug, Clone)]
322pub struct AntigravityExecutionError {
323 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#[derive(Debug, Clone, Serialize, Deserialize)]
347pub struct ToolCall {
348 pub id: String,
350 pub name: String,
352 pub args: Value,
354 #[serde(skip_serializing_if = "Option::is_none")]
356 pub canonical_path: Option<String>,
357}
358
359#[derive(Debug, Clone, Serialize, Deserialize)]
361pub struct ToolResult {
362 pub name: String,
364 #[serde(skip_serializing_if = "Option::is_none")]
366 pub id: Option<String>,
367 #[serde(skip_serializing_if = "Option::is_none")]
369 pub result: Option<Value>,
370 #[serde(skip_serializing_if = "Option::is_none")]
372 pub error: Option<String>,
373}
374
375#[derive(Debug, Clone, Serialize, Deserialize, Default)]
377pub struct UsageMetadata {
378 pub prompt_token_count: i32,
380 pub candidates_token_count: i32,
382 pub total_token_count: i32,
384 #[serde(default)]
386 pub cached_content_token_count: i32,
387 #[serde(default)]
389 pub thoughts_token_count: i32,
390}
391
392#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
394pub enum StepType {
395 #[serde(rename = "TEXT_RESPONSE")]
397 TextResponse,
398 #[serde(rename = "TOOL_CALL")]
400 ToolCall,
401 #[serde(rename = "SYSTEM_MESSAGE")]
403 SystemMessage,
404 #[serde(rename = "COMPACTION")]
406 Compaction,
407 #[serde(rename = "FINISH")]
409 Finish,
410 #[serde(rename = "UNKNOWN")]
412 Unknown,
413}
414
415#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
417pub enum StepSource {
418 #[serde(rename = "SYSTEM")]
420 System,
421 #[serde(rename = "USER")]
423 User,
424 #[serde(rename = "MODEL")]
426 Model,
427 #[serde(rename = "UNKNOWN")]
429 Unknown,
430}
431
432#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
434pub enum StepTarget {
435 #[serde(rename = "TARGET_USER")]
437 User,
438 #[serde(rename = "TARGET_ENVIRONMENT")]
440 Environment,
441 #[serde(rename = "TARGET_UNSPECIFIED")]
443 Unspecified,
444 #[serde(rename = "UNKNOWN")]
446 Unknown,
447}
448
449#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
451pub enum StepStatus {
452 #[serde(rename = "ACTIVE")]
454 Active,
455 #[serde(rename = "DONE")]
457 Done,
458 #[serde(rename = "WAITING_FOR_USER")]
460 WaitingForUser,
461 #[serde(rename = "ERROR")]
463 Error,
464 #[serde(rename = "CANCELED")]
466 Canceled,
467 #[serde(rename = "TERMINAL_ERROR")]
469 TerminalError,
470 #[serde(rename = "UNKNOWN")]
472 Unknown,
473}
474
475#[derive(Debug, Clone, Serialize, Deserialize)]
477pub struct Step {
478 pub id: String,
480 pub step_index: u32,
482 pub r#type: StepType,
484 pub source: StepSource,
486 pub target: StepTarget,
488 pub status: StepStatus,
490 pub content: String,
492 pub content_delta: String,
494 pub thinking: String,
496 pub thinking_delta: String,
498 pub tool_calls: Vec<ToolCall>,
500 pub error: String,
502 pub is_complete_response: Option<bool>,
504 pub structured_output: Option<Value>,
506 pub usage_metadata: Option<UsageMetadata>,
508 #[serde(default)]
510 pub cascade_id: String,
511 #[serde(default)]
513 pub trajectory_id: String,
514 #[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#[derive(Debug, Clone, Serialize, Deserialize, Default)]
546pub struct HookResult {
547 pub allow: bool,
549 #[serde(default)]
551 pub message: String,
552}
553
554#[derive(Debug, Clone, Serialize, Deserialize)]
556pub struct QuestionResponse {
557 pub selected_option_ids: Option<Vec<String>>,
559 #[serde(default)]
561 pub freeform_response: String,
562 #[serde(default)]
564 pub skipped: bool,
565}
566
567#[derive(Debug, Clone, Serialize, Deserialize)]
569pub struct QuestionHookResult {
570 pub responses: Vec<QuestionResponse>,
572 #[serde(default)]
574 pub cancelled: bool,
575}
576
577#[derive(Debug, Clone, Serialize, Deserialize)]
579pub struct AskQuestionOption {
580 pub id: String,
582 pub text: String,
584}
585
586#[derive(Debug, Clone, Serialize, Deserialize)]
588pub struct AskQuestionEntry {
589 pub question: String,
591 pub options: Vec<AskQuestionOption>,
593 #[serde(default)]
595 pub is_multi_select: bool,
596}
597
598#[derive(Debug, Clone, Serialize, Deserialize)]
600pub struct ChatResponse {
601 pub text: String,
603 pub thinking: String,
605 pub steps: Vec<Step>,
607 pub usage_metadata: UsageMetadata,
609}
610
611#[derive(Debug, Clone, Serialize, Deserialize)]
613#[serde(tag = "chunk_type")]
614pub enum StreamChunk {
615 Thought {
617 step_index: u32,
619 text: String,
621 },
622 Text {
624 step_index: u32,
626 text: String,
628 },
629 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); 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}