Coverage for src / agent / config / schema.py: 93%

412 statements  

« prev     ^ index     » next       coverage.py v7.13.0, created at 2025-12-11 14:30 +0000

1# Copyright 2025-2026 Microsoft Corporation 

2# 

3# Licensed under the Apache License, Version 2.0 (the "License"); 

4# you may not use this file except in compliance with the License. 

5# You may obtain a copy of the License at 

6# 

7# http://www.apache.org/licenses/LICENSE-2.0 

8# 

9# Unless required by applicable law or agreed to in writing, software 

10# distributed under the License is distributed on an "AS IS" BASIS, 

11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 

12# See the License for the specific language governing permissions and 

13# limitations under the License. 

14 

15"""Pydantic models for agent configuration schema.""" 

16 

17import json 

18from pathlib import Path 

19from typing import Any 

20 

21from pydantic import BaseModel, Field, field_validator, model_validator 

22 

23from agent.config.constants import ( 

24 DEFAULT_ANTHROPIC_MODEL, 

25 DEFAULT_AZURE_API_VERSION, 

26 DEFAULT_GEMINI_MODEL, 

27 DEFAULT_GITHUB_ENDPOINT, 

28 DEFAULT_GITHUB_MODEL, 

29 DEFAULT_LOCAL_BASE_URL, 

30 DEFAULT_LOCAL_MODEL, 

31 DEFAULT_OPENAI_MODEL, 

32) 

33 

34# Module-level constants for validation 

35VALID_PROVIDERS = {"local", "openai", "anthropic", "azure", "foundry", "gemini", "github"} 

36VALID_MEMORY_TYPES = {"in_memory", "mem0"} 

37 

38 

39class LocalProviderConfig(BaseModel): 

40 """Local provider configuration (Docker Desktop Model Runner).""" 

41 

42 enabled: bool = False # NOTE: Overwritten by sync_enabled_flags() validator. 

43 # Actual enabled state is determined by ProviderConfig.enabled list. 

44 base_url: str = DEFAULT_LOCAL_BASE_URL 

45 model: str = DEFAULT_LOCAL_MODEL 

46 

47 

48class OpenAIProviderConfig(BaseModel): 

49 """OpenAI provider configuration.""" 

50 

51 enabled: bool = False 

52 api_key: str | None = None 

53 model: str = DEFAULT_OPENAI_MODEL 

54 

55 

56class AnthropicProviderConfig(BaseModel): 

57 """Anthropic provider configuration.""" 

58 

59 enabled: bool = False 

60 api_key: str | None = None 

61 model: str = DEFAULT_ANTHROPIC_MODEL 

62 

63 

64class AzureOpenAIProviderConfig(BaseModel): 

65 """Azure OpenAI provider configuration.""" 

66 

67 enabled: bool = False 

68 endpoint: str | None = None 

69 deployment: str | None = None 

70 api_version: str = DEFAULT_AZURE_API_VERSION 

71 api_key: str | None = None 

72 

73 

74class FoundryProviderConfig(BaseModel): 

75 """Azure AI Foundry provider configuration.""" 

76 

77 enabled: bool = False 

78 project_endpoint: str | None = None 

79 model_deployment: str | None = None 

80 

81 

82class GeminiProviderConfig(BaseModel): 

83 """Google Gemini provider configuration.""" 

84 

85 enabled: bool = False 

86 api_key: str | None = None 

87 model: str = DEFAULT_GEMINI_MODEL 

88 use_vertexai: bool = False 

89 project_id: str | None = None 

90 location: str | None = None 

91 

92 

93class GitHubProviderConfig(BaseModel): 

94 """GitHub Models provider configuration.""" 

95 

96 enabled: bool = False 

97 token: str | None = None 

98 model: str = DEFAULT_GITHUB_MODEL 

99 endpoint: str = DEFAULT_GITHUB_ENDPOINT 

100 org: str | None = None # Optional: org name for enterprise rate limits 

101 

102 

103class ProviderConfig(BaseModel): 

