idun-agent-schema 0.1.4__py3-none-any.whl → 0.1.5__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.

Potentially problematic release.


This version of idun-agent-schema might be problematic. Click here for more details.

@@ -1,276 +1,276 @@
1
- """Domain entities and enums for Agents and Tenants."""
2
-
3
- from datetime import UTC, datetime
4
- from enum import Enum
5
- from typing import Any
6
- from uuid import UUID, uuid4
7
-
8
- from pydantic import BaseModel, Field
9
-
10
-
11
- class AgentStatus(str, Enum):
12
- """Agent status enumeration."""
13
-
14
- DRAFT = "draft"
15
- ACTIVE = "active"
16
- INACTIVE = "inactive"
17
- DEPRECATED = "deprecated"
18
- ERROR = "error"
19
-
20
-
21
- class AgentFramework(str, Enum):
22
- """Supported agent frameworks."""
23
-
24
- LANGGRAPH = "langgraph"
25
- CREWAI = "crewai"
26
- AUTOGEN = "autogen"
27
- CUSTOM = "custom"
28
-
29
-
30
- class AgentEntity(BaseModel):
31
- """Agent domain entity."""
32
-
33
- id: UUID = Field(default_factory=uuid4)
34
- name: str = Field(..., min_length=1, max_length=255)
35
- description: str | None = Field(None, max_length=1000)
36
- framework: AgentFramework
37
- status: AgentStatus = Field(default=AgentStatus.DRAFT)
38
-
39
- # Configuration
40
- config: dict[str, Any] = Field(default_factory=dict)
41
- environment_variables: dict[str, str] = Field(default_factory=dict)
42
-
43
- # Metadata
44
- version: str = Field(default="1.0.0")
45
- tags: list[str] = Field(default_factory=list)
46
-
47
- # Tenant isolation
48
- tenant_id: UUID
49
-
50
- # Timestamps
51
- created_at: datetime
52
- updated_at: datetime
53
- deployed_at: datetime | None = None
54
-
55
- # Performance metrics
56
- total_runs: int = Field(default=0)
57
- success_rate: float | None = Field(None, ge=0.0, le=1.0)
58
- avg_response_time_ms: float | None = Field(None, ge=0)
59
-
60
- def activate(self) -> None:
61
- """Transition agent from DRAFT to ACTIVE and set deployment time."""
62
- if self.status == AgentStatus.DRAFT:
63
- self.status = AgentStatus.ACTIVE
64
- self.deployed_at = datetime.now(UTC)
65
- else:
66
- raise ValueError(f"Cannot activate agent in {self.status} status")
67
-
68
- def deactivate(self) -> None:
69
- """Transition agent from ACTIVE to INACTIVE."""
70
- if self.status == AgentStatus.ACTIVE:
71
- self.status = AgentStatus.INACTIVE
72
- else:
73
- raise ValueError(f"Cannot deactivate agent in {self.status} status")
74
-
75
- def update_metrics(self, success: bool, response_time_ms: float) -> None:
76
- """Update running success rate and average response time metrics."""
77
- self.total_runs += 1
78
-
79
- if self.success_rate is None:
80
- self.success_rate = 1.0 if success else 0.0
81
- else:
82
- current_successes = self.success_rate * (self.total_runs - 1)
83
- if success:
84
- current_successes += 1
85
- self.success_rate = current_successes / self.total_runs
86
-
87
- if self.avg_response_time_ms is None:
88
- self.avg_response_time_ms = response_time_ms
89
- else:
90
- total_time = self.avg_response_time_ms * (self.total_runs - 1)
91
- self.avg_response_time_ms = (total_time + response_time_ms) / self.total_runs
92
-
93
- def can_be_deployed(self) -> bool:
94
- """Return True if the agent is eligible for deployment."""
95
- return (
96
- self.status in [AgentStatus.DRAFT, AgentStatus.INACTIVE]
97
- and bool(self.name)
98
- and bool(self.config)
99
- )
100
-
101
-
102
- class AgentRunEntity(BaseModel):
103
- """Agent run domain entity."""
104
-
105
- id: UUID = Field(default_factory=uuid4)
106
- agent_id: UUID
107
- tenant_id: UUID
108
-
109
- # Input/Output
110
- input_data: dict[str, Any]
111
- output_data: dict[str, Any] | None = None
112
-
113
- # Execution details
114
- status: str # running, completed, failed
115
- started_at: datetime
116
- completed_at: datetime | None = None
117
- error_message: str | None = None
118
-
119
- # Performance
120
- response_time_ms: float | None = None
121
- tokens_used: int | None = None
122
- cost_usd: float | None = None
123
-
124
- # Tracing
125
- trace_id: str | None = None
126
- span_id: str | None = None
127
-
128
- def complete(self, output_data: dict[str, Any], response_time_ms: float) -> None:
129
- """Mark run as completed with outputs and timing."""
130
- self.status = "completed"
131
- self.output_data = output_data
132
- self.completed_at = datetime.now(UTC)
133
- self.response_time_ms = response_time_ms
134
-
135
- def fail(self, error_message: str) -> None:
136
- """Mark run as failed with an error message."""
137
- self.status = "failed"
138
- self.error_message = error_message
139
- self.completed_at = datetime.now(UTC)
140
-
141
- @property
142
- def is_completed(self) -> bool:
143
- """Return True if this run has finished (completed or failed)."""
144
- return self.status in ["completed", "failed"]
145
-
146
- @property
147
- def was_successful(self) -> bool:
148
- """Return True if the run completed successfully."""
149
- return self.status == "completed"
150
-
151
-
152
- class TenantStatus(str, Enum):
153
- """Tenant status enumeration."""
154
-
155
- ACTIVE = "active"
156
- SUSPENDED = "suspended"
157
- DEACTIVATED = "deactivated"
158
-
159
-
160
- class TenantPlan(str, Enum):
161
- """Tenant subscription plan."""
162
-
163
- FREE = "free"
164
- STARTER = "starter"
165
- PROFESSIONAL = "professional"
166
- ENTERPRISE = "enterprise"
167
-
168
-
169
- class TenantEntity(BaseModel):
170
- """Tenant domain entity."""
171
-
172
- id: UUID = Field(default_factory=uuid4)
173
- name: str = Field(..., min_length=1, max_length=255)
174
- slug: str = Field(..., min_length=1, max_length=100)
175
-
176
- # Contact information
177
- email: str = Field(..., description="Primary contact email")
178
- website: str | None = Field(None)
179
-
180
- # Status and plan
181
- status: TenantStatus = Field(default=TenantStatus.ACTIVE)
182
- plan: TenantPlan = Field(default=TenantPlan.FREE)
183
-
184
- # Settings
185
- settings: dict[str, Any] = Field(default_factory=dict)
186
-
187
- # Quotas and limits
188
- max_agents: int = Field(default=5)
189
- max_runs_per_month: int = Field(default=1000)
190
- max_storage_mb: int = Field(default=100)
191
-
192
- # Usage tracking
193
- current_agents: int = Field(default=0)
194
- current_runs_this_month: int = Field(default=0)
195
- current_storage_mb: float = Field(default=0.0)
196
-
197
- # Timestamps
198
- created_at: datetime
199
- updated_at: datetime
200
- suspended_at: datetime | None = None
201
-
202
- def can_create_agent(self) -> bool:
203
- """Return True if tenant has capacity to create a new agent."""
204
- return self.status == TenantStatus.ACTIVE and self.current_agents < self.max_agents
205
-
206
- def can_run_agent(self) -> bool:
207
- """Return True if tenant is below monthly run quota."""
208
- return self.status == TenantStatus.ACTIVE and self.current_runs_this_month < self.max_runs_per_month
209
-
210
- def suspend(self, reason: str) -> None:
211
- """Suspend tenant and record the reason."""
212
- self.status = TenantStatus.SUSPENDED
213
- self.suspended_at = datetime.utcnow()
214
- self.settings["suspension_reason"] = reason
215
-
216
- def reactivate(self) -> None:
217
- """Reactivate a previously suspended tenant."""
218
- if self.status == TenantStatus.SUSPENDED:
219
- self.status = TenantStatus.ACTIVE
220
- self.suspended_at = None
221
- if "suspension_reason" in self.settings:
222
- del self.settings["suspension_reason"]
223
-
224
- def upgrade_plan(self, new_plan: TenantPlan) -> None:
225
- """Upgrade plan and adjust quotas accordingly."""
226
- self.plan = new_plan
227
- if new_plan == TenantPlan.STARTER:
228
- self.max_agents = 20
229
- self.max_runs_per_month = 10000
230
- self.max_storage_mb = 1000
231
- elif new_plan == TenantPlan.PROFESSIONAL:
232
- self.max_agents = 100
233
- self.max_runs_per_month = 100000
234
- self.max_storage_mb = 10000
235
- elif new_plan == TenantPlan.ENTERPRISE:
236
- self.max_agents = 1000
237
- self.max_runs_per_month = 1000000
238
- self.max_storage_mb = 100000
239
-
240
- def increment_usage(self, agents: int = 0, runs: int = 0, storage_mb: float = 0) -> None:
241
- """Increment tracked usage counters."""
242
- self.current_agents += agents
243
- self.current_runs_this_month += runs
244
- self.current_storage_mb += storage_mb
245
-
246
- def reset_monthly_usage(self) -> None:
247
- """Reset monthly run counter at billing cycle start."""
248
- self.current_runs_this_month = 0
249
-
250
-
251
- class TenantUserEntity(BaseModel):
252
- """Tenant user domain entity for multi-user tenants."""
253
-
254
- id: UUID = Field(default_factory=uuid4)
255
- tenant_id: UUID
256
- user_id: str
257
- email: str
258
-
259
- # Role and permissions
260
- role: str = Field(default="member")
261
- permissions: list[str] = Field(default_factory=list)
262
-
263
- # Status
264
- is_active: bool = Field(default=True)
265
-
266
- # Timestamps
267
- joined_at: datetime
268
- last_active_at: datetime | None = None
269
-
270
- def has_permission(self, permission: str) -> bool:
271
- """Return True if the user has an explicit permission or is owner."""
272
- return permission in self.permissions or self.role == "owner"
273
-
274
- def is_admin(self) -> bool:
275
- """Return True if the user is an admin or owner."""
276
- return self.role in ["owner", "admin"]
1
+ """Domain entities and enums for Agents and Tenants."""
2
+
3
+ from datetime import UTC, datetime
4
+ from enum import Enum
5
+ from typing import Any
6
+ from uuid import UUID, uuid4
7
+
8
+ from pydantic import BaseModel, Field
9
+
10
+
11
+ class AgentStatus(str, Enum):
12
+ """Agent status enumeration."""
13
+
14
+ DRAFT = "draft"
15
+ ACTIVE = "active"
16
+ INACTIVE = "inactive"
17
+ DEPRECATED = "deprecated"
18
+ ERROR = "error"
19
+
20
+
21
+ class AgentFramework(str, Enum):
22
+ """Supported agent frameworks."""
23
+
24
+ LANGGRAPH = "langgraph"
25
+ CREWAI = "crewai"
26
+ AUTOGEN = "autogen"
27
+ CUSTOM = "custom"
28
+
29
+
30
+ class AgentEntity(BaseModel):
31
+ """Agent domain entity."""
32
+
33
+ id: UUID = Field(default_factory=uuid4)
34
+ name: str = Field(..., min_length=1, max_length=255)
35
+ description: str | None = Field(None, max_length=1000)
36
+ framework: AgentFramework
37
+ status: AgentStatus = Field(default=AgentStatus.DRAFT)
38
+
39
+ # Configuration
40
+ config: dict[str, Any] = Field(default_factory=dict)
41
+ environment_variables: dict[str, str] = Field(default_factory=dict)
42
+
43
+ # Metadata
44
+ version: str = Field(default="1.0.0")
45
+ tags: list[str] = Field(default_factory=list)
46
+
47
+ # Tenant isolation
48
+ tenant_id: UUID
49
+
50
+ # Timestamps
51
+ created_at: datetime
52
+ updated_at: datetime
53
+ deployed_at: datetime | None = None
54
+
55
+ # Performance metrics
56
+ total_runs: int = Field(default=0)
57
+ success_rate: float | None = Field(None, ge=0.0, le=1.0)
58
+ avg_response_time_ms: float | None = Field(None, ge=0)
59
+
60
+ def activate(self) -> None:
61
+ """Transition agent from DRAFT to ACTIVE and set deployment time."""
62
+ if self.status == AgentStatus.DRAFT:
63
+ self.status = AgentStatus.ACTIVE
64
+ self.deployed_at = datetime.now(UTC)
65
+ else:
66
+ raise ValueError(f"Cannot activate agent in {self.status} status")
67
+
68
+ def deactivate(self) -> None:
69
+ """Transition agent from ACTIVE to INACTIVE."""
70
+ if self.status == AgentStatus.ACTIVE:
71
+ self.status = AgentStatus.INACTIVE
72
+ else:
73
+ raise ValueError(f"Cannot deactivate agent in {self.status} status")
74
+
75
+ def update_metrics(self, success: bool, response_time_ms: float) -> None:
76
+ """Update running success rate and average response time metrics."""
77
+ self.total_runs += 1
78
+
79
+ if self.success_rate is None:
80
+ self.success_rate = 1.0 if success else 0.0
81
+ else:
82
+ current_successes = self.success_rate * (self.total_runs - 1)
83
+ if success:
84
+ current_successes += 1
85
+ self.success_rate = current_successes / self.total_runs
86
+
87
+ if self.avg_response_time_ms is None:
88
+ self.avg_response_time_ms = response_time_ms
89
+ else:
90
+ total_time = self.avg_response_time_ms * (self.total_runs - 1)
91
+ self.avg_response_time_ms = (total_time + response_time_ms) / self.total_runs
92
+
93
+ def can_be_deployed(self) -> bool:
94
+ """Return True if the agent is eligible for deployment."""
95
+ return (
96
+ self.status in [AgentStatus.DRAFT, AgentStatus.INACTIVE]
97
+ and bool(self.name)
98
+ and bool(self.config)
99
+ )
100
+
101
+
102
+ class AgentRunEntity(BaseModel):
103
+ """Agent run domain entity."""
104
+
105
+ id: UUID = Field(default_factory=uuid4)
106
+ agent_id: UUID
107
+ tenant_id: UUID
108
+
109
+ # Input/Output
110
+ input_data: dict[str, Any]
111
+ output_data: dict[str, Any] | None = None
112
+
113
+ # Execution details
114
+ status: str # running, completed, failed
115
+ started_at: datetime
116
+ completed_at: datetime | None = None
117
+ error_message: str | None = None
118
+
119
+ # Performance
120
+ response_time_ms: float | None = None
121
+ tokens_used: int | None = None
122
+ cost_usd: float | None = None
123
+
124
+ # Tracing
125
+ trace_id: str | None = None
126
+ span_id: str | None = None
127
+
128
+ def complete(self, output_data: dict[str, Any], response_time_ms: float) -> None:
129
+ """Mark run as completed with outputs and timing."""
130
+ self.status = "completed"
131
+ self.output_data = output_data
132
+ self.completed_at = datetime.now(UTC)
133
+ self.response_time_ms = response_time_ms
134
+
135
+ def fail(self, error_message: str) -> None:
136
+ """Mark run as failed with an error message."""
137
+ self.status = "failed"
138
+ self.error_message = error_message
139
+ self.completed_at = datetime.now(UTC)
140
+
141
+ @property
142
+ def is_completed(self) -> bool:
143
+ """Return True if this run has finished (completed or failed)."""
144
+ return self.status in ["completed", "failed"]
145
+
146
+ @property
147
+ def was_successful(self) -> bool:
148
+ """Return True if the run completed successfully."""
149
+ return self.status == "completed"
150
+
151
+
152
+ class TenantStatus(str, Enum):
153
+ """Tenant status enumeration."""
154
+
155
+ ACTIVE = "active"
156
+ SUSPENDED = "suspended"
157
+ DEACTIVATED = "deactivated"
158
+
159
+
160
+ class TenantPlan(str, Enum):
161
+ """Tenant subscription plan."""
162
+
163
+ FREE = "free"
164
+ STARTER = "starter"
165
+ PROFESSIONAL = "professional"
166
+ ENTERPRISE = "enterprise"
167
+
168
+
169
+ class TenantEntity(BaseModel):
170
+ """Tenant domain entity."""
171
+
172
+ id: UUID = Field(default_factory=uuid4)
173
+ name: str = Field(..., min_length=1, max_length=255)
174
+ slug: str = Field(..., min_length=1, max_length=100)
175
+
176
+ # Contact information
177
+ email: str = Field(..., description="Primary contact email")
178
+ website: str | None = Field(None)
179
+
180
+ # Status and plan
181
+ status: TenantStatus = Field(default=TenantStatus.ACTIVE)
182
+ plan: TenantPlan = Field(default=TenantPlan.FREE)
183
+
184
+ # Settings
185
+ settings: dict[str, Any] = Field(default_factory=dict)
186
+
187
+ # Quotas and limits
188
+ max_agents: int = Field(default=5)
189
+ max_runs_per_month: int = Field(default=1000)
190
+ max_storage_mb: int = Field(default=100)
191
+
192
+ # Usage tracking
193
+ current_agents: int = Field(default=0)
194
+ current_runs_this_month: int = Field(default=0)
195
+ current_storage_mb: float = Field(default=0.0)
196
+
197
+ # Timestamps
198
+ created_at: datetime
199
+ updated_at: datetime
200
+ suspended_at: datetime | None = None
201
+
202
+ def can_create_agent(self) -> bool:
203
+ """Return True if tenant has capacity to create a new agent."""
204
+ return self.status == TenantStatus.ACTIVE and self.current_agents < self.max_agents
205
+
206
+ def can_run_agent(self) -> bool:
207
+ """Return True if tenant is below monthly run quota."""
208
+ return self.status == TenantStatus.ACTIVE and self.current_runs_this_month < self.max_runs_per_month
209
+
210
+ def suspend(self, reason: str) -> None:
211
+ """Suspend tenant and record the reason."""
212
+ self.status = TenantStatus.SUSPENDED
213
+ self.suspended_at = datetime.utcnow()
214
+ self.settings["suspension_reason"] = reason
215
+
216
+ def reactivate(self) -> None:
217
+ """Reactivate a previously suspended tenant."""
218
+ if self.status == TenantStatus.SUSPENDED:
219
+ self.status = TenantStatus.ACTIVE
220
+ self.suspended_at = None
221
+ if "suspension_reason" in self.settings:
222
+ del self.settings["suspension_reason"]
223
+
224
+ def upgrade_plan(self, new_plan: TenantPlan) -> None:
225
+ """Upgrade plan and adjust quotas accordingly."""
226
+ self.plan = new_plan
227
+ if new_plan == TenantPlan.STARTER:
228
+ self.max_agents = 20
229
+ self.max_runs_per_month = 10000
230
+ self.max_storage_mb = 1000
231
+ elif new_plan == TenantPlan.PROFESSIONAL:
232
+ self.max_agents = 100
233
+ self.max_runs_per_month = 100000
234
+ self.max_storage_mb = 10000
235
+ elif new_plan == TenantPlan.ENTERPRISE:
236
+ self.max_agents = 1000
237
+ self.max_runs_per_month = 1000000
238
+ self.max_storage_mb = 100000
239
+
240
+ def increment_usage(self, agents: int = 0, runs: int = 0, storage_mb: float = 0) -> None:
241
+ """Increment tracked usage counters."""
242
+ self.current_agents += agents
243
+ self.current_runs_this_month += runs
244
+ self.current_storage_mb += storage_mb
245
+
246
+ def reset_monthly_usage(self) -> None:
247
+ """Reset monthly run counter at billing cycle start."""
248
+ self.current_runs_this_month = 0
249
+
250
+
251
+ class TenantUserEntity(BaseModel):
252
+ """Tenant user domain entity for multi-user tenants."""
253
+
254
+ id: UUID = Field(default_factory=uuid4)
255
+ tenant_id: UUID
256
+ user_id: str
257
+ email: str
258
+
259
+ # Role and permissions
260
+ role: str = Field(default="member")
261
+ permissions: list[str] = Field(default_factory=list)
262
+
263
+ # Status
264
+ is_active: bool = Field(default=True)
265
+
266
+ # Timestamps
267
+ joined_at: datetime
268
+ last_active_at: datetime | None = None
269
+
270
+ def has_permission(self, permission: str) -> bool:
271
+ """Return True if the user has an explicit permission or is owner."""
272
+ return permission in self.permissions or self.role == "owner"
273
+
274
+ def is_admin(self) -> bool:
275
+ """Return True if the user is an admin or owner."""
276
+ return self.role in ["owner", "admin"]