From 4894c7909d507c1c13edea10f6c06b70ebeb902b Mon Sep 17 00:00:00 2001 From: Saurabh Jain Date: Thu, 19 Feb 2026 02:53:29 +0300 Subject: [PATCH 1/9] feat: add media governance config methods and category constants - get_media_governance_config() / get_media_governance_config_sync() - update_media_governance_config() / update_media_governance_config_sync() - get_media_governance_status() / get_media_governance_status_sync() - Category constants: CATEGORY_MEDIA_SAFETY, CATEGORY_MEDIA_BIOMETRIC, CATEGORY_MEDIA_DOCUMENT, CATEGORY_MEDIA_PII --- axonflow/__init__.py | 16 ++++++++++ axonflow/client.py | 76 ++++++++++++++++++++++++++++++++++++++++++++ axonflow/types.py | 56 ++++++++++++++++++++++++++++++++ 3 files changed, 148 insertions(+) diff --git a/axonflow/__init__.py b/axonflow/__init__.py index 1087d88..7cd06ad 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, @@ -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..e1c3c45 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, @@ -3966,6 +3969,62 @@ 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 +6450,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/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" From 3960dd2788181a34c2e4751887604625d97a45c7 Mon Sep 17 00:00:00 2001 From: Saurabh Jain Date: Thu, 19 Feb 2026 10:15:38 +0300 Subject: [PATCH 2/9] fix: update __version__ to 3.6.0 and add CHANGELOG entry Version string was still at 3.4.0 after media governance methods were added. Bump to 3.6.0 and add changelog documenting new media governance configuration methods and category constants. --- CHANGELOG.md | 10 ++++++++++ axonflow/__init__.py | 2 +- 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9bcf389..099c632 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,16 @@ 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` + +--- + ## [3.5.0] - 2026-02-19 ### Added diff --git a/axonflow/__init__.py b/axonflow/__init__.py index 7cd06ad..01e6716 100644 --- a/axonflow/__init__.py +++ b/axonflow/__init__.py @@ -209,7 +209,7 @@ WorkflowStepInfo, ) -__version__ = "3.5.0" +__version__ = "3.6.0" __all__ = [ # Main client "AxonFlow", From 79ab74e6b09518ae2f014301fe88eb538016c818 Mon Sep 17 00:00:00 2001 From: Saurabh Jain Date: Thu, 19 Feb 2026 18:55:47 +0300 Subject: [PATCH 3/9] fix: wrap long line in update_media_governance_config docstring --- axonflow/client.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/axonflow/client.py b/axonflow/client.py index e1c3c45..b5e8c15 100644 --- a/axonflow/client.py +++ b/axonflow/client.py @@ -4001,7 +4001,9 @@ async def update_media_governance_config( Example: >>> from axonflow import UpdateMediaGovernanceConfigRequest >>> config = await client.update_media_governance_config( - ... UpdateMediaGovernanceConfigRequest(enabled=True, allowed_analyzers=["nsfw", "pii"]) + ... UpdateMediaGovernanceConfigRequest( + ... enabled=True, allowed_analyzers=["nsfw", "pii"] + ... ) ... ) >>> print(f"Enabled: {config.enabled}") """ From b9b7035da5e4059502aa6700a00adb262f392651 Mon Sep 17 00:00:00 2001 From: Saurabh Jain Date: Sun, 22 Feb 2026 01:31:41 +0300 Subject: [PATCH 4/9] fix: bump pyproject.toml version to 3.6.0 --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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"} From 850f52f0e5af210180691a57c61fb8a8980df399 Mon Sep 17 00:00:00 2001 From: Saurabh Jain Date: Sun, 22 Feb 2026 11:55:42 +0300 Subject: [PATCH 5/9] feat: add post-execution metrics to MarkStepCompletedRequest tokens_in, tokens_out, cost_usd fields on MarkStepCompletedRequest and mark_step_completed() body construction allow reporting actual LLM usage at step completion time. --- CHANGELOG.md | 1 + axonflow/client.py | 6 ++++++ axonflow/workflow.py | 3 +++ 3 files changed, 10 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 099c632..97c7576 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - 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` +- StepComplete now accepts post-execution metrics (`tokens_in`, `tokens_out`, `cost_usd`) --- diff --git a/axonflow/client.py b/axonflow/client.py index b5e8c15..8f86e1b 100644 --- a/axonflow/client.py +++ b/axonflow/client.py @@ -3369,6 +3369,12 @@ async def mark_step_completed( body = {} 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", diff --git a/axonflow/workflow.py b/axonflow/workflow.py index 94598c0..d11f3e8 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, description="Input tokens consumed by this step") + tokens_out: int | None = Field(default=None, description="Output tokens produced by this step") + cost_usd: float | None = Field(default=None, description="Cost in USD for this step execution") class AbortWorkflowRequest(BaseModel): From decee9262b25a3bae3aaaab81480b693e9511f5f Mon Sep 17 00:00:00 2001 From: Saurabh Jain Date: Sun, 22 Feb 2026 12:17:12 +0300 Subject: [PATCH 6/9] fix: add ge=0 validation to StepComplete metrics fields Ensures tokens_in, tokens_out, cost_usd reject negative values at the Pydantic model layer, consistent with all other metric fields in the SDK. --- CHANGELOG.md | 2 +- axonflow/workflow.py | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 97c7576..3567bf9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,7 +12,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - 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` -- StepComplete now accepts post-execution metrics (`tokens_in`, `tokens_out`, `cost_usd`) +- `mark_step_completed()` now accepts post-execution metrics (`tokens_in`, `tokens_out`, `cost_usd`) via `MarkStepCompletedRequest` --- diff --git a/axonflow/workflow.py b/axonflow/workflow.py index d11f3e8..d5c0222 100644 --- a/axonflow/workflow.py +++ b/axonflow/workflow.py @@ -211,9 +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, description="Input tokens consumed by this step") - tokens_out: int | None = Field(default=None, description="Output tokens produced by this step") - cost_usd: float | None = Field(default=None, description="Cost in USD for this step execution") + tokens_in: int | None = Field(default=None, ge=0, description="Input tokens consumed by this step") + tokens_out: int | None = Field(default=None, ge=0, description="Output tokens produced by this step") + cost_usd: float | None = Field(default=None, ge=0, description="Cost in USD for this step execution") class AbortWorkflowRequest(BaseModel): From 7e74f96260b190fd9a407e9eef0b1b9a04863964 Mon Sep 17 00:00:00 2001 From: Saurabh Jain Date: Sun, 22 Feb 2026 12:18:27 +0300 Subject: [PATCH 7/9] fix: shorten field descriptions to satisfy E501 line length --- axonflow/workflow.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/axonflow/workflow.py b/axonflow/workflow.py index d5c0222..70ff19a 100644 --- a/axonflow/workflow.py +++ b/axonflow/workflow.py @@ -211,9 +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 by this step") - tokens_out: int | None = Field(default=None, ge=0, description="Output tokens produced by this step") - cost_usd: float | None = Field(default=None, ge=0, description="Cost in USD for this step execution") + 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): From 1024cfed69768b5867484e481c31fb755db694d5 Mon Sep 17 00:00:00 2001 From: Saurabh Jain Date: Sun, 22 Feb 2026 12:21:02 +0300 Subject: [PATCH 8/9] fix: type annotation for MyPy compatibility in mark_step_completed --- axonflow/client.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/axonflow/client.py b/axonflow/client.py index 8f86e1b..791cde9 100644 --- a/axonflow/client.py +++ b/axonflow/client.py @@ -3366,7 +3366,7 @@ 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: From da836cad92725327ab09c3e910c76a00a2a94b1b Mon Sep 17 00:00:00 2001 From: Saurabh Jain Date: Sun, 22 Feb 2026 12:29:37 +0300 Subject: [PATCH 9/9] fix: add media governance categories to PolicyCategory enum --- axonflow/policies.py | 6 ++++++ 1 file changed, 6 insertions(+) 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."""