104 """Provider configurations.""" 

105 

106 enabled: list[str] = Field(default_factory=list) # Empty by default - requires explicit config 

107 local: LocalProviderConfig = Field(default_factory=LocalProviderConfig) 

108 openai: OpenAIProviderConfig = Field(default_factory=OpenAIProviderConfig) 

109 anthropic: AnthropicProviderConfig = Field(default_factory=AnthropicProviderConfig) 

110 azure: AzureOpenAIProviderConfig = Field(default_factory=AzureOpenAIProviderConfig) 

111 foundry: FoundryProviderConfig = Field(default_factory=FoundryProviderConfig) 

112 gemini: GeminiProviderConfig = Field(default_factory=GeminiProviderConfig) 

113 github: GitHubProviderConfig = Field(default_factory=GitHubProviderConfig) 

114 

115 @field_validator("enabled") 

116 @classmethod 

117 def validate_enabled_providers(cls, v: list[str]) -> list[str]: 

118 """Validate that enabled providers list contains valid provider names.""" 

119 invalid = set(v) - VALID_PROVIDERS 

120 if invalid: 

121 raise ValueError( 

122 f"Invalid provider names in enabled list: {invalid}. " 

123 f"Valid providers: {VALID_PROVIDERS}" 

124 ) 

125 return v 

126 

127 @model_validator(mode="after") 

128 def sync_enabled_flags(self) -> "ProviderConfig": 

129 """Sync enabled list with individual provider enabled flags.""" 

130 # Update individual provider enabled flags based on enabled list 

131 self.local.enabled = "local" in self.enabled 

132 self.openai.enabled = "openai" in self.enabled 

133 self.anthropic.enabled = "anthropic" in self.enabled 

134 self.azure.enabled = "azure" in self.enabled 

135 self.foundry.enabled = "foundry" in self.enabled 

136 self.gemini.enabled = "gemini" in self.enabled 

137 self.github.enabled = "github" in self.enabled 

138 return self 

139 

140 

141class PluginSkillSource(BaseModel): 

142 """Git-based plugin skill source configuration.""" 

143 

144 name: str = Field(description="Canonical skill name") 

145 git_url: str = Field(description="Git repository URL") 

146 branch: str = Field(default="main", description="Git branch to use") 

147 enabled: bool = Field(default=True, description="Enable/disable this plugin skill") 

148 installed_path: str | None = Field( 

149 default=None, description="Installation path (auto-populated by skill manager)" 

150 ) 

151 

152 @field_validator("installed_path") 

153 @classmethod 

154 def expand_installed_path(cls, v: str | None) -> str | None: 

155 """Expand user home directory in installed_path.""" 

156 if v is None: 

157 return None 

158 return str(Path(v).expanduser().resolve()) 

159 

160 

161class SkillsConfig(BaseModel): 

162 """Skills system configuration (matches memory pattern).""" 

163 

164 # Plugin skills (git-based, user-installed) 

165 plugins: list[PluginSkillSource] = Field( 

166 default_factory=list, 

167 description="Git-based plugin skills with source configuration", 

168 ) 

169 

170 # Bundled skills control (three-state: user enabled, user disabled, manifest default) 

171 disabled_bundled: list[str] = Field( 

172 default_factory=list, 

173 description="Bundled skills explicitly disabled by user (overrides default_enabled: true).", 

174 ) 

175 enabled_bundled: list[str] = Field( 

176 default_factory=list, 

177 description="Bundled skills explicitly enabled by user (overrides default_enabled: false).", 

178 ) 

179 

180 # Directory configuration 

181 user_dir: str = Field( 

182 default="~/.osdu-agent/skills", 

183 description="Directory for user-installed plugin skills", 

184 ) 

185 bundled_dir: str | None = Field( 

186 default=None, 

187 description="Directory for bundled core skills. Auto-detected from repo if None.", 

188 ) 

189 

190 # Script execution configuration 

