mainsequence 8.1.0__tar.gz → 8.1.2__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (138) hide show
  1. {mainsequence-8.1.0/mainsequence.egg-info → mainsequence-8.1.2}/PKG-INFO +1 -1
  2. {mainsequence-8.1.0 → mainsequence-8.1.2}/mainsequence/client/agent_runtime_models.py +27 -0
  3. {mainsequence-8.1.0 → mainsequence-8.1.2}/mainsequence/client/models_helpers.py +24 -0
  4. {mainsequence-8.1.0 → mainsequence-8.1.2/mainsequence.egg-info}/PKG-INFO +1 -1
  5. {mainsequence-8.1.0 → mainsequence-8.1.2}/pyproject.toml +1 -1
  6. {mainsequence-8.1.0 → mainsequence-8.1.2}/tests/test_environment_response_contracts.py +3 -2
  7. {mainsequence-8.1.0 → mainsequence-8.1.2}/tests/test_filter_normalization.py +223 -0
  8. {mainsequence-8.1.0 → mainsequence-8.1.2}/tests/test_observability_client.py +5 -0
  9. {mainsequence-8.1.0 → mainsequence-8.1.2}/LICENSE +0 -0
  10. {mainsequence-8.1.0 → mainsequence-8.1.2}/MANIFEST.in +0 -0
  11. {mainsequence-8.1.0 → mainsequence-8.1.2}/README.md +0 -0
  12. {mainsequence-8.1.0 → mainsequence-8.1.2}/agent_scaffold/AGENTS.md +0 -0
  13. {mainsequence-8.1.0 → mainsequence-8.1.2}/agent_scaffold/skills/a2a_sdk_execution/SKILL.md +0 -0
  14. {mainsequence-8.1.0 → mainsequence-8.1.2}/agent_scaffold/skills/application_surfaces/api_surfaces/SKILL.md +0 -0
  15. {mainsequence-8.1.0 → mainsequence-8.1.2}/agent_scaffold/skills/data_access/exploration/SKILL.md +0 -0
  16. {mainsequence-8.1.0 → mainsequence-8.1.2}/agent_scaffold/skills/data_publishing/meta_table_migrations/SKILL.md +0 -0
  17. {mainsequence-8.1.0 → mainsequence-8.1.2}/agent_scaffold/skills/data_publishing/meta_tables/SKILL.md +0 -0
  18. {mainsequence-8.1.0 → mainsequence-8.1.2}/agent_scaffold/skills/data_publishing/time_index_table_updates/SKILL.md +0 -0
  19. {mainsequence-8.1.0 → mainsequence-8.1.2}/agent_scaffold/skills/maintenance/bug_auditor/SKILL.md +0 -0
  20. {mainsequence-8.1.0 → mainsequence-8.1.2}/agent_scaffold/skills/maintenance/code_repository_maintenance/SKILL.md +0 -0
  21. {mainsequence-8.1.0 → mainsequence-8.1.2}/agent_scaffold/skills/platform_operations/access_control_and_sharing/SKILL.md +0 -0
  22. {mainsequence-8.1.0 → mainsequence-8.1.2}/agent_scaffold/skills/platform_operations/orchestration_and_releases/SKILL.md +0 -0
  23. {mainsequence-8.1.0 → mainsequence-8.1.2}/agent_scaffold/skills/sdk_code_repository_execution/SKILL.md +0 -0
  24. {mainsequence-8.1.0 → mainsequence-8.1.2}/mainsequence/__init__.py +0 -0
  25. {mainsequence-8.1.0 → mainsequence-8.1.2}/mainsequence/__main__.py +0 -0
  26. {mainsequence-8.1.0 → mainsequence-8.1.2}/mainsequence/bootstrap.py +0 -0
  27. {mainsequence-8.1.0 → mainsequence-8.1.2}/mainsequence/cli/__init__.py +0 -0
  28. {mainsequence-8.1.0 → mainsequence-8.1.2}/mainsequence/cli/api.py +0 -0
  29. {mainsequence-8.1.0 → mainsequence-8.1.2}/mainsequence/cli/browser_auth.py +0 -0
  30. {mainsequence-8.1.0 → mainsequence-8.1.2}/mainsequence/cli/cli.py +0 -0
  31. {mainsequence-8.1.0 → mainsequence-8.1.2}/mainsequence/cli/code_repository_status.py +0 -0
  32. {mainsequence-8.1.0 → mainsequence-8.1.2}/mainsequence/cli/config.py +0 -0
  33. {mainsequence-8.1.0 → mainsequence-8.1.2}/mainsequence/cli/docker_utils.py +0 -0
  34. {mainsequence-8.1.0 → mainsequence-8.1.2}/mainsequence/cli/doctor.py +0 -0
  35. {mainsequence-8.1.0 → mainsequence-8.1.2}/mainsequence/cli/local_ops.py +0 -0
  36. {mainsequence-8.1.0 → mainsequence-8.1.2}/mainsequence/cli/migrations.py +0 -0
  37. {mainsequence-8.1.0 → mainsequence-8.1.2}/mainsequence/cli/model_filters.py +0 -0
  38. {mainsequence-8.1.0 → mainsequence-8.1.2}/mainsequence/cli/pydantic_cli.py +0 -0
  39. {mainsequence-8.1.0 → mainsequence-8.1.2}/mainsequence/cli/sdk_utils.py +0 -0
  40. {mainsequence-8.1.0 → mainsequence-8.1.2}/mainsequence/cli/ssh_utils.py +0 -0
  41. {mainsequence-8.1.0 → mainsequence-8.1.2}/mainsequence/cli/ui.py +0 -0
  42. {mainsequence-8.1.0 → mainsequence-8.1.2}/mainsequence/client/__init__.py +0 -0
  43. {mainsequence-8.1.0 → mainsequence-8.1.2}/mainsequence/client/base.py +0 -0
  44. {mainsequence-8.1.0 → mainsequence-8.1.2}/mainsequence/client/client.py +0 -0
  45. {mainsequence-8.1.0 → mainsequence-8.1.2}/mainsequence/client/compute_validation.py +0 -0
  46. {mainsequence-8.1.0 → mainsequence-8.1.2}/mainsequence/client/data_sources_interfaces/__init__.py +0 -0
  47. {mainsequence-8.1.0 → mainsequence-8.1.2}/mainsequence/client/data_sources_interfaces/duckdb.py +0 -0
  48. {mainsequence-8.1.0 → mainsequence-8.1.2}/mainsequence/client/data_sources_interfaces/local_paths.py +0 -0
  49. {mainsequence-8.1.0 → mainsequence-8.1.2}/mainsequence/client/data_sources_interfaces/sqlite.py +0 -0
  50. {mainsequence-8.1.0 → mainsequence-8.1.2}/mainsequence/client/dtype_codec.py +0 -0
  51. {mainsequence-8.1.0 → mainsequence-8.1.2}/mainsequence/client/exceptions.py +0 -0
  52. {mainsequence-8.1.0 → mainsequence-8.1.2}/mainsequence/client/metatables/__init__.py +0 -0
  53. {mainsequence-8.1.0 → mainsequence-8.1.2}/mainsequence/client/metatables/core.py +0 -0
  54. {mainsequence-8.1.0 → mainsequence-8.1.2}/mainsequence/client/models_foundry.py +0 -0
  55. {mainsequence-8.1.0 → mainsequence-8.1.2}/mainsequence/client/models_user.py +0 -0
  56. {mainsequence-8.1.0 → mainsequence-8.1.2}/mainsequence/client/observability.py +0 -0
  57. {mainsequence-8.1.0 → mainsequence-8.1.2}/mainsequence/client/utils.py +0 -0
  58. {mainsequence-8.1.0 → mainsequence-8.1.2}/mainsequence/code_repository_context.py +0 -0
  59. {mainsequence-8.1.0 → mainsequence-8.1.2}/mainsequence/code_repository_skills.py +0 -0
  60. {mainsequence-8.1.0 → mainsequence-8.1.2}/mainsequence/defaults.py +0 -0
  61. {mainsequence-8.1.0 → mainsequence-8.1.2}/mainsequence/instrumentation/__init__.py +0 -0
  62. {mainsequence-8.1.0 → mainsequence-8.1.2}/mainsequence/instrumentation/utils.py +0 -0
  63. {mainsequence-8.1.0 → mainsequence-8.1.2}/mainsequence/logconf.py +0 -0
  64. {mainsequence-8.1.0 → mainsequence-8.1.2}/mainsequence/meta_tables/__init__.py +0 -0
  65. {mainsequence-8.1.0 → mainsequence-8.1.2}/mainsequence/meta_tables/__main__.py +0 -0
  66. {mainsequence-8.1.0 → mainsequence-8.1.2}/mainsequence/meta_tables/compiled_sql/__init__.py +0 -0
  67. {mainsequence-8.1.0 → mainsequence-8.1.2}/mainsequence/meta_tables/compiled_sql/v1.py +0 -0
  68. {mainsequence-8.1.0 → mainsequence-8.1.2}/mainsequence/meta_tables/future_registry.py +0 -0
  69. {mainsequence-8.1.0 → mainsequence-8.1.2}/mainsequence/meta_tables/hashing.py +0 -0
  70. {mainsequence-8.1.0 → mainsequence-8.1.2}/mainsequence/meta_tables/migrations/__init__.py +0 -0
  71. {mainsequence-8.1.0 → mainsequence-8.1.2}/mainsequence/meta_tables/migrations/alembic.py +0 -0
  72. {mainsequence-8.1.0 → mainsequence-8.1.2}/mainsequence/meta_tables/migrations/env.py +0 -0
  73. {mainsequence-8.1.0 → mainsequence-8.1.2}/mainsequence/meta_tables/migrations/provider.py +0 -0
  74. {mainsequence-8.1.0 → mainsequence-8.1.2}/mainsequence/meta_tables/migrations/registry.py +0 -0
  75. {mainsequence-8.1.0 → mainsequence-8.1.2}/mainsequence/meta_tables/migrations/scaffold.py +0 -0
  76. {mainsequence-8.1.0 → mainsequence-8.1.2}/mainsequence/meta_tables/migrations/templates/__init__.py +0 -0
  77. {mainsequence-8.1.0 → mainsequence-8.1.2}/mainsequence/meta_tables/migrations/templates/env.py.mako +0 -0
  78. {mainsequence-8.1.0 → mainsequence-8.1.2}/mainsequence/meta_tables/migrations/templates/script.py.mako +0 -0
  79. {mainsequence-8.1.0 → mainsequence-8.1.2}/mainsequence/meta_tables/pydantic_metadata.py +0 -0
  80. {mainsequence-8.1.0 → mainsequence-8.1.2}/mainsequence/meta_tables/schema_names.py +0 -0
  81. {mainsequence-8.1.0 → mainsequence-8.1.2}/mainsequence/meta_tables/sqlalchemy_contracts.py +0 -0
  82. {mainsequence-8.1.0 → mainsequence-8.1.2}/mainsequence/meta_tables/time_index_table_refs.py +0 -0
  83. {mainsequence-8.1.0 → mainsequence-8.1.2}/mainsequence/meta_tables/time_index_table_updates/__init__.py +0 -0
  84. {mainsequence-8.1.0 → mainsequence-8.1.2}/mainsequence/meta_tables/time_index_table_updates/configuration.py +0 -0
  85. {mainsequence-8.1.0 → mainsequence-8.1.2}/mainsequence/meta_tables/time_index_table_updates/dependencies.py +0 -0
  86. {mainsequence-8.1.0 → mainsequence-8.1.2}/mainsequence/meta_tables/time_index_table_updates/managers.py +0 -0
  87. {mainsequence-8.1.0 → mainsequence-8.1.2}/mainsequence/meta_tables/time_index_table_updates/namespacing.py +0 -0
  88. {mainsequence-8.1.0 → mainsequence-8.1.2}/mainsequence/meta_tables/time_index_table_updates/runner.py +0 -0
  89. {mainsequence-8.1.0 → mainsequence-8.1.2}/mainsequence/meta_tables/time_index_table_updates/updaters.py +0 -0
  90. {mainsequence-8.1.0 → mainsequence-8.1.2}/mainsequence/meta_tables/time_index_table_updates/utils.py +0 -0
  91. {mainsequence-8.1.0 → mainsequence-8.1.2}/mainsequence/repository_identity_security.py +0 -0
  92. {mainsequence-8.1.0 → mainsequence-8.1.2}/mainsequence/runtime_flags.py +0 -0
  93. {mainsequence-8.1.0 → mainsequence-8.1.2}/mainsequence/scaffold_skills.py +0 -0
  94. {mainsequence-8.1.0 → mainsequence-8.1.2}/mainsequence.egg-info/SOURCES.txt +0 -0
  95. {mainsequence-8.1.0 → mainsequence-8.1.2}/mainsequence.egg-info/dependency_links.txt +0 -0
  96. {mainsequence-8.1.0 → mainsequence-8.1.2}/mainsequence.egg-info/entry_points.txt +0 -0
  97. {mainsequence-8.1.0 → mainsequence-8.1.2}/mainsequence.egg-info/requires.txt +0 -0
  98. {mainsequence-8.1.0 → mainsequence-8.1.2}/mainsequence.egg-info/top_level.txt +0 -0
  99. {mainsequence-8.1.0 → mainsequence-8.1.2}/setup.cfg +0 -0
  100. {mainsequence-8.1.0 → mainsequence-8.1.2}/tests/fixtures/time_index_table_update_hash_v2_golden.json +0 -0
  101. {mainsequence-8.1.0 → mainsequence-8.1.2}/tests/test_auth_precedence.py +0 -0
  102. {mainsequence-8.1.0 → mainsequence-8.1.2}/tests/test_canonical_api_route_contract.py +0 -0
  103. {mainsequence-8.1.0 → mainsequence-8.1.2}/tests/test_cli.py +0 -0
  104. {mainsequence-8.1.0 → mainsequence-8.1.2}/tests/test_cli_browser_auth.py +0 -0
  105. {mainsequence-8.1.0 → mainsequence-8.1.2}/tests/test_cli_migrations.py +0 -0
  106. {mainsequence-8.1.0 → mainsequence-8.1.2}/tests/test_client.py +0 -0
  107. {mainsequence-8.1.0 → mainsequence-8.1.2}/tests/test_code_repository_batch_jobs_from_file.py +0 -0
  108. {mainsequence-8.1.0 → mainsequence-8.1.2}/tests/test_code_repository_branch_contract.py +0 -0
  109. {mainsequence-8.1.0 → mainsequence-8.1.2}/tests/test_code_repository_documentation_contract.py +0 -0
  110. {mainsequence-8.1.0 → mainsequence-8.1.2}/tests/test_code_repository_runtime_context.py +0 -0
  111. {mainsequence-8.1.0 → mainsequence-8.1.2}/tests/test_code_repository_skills.py +0 -0
  112. {mainsequence-8.1.0 → mainsequence-8.1.2}/tests/test_dependency_extras.py +0 -0
  113. {mainsequence-8.1.0 → mainsequence-8.1.2}/tests/test_duckdb_interface_dimensions.py +0 -0
  114. {mainsequence-8.1.0 → mainsequence-8.1.2}/tests/test_environment_resource_context.py +0 -0
  115. {mainsequence-8.1.0 → mainsequence-8.1.2}/tests/test_instrumentation.py +0 -0
  116. {mainsequence-8.1.0 → mainsequence-8.1.2}/tests/test_local_ops.py +0 -0
  117. {mainsequence-8.1.0 → mainsequence-8.1.2}/tests/test_logconf.py +0 -0
  118. {mainsequence-8.1.0 → mainsequence-8.1.2}/tests/test_meta_table_migrations.py +0 -0
  119. {mainsequence-8.1.0 → mainsequence-8.1.2}/tests/test_meta_tables_client_models.py +0 -0
  120. {mainsequence-8.1.0 → mainsequence-8.1.2}/tests/test_meta_tables_sqlalchemy_contracts.py +0 -0
  121. {mainsequence-8.1.0 → mainsequence-8.1.2}/tests/test_models_user_request_bound_auth.py +0 -0
  122. {mainsequence-8.1.0 → mainsequence-8.1.2}/tests/test_notification_uid_contract.py +0 -0
  123. {mainsequence-8.1.0 → mainsequence-8.1.2}/tests/test_pod_project_resolution.py +0 -0
  124. {mainsequence-8.1.0 → mainsequence-8.1.2}/tests/test_scaffold_skills.py +0 -0
  125. {mainsequence-8.1.0 → mainsequence-8.1.2}/tests/test_schema_names.py +0 -0
  126. {mainsequence-8.1.0 → mainsequence-8.1.2}/tests/test_secret_client_model.py +0 -0
  127. {mainsequence-8.1.0 → mainsequence-8.1.2}/tests/test_source_table_configuration.py +0 -0
  128. {mainsequence-8.1.0 → mainsequence-8.1.2}/tests/test_sqlite_interface_dimensions.py +0 -0
  129. {mainsequence-8.1.0 → mainsequence-8.1.2}/tests/test_ssh_utils.py +0 -0
  130. {mainsequence-8.1.0 → mainsequence-8.1.2}/tests/test_time_index_table_access_dimension_audit.py +0 -0
  131. {mainsequence-8.1.0 → mainsequence-8.1.2}/tests/test_time_index_table_dimension_queries.py +0 -0
  132. {mainsequence-8.1.0 → mainsequence-8.1.2}/tests/test_time_index_table_update_configuration.py +0 -0
  133. {mainsequence-8.1.0 → mainsequence-8.1.2}/tests/test_time_index_table_update_configuration_client.py +0 -0
  134. {mainsequence-8.1.0 → mainsequence-8.1.2}/tests/test_time_index_table_update_flow.py +0 -0
  135. {mainsequence-8.1.0 → mainsequence-8.1.2}/tests/test_time_index_table_update_hard_cut.py +0 -0
  136. {mainsequence-8.1.0 → mainsequence-8.1.2}/tests/test_update_runner_uid_runtime.py +0 -0
  137. {mainsequence-8.1.0 → mainsequence-8.1.2}/tests/test_update_statistics.py +0 -0
  138. {mainsequence-8.1.0 → mainsequence-8.1.2}/tests/test_update_uid_guards.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: mainsequence
