Skip to content

Commit b506ba4

Browse files
authored
feat: implement auxiliary tool categories -- design, communication, analytics (#1152)
## Summary Implements three auxiliary tool categories for specialized agent workflows, resolving the consolidated requirements from #215, #216, and #217. Closes #1035 ## Tool Categories ### Design Tools (`src/synthorg/tools/design/`) - **ImageGeneratorTool** -- generates images via injectable `ImageProvider` protocol (no provider shipped -- users inject their own) - **DiagramGeneratorTool** -- produces Mermaid/Graphviz DSL markup from structured descriptions (no external API needed) - **AssetManagerTool** -- in-memory CRUD registry for generated design assets (list, get, delete, search) ### Communication Tools (`src/synthorg/tools/communication/`) - **EmailSenderTool** -- SMTP email via stdlib `smtplib` + `asyncio.to_thread` (same pattern as `EmailNotificationSink`) - **NotificationSenderTool** -- delegates to existing `NotificationDispatcher` via `NotificationDispatcherProtocol` - **TemplateFormatterTool** -- Jinja2 `SandboxedEnvironment` for safe template rendering (autoescape enabled for HTML output) ### Analytics Tools (`src/synthorg/tools/analytics/`) - **DataAggregatorTool** -- queries analytics data via injectable `AnalyticsProvider` protocol with metric whitelisting and ISO 8601 date validation - **ReportGeneratorTool** -- formatted reports (text/markdown/JSON) from analytics data - **MetricCollectorTool** -- records custom metrics via injectable `MetricSink` protocol ## Integration - Factory functions (`_build_design_tools`, `_build_communication_tools`, `_build_analytics_tools`) wired into `build_default_tools` and `build_default_tools_from_config` - All three categories are **opt-in**: `None` config (default) = no tools created - `RootConfig` extended with `design_tools`, `communication_tools`, `analytics_tools` fields - `config/defaults.py` updated with new field defaults ## Observability - New `events/design.py` (14 event constants) - Extended `events/communication.py` (11 new tool-specific events) - Extended `events/analytics.py` (11 new tool-specific events) - CLAUDE.md event reference updated with all new constants ## Security - Email header injection prevention (newline rejection in addresses) - Jinja2 `SandboxedEnvironment` with autoescape for HTML output - Graphviz title escaping (double-quote injection prevention) - ISO 8601 date validation in analytics custom periods - Control character sanitization in email subjects - `EmailConfig` cross-field validation (username + password must be both or neither) - `ImageResult` field validation (`width`/`height` gt=0, `data` min_length=1) ## Test Plan - **175 new tests** across all categories (design: 51, communication: 58, analytics: 47, factory: 19) - Config validation: frozen, NaN/Inf rejection, bounds, cross-field validators - Tool execution: success paths, error paths, missing providers, invalid params - Security: email header injection, Jinja2 sandbox attribute access, HTML XSS escaping, Graphviz title escaping, date format validation - Factory: config-conditional tool creation for all 3 categories - All existing 15,959 tests continue to pass ## Review Coverage Pre-reviewed by 13 agents, 15 valid findings addressed: - code-reviewer, python-reviewer, security-reviewer, type-design-analyzer, test-analyzer, test-quality-reviewer, silent-failure-hunter, logging-audit, conventions-enforcer, resilience-audit, async-concurrency-reviewer, docs-consistency, issue-resolution-verifier
1 parent 5f563ce commit b506ba4

48 files changed

Lines changed: 5719 additions & 5 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

CLAUDE.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -90,7 +90,7 @@ See `web/CLAUDE.md` for the full component inventory, design token rules, and po
9090
- **Every module** with business logic MUST have: `from synthorg.observability import get_logger` then `logger = get_logger(__name__)`
9191
- **Never** use `import logging` / `logging.getLogger()` / `print()` in application code (exception: `observability/setup.py`, `observability/sinks.py`, `observability/syslog_handler.py`, `observability/http_handler.py`, and `observability/otlp_handler.py` may use stdlib `logging` and `print(..., file=sys.stderr)` for handler construction, bootstrap, and error reporting code that runs before or during logging system configuration)
9292
- **Variable name**: always `logger` (not `_logger`, not `log`)
93-
- **Event names**: always use constants from the domain-specific module under `synthorg.observability.events` (e.g., `API_REQUEST_STARTED` from `events.api`, `TOOL_INVOKE_START` from `events.tool`, `GIT_COMMAND_START` from `events.git`, `CONTEXT_BUDGET_FILL_UPDATED`, `CONTEXT_BUDGET_COMPACTION_STARTED`, `CONTEXT_BUDGET_COMPACTION_COMPLETED`, `CONTEXT_BUDGET_COMPACTION_FAILED`, `CONTEXT_BUDGET_COMPACTION_SKIPPED`, `CONTEXT_BUDGET_COMPACTION_FALLBACK`, `CONTEXT_BUDGET_INDICATOR_INJECTED`, `CONTEXT_BUDGET_AGENT_COMPACTION_REQUESTED`, `CONTEXT_BUDGET_EPISTEMIC_MARKERS_PRESERVED` from `events.context_budget`, `BACKUP_STARTED` from `events.backup`, `SETUP_COMPLETED` from `events.setup`, `ROUTING_CANDIDATE_SELECTED` from `events.routing`, `SHIPPING_HTTP_BATCH_SENT` from `events.shipping`, `EVAL_REPORT_COMPUTED` from `events.evaluation`, `PROMPT_PROFILE_SELECTED` from `events.prompt`, `PROCEDURAL_MEMORY_START` from `events.procedural_memory`, `PERF_LLM_JUDGE_STARTED` from `events.performance`, `TASK_ENGINE_OBSERVER_FAILED` from `events.task_engine`, `TASK_ASSIGNMENT_PROJECT_FILTERED` and `TASK_ASSIGNMENT_PROJECT_NO_ELIGIBLE` from `events.task_assignment`, `EXECUTION_SHUTDOWN_IMMEDIATE_CANCEL`, `EXECUTION_SHUTDOWN_TOOL_WAIT`, `EXECUTION_SHUTDOWN_CHECKPOINT_SAVE`, `EXECUTION_SHUTDOWN_CHECKPOINT_FAILED`, and `EXECUTION_PROJECT_VALIDATION_FAILED` from `events.execution`, `WORKFLOW_EXEC_COMPLETED` from `events.workflow_execution`, `BLUEPRINT_INSTANTIATE_START` from `events.blueprint`, `WORKFLOW_DEF_ROLLED_BACK` from `events.workflow_definition`, `WORKFLOW_VERSION_SAVED` from `events.workflow_version`, `MEMORY_FINE_TUNE_STARTED`, `MEMORY_SELF_EDIT_TOOL_EXECUTE`, `MEMORY_SELF_EDIT_CORE_READ`, `MEMORY_SELF_EDIT_CORE_WRITE`, `MEMORY_SELF_EDIT_CORE_WRITE_REJECTED`, `MEMORY_SELF_EDIT_ARCHIVAL_SEARCH`, `MEMORY_SELF_EDIT_ARCHIVAL_WRITE`, `MEMORY_SELF_EDIT_RECALL_READ`, `MEMORY_SELF_EDIT_RECALL_WRITE`, `MEMORY_SELF_EDIT_WRITE_FAILED` from `events.memory`, `REPORTING_GENERATION_STARTED` from `events.reporting`, `RISK_BUDGET_SCORE_COMPUTED` from `events.risk_budget`, `BUDGET_PROJECT_COST_QUERIED`, `BUDGET_PROJECT_RECORDS_QUERIED`, `BUDGET_PROJECT_BUDGET_EXCEEDED`, `BUDGET_PROJECT_ENFORCEMENT_CHECK`, `BUDGET_PROJECT_COST_AGGREGATED`, `BUDGET_PROJECT_COST_AGGREGATION_FAILED`, and `BUDGET_PROJECT_BASELINE_SOURCE` from `events.budget`, `LLM_STRATEGY_SYNTHESIZED` and `DISTILLATION_CAPTURED` from `events.consolidation`, `MEMORY_DIVERSITY_RERANKED`, `MEMORY_DIVERSITY_RERANK_FAILED`, and `MEMORY_REFORMULATION_ROUND` from `events.memory`, `NOTIFICATION_DISPATCHED` and `NOTIFICATION_DISPATCH_FAILED` from `events.notification`, `QUALITY_STEP_CLASSIFIED` from `events.quality`, `HEALTH_TICKET_EMITTED` from `events.health`, `TRAJECTORY_SCORING_START` from `events.trajectory`, `COORD_METRICS_AMDAHL_COMPUTED` from `events.coordination_metrics`, `COORDINATION_STARTED`, `COORDINATION_COMPLETED`, `COORDINATION_FAILED`, `COORDINATION_PHASE_STARTED`, `COORDINATION_PHASE_COMPLETED`, `COORDINATION_PHASE_FAILED`, `COORDINATION_WAVE_STARTED`, `COORDINATION_WAVE_COMPLETED`, `COORDINATION_TOPOLOGY_RESOLVED`, `COORDINATION_CLEANUP_STARTED`, `COORDINATION_CLEANUP_COMPLETED`, `COORDINATION_CLEANUP_FAILED`, `COORDINATION_WAVE_BUILT`, `COORDINATION_FACTORY_BUILT`, and `COORDINATION_ATTRIBUTION_BUILT` from `events.coordination`, `WEB_REQUEST_START` and `WEB_SSRF_BLOCKED` from `events.web`, `DB_QUERY_START` and `DB_WRITE_BLOCKED` from `events.database`, `TERMINAL_COMMAND_START` and `TERMINAL_COMMAND_BLOCKED` from `events.terminal`, `SUB_CONSTRAINT_RESOLVED` and `SUB_CONSTRAINT_DENIED` from `events.sub_constraint`, `VERSION_SAVED`, `VERSION_SNAPSHOT_FAILED`, `VERSION_LISTED`, and `VERSION_NOT_FOUND` from `events.versioning`, `ANALYTICS_AGGREGATION_COMPUTED` and `ANALYTICS_RETRY_RATE_ALERT` from `events.analytics`, `CALL_CLASSIFICATION_COMPUTED` from `events.call_classification`, `QUOTA_THRESHOLD_ALERT` and `QUOTA_POLL_FAILED` from `events.quota`, `CONFLICT_DEBATE_EVALUATOR_FAILED` from `events.conflict`, `DELEGATION_LOOP_CIRCUIT_BACKOFF` and `DELEGATION_LOOP_CIRCUIT_PERSIST_FAILED` from `events.delegation`, `MEETING_EVENT_COOLDOWN_SKIPPED` and `MEETING_TASKS_CAPPED` from `events.meeting`, `PERSISTENCE_CIRCUIT_BREAKER_SAVED`, `PERSISTENCE_CIRCUIT_BREAKER_SAVE_FAILED`, `PERSISTENCE_CIRCUIT_BREAKER_LOADED`, `PERSISTENCE_CIRCUIT_BREAKER_LOAD_FAILED`, `PERSISTENCE_CIRCUIT_BREAKER_DELETED`, `PERSISTENCE_CIRCUIT_BREAKER_DELETE_FAILED`, `PERSISTENCE_PROJECT_COST_AGG_INCREMENTED`, `PERSISTENCE_PROJECT_COST_AGG_INCREMENT_FAILED`, `PERSISTENCE_PROJECT_COST_AGG_FETCHED`, `PERSISTENCE_PROJECT_COST_AGG_FETCH_FAILED`, and `PERSISTENCE_PROJECT_COST_AGG_DESERIALIZE_FAILED` from `events.persistence`, `METRICS_SCRAPE_COMPLETED`, `METRICS_SCRAPE_FAILED`, `METRICS_COLLECTOR_INITIALIZED`, `METRICS_COORDINATION_RECORDED`, `METRICS_OTLP_EXPORT_COMPLETED` and `METRICS_OTLP_FLUSHER_STOPPED` from `events.metrics`, `ORG_MEMORY_QUERY_START`, `ORG_MEMORY_QUERY_COMPLETE`, `ORG_MEMORY_QUERY_FAILED`, `ORG_MEMORY_WRITE_START`, `ORG_MEMORY_WRITE_COMPLETE`, `ORG_MEMORY_WRITE_DENIED`, `ORG_MEMORY_WRITE_FAILED`, `ORG_MEMORY_POLICIES_LISTED`, `ORG_MEMORY_BACKEND_CREATED`, `ORG_MEMORY_CONNECT_FAILED`, `ORG_MEMORY_DISCONNECT_FAILED`, `ORG_MEMORY_NOT_CONNECTED`, `ORG_MEMORY_ROW_PARSE_FAILED`, `ORG_MEMORY_CONFIG_INVALID`, `ORG_MEMORY_MODEL_INVALID`, `ORG_MEMORY_MVCC_PUBLISH_APPENDED`, `ORG_MEMORY_MVCC_RETRACT_APPENDED`, `ORG_MEMORY_MVCC_SNAPSHOT_AT_QUERIED`, and `ORG_MEMORY_MVCC_LOG_QUERIED` from `events.org_memory`). Each domain has its own module -- see `src/synthorg/observability/events/` for the full inventory of constants. Import directly: `from synthorg.observability.events.<domain> import EVENT_CONSTANT`
93+
- **Event names**: always use constants from the domain-specific module under `synthorg.observability.events` (e.g., `API_REQUEST_STARTED` from `events.api`, `TOOL_INVOKE_START` from `events.tool`). Each domain has its own module -- see `src/synthorg/observability/events/` for the full inventory of constants. Import directly: `from synthorg.observability.events.<domain> import EVENT_CONSTANT`
9494
- **Structured kwargs**: always `logger.info(EVENT, key=value)` -- never `logger.info("msg %s", val)`
9595
- **All error paths** must log at WARNING or ERROR with context before raising
9696
- **All state transitions** must log at INFO

docs/design/operations.md

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -691,8 +691,14 @@ Per-category backend selection is implemented in `tools/sandbox/factory.py` via
691691
`build_sandbox_backends` (instantiates only the backends referenced by config),
692692
`resolve_sandbox_for_category` (looks up the correct backend for a `ToolCategory`), and
693693
`cleanup_sandbox_backends` (parallel cleanup with error isolation). The tool factory
694-
(`build_default_tools_from_config`) wires `VERSION_CONTROL` category; other categories will
695-
be wired as their tool builders are added.
694+
(`build_default_tools_from_config`) wires tool categories. Core tools
695+
(`FILE_SYSTEM`, `VERSION_CONTROL`, web, etc.) are part of the default toolset
696+
and always registered. The
697+
auxiliary categories `DESIGN`, `COMMUNICATION`, and `ANALYTICS` are opt-in: tools
698+
are only registered when the corresponding config section is present, and some
699+
individual tools additionally require a runtime dependency (e.g. image tools
700+
require an ``ImageProvider``, notification tools require a dispatcher, analytics
701+
query/metric tools require a provider or sink).
696702

697703
Docker is optional -- only required when code execution, terminal, web, or database tools are
698704
enabled. File system and git tools work out of the box with subprocess isolation. This keeps

docs/reference/claude-reference.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ src/synthorg/
5555
settings/ # Runtime-editable settings (DB > env > YAML > code), Fernet encryption, ConfigResolver, definitions/, subscribers/ (SecuritySubscriber for discovery allowlist hot-reload)
5656
security/ # Rule engine, audit log, output scanner, progressive trust, autonomy levels, timeout policies, LLM fallback evaluator, custom policy rules, risk scoring (pluggable RiskScorer protocol, multi-dimensional RiskScore, DefaultRiskScorer), enforcement modes (active/shadow/disabled via SecurityEnforcementMode), risk override (SecOps risk tier reclassification via RiskTierOverride + SecOpsRiskClassifier), SSRF violation tracking (SsrfViolation model, pending/allowed/denied status for self-healing discovery allowlist)
5757
templates/ # Pre-built company templates (inheritance tree), template merge engine, personality presets, preset discovery/CRUD service, model requirements, tier-to-model matching, locale-aware name generation, workflow config rendering, pack_loader (additive team packs), packs/ (built-in pack YAMLs), uses_packs composition
58-
tools/ # Tool registry, built-in tools, git SSRF prevention, MCP bridge, sandbox factory (gVisor default overrides via merge_gvisor_defaults), invocation tracking, network_validator (shared SSRF), sub_constraints (per-level constraint models), sub_constraint_enforcer (granular enforcement), web/ (HTTP requests, HTML parsing, web search), database/ (SQL query, schema inspection), terminal/ (sandboxed shell commands), sandbox/ (4-domain SandboxPolicy model (filesystem/network/process/inference), SandboxRuntimeResolver (gVisor probe + per-category runtime resolution with fallback), SandboxCredentialManager (env var credential stripping), SandboxAuthProxy (LLM traffic auth proxy stub))
58+
tools/ # Tool registry, built-in tools, git SSRF prevention, MCP bridge, sandbox factory (gVisor default overrides via merge_gvisor_defaults), invocation tracking, network_validator (shared SSRF), sub_constraints (per-level constraint models), sub_constraint_enforcer (granular enforcement), web/ (HTTP requests, HTML parsing, web search), database/ (SQL query, schema inspection), terminal/ (sandboxed shell commands), design/ (image generation via ImageProvider protocol, diagram DSL generation, asset management), communication/ (SMTP email sending, notification dispatch via NotificationDispatcherProtocol, Jinja2 template formatting), analytics/ (data aggregation via AnalyticsProvider protocol, report generation, metric collection via MetricSink protocol), sandbox/ (4-domain SandboxPolicy model (filesystem/network/process/inference), SandboxRuntimeResolver (gVisor probe + per-category runtime resolution with fallback), SandboxCredentialManager (env var credential stripping), SandboxAuthProxy (LLM traffic auth proxy stub))
5959
6060
web/src/ # React 19 dashboard (see web/CLAUDE.md for full structure)
6161
cli/ # Go CLI binary (see cli/CLAUDE.md for full structure)

src/synthorg/config/defaults.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,4 +48,7 @@ def default_config_dict() -> dict[str, object]:
4848
"web": None,
4949
"database": None,
5050
"terminal": None,
51+
"design_tools": None,
52+
"communication_tools": None,
53+
"analytics_tools": None,
5154
}