191 script_timeout: int = Field( 

192 default=60, 

193 description="Timeout in seconds for script execution", 

194 ) 

195 max_script_output: int = Field( 

196 default=1_048_576, # 1MB 

197 description="Maximum output size in bytes for script execution", 

198 ) 

199 

200 @model_validator(mode="after") 

201 def expand_paths(self) -> "SkillsConfig": 

202 """Expand user home directory in paths after validation.""" 

203 # Expand user_dir if it contains ~ 

204 if "~" in self.user_dir: 

205 self.user_dir = str(Path(self.user_dir).expanduser().resolve()) 

206 

207 # Expand bundled_dir if set and contains ~ 

208 if self.bundled_dir and "~" in self.bundled_dir: 

209 self.bundled_dir = str(Path(self.bundled_dir).expanduser().resolve()) 

210 

211 return self 

212 

213 

214class AgentConfig(BaseModel): 

215 """Agent-specific configuration.""" 

216 

217 data_dir: str = "~/.osdu-agent" 

218 log_level: str = "info" 

219 system_prompt_file: str | None = None 

220 

221 # Filesystem tools configuration 

222 workspace_root: Path | None = Field( 

223 default=None, 

224 description="Root directory for filesystem tools. Defaults to current working directory if not set.", 

225 ) 

226 filesystem_writes_enabled: bool = Field( 

227 default=False, 

228 description="Enable filesystem write operations (write_file, apply_text_edit, create_directory)", 

229 ) 

230 filesystem_max_read_bytes: int = Field( 

231 default=10_485_760, description="Maximum file size in bytes for read operations" # 10MB 

232 ) 

233 filesystem_max_write_bytes: int = Field( 

234 default=1_048_576, description="Maximum content size in bytes for write operations" # 1MB 

235 ) 

236 

237 # Git tools configuration 

238 repos_root: Path | None = Field( 

239 default=None, 

240 description="Root directory for git repositories. Defaults to ~/.osdu-agent/repos if not set.", 

241 ) 

242 git_default_remote: str = Field( 

243 default="origin", 

244 description="Default remote name for git operations", 

245 ) 

246 git_clone_timeout: int = Field( 

247 default=300, 

248 description="Timeout in seconds for git clone operations", 

249 ) 

250 

251 # GitLab tools configuration 

252 gitlab_url: str = Field( 

253 default="https://community.opengroup.org", 

254 description="GitLab instance URL", 

255 ) 

256 gitlab_token: str | None = Field( 

257 default=None, 

258 description="GitLab personal access token (or use GITLAB_TOKEN env var)", 

259 ) 

260 gitlab_api_timeout: int = Field( 

261 default=30, 

262 description="Timeout in seconds for GitLab API requests", 

263 ) 

264 

265 # Maven tools configuration 

266 maven_cache_ttl: int = Field( 

267 default=3600, 

268 description="Cache TTL in seconds for Maven API responses (default: 1 hour)", 

269 ) 

270 maven_timeout: int = Field( 

271 default=30, 

272 description="Timeout in seconds for Maven API requests", 

273 ) 

274 

275 @field_validator("repos_root") 

276 @classmethod 

277 def expand_repos_root(cls, v: Path | None) -> Path | None: 

278 """Expand user home directory in repos_root and resolve to absolute path.""" 

279 if v is None: 

280 return None 

281 path = Path(v).expanduser().resolve() 

282 return path 

283 

284 @field_validator("data_dir") 

285 @classmethod 

286 def expand_data_dir(cls, v: str) -> str: 

287 """Expand user home directory in data_dir.""" 

288 return str(Path(v).expanduser()) 

289 

290 @field_validator("workspace_root") 

291 @classmethod 

292 def expand_workspace_root(cls, v: Path | None) -> Path | None: 

293 """Expand user home directory in workspace_root and resolve to absolute path.""" 

294 if v is None: 

295 return None 

296 # Convert to Path if string, expand user, and resolve to absolute 

297 path = Path(v).expanduser().resolve() 

298 return path 

299 

300 

