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
« 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.
15"""Pydantic models for agent configuration schema."""
17import json
18from pathlib import Path
19from typing import Any
21from pydantic import BaseModel, Field, field_validator, model_validator
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)
34# Module-level constants for validation
35VALID_PROVIDERS = {"local", "openai", "anthropic", "azure", "foundry", "gemini", "github"}
36VALID_MEMORY_TYPES = {"in_memory", "mem0"}
39class LocalProviderConfig(BaseModel):
40 """Local provider configuration (Docker Desktop Model Runner)."""
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
48class OpenAIProviderConfig(BaseModel):
49 """OpenAI provider configuration."""
51 enabled: bool = False
52 api_key: str | None = None
53 model: str = DEFAULT_OPENAI_MODEL
56class AnthropicProviderConfig(BaseModel):
57 """Anthropic provider configuration."""
59 enabled: bool = False
60 api_key: str | None = None
61 model: str = DEFAULT_ANTHROPIC_MODEL
64class AzureOpenAIProviderConfig(BaseModel):
65 """Azure OpenAI provider configuration."""
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
74class FoundryProviderConfig(BaseModel):
75 """Azure AI Foundry provider configuration."""
77 enabled: bool = False
78 project_endpoint: str | None = None
79 model_deployment: str | None = None
82class GeminiProviderConfig(BaseModel):
83 """Google Gemini provider configuration."""
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
93class GitHubProviderConfig(BaseModel):
94 """GitHub Models provider configuration."""
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
103class ProviderConfig(BaseModel):
104 """Provider configurations."""
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)
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
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
141class PluginSkillSource(BaseModel):
142 """Git-based plugin skill source configuration."""
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 )
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())
161class SkillsConfig(BaseModel):
162 """Skills system configuration (matches memory pattern)."""
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 )
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 )
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 )
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 )
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())
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())
211 return self
214class AgentConfig(BaseModel):
215 """Agent-specific configuration."""
217 data_dir: str = "~/.osdu-agent"
218 log_level: str = "info"
219 system_prompt_file: str | None = None
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 )
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 )
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 )
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 )
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
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())
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
301class TelemetryConfig(BaseModel):
302 """Telemetry and observability configuration."""
304 enabled: bool = False
305 enable_sensitive_data: bool = False
306 otlp_endpoint: str = "http://localhost:4317"
307 applicationinsights_connection_string: str | None = None
310class Mem0Config(BaseModel):
311 """Mem0-specific configuration."""
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
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
328class MemoryConfig(BaseModel):
329 """Memory configuration."""
331 enabled: bool = True
332 type: str = "in_memory"
333 history_limit: int = 20
334 mem0: Mem0Config = Field(default_factory=Mem0Config)
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
345class AgentSettings(BaseModel):
346 """Root configuration model for agent settings."""
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)
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)
359 def model_dump_json_minimal(self) -> str:
360 """Dump model to minimal JSON string (progressive disclosure).
362 Only includes:
363 - Enabled providers (not disabled ones)
364 - Non-null values
366 This creates a cleaner, more user-friendly config file that shows
367 only what the user has explicitly configured.
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)
375 Returns:
376 JSON string with minimal configuration
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)
388 # Filter providers: only include enabled ones
389 if "providers" in data:
390 enabled = data["providers"].get("enabled", [])
391 filtered_providers = {"enabled": enabled}
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
403 data["providers"] = filtered_providers
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"]
411 return json.dumps(data, indent=2)
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()
418 def validate_enabled_providers(self) -> list[str]:
419 """Validate all enabled providers have required configuration.
421 Returns:
422 List of validation errors (empty if all valid)
423 """
424 errors = []
426 for provider_name in self.providers.enabled:
427 provider = getattr(self.providers, provider_name)
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 )
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 )
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 )
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 )
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 )
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 )
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
500 return errors
502 @property
503 def llm_provider(self) -> str:
504 """Get the primary LLM provider (first enabled provider).
506 Returns:
507 Primary provider name from enabled list
509 Raises:
510 ValueError: If no providers are enabled
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]
522 @property
523 def agent_data_dir(self) -> Path:
524 """Get agent data directory as Path object.
526 Returns:
527 Path to agent data directory (~/.osdu-agent by default)
528 """
529 return Path(self.agent.data_dir).expanduser()
531 @property
532 def agent_session_dir(self) -> Path:
533 """Get agent session directory as Path object.
535 Returns:
536 Path to agent session directory (data_dir/sessions)
537 """
538 return self.agent_data_dir / "sessions"
540 @property
541 def memory_enabled(self) -> bool:
542 """Get memory enabled status.
544 Returns:
545 True if memory is enabled
546 """
547 return self.memory.enabled
549 @property
550 def memory_type(self) -> str:
551 """Get memory type.
553 Returns:
554 Memory type ('in_memory' or 'mem0')
555 """
556 return self.memory.type
558 @property
559 def memory_history_limit(self) -> int:
560 """Get memory history limit.
562 Returns:
563 Maximum number of messages to keep in memory
564 """
565 return self.memory.history_limit
567 @property
568 def system_prompt_file(self) -> str | None:
569 """Get system prompt file path.
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
579 # Fall back to environment variable
580 import os
582 return os.getenv("AGENT_SYSTEM_PROMPT")
584 def get_model_display_name(self) -> str:
585 """Get display name for the current model with provider.
587 Returns:
588 Model name with provider prefix (e.g., "OpenAI/gpt-5-mini")
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
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"
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
625 @property
626 def openai_model(self) -> str:
627 """Legacy: Get OpenAI model."""
628 return self.providers.openai.model
630 @property
631 def anthropic_api_key(self) -> str | None:
632 """Legacy: Get Anthropic API key."""
633 return self.providers.anthropic.api_key
635 @property
636 def anthropic_model(self) -> str:
637 """Legacy: Get Anthropic model."""
638 return self.providers.anthropic.model
640 @property
641 def azure_openai_endpoint(self) -> str | None:
642 """Legacy: Get Azure OpenAI endpoint."""
643 return self.providers.azure.endpoint
645 @property
646 def azure_openai_deployment(self) -> str | None:
647 """Legacy: Get Azure OpenAI deployment."""
648 return self.providers.azure.deployment
650 @property
651 def azure_openai_api_version(self) -> str:
652 """Legacy: Get Azure OpenAI API version."""
653 return self.providers.azure.api_version
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
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
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
675 @property
676 def gemini_api_key(self) -> str | None:
677 """Legacy: Get Gemini API key."""
678 return self.providers.gemini.api_key
680 @property
681 def gemini_model(self) -> str:
682 """Legacy: Get Gemini model."""
683 return self.providers.gemini.model
685 @property
686 def gemini_project_id(self) -> str | None:
687 """Legacy: Get Gemini project ID."""
688 return self.providers.gemini.project_id
690 @property
691 def gemini_location(self) -> str | None:
692 """Legacy: Get Gemini location."""
693 return self.providers.gemini.location
695 @property
696 def gemini_use_vertexai(self) -> bool:
697 """Legacy: Get Gemini Vertex AI usage flag."""
698 return self.providers.gemini.use_vertexai
700 @property
701 def github_token(self) -> str | None:
702 """Legacy: Get GitHub token."""
703 return self.providers.github.token
705 @property
706 def github_model(self) -> str:
707 """Legacy: Get GitHub model."""
708 return self.providers.github.model
710 @property
711 def github_endpoint(self) -> str:
712 """Legacy: Get GitHub endpoint."""
713 return self.providers.github.endpoint
715 @property
716 def github_org(self) -> str | None:
717 """Legacy: Get GitHub organization."""
718 return self.providers.github.org
720 @property
721 def local_base_url(self) -> str:
722 """Legacy: Get local base URL."""
723 return self.providers.local.base_url
725 @property
726 def local_model(self) -> str:
727 """Legacy: Get local model."""
728 return self.providers.local.model
730 @property
731 def workspace_root(self) -> Path | None:
732 """Legacy: Get workspace root for filesystem tools."""
733 return self.agent.workspace_root
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
740 @property
741 def filesystem_writes_enabled(self) -> bool:
742 """Legacy: Get filesystem writes enabled flag."""
743 return self.agent.filesystem_writes_enabled
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
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
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
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
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
770 @property
771 def mem0_user_id(self) -> str | None:
772 """Legacy: Get Mem0 user ID."""
773 return self.memory.mem0.user_id
775 @property
776 def mem0_project_id(self) -> str | None:
777 """Legacy: Get Mem0 project ID."""
778 return self.memory.mem0.project_id
780 @property
781 def mem0_storage_path(self) -> str | None:
782 """Legacy: Get Mem0 storage path."""
783 return self.memory.mem0.storage_path
785 @property
786 def mem0_api_key(self) -> str | None:
787 """Legacy: Get Mem0 API key."""
788 return self.memory.mem0.api_key
790 @property
791 def mem0_org_id(self) -> str | None:
792 """Legacy: Get Mem0 organization ID."""
793 return self.memory.mem0.org_id
795 @property
796 def memory_dir(self) -> Path:
797 """Legacy: Get memory directory path."""
798 return self.agent_data_dir / "memory"
800 @property
801 def enabled_providers(self) -> list[str]:
802 """Legacy: Get list of enabled providers."""
803 return self.providers.enabled
805 @property
806 def enable_otel(self) -> bool:
807 """Legacy: Get telemetry enabled flag."""
808 return self.telemetry.enabled
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
815 @property
816 def otlp_endpoint(self) -> str:
817 """Legacy: Get OTLP endpoint."""
818 return self.telemetry.otlp_endpoint
820 @property
821 def applicationinsights_connection_string(self) -> str | None:
822 """Legacy: Get Application Insights connection string."""
823 return self.telemetry.applicationinsights_connection_string
825 @property
826 def enable_sensitive_data(self) -> bool:
827 """Legacy: Get enable sensitive data flag."""
828 return self.telemetry.enable_sensitive_data
830 @property
831 def repos_root(self) -> Path | None:
832 """Legacy: Get repos root for git tools."""
833 return self.agent.repos_root
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
840 @property
841 def git_default_remote(self) -> str:
842 """Legacy: Get git default remote."""
843 return self.agent.git_default_remote
845 @property
846 def git_clone_timeout(self) -> int:
847 """Legacy: Get git clone timeout."""
848 return self.agent.git_clone_timeout
850 @property
851 def gitlab_url(self) -> str:
852 """Legacy: Get GitLab instance URL."""
853 return self.agent.gitlab_url
855 @property
856 def gitlab_token(self) -> str | None:
857 """Legacy: Get GitLab personal access token."""
858 return self.agent.gitlab_token
860 @property
861 def gitlab_api_timeout(self) -> int:
862 """Legacy: Get GitLab API timeout."""
863 return self.agent.gitlab_api_timeout