diff --git a/CHANGELOG.md b/CHANGELOG.md index 9bcf389..3567bf9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,17 @@ All notable changes to the AxonFlow Python SDK will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [3.6.0] - 2026-02-22 + +### Added + +- Media governance configuration methods: `get_media_governance_config()`, `update_media_governance_config()`, `get_media_governance_status()` +- Media governance types: `MediaGovernanceConfig`, `MediaGovernanceStatus` +- Media policy category constants: `CATEGORY_MEDIA_SAFETY`, `CATEGORY_MEDIA_BIOMETRIC`, `CATEGORY_MEDIA_PII`, `CATEGORY_MEDIA_DOCUMENT` +- `mark_step_completed()` now accepts post-execution metrics (`tokens_in`, `tokens_out`, `cost_usd`) via `MarkStepCompletedRequest` + +--- + ## [3.5.0] - 2026-02-19 ### Added diff --git a/axonflow/__init__.py b/axonflow/__init__.py index 1087d88..01e6716 100644 --- a/axonflow/__init__.py +++ b/axonflow/__init__.py @@ -117,6 +117,10 @@ UpdateStaticPolicyRequest, ) from axonflow.types import ( + CATEGORY_MEDIA_BIOMETRIC, + CATEGORY_MEDIA_DOCUMENT, + CATEGORY_MEDIA_PII, + CATEGORY_MEDIA_SAFETY, AuditResult, Budget, BudgetAlert, @@ -151,6 +155,8 @@ MediaAnalysisResponse, MediaAnalysisResult, MediaContent, + MediaGovernanceConfig, + MediaGovernanceStatus, Mode, ModelPricing, PlanExecutionResponse, @@ -170,6 +176,7 @@ TimelineEntry, TokenUsage, UpdateBudgetRequest, + UpdateMediaGovernanceConfigRequest, UpdatePlanRequest, UpdatePlanResponse, UsageBreakdown, @@ -202,7 +209,7 @@ WorkflowStepInfo, ) -__version__ = "3.5.0" +__version__ = "3.6.0" __all__ = [ # Main client "AxonFlow", @@ -220,6 +227,15 @@ "MediaContent", "MediaAnalysisResult", "MediaAnalysisResponse", + # Media Governance Config types + "MediaGovernanceConfig", + "MediaGovernanceStatus", + "UpdateMediaGovernanceConfigRequest", + # Media Governance Category constants + "CATEGORY_MEDIA_SAFETY", + "CATEGORY_MEDIA_BIOMETRIC", + "CATEGORY_MEDIA_DOCUMENT", + "CATEGORY_MEDIA_PII", # Connector types "ConnectorMetadata", "ConnectorInstallRequest", diff --git a/axonflow/client.py b/axonflow/client.py index 76ce36b..791cde9 100644 --- a/axonflow/client.py +++ b/axonflow/client.py @@ -152,6 +152,8 @@ ListUsageRecordsOptions, ListWebhooksResponse, MediaContent, + MediaGovernanceConfig, + MediaGovernanceStatus, Mode, PlanExecutionResponse, PlanResponse, @@ -167,6 +169,7 @@ TimelineEntry, TokenUsage, UpdateBudgetRequest, + UpdateMediaGovernanceConfigRequest, UpdatePlanRequest, UpdatePlanResponse, UsageBreakdown, @@ -3363,9 +3366,15 @@ async def mark_step_completed( ... MarkStepCompletedRequest(output={"result": "Code generated"}) ... ) """ - body = {} + body: dict[str, Any] = {} if request: body = {"output": request.output, "metadata": request.metadata} + if request.tokens_in is not None: + body["tokens_in"] = request.tokens_in + if request.tokens_out is not None: + body["tokens_out"] = request.tokens_out + if request.cost_usd is not None: + body["cost_usd"] = request.cost_usd await self._orchestrator_request( "POST", @@ -3966,6 +3975,64 @@ async def list_webhooks(self) -> ListWebhooksResponse: total=response.get("total", len(webhooks)), ) + # ========================================================================= + # MEDIA GOVERNANCE CONFIG + # ========================================================================= + + async def get_media_governance_config(self) -> MediaGovernanceConfig: + """Get the media governance configuration for the current tenant. + + Returns: + MediaGovernanceConfig with current tenant media governance settings + + Example: + >>> config = await client.get_media_governance_config() + >>> print(f"Enabled: {config.enabled}, Analyzers: {config.allowed_analyzers}") + """ + response = await self._request("GET", "/api/v1/media-governance/config") + return MediaGovernanceConfig.model_validate(response) + + async def update_media_governance_config( + self, + request: UpdateMediaGovernanceConfigRequest, + ) -> MediaGovernanceConfig: + """Update the media governance configuration for the current tenant. + + Args: + request: Update request with fields to change + + Returns: + Updated MediaGovernanceConfig + + Example: + >>> from axonflow import UpdateMediaGovernanceConfigRequest + >>> config = await client.update_media_governance_config( + ... UpdateMediaGovernanceConfigRequest( + ... enabled=True, allowed_analyzers=["nsfw", "pii"] + ... ) + ... ) + >>> print(f"Enabled: {config.enabled}") + """ + response = await self._request( + "PUT", + "/api/v1/media-governance/config", + json_data=request.model_dump(exclude_none=True), + ) + return MediaGovernanceConfig.model_validate(response) + + async def get_media_governance_status(self) -> MediaGovernanceStatus: + """Get the platform-level media governance status. + + Returns: + MediaGovernanceStatus with availability and default configuration + + Example: + >>> status = await client.get_media_governance_status() + >>> print(f"Available: {status.available}, Tier: {status.tier}") + """ + response = await self._request("GET", "/api/v1/media-governance/status") + return MediaGovernanceStatus.model_validate(response) + # ========================================================================= # MAS FEAT COMPLIANCE (Enterprise) # ========================================================================= @@ -6391,6 +6458,23 @@ def get_pricing( """Get pricing information for models.""" return self._run_sync(self._async_client.get_pricing(provider, model)) + # Media Governance Config sync wrappers + + def get_media_governance_config(self) -> MediaGovernanceConfig: + """Get the media governance configuration for the current tenant.""" + return self._run_sync(self._async_client.get_media_governance_config()) + + def update_media_governance_config( + self, + request: UpdateMediaGovernanceConfigRequest, + ) -> MediaGovernanceConfig: + """Update the media governance configuration for the current tenant.""" + return self._run_sync(self._async_client.update_media_governance_config(request)) + + def get_media_governance_status(self) -> MediaGovernanceStatus: + """Get the platform-level media governance status.""" + return self._run_sync(self._async_client.get_media_governance_status()) + # ========================================================================= # MAS FEAT Compliance sync wrappers (Enterprise) # ========================================================================= diff --git a/axonflow/policies.py b/axonflow/policies.py index b10d029..0d3334e 100644 --- a/axonflow/policies.py +++ b/axonflow/policies.py @@ -45,6 +45,12 @@ class PolicyCategory(str, Enum): DYNAMIC_COST = "dynamic-cost" DYNAMIC_ACCESS = "dynamic-access" + # Media governance categories + MEDIA_SAFETY = "media-safety" + MEDIA_BIOMETRIC = "media-biometric" + MEDIA_DOCUMENT = "media-document" + MEDIA_PII = "media-pii" + class PolicyTier(str, Enum): """Policy tiers determine where policies apply.""" diff --git a/axonflow/types.py b/axonflow/types.py index 5a8717e..ca85495 100644 --- a/axonflow/types.py +++ b/axonflow/types.py @@ -1028,3 +1028,59 @@ class ListWebhooksResponse(BaseModel): default_factory=list, description="List of webhook subscriptions" ) total: int = Field(default=0, ge=0, description="Total count of webhooks") + + +# ========================================================================= +# Media Governance Config Types +# ========================================================================= + + +class MediaGovernanceConfig(BaseModel): + """Per-tenant media governance configuration. + + Controls whether media analysis is enabled and which analyzers + are allowed for a given tenant. + """ + + tenant_id: str = Field(default="", description="Tenant ID") + enabled: bool = Field(default=False, description="Whether media analysis is enabled") + allowed_analyzers: list[str] = Field( + default_factory=list, description="List of allowed analyzer IDs" + ) + updated_at: str = Field(default="", description="Last updated timestamp") + updated_by: str = Field(default="", description="User who last updated the config") + + +class MediaGovernanceStatus(BaseModel): + """Platform-level media governance status. + + Reports availability and default configuration for media governance. + """ + + available: bool = Field(default=False, description="Whether media governance is available") + enabled_by_default: bool = Field( + default=False, description="Whether media governance is enabled by default for new tenants" + ) + per_tenant_control: bool = Field( + default=False, description="Whether per-tenant media governance control is supported" + ) + tier: str = Field(default="", description="License tier (community, enterprise, etc.)") + + +class UpdateMediaGovernanceConfigRequest(BaseModel): + """Request to update per-tenant media governance configuration.""" + + enabled: bool | None = Field(default=None, description="Enable or disable media analysis") + allowed_analyzers: list[str] | None = Field( + default=None, description="List of allowed analyzer IDs" + ) + + +# ========================================================================= +# Media Governance Category Constants +# ========================================================================= + +CATEGORY_MEDIA_SAFETY: str = "media-safety" +CATEGORY_MEDIA_BIOMETRIC: str = "media-biometric" +CATEGORY_MEDIA_DOCUMENT: str = "media-document" +CATEGORY_MEDIA_PII: str = "media-pii" diff --git a/axonflow/workflow.py b/axonflow/workflow.py index 94598c0..70ff19a 100644 --- a/axonflow/workflow.py +++ b/axonflow/workflow.py @@ -211,6 +211,9 @@ class MarkStepCompletedRequest(BaseModel): output: dict[str, Any] = Field(default_factory=dict, description="Output data from the step") metadata: dict[str, Any] = Field(default_factory=dict, description="Additional metadata") + tokens_in: int | None = Field(default=None, ge=0, description="Input tokens consumed") + tokens_out: int | None = Field(default=None, ge=0, description="Output tokens produced") + cost_usd: float | None = Field(default=None, ge=0, description="Cost in USD") class AbortWorkflowRequest(BaseModel): diff --git a/pyproject.toml b/pyproject.toml index 7728e11..85177cd 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "axonflow" -version = "3.5.0" +version = "3.6.0" description = "AxonFlow Python SDK - Enterprise AI Governance in 3 Lines of Code" readme = "README.md" license = {text = "MIT"}