301class TelemetryConfig(BaseModel): 

302 """Telemetry and observability configuration.""" 

303 

304 enabled: bool = False 

305 enable_sensitive_data: bool = False 

306 otlp_endpoint: str = "http://localhost:4317" 

307 applicationinsights_connection_string: str | None = None 

308 

309 

310class Mem0Config(BaseModel): 

311 """Mem0-specific configuration.""" 

312 

313 storage_path: str | None = None 

314 api_key: str | None = None 

315 org_id: str | None = None 

316 user_id: str | None = None 

317 project_id: str | None = None 

318 

319 @field_validator("storage_path") 

320 @classmethod 

321 def expand_storage_path(cls, v: str | None) -> str | None: 

322 """Expand user home directory in storage_path.""" 

323 if v: 

324 return str(Path(v).expanduser()) 

325 return v 

326 

327 

328class MemoryConfig(BaseModel): 

329 """Memory configuration.""" 

330 

331 enabled: bool = True 

332 type: str = "in_memory" 

333 history_limit: int = 20 

334 mem0: Mem0Config = Field(default_factory=Mem0Config) 

335 

336 @field_validator("type") 

337 @classmethod 

338 def validate_memory_type(cls, v: str) -> str: 

339 """Validate memory type.""" 

340 if v not in VALID_MEMORY_TYPES: 

341 raise ValueError(f"Invalid memory type: {v}. Valid types: {VALID_MEMORY_TYPES}") 

342 return v 

343 

344 

345class AgentSettings(BaseModel): 

346 """Root configuration model for agent settings.""" 

347 

348 version: str = "1.0" 

349 providers: ProviderConfig = Field(default_factory=ProviderConfig) 

350 agent: AgentConfig = Field(default_factory=AgentConfig) 

351 telemetry: TelemetryConfig = Field(default_factory=TelemetryConfig) 

352 memory: MemoryConfig = Field(default_factory=MemoryConfig) 

353 skills: SkillsConfig = Field(default_factory=SkillsConfig) 

354 

355 def model_dump_json_pretty(self, **kwargs: Any) -> str: 

356 """Dump model to pretty-printed JSON string.""" 

357 return self.model_dump_json(indent=2, exclude_none=False, **kwargs) 

358 

359 def model_dump_json_minimal(self) -> str: 

360 """Dump model to minimal JSON string (progressive disclosure). 

361 

362 Only includes: 

363 - Enabled providers (not disabled ones) 

364 - Non-null values 

365 

366 This creates a cleaner, more user-friendly config file that shows 

367 only what the user has explicitly configured. 

368 

369 Benefits: 

370 - Reduced clutter (100+ lines → ~20 lines) 

371 - Clear intent (only see what's configured) 

372 - Git-friendly (smaller diffs) 

373 - Industry standard (like package.json, docker-compose) 

374 

375 Returns: 

376 JSON string with minimal configuration 

377 

378 Example: 

379 >>> settings = AgentSettings() 

380 >>> settings.providers.enabled = ["openai"] 

381 >>> settings.providers.openai.api_key = "sk-..." 

382 >>> json_str = settings.model_dump_json_minimal() 

383 # Only shows openai config, not disabled providers 

384 """ 

385 # Get full data excluding None values 

386 data = self.model_dump(exclude_none=True) 

387 

388 # Filter providers: only include enabled ones 

389 if "providers" in data: 

390 enabled = data["providers"].get("enabled", []) 

391 filtered_providers = {"enabled": enabled} 

392 

393 # Only include enabled provider configs 

394 for provider in enabled: 

395 if provider in data["providers"]: 

396 provider_data = data["providers"][provider] 

397 # Remove the redundant 'enabled' flag from individual providers 

398 # (it's already in the enabled list) 

399 if isinstance(provider_data, dict): 

400 provider_data.pop("enabled", None) 

401 filtered_providers[provider] = provider_data 

402 

403 data["providers"] = filtered_providers 

404 

405 # Clean up empty nested objects (like mem0 if all values are None) 