3
- Version: 8.1.0
3
+ Version: 8.1.2
4
4
  Summary: Main Sequence SDK
5
5
  Author-email: Main Sequence GmbH <dev@main-sequence.io>
6
6
  License-Expression: LicenseRef-MainSequence-Proprietary
@@ -122,6 +122,23 @@ class AgentHarnessProtocol(str, Enum):
122
122
  TAU_SESSION_V1 = "tau-session-v1"
123
123
 
124
124
 
125
+ class AgentRuntimeUpdateRemediation(BasePydanticModel):
126
+ tool: Literal["agent.update_runtime"]
127
+
128
+
129
+ class AgentRuntimeUpdate(BasePydanticModel):
130
+ state: Literal[
131
+ "current",
132
+ "update_required",
133
+ "updating",
134
+ "update_failed",
135
+ "not_deployed",
136
+ "unknown",
137
+ ]
138
+ needs_redeploy: bool | None
139
+ remediation: AgentRuntimeUpdateRemediation | None
140
+
141
+
125
142
  class AgentSemanticSearchResult(BasePydanticModel):
126
143
  uid: str = Field(..., description="Public UID of the matched agent.")
127
144
  name: str = Field(..., description="Human-readable display name of the matched agent.")
@@ -150,6 +167,10 @@ class AgentSemanticSearchResult(BasePydanticModel):
150
167
  ...,
