dharta-runtime 0.6.77__py3-none-any.whl
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.
- dharta/__init__.py +46 -0
- dharta/__main__.py +4 -0
- dharta/active_releases.py +243 -0
- dharta/agent.py +59 -0
- dharta/agent_builder_surface_authority.py +326 -0
- dharta/agent_runtime.py +290 -0
- dharta/app.py +757 -0
- dharta/artifacts/__init__.py +227 -0
- dharta/artifacts/agent_activation.py +146 -0
- dharta/artifacts/agent_activation_authority.py +204 -0
- dharta/artifacts/agent_activation_transport.py +148 -0
- dharta/artifacts/agent_delegation_contract.py +284 -0
- dharta/artifacts/agent_package.py +74 -0
- dharta/artifacts/agentcore_renderer_backend.py +1284 -0
- dharta/artifacts/artifact-runtime.json +750 -0
- dharta/artifacts/builtin_runner.py +230 -0
- dharta/artifacts/builtin_views.py +181 -0
- dharta/artifacts/cli.py +391 -0
- dharta/artifacts/contract.py +328 -0
- dharta/artifacts/contract_generated.py +204 -0
- dharta/artifacts/executable_ingest.py +305 -0
- dharta/artifacts/executable_models.py +202 -0
- dharta/artifacts/execution_coordinator.py +109 -0
- dharta/artifacts/execution_cost.py +77 -0
- dharta/artifacts/execution_materializer.py +351 -0
- dharta/artifacts/execution_service.py +118 -0
- dharta/artifacts/grounded.py +108 -0
- dharta/artifacts/helper.py +326 -0
- dharta/artifacts/http_gateway.py +525 -0
- dharta/artifacts/index.py +246 -0
- dharta/artifacts/ingest.py +492 -0
- dharta/artifacts/injection.py +85 -0
- dharta/artifacts/legacy_adapter.py +84 -0
- dharta/artifacts/manifest.py +193 -0
- dharta/artifacts/manifest_store.py +208 -0
- dharta/artifacts/pack_export.py +408 -0
- dharta/artifacts/portable_execution_pack.py +255 -0
- dharta/artifacts/portable_pack_codec.py +206 -0
- dharta/artifacts/portable_pack_contract.py +224 -0
- dharta/artifacts/portable_pack_files.py +352 -0
- dharta/artifacts/portable_share_runtime.py +42 -0
- dharta/artifacts/portable_share_service.py +61 -0
- dharta/artifacts/presentation.py +209 -0
- dharta/artifacts/records.py +395 -0
- dharta/artifacts/release_context.py +68 -0
- dharta/artifacts/runner_cli.py +116 -0
- dharta/artifacts/runner_supervisor.py +789 -0
- dharta/artifacts/s3_posix_tree.py +716 -0
- dharta/artifacts/share_pack.py +729 -0
- dharta/artifacts/share_retention.py +77 -0
- dharta/artifacts/skill/SKILL.md +232 -0
- dharta/artifacts/snapshot_coordinator.py +180 -0
- dharta/artifacts/supervisor_transport.py +110 -0
- dharta/artifacts/tree_resolver.py +457 -0
- dharta/artifacts/types.py +244 -0
- dharta/billing/__init__.py +23 -0
- dharta/billing/agentcore_resource_aggregation.py +246 -0
- dharta/billing/agentcore_resource_collector.py +639 -0
- dharta/billing/agentcore_resource_gaps.py +313 -0
- dharta/billing/agentcore_resource_schema.py +148 -0
- dharta/billing/agentcore_resource_store.py +317 -0
- dharta/billing/agentcore_usage_delivery.py +221 -0
- dharta/billing/agentcore_usage_logs.py +238 -0
- dharta/billing/invocation_emitter.py +483 -0
- dharta/billing/telemetry_event_contract.py +282 -0
- dharta/billing/telemetry_identity.py +53 -0
- dharta/billing/telemetry_microvm_bindings.py +142 -0
- dharta/billing/telemetry_outbox.py +442 -0
- dharta/billing/telemetry_outbox_schema.py +71 -0
- dharta/billing/turn_admission.py +349 -0
- dharta/billing/turn_payload_contract.py +160 -0
- dharta/billing/usage_emitter.py +794 -0
- dharta/billing/usage_outbox.py +1461 -0
- dharta/billing/usage_outbox_schema.py +566 -0
- dharta/browser_url_policy.py +41 -0
- dharta/builder.py +222 -0
- dharta/byok.py +224 -0
- dharta/cache.py +257 -0
- dharta/capture_contract.py +88 -0
- dharta/cli.py +913 -0
- dharta/cli_renderer.py +248 -0
- dharta/connector.py +42 -0
- dharta/consumer/__init__.py +7 -0
- dharta/consumer/activity.py +708 -0
- dharta/consumer/agent_origins.py +98 -0
- dharta/consumer/challenge.py +116 -0
- dharta/consumer/context_envelope.py +231 -0
- dharta/consumer/embed.py +411 -0
- dharta/consumer/event_log.py +244 -0
- dharta/consumer/events.py +182 -0
- dharta/consumer/managed_agents.py +297 -0
- dharta/consumer/openai_chat.py +113 -0
- dharta/consumer/openai_responses.py +137 -0
- dharta/consumer/session_token.py +35 -0
- dharta/consumer/tasks.py +326 -0
- dharta/consumer/transcript.py +243 -0
- dharta/consumer/turn_projection.py +118 -0
- dharta/consumer/wire_events.py +91 -0
- dharta/control_plane.py +372 -0
- dharta/di/__init__.py +32 -0
- dharta/di/allocator.py +287 -0
- dharta/di/anchor.py +191 -0
- dharta/di/audit.py +91 -0
- dharta/di/bik.py +58 -0
- dharta/di/conversation.py +155 -0
- dharta/di/crypto.py +100 -0
- dharta/di/envelope.py +73 -0
- dharta/di/instance_token.py +86 -0
- dharta/di/keys.py +250 -0
- dharta/di/leak_guard.py +112 -0
- dharta/di/lifecycle.py +88 -0
- dharta/di/object_cipher.py +364 -0
- dharta/di/revocation.py +155 -0
- dharta/di/tenant.py +149 -0
- dharta/durable/__init__.py +22 -0
- dharta/durable/local.py +112 -0
- dharta/durable/port.py +24 -0
- dharta/durable/s3.py +117 -0
- dharta/end_user_context.py +239 -0
- dharta/env_policy.py +85 -0
- dharta/events.py +21 -0
- dharta/execution/__init__.py +24 -0
- dharta/execution/fake.py +78 -0
- dharta/execution/port.py +85 -0
- dharta/gateway/__init__.py +15 -0
- dharta/gateway/base.py +29 -0
- dharta/gateway/events.py +73 -0
- dharta/gateway/http.py +80 -0
- dharta/gateway/local.py +151 -0
- dharta/gateway/registry.py +65 -0
- dharta/genui/__init__.py +29 -0
- dharta/genui/bundle.py +179 -0
- dharta/genui/injection.py +87 -0
- dharta/genui/manifest.py +133 -0
- dharta/genui/skill/SKILL.md +114 -0
- dharta/harness/__init__.py +4 -0
- dharta/harness/account_mcp.py +189 -0
- dharta/harness/base.py +492 -0
- dharta/harness/claude.py +697 -0
- dharta/harness/claude_message_mapper.py +899 -0
- dharta/harness/claude_runtime.py +777 -0
- dharta/harness/claude_runtime_types.py +253 -0
- dharta/harness/codex.py +665 -0
- dharta/harness/config_env.py +101 -0
- dharta/harness/deepagents.py +750 -0
- dharta/harness/factory.py +91 -0
- dharta/harness/gateway_usage.py +84 -0
- dharta/harness/goose.py +767 -0
- dharta/harness/opencode.py +610 -0
- dharta/harness/platform_context.py +100 -0
- dharta/harness/spec.py +173 -0
- dharta/harness/stub.py +301 -0
- dharta/harness/subprocess.py +343 -0
- dharta/harness/utils.py +103 -0
- dharta/harness/wakeups_mcp.py +218 -0
- dharta/host_assets.py +64 -0
- dharta/instance.py +739 -0
- dharta/model_broker_quota_contracts.py +153 -0
- dharta/model_broker_transport_contracts/__init__.py +101 -0
- dharta/model_broker_transport_contracts/_common.py +239 -0
- dharta/model_broker_transport_contracts/binding.py +282 -0
- dharta/model_broker_transport_contracts/relay.py +320 -0
- dharta/model_broker_transport_contracts/request.py +642 -0
- dharta/model_broker_transport_contracts/spool.py +489 -0
- dharta/model_broker_transport_contracts/spool_reader.py +455 -0
- dharta/model_broker_transport_contracts/spool_sequence.py +188 -0
- dharta/model_broker_transport_contracts/verification.py +417 -0
- dharta/model_connector_adapter_registry.py +708 -0
- dharta/model_contracts.py +2483 -0
- dharta/model_egress_contracts/__init__.py +85 -0
- dharta/model_egress_contracts/_common.py +365 -0
- dharta/model_egress_contracts/bedrock_capability.py +699 -0
- dharta/model_egress_contracts/dispatch_authorization.py +383 -0
- dharta/model_egress_contracts/dispatch_context.py +146 -0
- dharta/model_egress_contracts/endpoint_policy.py +488 -0
- dharta/model_egress_contracts/exact_grant.py +652 -0
- dharta/model_egress_contracts/manifest.py +878 -0
- dharta/model_egress_contracts/manifest_verification.py +290 -0
- dharta/model_endpoint_policy.py +95 -0
- dharta/model_failure.py +248 -0
- dharta/model_gateway.py +1745 -0
- dharta/model_http_transport.py +106 -0
- dharta/model_readiness.py +348 -0
- dharta/model_readiness_credentials.py +199 -0
- dharta/model_route.py +867 -0
- dharta/model_settings.py +691 -0
- dharta/observability/__init__.py +172 -0
- dharta/observability/_noop.py +75 -0
- dharta/observability/_otel.py +104 -0
- dharta/observability/attribute_policy.py +217 -0
- dharta/observability/gen_ai_emitter.py +547 -0
- dharta/observability/gen_ai_semconv.py +76 -0
- dharta/observability/log_metric_policy.py +132 -0
- dharta/observability/logging_setup.py +100 -0
- dharta/observability/metrics.py +107 -0
- dharta/observability/secret_redaction.py +96 -0
- dharta/observability/sentry.py +154 -0
- dharta/observability/stream_event_contract.py +87 -0
- dharta/observability/trace_propagation.py +88 -0
- dharta/participant.py +55 -0
- dharta/persistence/__init__.py +44 -0
- dharta/persistence/encrypting_backend.py +44 -0
- dharta/persistence/erase.py +130 -0
- dharta/persistence/fork.py +241 -0
- dharta/persistence/gc.py +823 -0
- dharta/persistence/local_backend.py +220 -0
- dharta/persistence/manifest.py +148 -0
- dharta/persistence/pack.py +243 -0
- dharta/persistence/profile.py +216 -0
- dharta/persistence/reconcile.py +76 -0
- dharta/persistence/s3_backend.py +341 -0
- dharta/persistence/setup_pack.py +206 -0
- dharta/persistence/store.py +1348 -0
- dharta/persistence/stress.py +539 -0
- dharta/persistence/text_merge.py +70 -0
- dharta/persistence/workspace_paths.py +60 -0
- dharta/persistence/workspace_snapshot.py +584 -0
- dharta/policies.py +29 -0
- dharta/preview_admission.py +147 -0
- dharta/preview_model_broker.py +510 -0
- dharta/preview_model_readiness.py +619 -0
- dharta/preview_runtime_probe.py +128 -0
- dharta/preview_runtime_proof.py +110 -0
- dharta/preview_storage_reconcile.py +121 -0
- dharta/registry.py +137 -0
- dharta/release_artifacts.py +349 -0
- dharta/release_model_intent.py +638 -0
- dharta/releases.py +1090 -0
- dharta/response.py +175 -0
- dharta/runtime/__init__.py +3 -0
- dharta/runtime/turn_accumulator.py +231 -0
- dharta/secrets_bootstrap.py +88 -0
- dharta/server/__init__.py +22 -0
- dharta/server/agentcore.py +755 -0
- dharta/server/agentcore_agent_runtime.py +879 -0
- dharta/server/agentcore_client.py +979 -0
- dharta/server/agentcore_client_config.py +356 -0
- dharta/server/agentcore_client_types.py +130 -0
- dharta/server/agentcore_gateway_client.py +96 -0
- dharta/server/agentcore_internal_frames.py +769 -0
- dharta/server/agentcore_quota.py +120 -0
- dharta/server/agentcore_router.py +545 -0
- dharta/server/agentcore_serve.py +552 -0
- dharta/server/agentcore_session_socket.py +121 -0
- dharta/server/agentcore_sessions.py +125 -0
- dharta/server/agentcore_stream.py +65 -0
- dharta/server/agentcore_turn_recorder.py +175 -0
- dharta/server/agentcore_workspace.py +899 -0
- dharta/server/approval_store.py +506 -0
- dharta/server/artifact_browser_policy.py +252 -0
- dharta/server/artifact_execution_sessions.py +314 -0
- dharta/server/artifact_qualification_runtime.py +74 -0
- dharta/server/artifact_renderer.py +1051 -0
- dharta/server/artifact_runtime_binding.py +44 -0
- dharta/server/artifact_runtime_serve.py +190 -0
- dharta/server/artifact_sandbox.py +923 -0
- dharta/server/artifact_share_routes.py +380 -0
- dharta/server/artifact_stream_bridge.py +173 -0
- dharta/server/artifact_surface_routes.py +527 -0
- dharta/server/auth.py +1111 -0
- dharta/server/body_limit.py +89 -0
- dharta/server/bounded_websocket_protocol.py +108 -0
- dharta/server/consumer_chat_routes.py +219 -0
- dharta/server/consumer_routes.py +942 -0
- dharta/server/continuation_registry.py +328 -0
- dharta/server/conversation_crypto.py +202 -0
- dharta/server/conversation_store.py +960 -0
- dharta/server/conversation_transcripts.py +110 -0
- dharta/server/credential_guard.py +47 -0
- dharta/server/crypto_stores.py +214 -0
- dharta/server/di_factory.py +121 -0
- dharta/server/di_key_factory.py +57 -0
- dharta/server/di_resolver.py +99 -0
- dharta/server/embed_routes.py +358 -0
- dharta/server/end_user_context_binding.py +92 -0
- dharta/server/end_user_context_store.py +275 -0
- dharta/server/endpoint_pool.py +517 -0
- dharta/server/fastapi_app.py +704 -0
- dharta/server/first_party_access.py +32 -0
- dharta/server/git_auth.py +112 -0
- dharta/server/git_routes.py +183 -0
- dharta/server/git_transcode.py +279 -0
- dharta/server/identity.py +329 -0
- dharta/server/instance_env.py +242 -0
- dharta/server/instance_env_cache.py +114 -0
- dharta/server/instance_env_delivery.py +225 -0
- dharta/server/instance_routes.py +218 -0
- dharta/server/instance_write_lease.py +164 -0
- dharta/server/internal_ip.py +56 -0
- dharta/server/internal_routes.py +883 -0
- dharta/server/invocation_leases.py +268 -0
- dharta/server/karta_env_routes.py +194 -0
- dharta/server/lease_maintenance.py +97 -0
- dharta/server/ma_artifact_execution_routes.py +379 -0
- dharta/server/ma_artifact_route_context.py +29 -0
- dharta/server/ma_artifact_routes.py +920 -0
- dharta/server/ma_routes.py +680 -0
- dharta/server/ma_session_history_routes.py +133 -0
- dharta/server/ma_session_routes.py +627 -0
- dharta/server/ma_surface_routes.py +300 -0
- dharta/server/ma_task_routes.py +135 -0
- dharta/server/ma_workspace_downloads.py +30 -0
- dharta/server/ma_workspace_routes.py +235 -0
- dharta/server/metering.py +323 -0
- dharta/server/publication_attempts.py +466 -0
- dharta/server/publication_recovery.py +180 -0
- dharta/server/ratelimit.py +65 -0
- dharta/server/release_inventory.py +108 -0
- dharta/server/release_mapping.py +1094 -0
- dharta/server/release_publisher.py +887 -0
- dharta/server/release_reaper.py +182 -0
- dharta/server/release_tree.py +83 -0
- dharta/server/runtime_generation_store.py +265 -0
- dharta/server/runtime_rotation.py +190 -0
- dharta/server/schemas.py +621 -0
- dharta/server/serialization.py +38 -0
- dharta/server/serve_box_crypto.py +198 -0
- dharta/server/serve_host_policy.py +160 -0
- dharta/server/serve_surfaces.py +542 -0
- dharta/server/session_catalog.py +85 -0
- dharta/server/session_db.py +156 -0
- dharta/server/session_routes.py +132 -0
- dharta/server/session_store.py +479 -0
- dharta/server/sse.py +10 -0
- dharta/server/state.py +99 -0
- dharta/server/static/agent-chat/assets/agent-chat-PreviewContent.js +6 -0
- dharta/server/static/agent-chat/assets/agent-chat-pdf-renderer.js +54 -0
- dharta/server/static/agent-chat/assets/agent-chat.css +2 -0
- dharta/server/static/agent-chat/assets/agent-chat.js +178 -0
- dharta/server/static/agent-chat/assets/agent-chat.mjs +29 -0
- dharta/server/static/agent-chat/assets/pentagraph.worker-BaB5eTrj.js +1 -0
- dharta/server/static/agent-chat/fonts/dharta-arc/DhartaArc-Bold.woff2 +0 -0
- dharta/server/static/agent-chat/fonts/dharta-arc/DhartaArc-ExtraBold.woff2 +0 -0
- dharta/server/static/agent-chat/fonts/dharta-arc/DhartaArc-Medium.woff2 +0 -0
- dharta/server/static/agent-chat/fonts/dharta-arc/DhartaArc-Regular.woff2 +0 -0
- dharta/server/static/agent-chat/fonts/dharta-arc/DhartaArc-SemiBold.woff2 +0 -0
- dharta/server/static/agent-chat/fonts/dharta-arc/OFL.txt +88 -0
- dharta/server/static/agent-chat/fonts/dharta-mono/DhartaMono-Bold.woff2 +0 -0
- dharta/server/static/agent-chat/fonts/dharta-mono/DhartaMono-BoldItalic.woff2 +0 -0
- dharta/server/static/agent-chat/fonts/dharta-mono/DhartaMono-Medium.woff2 +0 -0
- dharta/server/static/agent-chat/fonts/dharta-mono/DhartaMono-MediumItalic.woff2 +0 -0
- dharta/server/static/agent-chat/fonts/dharta-mono/DhartaMono-Regular.woff2 +0 -0
- dharta/server/static/agent-chat/fonts/dharta-mono/DhartaMono-RegularItalic.woff2 +0 -0
- dharta/server/static/agent-chat/fonts/dharta-mono/DhartaMono-SemiBold.woff2 +0 -0
- dharta/server/static/agent-chat/fonts/dharta-mono/DhartaMono-SemiBoldItalic.woff2 +0 -0
- dharta/server/static/agent-chat/fonts/dharta-mono/OFL.txt +88 -0
- dharta/server/static/agent-chat/index.html +14 -0
- dharta/server/static/widget/v1/dharta.js +1 -0
- dharta/server/static/widget/v1/dharta.js.README.md +17 -0
- dharta/server/store_db.py +247 -0
- dharta/server/surface_preview_routes.py +317 -0
- dharta/server/surface_reaper.py +270 -0
- dharta/server/task_ledger.py +71 -0
- dharta/server/task_store.py +515 -0
- dharta/server/tenant_store.py +139 -0
- dharta/server/turn_admissions.py +327 -0
- dharta/server/turn_agentcore_driver.py +852 -0
- dharta/server/turn_continuations.py +378 -0
- dharta/server/turn_failure.py +78 -0
- dharta/server/turn_finalization.py +133 -0
- dharta/server/turn_identity.py +203 -0
- dharta/server/turn_local_driver.py +823 -0
- dharta/server/turn_support.py +501 -0
- dharta/server/turn_telemetry.py +139 -0
- dharta/server/turns.py +173 -0
- dharta/server/webhooks.py +60 -0
- dharta/server/workspace_access.py +164 -0
- dharta/server/workspace_auth.py +97 -0
- dharta/server/workspace_read.py +657 -0
- dharta/server/workspace_routes.py +152 -0
- dharta/session.py +166 -0
- dharta/subscriber_authorization_contracts.py +809 -0
- dharta/subscriber_sponsorship_contracts.py +781 -0
- dharta/subscription_credential.py +96 -0
- dharta/surfaces/__init__.py +57 -0
- dharta/surfaces/access.py +69 -0
- dharta/surfaces/artifact.py +62 -0
- dharta/surfaces/artifact_access.py +256 -0
- dharta/surfaces/artifact_delegation.py +205 -0
- dharta/surfaces/artifact_runtime.py +191 -0
- dharta/surfaces/browser.py +52 -0
- dharta/surfaces/manifest.py +99 -0
- dharta/surfaces/preview_bridge.py +224 -0
- dharta/surfaces/published_applications.py +66 -0
- dharta/token_signing.py +160 -0
- dharta/vault/__init__.py +21 -0
- dharta/vault/agentcore.py +33 -0
- dharta/vault/local.py +43 -0
- dharta/vault/port.py +28 -0
- dharta/workspace.py +487 -0
- dharta_runtime-0.6.77.dist-info/LICENSE +21 -0
- dharta_runtime-0.6.77.dist-info/METADATA +429 -0
- dharta_runtime-0.6.77.dist-info/RECORD +395 -0
- dharta_runtime-0.6.77.dist-info/WHEEL +4 -0
- dharta_runtime-0.6.77.dist-info/entry_points.txt +6 -0
dharta/__init__.py
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
from dharta.agent import Agent
|
|
2
|
+
from dharta.app import Dharta
|
|
3
|
+
from dharta.gateway import (
|
|
4
|
+
Gateway,
|
|
5
|
+
GatewayDelivery,
|
|
6
|
+
GatewayEvent,
|
|
7
|
+
HttpGateway,
|
|
8
|
+
LocalGateway,
|
|
9
|
+
ParticipantRegistry,
|
|
10
|
+
)
|
|
11
|
+
from dharta.instance import AgentInstance, InstanceManager, InstanceSession, InstanceStore
|
|
12
|
+
from dharta.participant import AIAgent, HumanAgent, Participant
|
|
13
|
+
from dharta.policies import PolicyViolationError
|
|
14
|
+
from dharta.registry import AgentRegistry, PublishedAgent
|
|
15
|
+
from dharta.response import Message, Part, Response, StreamEvent, Usage
|
|
16
|
+
from dharta.session import Session
|
|
17
|
+
from dharta.workspace import UserAgentView, WorkspaceManager
|
|
18
|
+
|
|
19
|
+
__all__ = [
|
|
20
|
+
"AgentInstance",
|
|
21
|
+
"Dharta",
|
|
22
|
+
"Agent",
|
|
23
|
+
"AIAgent",
|
|
24
|
+
"Gateway",
|
|
25
|
+
"GatewayDelivery",
|
|
26
|
+
"GatewayEvent",
|
|
27
|
+
"HttpGateway",
|
|
28
|
+
"HumanAgent",
|
|
29
|
+
"InstanceManager",
|
|
30
|
+
"InstanceSession",
|
|
31
|
+
"InstanceStore",
|
|
32
|
+
"LocalGateway",
|
|
33
|
+
"Message",
|
|
34
|
+
"Part",
|
|
35
|
+
"Participant",
|
|
36
|
+
"ParticipantRegistry",
|
|
37
|
+
"PolicyViolationError",
|
|
38
|
+
"AgentRegistry",
|
|
39
|
+
"PublishedAgent",
|
|
40
|
+
"Response",
|
|
41
|
+
"Session",
|
|
42
|
+
"StreamEvent",
|
|
43
|
+
"Usage",
|
|
44
|
+
"UserAgentView",
|
|
45
|
+
"WorkspaceManager",
|
|
46
|
+
]
|
dharta/__main__.py
ADDED
|
@@ -0,0 +1,243 @@
|
|
|
1
|
+
"""Active-release resolution for the data plane.
|
|
2
|
+
|
|
3
|
+
When a session is created for a agent URL, the data plane must learn
|
|
4
|
+
WHICH materialized release to run. dharta-web is authoritative: it owns
|
|
5
|
+
the ``Agent#current_release`` pointer that the deploy/activate
|
|
6
|
+
flow flips. This module fetches that pointer for an ``(org, agent)``
|
|
7
|
+
pair and caches it briefly so the hot path doesn't round-trip the
|
|
8
|
+
control plane on every session create.
|
|
9
|
+
|
|
10
|
+
Mirrors :mod:`karta.model_settings` in shape (cached control-plane GET,
|
|
11
|
+
``OrgScopedTtlCache`` keyed by org first so push-invalidation matches).
|
|
12
|
+
The returned :class:`ActiveRelease` carries the agent's numeric id and
|
|
13
|
+
version, which :mod:`karta.agent_runtime` turns into a deterministic
|
|
14
|
+
on-disk template path via :func:`karta.releases.template_path_for`.
|
|
15
|
+
|
|
16
|
+
Nothing here is secret: a template path / commit sha / version is
|
|
17
|
+
operational metadata already surfaced in dashboards, so no scrubbing.
|
|
18
|
+
"""
|
|
19
|
+
|
|
20
|
+
from __future__ import annotations
|
|
21
|
+
|
|
22
|
+
import os
|
|
23
|
+
from dataclasses import dataclass, field
|
|
24
|
+
from typing import Any, Dict, Optional, Tuple
|
|
25
|
+
from urllib.parse import quote
|
|
26
|
+
|
|
27
|
+
from dharta import observability as obs
|
|
28
|
+
from dharta.cache import OrgScopedTtlCache, env_number
|
|
29
|
+
from dharta.control_plane import ControlPlaneUnavailable, control_plane_get
|
|
30
|
+
|
|
31
|
+
logger = obs.get_logger("dharta.active_releases")
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
@dataclass(frozen=True)
|
|
35
|
+
class ActiveRelease:
|
|
36
|
+
"""The agent's currently-active release, as resolved by dharta-web."""
|
|
37
|
+
|
|
38
|
+
agent_id: int
|
|
39
|
+
version: int
|
|
40
|
+
build_kind: str
|
|
41
|
+
template_path: Optional[str]
|
|
42
|
+
commit_sha: Optional[str]
|
|
43
|
+
status: Optional[str] = None
|
|
44
|
+
image_ref: Optional[str] = None
|
|
45
|
+
# RFC 0035 P3: the frozen per-(harness,version) image digest, and whether it's a
|
|
46
|
+
# REAL built image (vs a registry placeholder). The runtime selects the microVM
|
|
47
|
+
# image by this digest only when built; else it runs the constant runtime image.
|
|
48
|
+
harness_image_digest: Optional[str] = None
|
|
49
|
+
harness_image_built: bool = False
|
|
50
|
+
# Agent-level config the control plane attaches (RFC 0015 Phase 2b:
|
|
51
|
+
# ``event_visibility`` toggles). Agent-scoped, not version-pinned.
|
|
52
|
+
config: Dict[str, Any] = field(default_factory=dict)
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
# Keyed by (organization_id, agent_ref) so invalidate_org matches on
|
|
56
|
+
# the first tuple element. TTL is short by default: the cached value
|
|
57
|
+
# carries the active version, so a longer window means a freshly
|
|
58
|
+
# activated release keeps serving the OLD version until expiry. A new
|
|
59
|
+
# session is cheap to mis-route only within this window; in-flight
|
|
60
|
+
# sessions are pinned to their start version regardless (see
|
|
61
|
+
# agent_runtime). dharta-web push-invalidates on activate when wired.
|
|
62
|
+
_CACHE_TTL_SECONDS = env_number("DHARTA_ACTIVE_RELEASE_CACHE_TTL", 10.0, float)
|
|
63
|
+
_cache: OrgScopedTtlCache[ActiveRelease] = OrgScopedTtlCache(_CACHE_TTL_SECONDS)
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
# RFC 0035 P3: the per-(harness,version) microVM image ref, keyed by
|
|
67
|
+
# (org, agent_id, version) so the image resolver — which only holds ids — can read
|
|
68
|
+
# what fetch_active_release (slug-keyed) learned. A successful fetch records the full
|
|
69
|
+
# `repo@sha256:...` ref when the release froze a REAL built image, or None (placeholder
|
|
70
|
+
# / unbuilt) so the resolver falls back to the constant image. Bounded by active
|
|
71
|
+
# (agent, version) count.
|
|
72
|
+
_image_ref_by_id: Dict[Tuple[int, int, int], Optional[str]] = {}
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
def reset_active_release_cache() -> None:
|
|
76
|
+
"""Test/ops hook: drop all cached active-release entries."""
|
|
77
|
+
_cache.clear()
|
|
78
|
+
_image_ref_by_id.clear()
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
def invalidate_active_release_for_org(organization_id: int) -> int:
|
|
82
|
+
"""Drop every cached active-release entry for ``organization_id``.
|
|
83
|
+
|
|
84
|
+
Called when dharta-web posts a CacheInvalidationJob after a release
|
|
85
|
+
is activated or rolled back. Returns the number of entries evicted.
|
|
86
|
+
"""
|
|
87
|
+
return _cache.invalidate_org(organization_id)
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
def ecr_repo_base() -> Optional[str]:
|
|
91
|
+
"""The repo (no :tag / @digest) of the constant runtime image, e.g.
|
|
92
|
+
``<acct>.dkr.ecr.<region>.amazonaws.com/dharta/agentcore-runtime``. A per-version
|
|
93
|
+
image lives in the SAME repo at a different digest, so ``repo@sha256:...`` selects
|
|
94
|
+
it. None when the constant image isn't configured."""
|
|
95
|
+
img = os.environ.get("DHARTA_AGENTCORE_RUNTIME_IMAGE")
|
|
96
|
+
if not img:
|
|
97
|
+
return None
|
|
98
|
+
img = img.split("@", 1)[0] # drop any digest suffix
|
|
99
|
+
# Drop a trailing :tag. ECR hosts carry no port, so a ':' in the LAST path
|
|
100
|
+
# segment is the tag separator (not a registry port).
|
|
101
|
+
return img.rsplit(":", 1)[0] if ":" in img.rsplit("/", 1)[-1] else img
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
def _record_image_ref(organization_id: int, release: ActiveRelease) -> None:
|
|
105
|
+
"""Index this release's per-version image for the id-keyed resolver. Records the
|
|
106
|
+
immutable ``repo@digest`` ref only when a REAL built image was frozen (RFC 0035
|
|
107
|
+
P3); a placeholder / unbuilt release records None so the resolver falls back."""
|
|
108
|
+
key = (organization_id, release.agent_id, release.version)
|
|
109
|
+
if release.harness_image_built and release.harness_image_digest:
|
|
110
|
+
repo = ecr_repo_base()
|
|
111
|
+
_image_ref_by_id[key] = f"{repo}@{release.harness_image_digest}" if repo else None
|
|
112
|
+
else:
|
|
113
|
+
_image_ref_by_id[key] = None
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
def image_ref_for(organization_id: int, agent_id: int, version: int) -> Optional[str]:
|
|
117
|
+
"""The immutable ``repo@digest`` microVM image to run for this release — AgentCore
|
|
118
|
+
pulls EXACTLY it — or None to fall back to the constant runtime image. Non-None
|
|
119
|
+
only when a real built per-version image was seen for this release (RFC 0035 P3:
|
|
120
|
+
"the right image is used, based on the agent's deployment config")."""
|
|
121
|
+
return _image_ref_by_id.get((organization_id, agent_id, version))
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
def fetch_active_release(
|
|
125
|
+
*,
|
|
126
|
+
organization_id: int,
|
|
127
|
+
agent_ref: str,
|
|
128
|
+
control_plane_url: Optional[str] = None,
|
|
129
|
+
service_token: Optional[str] = None,
|
|
130
|
+
) -> Optional[ActiveRelease]:
|
|
131
|
+
"""Return the active release for ``(organization_id, agent_ref)``.
|
|
132
|
+
|
|
133
|
+
``agent_ref`` is the agent slug from the agent URL. Returns
|
|
134
|
+
``None`` when the agent has no active release, is unknown, or the
|
|
135
|
+
control plane is unreachable. The two are distinguished by caching:
|
|
136
|
+
a definitive miss (404 / non-200) is negative-cached; an outage
|
|
137
|
+
(:class:`ControlPlaneUnavailable`) is not, so the next create retries.
|
|
138
|
+
"""
|
|
139
|
+
key = (organization_id, agent_ref)
|
|
140
|
+
hit, cached = _cache.get(key)
|
|
141
|
+
if hit:
|
|
142
|
+
return cached
|
|
143
|
+
|
|
144
|
+
path = f"/internal/agents/{quote(agent_ref, safe='')}/active_release"
|
|
145
|
+
try:
|
|
146
|
+
payload = control_plane_get(
|
|
147
|
+
path,
|
|
148
|
+
params={"organization_id": organization_id},
|
|
149
|
+
control_plane_url=control_plane_url,
|
|
150
|
+
service_token=service_token,
|
|
151
|
+
)
|
|
152
|
+
except ControlPlaneUnavailable:
|
|
153
|
+
return None
|
|
154
|
+
|
|
155
|
+
if payload is None:
|
|
156
|
+
_cache.put(key, None)
|
|
157
|
+
return None
|
|
158
|
+
|
|
159
|
+
agent_id = payload.get("agent_id")
|
|
160
|
+
version = payload.get("version")
|
|
161
|
+
if not isinstance(agent_id, int) or not isinstance(version, int):
|
|
162
|
+
# Malformed answer — treat as a definitive miss rather than
|
|
163
|
+
# constructing a half-formed release we can't resolve a path for.
|
|
164
|
+
logger.warning(
|
|
165
|
+
"active_release for org=%s agent=%s missing agent_id/version",
|
|
166
|
+
organization_id,
|
|
167
|
+
agent_ref,
|
|
168
|
+
)
|
|
169
|
+
_cache.put(key, None)
|
|
170
|
+
return None
|
|
171
|
+
|
|
172
|
+
release = _release_from_payload(payload)
|
|
173
|
+
_cache.put(key, release)
|
|
174
|
+
_record_image_ref(organization_id, release)
|
|
175
|
+
return release
|
|
176
|
+
|
|
177
|
+
|
|
178
|
+
def fetch_release_version(
|
|
179
|
+
*,
|
|
180
|
+
organization_id: int,
|
|
181
|
+
agent_ref: str,
|
|
182
|
+
version: int,
|
|
183
|
+
control_plane_url: Optional[str] = None,
|
|
184
|
+
service_token: Optional[str] = None,
|
|
185
|
+
) -> Optional[ActiveRelease]:
|
|
186
|
+
"""Resolve a SPECIFIC activatable release (RFC 0021 P3.3 pinned scheduling).
|
|
187
|
+
|
|
188
|
+
Mirrors :func:`fetch_active_release` but hits the version-pinned resolver,
|
|
189
|
+
which 404s unless the release exists and is activatable. Returns ``None``
|
|
190
|
+
on a definitive miss (unknown org/agent/version, non-activatable) and on a
|
|
191
|
+
control-plane outage alike — the caller turns either into a 404/409.
|
|
192
|
+
|
|
193
|
+
Not cached: a pinned release is immutable, but pinned scheduled runs are
|
|
194
|
+
rare (one per cron tick), so the round-trip isn't a hot path.
|
|
195
|
+
# ponytail: no cache; add an immutable (org,agent,version) cache if pinned
|
|
196
|
+
# runs ever become hot.
|
|
197
|
+
"""
|
|
198
|
+
path = (
|
|
199
|
+
f"/internal/agents/{quote(agent_ref, safe='')}"
|
|
200
|
+
f"/releases/{quote(str(version), safe='')}"
|
|
201
|
+
)
|
|
202
|
+
try:
|
|
203
|
+
payload = control_plane_get(
|
|
204
|
+
path,
|
|
205
|
+
params={"organization_id": organization_id},
|
|
206
|
+
control_plane_url=control_plane_url,
|
|
207
|
+
service_token=service_token,
|
|
208
|
+
)
|
|
209
|
+
except ControlPlaneUnavailable:
|
|
210
|
+
return None
|
|
211
|
+
|
|
212
|
+
if payload is None:
|
|
213
|
+
return None
|
|
214
|
+
|
|
215
|
+
if not isinstance(payload.get("agent_id"), int) or not isinstance(payload.get("version"), int):
|
|
216
|
+
logger.warning(
|
|
217
|
+
"release v%s for org=%s agent=%s missing agent_id/version",
|
|
218
|
+
version,
|
|
219
|
+
organization_id,
|
|
220
|
+
agent_ref,
|
|
221
|
+
)
|
|
222
|
+
return None
|
|
223
|
+
|
|
224
|
+
release = _release_from_payload(payload)
|
|
225
|
+
_record_image_ref(organization_id, release)
|
|
226
|
+
return release
|
|
227
|
+
|
|
228
|
+
|
|
229
|
+
def _release_from_payload(payload: Dict[str, Any]) -> ActiveRelease:
|
|
230
|
+
return ActiveRelease(
|
|
231
|
+
agent_id=int(payload["agent_id"]),
|
|
232
|
+
version=int(payload["version"]),
|
|
233
|
+
build_kind=str(payload.get("build_kind") or "file_copy"),
|
|
234
|
+
template_path=(str(payload["template_path"]) if payload.get("template_path") else None),
|
|
235
|
+
commit_sha=(str(payload["commit_sha"]) if payload.get("commit_sha") else None),
|
|
236
|
+
status=(str(payload["status"]) if payload.get("status") else None),
|
|
237
|
+
image_ref=(str(payload["image_ref"]) if payload.get("image_ref") else None),
|
|
238
|
+
harness_image_digest=(
|
|
239
|
+
str(payload["harness_image_digest"]) if payload.get("harness_image_digest") else None
|
|
240
|
+
),
|
|
241
|
+
harness_image_built=bool(payload.get("harness_image_built")),
|
|
242
|
+
config=(payload["config"] if isinstance(payload.get("config"), dict) else {}),
|
|
243
|
+
)
|
dharta/agent.py
ADDED
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import TYPE_CHECKING, Any, AsyncIterator, Dict, Optional
|
|
4
|
+
|
|
5
|
+
from dharta.harness.base import AgentDefinition
|
|
6
|
+
from dharta.response import Response, StreamEvent
|
|
7
|
+
|
|
8
|
+
if TYPE_CHECKING:
|
|
9
|
+
from dharta.app import Dharta
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class Agent:
|
|
13
|
+
def __init__(self, app: "Dharta", definition: AgentDefinition) -> None:
|
|
14
|
+
self._app = app
|
|
15
|
+
self.definition = definition
|
|
16
|
+
|
|
17
|
+
@property
|
|
18
|
+
def name(self) -> str:
|
|
19
|
+
return self.definition.name
|
|
20
|
+
|
|
21
|
+
async def send(
|
|
22
|
+
self,
|
|
23
|
+
text: str,
|
|
24
|
+
*,
|
|
25
|
+
metadata: Optional[Dict[str, Any]] = None,
|
|
26
|
+
thinking: bool = False,
|
|
27
|
+
) -> Response:
|
|
28
|
+
return await self._app.send(text, metadata=metadata, agent=self, thinking=thinking)
|
|
29
|
+
|
|
30
|
+
def send_sync(
|
|
31
|
+
self,
|
|
32
|
+
text: str,
|
|
33
|
+
*,
|
|
34
|
+
metadata: Optional[Dict[str, Any]] = None,
|
|
35
|
+
thinking: bool = False,
|
|
36
|
+
) -> Response:
|
|
37
|
+
return self._app.send_sync(text, metadata=metadata, agent=self, thinking=thinking)
|
|
38
|
+
|
|
39
|
+
async def stream(
|
|
40
|
+
self,
|
|
41
|
+
text: str,
|
|
42
|
+
*,
|
|
43
|
+
metadata: Optional[Dict[str, Any]] = None,
|
|
44
|
+
thinking: bool = False,
|
|
45
|
+
) -> AsyncIterator[StreamEvent]:
|
|
46
|
+
async for event in self._app.stream(text, metadata=metadata, agent=self, thinking=thinking):
|
|
47
|
+
yield event
|
|
48
|
+
|
|
49
|
+
def stream_sync(
|
|
50
|
+
self,
|
|
51
|
+
text: str,
|
|
52
|
+
*,
|
|
53
|
+
metadata: Optional[Dict[str, Any]] = None,
|
|
54
|
+
thinking: bool = False,
|
|
55
|
+
):
|
|
56
|
+
return self._app.stream_sync(text, metadata=metadata, agent=self, thinking=thinking)
|
|
57
|
+
|
|
58
|
+
def __repr__(self) -> str:
|
|
59
|
+
return f"Agent(name={self.name!r})"
|
|
@@ -0,0 +1,326 @@
|
|
|
1
|
+
"""Canonical RFC 0070 AgentBuilder surface authority.
|
|
2
|
+
|
|
3
|
+
This value is parsed and compared in shadow only. The existing ``abs`` /
|
|
4
|
+
``abid`` / ``abep`` bridge remains the sole serving authority until cutover.
|
|
5
|
+
Customer editing coordinates intentionally have no representation here.
|
|
6
|
+
|
|
7
|
+
``release_status_at_issue`` is snapshot evidence, not a continuation-policy
|
|
8
|
+
decision. Immutable artifact/model-intent digests exclude never-built stale
|
|
9
|
+
reservations while a later serving milestone remains free to require active
|
|
10
|
+
status or permit an already-pinned built release.
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
from __future__ import annotations
|
|
14
|
+
|
|
15
|
+
import hashlib
|
|
16
|
+
import re
|
|
17
|
+
import unicodedata
|
|
18
|
+
from dataclasses import dataclass
|
|
19
|
+
from datetime import datetime, timezone
|
|
20
|
+
from typing import Mapping, cast
|
|
21
|
+
|
|
22
|
+
from dharta.model_contracts import ModelContractError, canonical_typed_bytes
|
|
23
|
+
|
|
24
|
+
DOMAIN = b"karta:agent-builder-surface-authority:v1\0"
|
|
25
|
+
AUTHORITY_TYPE = "agent_builder_surface_authority_v1"
|
|
26
|
+
MAX_VALIDITY_SECONDS = 3600
|
|
27
|
+
MAX_DATABASE_ID = (2**63) - 1
|
|
28
|
+
_CANONICAL_UTC = re.compile(r"\A\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{6}Z\Z")
|
|
29
|
+
_AUTHORITY_PUBLIC_ID = re.compile(r"\Aabsa_[A-Za-z0-9_-]{22,86}\Z")
|
|
30
|
+
_SESSION_IDENTITY = re.compile(r"\Aabss_[A-Za-z0-9_-]{22,86}\Z")
|
|
31
|
+
_SESSION_AUTHENTICATION_EPOCH = re.compile(r"\Aabse_[A-Za-z0-9_-]{22,86}\Z")
|
|
32
|
+
_CANONICAL_DATABASE_ID = re.compile(r"\A[1-9][0-9]*\Z")
|
|
33
|
+
_SHA256_HEX = re.compile(r"\A[0-9a-f]{64}\Z")
|
|
34
|
+
|
|
35
|
+
_ATTRIBUTE_NAMES = (
|
|
36
|
+
"authority_public_id",
|
|
37
|
+
"slot",
|
|
38
|
+
"assignment_id",
|
|
39
|
+
"assignment_revision",
|
|
40
|
+
"assignment_authorization_revision",
|
|
41
|
+
"assignment_status",
|
|
42
|
+
"agent_id",
|
|
43
|
+
"agent_organization_id",
|
|
44
|
+
"agent_authorization_revision",
|
|
45
|
+
"agent_status",
|
|
46
|
+
"release_id",
|
|
47
|
+
"release_version",
|
|
48
|
+
"release_agent_id",
|
|
49
|
+
"release_status_at_issue",
|
|
50
|
+
"release_kind",
|
|
51
|
+
"release_execution_artifact_digest",
|
|
52
|
+
"release_model_intent_manifest_digest",
|
|
53
|
+
"embed_key_id",
|
|
54
|
+
"embed_key_agent_id",
|
|
55
|
+
"embed_key_organization_id",
|
|
56
|
+
"embed_key_authorization_revision",
|
|
57
|
+
"embed_key_mode",
|
|
58
|
+
"embed_key_status",
|
|
59
|
+
"embed_key_livemode",
|
|
60
|
+
"model_policy_organization_id",
|
|
61
|
+
"billing_organization_id",
|
|
62
|
+
"organization_authorization_revision",
|
|
63
|
+
"organization_status",
|
|
64
|
+
"organization_first_party",
|
|
65
|
+
"plan_id",
|
|
66
|
+
"plan_key",
|
|
67
|
+
"plan_authorization_revision",
|
|
68
|
+
"session_identity",
|
|
69
|
+
"issued_at",
|
|
70
|
+
"expires_at",
|
|
71
|
+
"session_authentication_epoch",
|
|
72
|
+
"data_plane_install_id",
|
|
73
|
+
"data_plane_install_authorization_revision",
|
|
74
|
+
"install_kind",
|
|
75
|
+
"install_status",
|
|
76
|
+
"runtime_audience",
|
|
77
|
+
)
|
|
78
|
+
_FIELDS = frozenset(("schema_version", "authority_type", *_ATTRIBUTE_NAMES))
|
|
79
|
+
_INTEGER_FIELDS = (
|
|
80
|
+
"assignment_id",
|
|
81
|
+
"assignment_revision",
|
|
82
|
+
"assignment_authorization_revision",
|
|
83
|
+
"agent_id",
|
|
84
|
+
"agent_organization_id",
|
|
85
|
+
"agent_authorization_revision",
|
|
86
|
+
"release_version",
|
|
87
|
+
"release_agent_id",
|
|
88
|
+
"embed_key_id",
|
|
89
|
+
"embed_key_agent_id",
|
|
90
|
+
"embed_key_organization_id",
|
|
91
|
+
"embed_key_authorization_revision",
|
|
92
|
+
"model_policy_organization_id",
|
|
93
|
+
"billing_organization_id",
|
|
94
|
+
"organization_authorization_revision",
|
|
95
|
+
"plan_id",
|
|
96
|
+
"plan_authorization_revision",
|
|
97
|
+
"data_plane_install_authorization_revision",
|
|
98
|
+
)
|
|
99
|
+
_IDENTIFIER_FIELDS = (
|
|
100
|
+
"data_plane_install_id",
|
|
101
|
+
"runtime_audience",
|
|
102
|
+
)
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
def _positive_integer(value: object, field: str) -> int:
|
|
106
|
+
if type(value) is not int or cast(int, value) <= 0:
|
|
107
|
+
raise ModelContractError(f"{field} must be a positive integer")
|
|
108
|
+
return cast(int, value)
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
def _identifier(value: object, field: str) -> str:
|
|
112
|
+
if not isinstance(value, str):
|
|
113
|
+
raise ModelContractError(f"{field} must be a stable identifier")
|
|
114
|
+
try:
|
|
115
|
+
encoded = value.encode("utf-8", errors="strict")
|
|
116
|
+
except UnicodeEncodeError as exc:
|
|
117
|
+
raise ModelContractError(f"{field} must be a stable identifier") from exc
|
|
118
|
+
invalid = (
|
|
119
|
+
not encoded
|
|
120
|
+
or len(encoded) > 2048
|
|
121
|
+
or unicodedata.normalize("NFC", value) != value
|
|
122
|
+
or value != value.strip()
|
|
123
|
+
or any(character.isspace() for character in value)
|
|
124
|
+
or any(ord(character) <= 0x1F or 0x7F <= ord(character) <= 0x9F for character in value)
|
|
125
|
+
)
|
|
126
|
+
if invalid:
|
|
127
|
+
raise ModelContractError(f"{field} must be a stable identifier")
|
|
128
|
+
return value
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
def _canonical_time(value: object, field: str) -> datetime:
|
|
132
|
+
if not isinstance(value, str) or _CANONICAL_UTC.fullmatch(value) is None:
|
|
133
|
+
raise ModelContractError(f"{field} must be canonical UTC with six fractional digits")
|
|
134
|
+
try:
|
|
135
|
+
parsed = datetime.strptime(value, "%Y-%m-%dT%H:%M:%S.%fZ").replace(tzinfo=timezone.utc)
|
|
136
|
+
except ValueError as exc:
|
|
137
|
+
raise ModelContractError(
|
|
138
|
+
f"{field} must be canonical UTC with six fractional digits"
|
|
139
|
+
) from exc
|
|
140
|
+
if parsed.strftime("%Y-%m-%dT%H:%M:%S.%fZ") != value:
|
|
141
|
+
raise ModelContractError(f"{field} must be canonical UTC with six fractional digits")
|
|
142
|
+
return parsed
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
def _validate_attributes(attributes: Mapping[str, object]) -> tuple[datetime, datetime]:
|
|
146
|
+
for field in _INTEGER_FIELDS:
|
|
147
|
+
_positive_integer(attributes[field], field)
|
|
148
|
+
for field in _IDENTIFIER_FIELDS:
|
|
149
|
+
_identifier(attributes[field], field)
|
|
150
|
+
|
|
151
|
+
opaque_ids = (
|
|
152
|
+
("authority_public_id", _AUTHORITY_PUBLIC_ID),
|
|
153
|
+
("session_identity", _SESSION_IDENTITY),
|
|
154
|
+
("session_authentication_epoch", _SESSION_AUTHENTICATION_EPOCH),
|
|
155
|
+
)
|
|
156
|
+
for field, pattern in opaque_ids:
|
|
157
|
+
value = attributes[field]
|
|
158
|
+
if not isinstance(value, str) or pattern.fullmatch(value) is None:
|
|
159
|
+
raise ModelContractError(f"{field} must be a server-minted opaque id")
|
|
160
|
+
value = attributes["release_id"]
|
|
161
|
+
if (
|
|
162
|
+
not isinstance(value, str)
|
|
163
|
+
or _CANONICAL_DATABASE_ID.fullmatch(value) is None
|
|
164
|
+
or int(value) > MAX_DATABASE_ID
|
|
165
|
+
):
|
|
166
|
+
raise ModelContractError("release_id must be a canonical decimal database id")
|
|
167
|
+
for field in (
|
|
168
|
+
"release_execution_artifact_digest",
|
|
169
|
+
"release_model_intent_manifest_digest",
|
|
170
|
+
):
|
|
171
|
+
value = attributes[field]
|
|
172
|
+
if not isinstance(value, str) or _SHA256_HEX.fullmatch(value) is None:
|
|
173
|
+
raise ModelContractError(f"{field} must be lowercase SHA-256 hex")
|
|
174
|
+
for field in ("embed_key_livemode", "organization_first_party"):
|
|
175
|
+
if type(attributes[field]) is not bool:
|
|
176
|
+
raise ModelContractError(f"{field} must be a boolean")
|
|
177
|
+
|
|
178
|
+
expected_literals = {
|
|
179
|
+
"slot": "agent_builder",
|
|
180
|
+
"assignment_status": "bound",
|
|
181
|
+
"agent_status": "active",
|
|
182
|
+
"release_kind": "production",
|
|
183
|
+
"embed_key_mode": "live",
|
|
184
|
+
"embed_key_status": "active",
|
|
185
|
+
"organization_status": "active",
|
|
186
|
+
"plan_key": "internal",
|
|
187
|
+
"install_kind": "karta_operated",
|
|
188
|
+
"install_status": "active",
|
|
189
|
+
}
|
|
190
|
+
for field, expected in expected_literals.items():
|
|
191
|
+
if attributes[field] != expected:
|
|
192
|
+
raise ModelContractError(f"{field} must be {expected}")
|
|
193
|
+
if attributes["release_status_at_issue"] not in ("active", "superseded"):
|
|
194
|
+
raise ModelContractError("release must be executable")
|
|
195
|
+
if attributes["embed_key_livemode"] is not True:
|
|
196
|
+
raise ModelContractError("embed key must be live-mode")
|
|
197
|
+
if attributes["organization_first_party"] is not True:
|
|
198
|
+
raise ModelContractError("organization must be first-party")
|
|
199
|
+
|
|
200
|
+
organization_ids = {
|
|
201
|
+
cast(int, attributes["agent_organization_id"]),
|
|
202
|
+
cast(int, attributes["embed_key_organization_id"]),
|
|
203
|
+
cast(int, attributes["model_policy_organization_id"]),
|
|
204
|
+
cast(int, attributes["billing_organization_id"]),
|
|
205
|
+
}
|
|
206
|
+
if len(organization_ids) != 1:
|
|
207
|
+
raise ModelContractError(
|
|
208
|
+
"agent, embed key, model policy, and billing organization must match"
|
|
209
|
+
)
|
|
210
|
+
agent_ids = {
|
|
211
|
+
cast(int, attributes["agent_id"]),
|
|
212
|
+
cast(int, attributes["release_agent_id"]),
|
|
213
|
+
cast(int, attributes["embed_key_agent_id"]),
|
|
214
|
+
}
|
|
215
|
+
if len(agent_ids) != 1:
|
|
216
|
+
raise ModelContractError("agent, release, and embed key agent must match")
|
|
217
|
+
|
|
218
|
+
normalized_times = []
|
|
219
|
+
for field in ("issued_at", "expires_at"):
|
|
220
|
+
value = attributes[field]
|
|
221
|
+
if not isinstance(value, datetime) or value.tzinfo is None or value.utcoffset() is None:
|
|
222
|
+
raise ModelContractError(f"{field} must be a timezone-aware datetime")
|
|
223
|
+
normalized_times.append(value.astimezone(timezone.utc))
|
|
224
|
+
issued_at, expires_at = normalized_times
|
|
225
|
+
validity = (expires_at - issued_at).total_seconds()
|
|
226
|
+
if not 0 < validity <= MAX_VALIDITY_SECONDS:
|
|
227
|
+
raise ModelContractError(
|
|
228
|
+
"AgentBuilder surface authority validity must be greater than zero "
|
|
229
|
+
f"and at most {MAX_VALIDITY_SECONDS} seconds"
|
|
230
|
+
)
|
|
231
|
+
return issued_at, expires_at
|
|
232
|
+
|
|
233
|
+
|
|
234
|
+
@dataclass(frozen=True)
|
|
235
|
+
class AgentBuilderSurfaceAuthorityV1:
|
|
236
|
+
authority_public_id: str
|
|
237
|
+
slot: str
|
|
238
|
+
assignment_id: int
|
|
239
|
+
assignment_revision: int
|
|
240
|
+
assignment_authorization_revision: int
|
|
241
|
+
assignment_status: str
|
|
242
|
+
agent_id: int
|
|
243
|
+
agent_organization_id: int
|
|
244
|
+
agent_authorization_revision: int
|
|
245
|
+
agent_status: str
|
|
246
|
+
release_id: str
|
|
247
|
+
release_version: int
|
|
248
|
+
release_agent_id: int
|
|
249
|
+
release_status_at_issue: str
|
|
250
|
+
release_kind: str
|
|
251
|
+
release_execution_artifact_digest: str
|
|
252
|
+
release_model_intent_manifest_digest: str
|
|
253
|
+
embed_key_id: int
|
|
254
|
+
embed_key_agent_id: int
|
|
255
|
+
embed_key_organization_id: int
|
|
256
|
+
embed_key_authorization_revision: int
|
|
257
|
+
embed_key_mode: str
|
|
258
|
+
embed_key_status: str
|
|
259
|
+
embed_key_livemode: bool
|
|
260
|
+
model_policy_organization_id: int
|
|
261
|
+
billing_organization_id: int
|
|
262
|
+
organization_authorization_revision: int
|
|
263
|
+
organization_status: str
|
|
264
|
+
organization_first_party: bool
|
|
265
|
+
plan_id: int
|
|
266
|
+
plan_key: str
|
|
267
|
+
plan_authorization_revision: int
|
|
268
|
+
session_identity: str
|
|
269
|
+
issued_at: datetime
|
|
270
|
+
expires_at: datetime
|
|
271
|
+
session_authentication_epoch: str
|
|
272
|
+
data_plane_install_id: str
|
|
273
|
+
data_plane_install_authorization_revision: int
|
|
274
|
+
install_kind: str
|
|
275
|
+
install_status: str
|
|
276
|
+
runtime_audience: str
|
|
277
|
+
|
|
278
|
+
def __post_init__(self) -> None:
|
|
279
|
+
# The public dataclass constructor is as strict as ``from_mapping``;
|
|
280
|
+
# callers cannot create or digest an invalid authority by bypassing the
|
|
281
|
+
# wire parser.
|
|
282
|
+
issued_at, expires_at = _validate_attributes(
|
|
283
|
+
{name: getattr(self, name) for name in _ATTRIBUTE_NAMES}
|
|
284
|
+
)
|
|
285
|
+
object.__setattr__(self, "issued_at", issued_at)
|
|
286
|
+
object.__setattr__(self, "expires_at", expires_at)
|
|
287
|
+
|
|
288
|
+
@classmethod
|
|
289
|
+
def from_mapping(cls, value: object) -> "AgentBuilderSurfaceAuthorityV1":
|
|
290
|
+
if not isinstance(value, Mapping) or not all(isinstance(key, str) for key in value):
|
|
291
|
+
raise ModelContractError(
|
|
292
|
+
"AgentBuilderSurfaceAuthorityV1 payload must be an object with string keys"
|
|
293
|
+
)
|
|
294
|
+
if frozenset(value) != _FIELDS:
|
|
295
|
+
raise ModelContractError(
|
|
296
|
+
"AgentBuilderSurfaceAuthorityV1 fields do not match the V1 contract"
|
|
297
|
+
)
|
|
298
|
+
if type(value.get("schema_version")) is not int or value.get("schema_version") != 1:
|
|
299
|
+
raise ModelContractError("AgentBuilderSurfaceAuthorityV1 schema_version must be 1")
|
|
300
|
+
if value.get("authority_type") != AUTHORITY_TYPE:
|
|
301
|
+
raise ModelContractError(f"authority_type must be {AUTHORITY_TYPE}")
|
|
302
|
+
|
|
303
|
+
attributes = {name: value[name] for name in _ATTRIBUTE_NAMES}
|
|
304
|
+
issued_at = _canonical_time(attributes["issued_at"], "issued_at")
|
|
305
|
+
expires_at = _canonical_time(attributes["expires_at"], "expires_at")
|
|
306
|
+
attributes["issued_at"] = issued_at
|
|
307
|
+
attributes["expires_at"] = expires_at
|
|
308
|
+
|
|
309
|
+
return cls(**attributes)
|
|
310
|
+
|
|
311
|
+
def as_dict(self) -> dict[str, object]:
|
|
312
|
+
values: dict[str, object] = {name: getattr(self, name) for name in _ATTRIBUTE_NAMES}
|
|
313
|
+
values["issued_at"] = self.issued_at.astimezone(timezone.utc).strftime(
|
|
314
|
+
"%Y-%m-%dT%H:%M:%S.%fZ"
|
|
315
|
+
)
|
|
316
|
+
values["expires_at"] = self.expires_at.astimezone(timezone.utc).strftime(
|
|
317
|
+
"%Y-%m-%dT%H:%M:%S.%fZ"
|
|
318
|
+
)
|
|
319
|
+
return {
|
|
320
|
+
"schema_version": 1,
|
|
321
|
+
"authority_type": AUTHORITY_TYPE,
|
|
322
|
+
**values,
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
def contract_digest(self) -> str:
|
|
326
|
+
return hashlib.sha256(DOMAIN + canonical_typed_bytes(self.as_dict())).hexdigest()
|