406 if "memory" in data and "mem0" in data["memory"]: 

407 if not data["memory"]["mem0"]: 

408 # Remove empty mem0 config 

409 del data["memory"]["mem0"] 

410 

411 return json.dumps(data, indent=2) 

412 

413 @classmethod 

414 def get_json_schema(cls) -> dict[str, Any]: 

415 """Get JSON schema for the settings model.""" 

416 return cls.model_json_schema() 

417 

418 def validate_enabled_providers(self) -> list[str]: 

419 """Validate all enabled providers have required configuration. 

420 

421 Returns: 

422 List of validation errors (empty if all valid) 

423 """ 

424 errors = [] 

425 

426 for provider_name in self.providers.enabled: 

427 provider = getattr(self.providers, provider_name) 

428 

429 if provider_name == "openai": 

430 if not provider.api_key: 

431 errors.append( 

432 "OpenAI provider enabled but missing api_key. " 

433 "Run: agent config enable openai" 

434 ) 

435 

436 elif provider_name == "anthropic": 

437 if not provider.api_key: 

438 errors.append( 

439 "Anthropic provider enabled but missing api_key. " 

440 "Run: agent config enable anthropic" 

441 ) 

442 

443 elif provider_name == "azure": 

444 if not provider.endpoint: 

445 errors.append( 

446 "Azure provider enabled but missing endpoint. " 

447 "Run: agent config enable azure" 

448 ) 

449 if not provider.deployment: 

450 errors.append( 

451 "Azure provider enabled but missing deployment. " 

452 "Run: agent config enable azure" 

453 ) 

454 

455 elif provider_name == "foundry": 

456 if not provider.project_endpoint: 

457 errors.append( 

458 "Foundry provider enabled but missing project_endpoint. " 

459 "Run: agent config enable foundry" 

460 ) 

461 if not provider.model_deployment: 

462 errors.append( 

463 "Foundry provider enabled but missing model_deployment. " 

464 "Run: agent config enable foundry" 

465 ) 

466 

467 elif provider_name == "gemini": 

468 if provider.use_vertexai: 

469 if not provider.project_id: 

470 errors.append( 

471 "Gemini Vertex AI enabled but missing project_id. " 

472 "Run: agent config enable gemini" 

473 ) 

474 if not provider.location: 

475 errors.append( 

476 "Gemini Vertex AI enabled but missing location. " 

477 "Run: agent config enable gemini" 

478 ) 

479 else: 

480 if not provider.api_key: 

481 errors.append( 

482 "Gemini provider enabled but missing api_key. " 

483 "Run: agent config enable gemini" 

484 ) 

485 

486 elif provider_name == "local": 

487 # Local provider requires base_url 

488 if not provider.base_url: 

489 errors.append( 

490 "Local provider enabled but missing base_url. " 

491 "Set LOCAL_BASE_URL environment variable or configure via: agent config enable local" 

492 ) 

493 

494 elif provider_name == "github": 

495 # GitHub authentication is handled at runtime via get_github_token() 

496 # which checks GITHUB_TOKEN env var or gh CLI 

497 # Token is optional in config - validation happens at runtime 

498 pass 

499 

500 return errors 

501 

502 @property 

503 def llm_provider(self) -> str: 

504 """Get the primary LLM provider (first enabled provider). 

505 

506 Returns: 

507 Primary provider name from enabled list 

508 

509 Raises: 

510 ValueError: If no providers are enabled 

511 

512 Example: 

513 >>> settings = AgentSettings() 

514 >>> settings.providers.enabled = ["openai"] 

515 >>> settings.llm_provider 

516 'openai' 

517 """ 

518 if not self.providers.enabled: 

519 raise ValueError("No providers enabled in configuration") 

520 return self.providers.enabled[0] 

521 

522 @property 

523 def agent_data_dir(self) -> Path: 

524 """Get agent data directory as Path object. 

525 

526 Returns: 

527 Path to agent data directory (~/.osdu-agent by default) 

528 """ 