src/synthorg/config/schema.py

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,10 @@
3737
from synthorg.providers.enums import AuthType
3838
from synthorg.security.config import SecurityConfig
3939
from synthorg.security.trust.config import TrustConfig
40+
from synthorg.tools.analytics.config import AnalyticsToolsConfig # noqa: TC001
41+
from synthorg.tools.communication.config import CommunicationToolsConfig # noqa: TC001
4042
from synthorg.tools.database.config import DatabaseConfig # noqa: TC001
43+
from synthorg.tools.design.config import DesignToolsConfig # noqa: TC001
4144
from synthorg.tools.git_url_validator import GitCloneNetworkPolicy
4245
from synthorg.tools.mcp.config import MCPConfig
4346
from synthorg.tools.sandbox.sandboxing_config import SandboxingConfig
@@ -592,6 +595,11 @@ class RootConfig(BaseModel):
592595
tools).
593596
terminal: Terminal tool configuration (``None`` = default
594597
terminal config).
598+
design_tools: Design tool configuration (``None`` = disabled).
599+
communication_tools: Communication tool configuration
600+
(``None`` = disabled).
601+
analytics_tools: Analytics tool configuration
602+
(``None`` = disabled).
595603
"""
596604

597605
model_config = ConfigDict(frozen=True, allow_inf_nan=False)
@@ -739,6 +747,18 @@ class RootConfig(BaseModel):
739747
default=None,
740748
description="Terminal tool configuration (None = default terminal config)",
741749
)
750+
design_tools: DesignToolsConfig | None = Field(
751+
default=None,
752+
description="Design tool configuration (None = disabled)",
753+
)
754+
communication_tools: CommunicationToolsConfig | None = Field(
755+
default=None,
756+
description="Communication tool configuration (None = disabled)",
757+
)
758+
analytics_tools: AnalyticsToolsConfig | None = Field(
759+
default=None,
760+
description="Analytics tool configuration (None = disabled)",
761+
)
742762

743763
@model_validator(mode="after")
744764
def _validate_unique_agent_names(self) -> Self:

src/synthorg/observability/events/analytics.py

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,3 +12,23 @@
1212
ANALYTICS_RETRY_RATE_ALERT: Final[str] = "analytics.retry_rate_alert"
1313
ANALYTICS_ORCHESTRATION_ALERT: Final[str] = "analytics.orchestration_alert"
1414
ANALYTICS_SERVICE_CREATED: Final[str] = "analytics.service_created"
15+
16+
# Tool: data aggregation queries
17+
ANALYTICS_TOOL_QUERY_START: Final[str] = "analytics.tool.query_start"
18+
ANALYTICS_TOOL_QUERY_SUCCESS: Final[str] = "analytics.tool.query_success"
19+
ANALYTICS_TOOL_QUERY_FAILED: Final[str] = "analytics.tool.query_failed"
20+
21+
# Tool: report generation
22+
ANALYTICS_TOOL_REPORT_START: Final[str] = "analytics.tool.report_start"
23+
ANALYTICS_TOOL_REPORT_SUCCESS: Final[str] = "analytics.tool.report_success"
24+
ANALYTICS_TOOL_REPORT_FAILED: Final[str] = "analytics.tool.report_failed"
25+
26+
# Tool: metric collection
27+
ANALYTICS_TOOL_METRIC_RECORDED: Final[str] = "analytics.tool.metric_recorded"
28+
ANALYTICS_TOOL_METRIC_RECORD_FAILED: Final[str] = "analytics.tool.metric_record_failed"
29+
ANALYTICS_TOOL_METRIC_NOT_ALLOWED: Final[str] = "analytics.tool.metric_not_allowed"
30+
31+
# Tool: provider
32+
ANALYTICS_TOOL_PROVIDER_NOT_CONFIGURED: Final[str] = (
33+
"analytics.tool.provider_not_configured"
34+
)

src/synthorg/observability/events/communication.py

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,3 +58,34 @@
5858

5959
# Shutdown
6060
COMM_BUS_SHUTDOWN_SIGNAL: Final[str] = "communication.bus.shutdown_signal"
61+
62+
# Tool: email sending
63+
COMM_TOOL_EMAIL_SEND_START: Final[str] = "communication.tool.email.send_start"
64+
COMM_TOOL_EMAIL_SEND_SUCCESS: Final[str] = "communication.tool.email.send_success"
65+
COMM_TOOL_EMAIL_SEND_FAILED: Final[str] = "communication.tool.email.send_failed"
66+
COMM_TOOL_EMAIL_VALIDATION_FAILED: Final[str] = (
67+
"communication.tool.email.validation_failed"
68+
)
69+
70+
# Tool: notification sending
71+
COMM_TOOL_NOTIFICATION_SEND_START: Final[str] = (
72+
"communication.tool.notification.send_start"
73+
)
74+
COMM_TOOL_NOTIFICATION_SEND_SUCCESS: Final[str] = (
75+
"communication.tool.notification.send_success"
76+
)
77+
COMM_TOOL_NOTIFICATION_SEND_FAILED: Final[str] = (
78+
"communication.tool.notification.send_failed"
79+
)
80+
81+
# Tool: template rendering
82+
COMM_TOOL_TEMPLATE_RENDER_START: Final[str] = "communication.tool.template.render_start"
83+
COMM_TOOL_TEMPLATE_RENDER_SUCCESS: Final[str] = (
84+
"communication.tool.template.render_success"
85+
)
86+
COMM_TOOL_TEMPLATE_RENDER_FAILED: Final[str] = (
87+
"communication.tool.template.render_failed"
88+
)
89+
COMM_TOOL_TEMPLATE_RENDER_INVALID: Final[str] = (
90+
"communication.tool.template.render_invalid"
91+
)
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
"""Design tool event constants."""
2+
3+
from typing import Final
4+
5+
# Image generation
6+
DESIGN_IMAGE_GENERATION_START: Final[str] = "design.image.generation_start"
7+
DESIGN_IMAGE_GENERATION_SUCCESS: Final[str] = "design.image.generation_success"
8+
DESIGN_IMAGE_GENERATION_FAILED: Final[str] = "design.image.generation_failed"
9+
DESIGN_IMAGE_GENERATION_TIMEOUT: Final[str] = "design.image.generation_timeout"
10+
11+
# Diagram generation
12+
DESIGN_DIAGRAM_GENERATION_START: Final[str] = "design.diagram.generation_start"
13+
DESIGN_DIAGRAM_GENERATION_SUCCESS: Final[str] = "design.diagram.generation_success"
14+
DESIGN_DIAGRAM_GENERATION_FAILED: Final[str] = "design.diagram.generation_failed"
15+
16+
# Asset management
17+
DESIGN_ASSET_STORED: Final[str] = "design.asset.stored"
18+
DESIGN_ASSET_RETRIEVED: Final[str] = "design.asset.retrieved"
19+
DESIGN_ASSET_DELETED: Final[str] = "design.asset.deleted"
20+
DESIGN_ASSET_LISTED: Final[str] = "design.asset.listed"
21+
DESIGN_ASSET_SEARCHED: Final[str] = "design.asset.searched"
22+
DESIGN_ASSET_VALIDATION_FAILED: Final[str] = "design.asset.validation_failed"
23+
24+
# Provider
25+
DESIGN_PROVIDER_NOT_CONFIGURED: Final[str] = "design.provider.not_configured"
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
"""Built-in analytics tools for data aggregation, reporting, and metrics."""
2+
3+
from synthorg.tools.analytics.base_analytics_tool import BaseAnalyticsTool
4+
from synthorg.tools.analytics.config import AnalyticsToolsConfig
5+
from synthorg.tools.analytics.data_aggregator import (
6+
AnalyticsProvider,
7+
DataAggregatorTool,
8+
)
9+
from synthorg.tools.analytics.metric_collector import (
10+
MetricCollectorTool,
11+
MetricSink,
12+
)
13+
from synthorg.tools.analytics.report_generator import ReportGeneratorTool
14+
15+
__all__ = [
16+
"AnalyticsProvider",
17+
"AnalyticsToolsConfig",
18+
"BaseAnalyticsTool",
19+
"DataAggregatorTool",
20+
"MetricCollectorTool",
21+
"MetricSink",
22+
"ReportGeneratorTool",
23+
]
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
"""Base class for analytics tools.
2+
3+
Provides the common ``ToolCategory.ANALYTICS`` category, a
4+
shared configuration reference, and a metric-name validation
5+
helper.
6+
"""
7+
8+
from abc import ABC
9+
from typing import Any
10+
11+
from synthorg.core.enums import ToolCategory
12+
from synthorg.tools.analytics.config import AnalyticsToolsConfig
13+
from synthorg.tools.base import BaseTool
14+
15+
16+
class BaseAnalyticsTool(BaseTool, ABC):
17+
"""Abstract base for all analytics tools.
18+
19+
Sets ``category=ToolCategory.ANALYTICS`` and holds a shared
20+
``AnalyticsToolsConfig``.
21+
"""
22+
23+
def __init__(
24+
self,
25+
*,
26+
name: str,
27+
description: str = "",
28+
parameters_schema: dict[str, Any] | None = None,
29+
action_type: str | None = None,
30+
config: AnalyticsToolsConfig | None = None,
31+
) -> None:
32+
"""Initialize an analytics tool with configuration.
33+
34+
Args:
35+
name: Tool name.
36+
description: Human-readable description.
37+
parameters_schema: JSON Schema for tool parameters.
38+
action_type: Security action type override.
39+
config: Analytics tool configuration.
40+
"""
41+
super().__init__(
42+
name=name,
43+
description=description,
44+
category=ToolCategory.ANALYTICS,
45+
parameters_schema=parameters_schema,
46+
action_type=action_type,
47+
)
48+
self._config = config or AnalyticsToolsConfig()
49+
50+
@property
51+
def config(self) -> AnalyticsToolsConfig:
52+
"""The analytics tool configuration."""
53+
return self._config
54+
55+
def _is_metric_allowed(self, metric_name: str) -> bool:
56+
"""Check if a metric name is allowed by the whitelist.
57+
58+
Args:
59+
metric_name: Name of the metric to check.
60+
61+
Returns:
62+
``True`` if the metric is allowed (or no whitelist
63+
is configured).
64+
"""
65+
if self._config.allowed_metrics is None:
66+
return True
67+
return metric_name in self._config.allowed_metrics

0 commit comments

Comments
 (0)