151
168
  description="Name of the Organization Environment that scoped the search result.",
152
169
  )
170
+ runtime_update: AgentRuntimeUpdate = Field(
171
+ ...,
172
+ description="Backend-owned runtime-currency state for the matched Agent.",
173
+ )
153
174
  semantic_score: float = Field(
154
175
  ...,
155
176
  description="Vector similarity component of the semantic-search ranking.",
@@ -550,6 +571,10 @@ class Agent(
550
571
  "CodeRepositoryBranch, or null when the Agent is not environment-scoped."
551
572
  ),
552
573
  )
574
+ runtime_update: AgentRuntimeUpdate = Field(
575
+ ...,
576
+ description="Read-only backend-owned runtime-currency state for this Agent.",
577
+ )
553
578
  observability: ObservabilityLinks | None = Field(
554
579
  default=None,
555
580
  description="Backend-owned runtime observability and related-resource capabilities.",
@@ -2126,6 +2151,8 @@ __all__ = [
2126
2151
  "AgentA2AProfile",
2127
2152
  "AgentRuntimeImageDrift",
2128
2153
  "AgentRuntimeImageDriftCheck",
2154
+ "AgentRuntimeUpdate",
2155
+ "AgentRuntimeUpdateRemediation",
2129
2156
  "AgentHarnessKind",
2130
2157
  "AgentHarnessProtocol",
2131
2158
  "AgentSessionInsights",
@@ -1397,6 +1397,29 @@ class DeploymentRunPipeline(BaseModel):
1397
1397
  steps: list[DeploymentRunStep] = Field(default_factory=list)
1398
1398
 
1399
1399
 
1400
+ class DeploymentRunBillingComponents(BasePydanticModel):
1401
+ image_build: Decimal | None = Field(max_digits=18, decimal_places=6)
1402
+ image_registry_storage: Decimal | None = Field(max_digits=18, decimal_places=6)
1403
+ image_registry_service: Decimal | None = Field(max_digits=18, decimal_places=6)
1404
+
1405
+
1406
+ class DeploymentRunBilling(BasePydanticModel):
1407
+ scope: Literal["image_lifecycle"]
1408
+ total_cost: Decimal | None = Field(max_digits=18, decimal_places=6)
1409
+ currency: str = Field(min_length=1)
1410
+ pricing_state: Literal[
1411
+ "priced",
1412
+ "partial",
1413
+ "pending",
1414
+ "unavailable",
1415
+ "failed",
1416
+ ]
1417
+ components: DeploymentRunBillingComponents
1418
+ priced_rows: int = Field(ge=0)
1419
+ unpriced_rows: int = Field(ge=0)
1420
+ reused_image_count: int = Field(ge=0)
1421
+
1422
+
1400
1423
  class DeploymentRunLogEntry(BaseModel):
1401
1424
  sequence: int
1402
1425
  timestamp: datetime.datetime | None = None
@@ -1473,6 +1496,7 @@ class DeploymentRun(CurrentCodeRepositoryBranchCollectionMixin, BaseObjectOrm, B
1473
1496
  builder_runtime: str = ""
1474
1497
  logs: DeploymentRunLogReference
1475
1498
  error: DeploymentRunError | None = None
1499
+ billing: DeploymentRunBilling
1476
1500
 
1477
1501
  def get_logs(
1478
1502
  self,
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: mainsequence
3
- Version: 8.1.0
3
+ Version: 8.1.2
4
4
  Summary: Main Sequence SDK
5
5
  Author-email: Main Sequence GmbH <dev@main-sequence.io>
6
6
  License-Expression: LicenseRef-MainSequence-Proprietary
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "mainsequence"
7
- version = "8.1.0"
7
+ version = "8.1.2"
8
8
  description = "Main Sequence SDK "
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.13"
@@ -7,6 +7,7 @@ import mainsequence.client.models_helpers as helper_models
7
7
 
8
8
  ENVIRONMENT_UID_FIELD = "organization_environment_uid"
9
9
  ENVIRONMENT_NAME_FIELD = "organization_environment_name"
10
+ RUNTIME_UPDATE_FIELD = "runtime_update"
10
11
 
11
12
 
12
13
  @pytest.mark.parametrize(
@@ -15,13 +16,13 @@ ENVIRONMENT_NAME_FIELD = "organization_environment_name"
15
16
  pytest.param(
16
17
  ("AgentSerializer",),
17
18
  agent_models.Agent,
18
- {ENVIRONMENT_UID_FIELD, ENVIRONMENT_NAME_FIELD},
19
+ {ENVIRONMENT_UID_FIELD, ENVIRONMENT_NAME_FIELD, RUNTIME_UPDATE_FIELD},
19
20
  id="agent",
20
21
  ),
21
22
  pytest.param(
22
23
  ("AgentSemanticSearchResultSerializer",),
23
24
  agent_models.AgentSemanticSearchResult,
24
- {ENVIRONMENT_UID_FIELD, ENVIRONMENT_NAME_FIELD},
25
+ {ENVIRONMENT_UID_FIELD, ENVIRONMENT_NAME_FIELD, RUNTIME_UPDATE_FIELD},
25
26
  id="agent-semantic-search-result",
26
27
  ),
27
28
  pytest.param(
@@ -3,6 +3,7 @@ import datetime
3
3
  import json
4
4
  import pathlib
5
5
  import uuid
6
+ from decimal import Decimal
6
7
  from types import SimpleNamespace
7
8
 
8
9
  import pytest
@@ -22,6 +23,67 @@ CODE_REPOSITORY_BRANCH_UID = "5a28020a-0f1b-47ee-aab8-334286234bea"
22
23
  ENVIRONMENT_UID = "58218213-5e4e-43de-a5bd-6757f4e1c8f6"
23
24
 
24
25
 
26
+ def _agent_runtime_update_contract(state: str = "current") -> dict:
27
+ values = {
28
+ "current": (False, None),
29
+ "update_required": (True, {"tool": "agent.update_runtime"}),
30
+ "updating": (True, None),
31
+ "update_failed": (True, {"tool": "agent.update_runtime"}),
32
+ "not_deployed": (False, None),
33
+ "unknown": (None, None),
34
+ }
35
+ needs_redeploy, remediation = values[state]
36
+ return {
37
+ "state": state,
38
+ "needs_redeploy": needs_redeploy,
39
+ "remediation": remediation,
40
+ }
41
+
42
+
43
+ @pytest.mark.parametrize(
44
+ ("state", "needs_redeploy", "remediation_tool"),
45
+ [
46
+ ("current", False, None),
47
+ ("update_required", True, "agent.update_runtime"),
48
+ ("updating", True, None),
49
+ ("update_failed", True, "agent.update_runtime"),
50
+ ("not_deployed", False, None),
51
+ ("unknown", None, None),
52
+ ],
53
+ )
54
+ def test_agent_runtime_update_parses_every_backend_state(
55
+ state,
56
+ needs_redeploy,
57
+ remediation_tool,
58
+ ):
59
+ runtime_update = agent_models_mod.AgentRuntimeUpdate.model_validate(
60
+ _agent_runtime_update_contract(state)
61
+ )
62
+
63
+ assert runtime_update.state == state
64
+ assert runtime_update.needs_redeploy is needs_redeploy
65
+ assert (
66
+ runtime_update.remediation.tool if runtime_update.remediation is not None else None
67
+ ) == remediation_tool
68
+
69
+
70
+ def test_agent_runtime_update_contract_is_complete_and_closed():
71
+ payload = _agent_runtime_update_contract("unknown")
72
+ for field_name in ("state", "needs_redeploy", "remediation"):
73
+ incomplete = dict(payload)
74
+ incomplete.pop(field_name)
75
+ with pytest.raises(ValidationError, match=field_name):
76
+ agent_models_mod.AgentRuntimeUpdate.model_validate(incomplete)
77
+
78
+ with pytest.raises(ValidationError, match="extra_forbidden"):
79
+ agent_models_mod.AgentRuntimeUpdate.model_validate(payload | {"future_state": "value"})
80
+
81
+ invalid_remediation = _agent_runtime_update_contract("update_required")
82
+ invalid_remediation["remediation"] = {"tool": "agent.update_session"}
83
+ with pytest.raises(ValidationError, match="agent.update_runtime"):
84
+ agent_models_mod.AgentRuntimeUpdate.model_validate(invalid_remediation)
85
+
86
+
25
87
  @pytest.fixture(autouse=True)
26
88
  def _resolved_code_repository_context(monkeypatch):
27
89
  code_repository_context._reset_code_repository_context()
@@ -1760,6 +1822,7 @@ def test_agent_runtime_models_deserialize_backend_uid_payloads():
1760
1822
  "repository_branch": "main",
1761
1823
  "organization_environment_uid": environment_uid,
1762
1824
  "organization_environment_name": "production",
1825
+ "runtime_update": _agent_runtime_update_contract(),
1763
1826
  }
1764
1827
  )
1765
1828
  assert agent.uid == agent_uid
@@ -1770,6 +1833,8 @@ def test_agent_runtime_models_deserialize_backend_uid_payloads():
1770
1833
  assert agent.repository_branch == "main"
1771
1834
  assert agent.organization_environment_uid == environment_uid
1772
1835
  assert agent.organization_environment_name == "production"
1836
+ assert agent.runtime_update.state == "current"
1837
+ assert agent.runtime_update.needs_redeploy is False
1773
1838
  assert agent.a2a_profile.supported_response_kinds == [agent_models_mod.A2AResponseKind.MESSAGE]
1774
1839
 
1775
1840
  search_result = agent_models_mod.AgentSemanticSearchResult.model_validate(
@@ -1782,6 +1847,7 @@ def test_agent_runtime_models_deserialize_backend_uid_payloads():
1782
1847
  "repository_branch": "main",
1783
1848
  "organization_environment_uid": environment_uid,
1784
1849
  "organization_environment_name": "production",
1850
+ "runtime_update": _agent_runtime_update_contract("update_required"),
1785
1851
  "semantic_score": 0.91,
1786
1852
  "text_score": 0.74,
1787
1853
  "combined_score": 0.85,
@@ -1792,6 +1858,8 @@ def test_agent_runtime_models_deserialize_backend_uid_payloads():
1792
1858
  assert search_result.repository_branch == "main"
1793
1859
  assert search_result.organization_environment_uid == environment_uid
1794
1860
  assert search_result.organization_environment_name == "production"
1861
+ assert search_result.runtime_update.state == "update_required"
1862
+ assert search_result.runtime_update.remediation.tool == "agent.update_runtime"
1795
1863
  assert search_result.a2a_profile.default_response_kind == (
1796
1864
  agent_models_mod.A2AResponseKind.MESSAGE
1797
1865
  )
@@ -1913,6 +1981,7 @@ def test_agent_scope_code_repositoryion_is_required_but_nullable():
1913
1981
  "repository_branch": None,
1914
1982
  "organization_environment_uid": None,
1915
1983
  "organization_environment_name": None,
1984
+ "runtime_update": _agent_runtime_update_contract("not_deployed"),
1916
1985
  }
1917
1986
 
1918
1987
  agent = agent_models_mod.Agent.model_validate(payload)
@@ -1946,6 +2015,7 @@ def test_agent_filter_sends_environment_read_scope_and_parses_code_repositoryion
1946
2015
  "repository_branch": "main",
1947
2016
  "organization_environment_uid": str(environment_uid),
1948
2017
  "organization_environment_name": "production",
2018
+ "runtime_update": _agent_runtime_update_contract("current"),
1949
2019
  }
1950
2020
  ],
1951
2021
  "next": None,
@@ -1989,6 +2059,63 @@ def test_agent_filter_sends_environment_read_scope_and_parses_code_repositoryion
1989
2059
  assert len(agents) == 1
1990
2060
  assert agents[0].repository_branch == "main"
1991
2061
  assert agents[0].organization_environment_uid == str(environment_uid)
2062
+ assert agents[0].runtime_update.state == "current"
2063
+
2064
+
2065
+ def test_agent_get_parses_runtime_update_projection(monkeypatch):
2066
+ captured = {}
2067
+ agent_uid = "e0e75693-4110-464c-93e0-82c7fd9c9a23"
2068
+ environment_uid = uuid.UUID("22222222-2222-4222-8222-222222222222")
2069
+
2070
+ class FakeResponse:
2071
+ status_code = 200
2072
+
2073
+ @staticmethod
2074
+ def json():
2075
+ return {
2076
+ "uid": agent_uid,
2077
+ "name": "CodeRepository Executor",
2078
+ "llm_thinking": "medium",
2079
+ "code_repository_branch_uid": "9d81d63f-b8c9-404d-9f1a-5f2ad29dbf16",
2080
+ "repository_branch": "main",
2081
+ "organization_environment_uid": str(environment_uid),
2082
+ "organization_environment_name": "production",
2083
+ "runtime_update": _agent_runtime_update_contract("update_failed"),
2084
+ }
2085
+
2086
+ def _fake_make_request(*, s, loaders, r_type, url, payload, time_out=None):
2087
+ captured.update(
2088
+ {
2089
+ "r_type": r_type,
2090
+ "url": url,
2091
+ "payload": payload,
2092
+ "timeout": time_out,
2093
+ }
2094
+ )
2095
+ return FakeResponse()
2096
+
2097
+ monkeypatch.setattr(base_mod, "make_request", _fake_make_request)
2098
+ monkeypatch.setattr(
2099
+ agent_models_mod.Agent,
2100
+ "build_session",
2101
+ classmethod(lambda cls: object()),
2102
+ )
2103
+
2104
+ agent = agent_models_mod.Agent.get(
2105
+ pk=agent_uid,
2106
+ organization_environment_uid=environment_uid,
2107
+ timeout=12,
2108
+ )
2109
+
2110
+ assert captured == {
2111
+ "r_type": "GET",
2112
+ "url": f"{agent_models_mod.Agent.get_object_url()}/{agent_uid}/",
2113
+ "payload": {"params": {"organization_environment_uid": str(environment_uid)}},
2114
+ "timeout": 12,
2115
+ }
2116
+ assert agent.runtime_update.state == "update_failed"
2117
+ assert agent.runtime_update.needs_redeploy is True
2118
+ assert agent.runtime_update.remediation.tool == "agent.update_runtime"
1992
2119
 
1993
2120
 
1994
2121
  def test_agent_semantic_search_sends_environment_scope_and_parses_code_repositoryion(monkeypatch):
@@ -2010,6 +2137,7 @@ def test_agent_semantic_search_sends_environment_scope_and_parses_code_repositor
2010
2137
  "repository_branch": "main",
2011
2138
  "organization_environment_uid": str(environment_uid),
2012
2139
  "organization_environment_name": "production",
2140
+ "runtime_update": _agent_runtime_update_contract("unknown"),
2013
2141
  "semantic_score": 0.91,
2014
2142
  "text_score": 0.74,
2015
2143
  "combined_score": 0.85,
@@ -2056,6 +2184,8 @@ def test_agent_semantic_search_sends_environment_scope_and_parses_code_repositor
2056
2184
  assert len(results) == 1
2057
2185
  assert results[0].code_repository_branch_uid == "9d81d63f-b8c9-404d-9f1a-5f2ad29dbf16"
2058
2186
  assert results[0].organization_environment_name == "production"
2187
+ assert results[0].runtime_update.state == "unknown"
2188
+ assert results[0].runtime_update.needs_redeploy is None
2059
2189
 
2060
2190
 
2061
2191
  def test_agent_session_filter_supports_archive_history_query(monkeypatch):
@@ -3016,6 +3146,83 @@ def _resource_release_pipeline_payload(*, running_step: str | None = None):
3016
3146
  }
3017
3147
 
3018
3148
 
3149
+ def _deployment_run_billing_payload(
3150
+ *,
3151
+ pricing_state: str = "priced",
3152
+ total_cost: str | None = "0.000000",
3153
+ ) -> dict:
3154
+ return {
3155
+ "scope": "image_lifecycle",
3156
+ "total_cost": total_cost,
3157
+ "currency": "USD",
3158
+ "pricing_state": pricing_state,
3159
+ "components": {
3160
+ "image_build": total_cost,
3161
+ "image_registry_storage": "0.000000" if total_cost is not None else None,
3162
+ "image_registry_service": "0.000000" if total_cost is not None else None,
3163
+ },
3164
+ "priced_rows": 3 if pricing_state == "priced" else 0,
3165
+ "unpriced_rows": 0 if pricing_state == "priced" else 3,
3166
+ "reused_image_count": 0,
3167
+ }
3168
+
3169
+
3170
+ @pytest.mark.parametrize(
3171
+ "pricing_state",
3172
+ ["priced", "partial", "pending", "unavailable", "failed"],
3173
+ )
3174
+ def test_deployment_run_billing_parses_every_backend_pricing_state(pricing_state):
3175
+ total_cost = "1.250000" if pricing_state in {"priced", "partial"} else None
3176
+
3177
+ billing = models_helpers_mod.DeploymentRunBilling.model_validate(
3178
+ _deployment_run_billing_payload(
3179
+ pricing_state=pricing_state,
3180
+ total_cost=total_cost,
3181
+ )
3182
+ )
3183
+
3184
+ assert billing.pricing_state == pricing_state
3185
+ assert billing.total_cost == (Decimal(total_cost) if total_cost is not None else None)
3186
+ assert billing.components.image_build == billing.total_cost
3187
+
3188
+
3189
+ def test_deployment_run_billing_contract_is_complete_and_closed():
3190
+ payload = _deployment_run_billing_payload()
3191
+ for field_name in (
3192
+ "scope",
3193
+ "total_cost",
3194
+ "currency",
3195
+ "pricing_state",
3196
+ "components",
3197
+ "priced_rows",
3198
+ "unpriced_rows",
3199
+ "reused_image_count",
3200
+ ):
3201
+ incomplete = dict(payload)
3202
+ incomplete.pop(field_name)
3203
+ with pytest.raises(ValidationError, match=field_name):
3204
+ models_helpers_mod.DeploymentRunBilling.model_validate(incomplete)
3205
+
3206
+ with pytest.raises(ValidationError, match="extra_forbidden"):
3207
+ models_helpers_mod.DeploymentRunBilling.model_validate(
3208
+ payload | {"future_billing_field": "value"}
3209
+ )
3210
+
3211
+ with pytest.raises(ValidationError, match="extra_forbidden"):
3212
+ models_helpers_mod.DeploymentRunBilling.model_validate(
3213
+ payload
3214
+ | {
3215
+ "components": payload["components"]
3216
+ | {"future_component": "0.000000"}
3217
+ }
3218
+ )
3219
+
3220
+ with pytest.raises(ValidationError, match="greater_than_equal"):
3221
+ models_helpers_mod.DeploymentRunBilling.model_validate(
3222
+ payload | {"unpriced_rows": -1}
3223
+ )
3224
+
3225
+
3019
3226
  def test_resource_release_deploy_current_version_posts_detail_action(monkeypatch):
3020
3227
  captured = {}
3021
3228
  release_uid = "2f4c4c3d-5669-4da5-9d86-b84633c1e6ed"
@@ -3060,6 +3267,7 @@ def test_resource_release_deploy_current_version_posts_detail_action(monkeypatch
3060
3267
  "retention_expires_at": None,
3061
3268
  },
3062
3269
  "error": None,
3270
+ "billing": _deployment_run_billing_payload(),
3063
3271
  }