529 return Path(self.agent.data_dir).expanduser() 

530 

531 @property 

532 def agent_session_dir(self) -> Path: 

533 """Get agent session directory as Path object. 

534 

535 Returns: 

536 Path to agent session directory (data_dir/sessions) 

537 """ 

538 return self.agent_data_dir / "sessions" 

539 

540 @property 

541 def memory_enabled(self) -> bool: 

542 """Get memory enabled status. 

543 

544 Returns: 

545 True if memory is enabled 

546 """ 

547 return self.memory.enabled 

548 

549 @property 

550 def memory_type(self) -> str: 

551 """Get memory type. 

552 

553 Returns: 

554 Memory type ('in_memory' or 'mem0') 

555 """ 

556 return self.memory.type 

557 

558 @property 

559 def memory_history_limit(self) -> int: 

560 """Get memory history limit. 

561 

562 Returns: 

563 Maximum number of messages to keep in memory 

564 """ 

565 return self.memory.history_limit 

566 

567 @property 

568 def system_prompt_file(self) -> str | None: 

569 """Get system prompt file path. 

570 

571 Returns: 

572 Path to custom system prompt file or None. 

573 Checks agent.system_prompt_file field first, then falls back to AGENT_SYSTEM_PROMPT env var. 

574 """ 

575 # Check if field is explicitly set 

576 if self.agent.system_prompt_file is not None: 

577 return self.agent.system_prompt_file 

578 

579 # Fall back to environment variable 

580 import os 

581 

582 return os.getenv("AGENT_SYSTEM_PROMPT") 

583 

584 def get_model_display_name(self) -> str: 

585 """Get display name for the current model with provider. 

586 

587 Returns: 

588 Model name with provider prefix (e.g., "OpenAI/gpt-5-mini") 

589 

590 Example: 

591 >>> settings = AgentSettings() 

592 >>> settings.providers.enabled = ["openai"] 

593 >>> settings.providers.openai.model = "gpt-5-mini" 

594 >>> settings.get_model_display_name() 

595 'OpenAI/gpt-5-mini' 

596 """ 

597 provider = self.llm_provider 

598 

599 # Format: Provider/model 

600 if provider == "openai": 

601 return f"OpenAI/{self.providers.openai.model}" 

602 elif provider == "anthropic": 

603 return f"Anthropic/{self.providers.anthropic.model}" 

604 elif provider == "azure": 

605 deployment = self.providers.azure.deployment or "unknown" 

606 return f"Azure OpenAI/{deployment}" 

607 elif provider == "foundry": 

608 deployment = self.providers.foundry.model_deployment or "unknown" 

609 return f"Azure AI Foundry/{deployment}" 

610 elif provider == "gemini": 

611 return f"Gemini/{self.providers.gemini.model}" 

612 elif provider == "github": 

613 return f"GitHub/{self.providers.github.model}" 

614 elif provider == "local": 

615 return f"Local/{self.providers.local.model}" 

616 else: 

617 return "unknown" 

618 

619 # Legacy compatibility aliases for smooth migration 

620 @property 

621 def openai_api_key(self) -> str | None: 

622 """Legacy: Get OpenAI API key.""" 

623 return self.providers.openai.api_key 

624 

625 @property 

626 def openai_model(self) -> str: 

627 """Legacy: Get OpenAI model.""" 

628 return self.providers.openai.model 

629 

630 @property 

631 def anthropic_api_key(self) -> str | None: 

632 """Legacy: Get Anthropic API key.""" 

633 return self.providers.anthropic.api_key 

634 

635 @property 

636 def anthropic_model(self) -> str: 

637 """Legacy: Get Anthropic model.""" 

638 return self.providers.anthropic.model 

639 

640 @property 

641 def azure_openai_endpoint(self) -> str | None: 

642 """Legacy: Get Azure OpenAI endpoint.""" 

643 return self.providers.azure.endpoint 

644 

645 @property 

646 def azure_openai_deployment(self) -> str | None: 

647 """Legacy: Get Azure OpenAI deployment.""" 

648 return self.providers.azure.deployment 

649 

650 @property 

651 def azure_openai_api_version(self) -> str: 

652 """Legacy: Get Azure OpenAI API version.""" 

653 return self.providers.azure.api_version 

654 

655 @property 

656 def azure_openai_api_key(self) -> str | None: 

657 """Legacy: Get Azure OpenAI API key.""" 

658 return self.providers.azure.api_key 

659 

660 @property 

661 def azure_project_endpoint(self) -> str | None: 

662 """Legacy: Get Azure AI Foundry project endpoint.""" 

663 return self.providers.foundry.project_endpoint 

664 

665 @property 

666 def azure_model_deployment(self) -> str | None: 

667 """Legacy: Get Azure model deployment (OpenAI or Foundry).""" 

668 # Check which Azure provider is enabled 

669 if "azure" in self.providers.enabled: 

670 return self.providers.azure.deployment 

671 elif "foundry" in self.providers.enabled: 

672 return self.providers.foundry.model_deployment 

673 return None 

674 

675 @property 

676 def gemini_api_key(self) -> str | None: 

677 """Legacy: Get Gemini API key.""" 

678 return self.providers.gemini.api_key 

679 

680 @property 

681 def gemini_model(self) -> str: 

682 """Legacy: Get Gemini model.""" 

683 return self.providers.gemini.model 

684 

685 @property 

686 def gemini_project_id(self) -> str | None: 

687 """Legacy: Get Gemini project ID.""" 

688 return self.providers.gemini.project_id 

689 

690 @property 

691 def gemini_location(self) -> str | None: 

692 """Legacy: Get Gemini location.""" 

693 return self.providers.gemini.location 

694 

695 @property 

696 def gemini_use_vertexai(self) -> bool: 

697 """Legacy: Get Gemini Vertex AI usage flag.""" 

698 return self.providers.gemini.use_vertexai 

699 

700 @property 

701 def github_token(self) -> str | None: 

702 """Legacy: Get GitHub token.""" 

703 return self.providers.github.token 

704 

705 @property 

706 def github_model(self) -> str: 

707 """Legacy: Get GitHub model.""" 

708 return self.providers.github.model 

709 

710 @property 

711 def github_endpoint(self) -> str: 

712 """Legacy: Get GitHub endpoint.""" 

713 return self.providers.github.endpoint 

714 

715 @property 

716 def github_org(self) -> str | None: 

717 """Legacy: Get GitHub organization.""" 

718 return self.providers.github.org 

719 

720 @property 

721 def local_base_url(self) -> str: 

722 """Legacy: Get local base URL.""" 

723 return self.providers.local.base_url 

724 

725 @property 

726 def local_model(self) -> str: 

727 """Legacy: Get local model.""" 

728 return self.providers.local.model 

729 

730 @property 

731 def workspace_root(self) -> Path | None: 

732 """Legacy: Get workspace root for filesystem tools.""" 

733 return self.agent.workspace_root 

734 

735 @workspace_root.setter 

736 def workspace_root(self, value: Path | None) -> None: 

737 """Legacy: Set workspace root for filesystem tools.""" 

738 self.agent.workspace_root = value 

739 

740 @property 

741 def filesystem_writes_enabled(self) -> bool: 

742 """Legacy: Get filesystem writes enabled flag.""" 

743 return self.agent.filesystem_writes_enabled 

744 

745 @filesystem_writes_enabled.setter 

746 def filesystem_writes_enabled(self, value: bool) -> None: 

747 """Legacy: Set filesystem writes enabled flag.""" 

748 self.agent.filesystem_writes_enabled = value 

749 

750 @property 

751 def filesystem_max_read_bytes(self) -> int: 

752 """Legacy: Get filesystem max read bytes.""" 

753 return self.agent.filesystem_max_read_bytes 

754 

755 @filesystem_max_read_bytes.setter 