3064
3272
 
3065
3273
  def _fake_make_request(*, s, loaders, r_type, url, payload, time_out=None):
@@ -3084,6 +3292,7 @@ def test_resource_release_deploy_current_version_posts_detail_action(monkeypatch
3084
3292
  assert run.builder_runtime == ""
3085
3293
  assert run.logs.state == "available"
3086
3294
  assert run.error is None
3295
+ assert run.billing.total_cost == Decimal("0.000000")
3087
3296
  assert captured == {
3088
3297
  "r_type": "POST",
3089
3298
  "url": (
@@ -3127,6 +3336,7 @@ def test_deployment_run_collection_and_detail_use_unified_resource_release_contr
3127
3336
  "retention_expires_at": None,
3128
3337
  },
3129
3338
  "error": None,
3339
+ "billing": _deployment_run_billing_payload(),
3130
3340
  }
3131
3341
 
3132
3342
  collection_payload = {
@@ -3182,6 +3392,8 @@ def test_deployment_run_collection_and_detail_use_unified_resource_release_contr
3182
3392
  assert detail.builder_runtime == ""
3183
3393
  assert detail.logs.state == "available"
3184
3394
  assert detail.error is None
3395
+ assert runs[0].billing.pricing_state == "priced"
3396
+ assert detail.billing.components.image_registry_storage == Decimal("0.000000")
3185
3397
  assert captured[0]["payload"] == {
3186
3398
  "params": {
3187
3399
  "code_repository_branch_uid": CODE_REPOSITORY_BRANCH_UID,
@@ -3221,6 +3433,10 @@ def test_unified_deployment_run_models_and_filters(monkeypatch):
3221
3433
  "retention_expires_at": None,
3222
3434
  },
3223
3435
  "error": None,
3436
+ "billing": _deployment_run_billing_payload(
3437
+ pricing_state="pending",
3438
+ total_cost=None,
3439
+ ),
3224
3440
  }
3225
3441
  )
3226
3442
 
@@ -3238,6 +3454,8 @@ def test_unified_deployment_run_models_and_filters(monkeypatch):
3238
3454
  assert run.pipeline.steps[4].state == "pending"
3239
3455
  assert run.builder_image == "us-docker.pkg.dev/platform/static-builder:latest"
3240
3456
  assert run.builder_runtime == "nodejs22"
3457
+ assert run.billing.pricing_state == "pending"
3458
+ assert run.billing.total_cost is None
3241
3459
  assert normalized == {
3242
3460
  "code_repository_branch_uid": code_repository_branch_uid,
3243
3461
  "target_type__in": ["resource_release", "static_site"],
@@ -4024,6 +4242,7 @@ def test_agent_respond_uses_agent_scoped_sessionless_contract(monkeypatch):
4024
4242
  repository_branch=None,
4025
4243
  organization_environment_uid=None,
4026
4244
  organization_environment_name=None,
4245
+ runtime_update=_agent_runtime_update_contract(),
4027
4246
  )
4028
4247
 
4029
4248
  class FakeAccessResponse:
@@ -4131,6 +4350,7 @@ def test_agent_respond_waits_for_transient_runtime_interaction(monkeypatch):
4131
4350
  repository_branch=None,
4132
4351
  organization_environment_uid=None,
4133
4352
  organization_environment_name=None,
4353
+ runtime_update=_agent_runtime_update_contract(),
4134
4354
  )