756 def filesystem_max_read_bytes(self, value: int) -> None: 

757 """Legacy: Set filesystem max read bytes.""" 

758 self.agent.filesystem_max_read_bytes = value 

759 

760 @property 

761 def filesystem_max_write_bytes(self) -> int: 

762 """Legacy: Get filesystem max write bytes.""" 

763 return self.agent.filesystem_max_write_bytes 

764 

765 @filesystem_max_write_bytes.setter 

766 def filesystem_max_write_bytes(self, value: int) -> None: 

767 """Legacy: Set filesystem max write bytes.""" 

768 self.agent.filesystem_max_write_bytes = value 

769 

770 @property 

771 def mem0_user_id(self) -> str | None: 

772 """Legacy: Get Mem0 user ID.""" 

773 return self.memory.mem0.user_id 

774 

775 @property 

776 def mem0_project_id(self) -> str | None: 

777 """Legacy: Get Mem0 project ID.""" 

778 return self.memory.mem0.project_id 

779 

780 @property 

781 def mem0_storage_path(self) -> str | None: 

782 """Legacy: Get Mem0 storage path.""" 

783 return self.memory.mem0.storage_path 

784 

785 @property 

786 def mem0_api_key(self) -> str | None: 

787 """Legacy: Get Mem0 API key.""" 

788 return self.memory.mem0.api_key 

789 

790 @property 

791 def mem0_org_id(self) -> str | None: 

792 """Legacy: Get Mem0 organization ID.""" 

793 return self.memory.mem0.org_id 

794 

795 @property 

796 def memory_dir(self) -> Path: 

797 """Legacy: Get memory directory path.""" 

798 return self.agent_data_dir / "memory" 

799 

800 @property 

801 def enabled_providers(self) -> list[str]: 

802 """Legacy: Get list of enabled providers.""" 

803 return self.providers.enabled 

804 

805 @property 

806 def enable_otel(self) -> bool: 

807 """Legacy: Get telemetry enabled flag.""" 

808 return self.telemetry.enabled 

809 

810 @property 

811 def enable_otel_explicit(self) -> bool: 

812 """Legacy: Check if telemetry was explicitly enabled (not auto-detected).""" 

813 return self.telemetry.enabled 

814 

815 @property 

816 def otlp_endpoint(self) -> str: 

817 """Legacy: Get OTLP endpoint.""" 

818 return self.telemetry.otlp_endpoint 

819 

820 @property 

821 def applicationinsights_connection_string(self) -> str | None: 

822 """Legacy: Get Application Insights connection string.""" 

823 return self.telemetry.applicationinsights_connection_string 

824 

825 @property 

826 def enable_sensitive_data(self) -> bool: 

827 """Legacy: Get enable sensitive data flag.""" 

828 return self.telemetry.enable_sensitive_data 

829 

830 @property 

831 def repos_root(self) -> Path | None: 

832 """Legacy: Get repos root for git tools.""" 

833 return self.agent.repos_root 

834 

835 @repos_root.setter 

836 def repos_root(self, value: Path | None) -> None: 

837 """Legacy: Set repos root for git tools.""" 

838 self.agent.repos_root = value 

839 

840 @property 

841 def git_default_remote(self) -> str: 

842 """Legacy: Get git default remote.""" 

843 return self.agent.git_default_remote 

844 

845 @property 

846 def git_clone_timeout(self) -> int: 

847 """Legacy: Get git clone timeout.""" 

848 return self.agent.git_clone_timeout 

849 

850 @property 

851 def gitlab_url(self) -> str: 

852 """Legacy: Get GitLab instance URL.""" 

853 return self.agent.gitlab_url 

854 

855 @property 

856 def gitlab_token(self) -> str | None: 

857 """Legacy: Get GitLab personal access token.""" 

858 return self.agent.gitlab_token 

859 

860 @property 

861 def gitlab_api_timeout(self) -> int: 

862 """Legacy: Get GitLab API timeout.""" 

863 return self.agent.gitlab_api_timeout