4135
4355
  accesses = iter(
4136
4356
  [
@@ -4221,6 +4441,7 @@ def test_agent_get_or_create_session_posts_new_contract(monkeypatch):
4221
4441
  repository_branch=None,
4222
4442
  organization_environment_uid=None,
4223
4443
  organization_environment_name=None,
4444
+ runtime_update=_agent_runtime_update_contract(),
4224
4445
  )
4225
4446
 
4226
4447
  class FakeResponse:
@@ -4315,6 +4536,7 @@ def test_agent_get_or_create_session_parses_reused_handle_capabilities(monkeypat
4315
4536
  repository_branch=None,
4316
4537
  organization_environment_uid=None,
4317
4538
  organization_environment_name=None,
4539
+ runtime_update=_agent_runtime_update_contract(),
4318
4540
  )
4319
4541
  captured = {}
4320
4542
 
@@ -4389,6 +4611,7 @@ def test_agent_get_or_create_session_by_uid_sends_only_session_uid(monkeypatch):
4389
4611
  repository_branch=None,
4390
4612
  organization_environment_uid=None,
4391
4613
  organization_environment_name=None,
4614
+ runtime_update=_agent_runtime_update_contract(),
4392
4615
  )
4393
4616
 
4394
4617
  class FakeResponse:
@@ -223,6 +223,11 @@ def test_agent_logs_support_optional_session_filter_without_environment_override
223
223
  repository_branch=None,
224
224
  organization_environment_uid=ENVIRONMENT_UID,
225
225
  organization_environment_name="Development",
226
+ runtime_update={
227
+ "state": "current",
228
+ "needs_redeploy": False,
229
+ "remediation": None,
230
+ },
226
231
  observability={
227
232
  "application_logs_url": (
228
233
  "/api/v1/agents/e0e75693-4110-464c-93e0-82c7fd9c9a23/logs/"
File without changes
File without changes
File without changes
File without changes