idun-agent-schema 0.1.0__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.
Potentially problematic release.
This version of idun-agent-schema might be problematic. Click here for more details.
- idun_agent_schema-0.1.0/.gitignore +201 -0
- idun_agent_schema-0.1.0/PKG-INFO +40 -0
- idun_agent_schema-0.1.0/README.md +20 -0
- idun_agent_schema-0.1.0/pyproject.toml +76 -0
- idun_agent_schema-0.1.0/src/idun_agent_schema/__init__.py +12 -0
- idun_agent_schema-0.1.0/src/idun_agent_schema/engine/__init__.py +13 -0
- idun_agent_schema-0.1.0/src/idun_agent_schema/engine/agent.py +18 -0
- idun_agent_schema-0.1.0/src/idun_agent_schema/engine/api.py +15 -0
- idun_agent_schema-0.1.0/src/idun_agent_schema/engine/config.py +27 -0
- idun_agent_schema-0.1.0/src/idun_agent_schema/engine/langgraph.py +42 -0
- idun_agent_schema-0.1.0/src/idun_agent_schema/engine/server.py +17 -0
- idun_agent_schema-0.1.0/src/idun_agent_schema/manager/__init__.py +52 -0
- idun_agent_schema-0.1.0/src/idun_agent_schema/manager/api.py +130 -0
- idun_agent_schema-0.1.0/src/idun_agent_schema/manager/deployments.py +14 -0
- idun_agent_schema-0.1.0/src/idun_agent_schema/manager/deps.py +16 -0
- idun_agent_schema-0.1.0/src/idun_agent_schema/manager/domain.py +261 -0
- idun_agent_schema-0.1.0/src/idun_agent_schema/manager/dto.py +109 -0
- idun_agent_schema-0.1.0/src/idun_agent_schema/manager/errors.py +24 -0
- idun_agent_schema-0.1.0/src/idun_agent_schema/manager/settings.py +139 -0
- idun_agent_schema-0.1.0/src/idun_agent_schema/py.typed +2 -0
- idun_agent_schema-0.1.0/src/idun_agent_schema/shared/__init__.py +5 -0
- idun_agent_schema-0.1.0/src/idun_agent_schema/shared/observability.py +58 -0
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
# Byte-compiled / optimized / DLL files
|
|
2
|
+
__pycache__/
|
|
3
|
+
*.py[cod]
|
|
4
|
+
*$py.class
|
|
5
|
+
|
|
6
|
+
# C extensions
|
|
7
|
+
*.so
|
|
8
|
+
|
|
9
|
+
# Distribution / packaging
|
|
10
|
+
.Python
|
|
11
|
+
build/
|
|
12
|
+
develop-eggs/
|
|
13
|
+
dist/
|
|
14
|
+
downloads/
|
|
15
|
+
eggs/
|
|
16
|
+
.eggs/
|
|
17
|
+
lib/
|
|
18
|
+
lib64/
|
|
19
|
+
parts/
|
|
20
|
+
sdist/
|
|
21
|
+
var/
|
|
22
|
+
wheels/
|
|
23
|
+
share/python-wheels/
|
|
24
|
+
*.egg-info/
|
|
25
|
+
.installed.cfg
|
|
26
|
+
*.egg
|
|
27
|
+
MANIFEST
|
|
28
|
+
|
|
29
|
+
# PyInstaller
|
|
30
|
+
# Usually these files are written by a python script from a template
|
|
31
|
+
# before PyInstaller builds the exe, so as to inject date/other infos into it.
|
|
32
|
+
*.manifest
|
|
33
|
+
*.spec
|
|
34
|
+
|
|
35
|
+
# Installer logs
|
|
36
|
+
pip-log.txt
|
|
37
|
+
pip-delete-this-directory.txt
|
|
38
|
+
|
|
39
|
+
# Unit test / coverage reports
|
|
40
|
+
htmlcov/
|
|
41
|
+
.tox/
|
|
42
|
+
.nox/
|
|
43
|
+
.coverage
|
|
44
|
+
.coverage.*
|
|
45
|
+
.cache
|
|
46
|
+
nosetests.xml
|
|
47
|
+
coverage.xml
|
|
48
|
+
*.cover
|
|
49
|
+
*.py,cover
|
|
50
|
+
.hypothesis/
|
|
51
|
+
.pytest_cache/
|
|
52
|
+
cover/
|
|
53
|
+
|
|
54
|
+
# Translations
|
|
55
|
+
*.mo
|
|
56
|
+
*.pot
|
|
57
|
+
|
|
58
|
+
# Django stuff:
|
|
59
|
+
*.log
|
|
60
|
+
local_settings.py
|
|
61
|
+
db.sqlite3
|
|
62
|
+
db.sqlite3-journal
|
|
63
|
+
|
|
64
|
+
# Flask stuff:
|
|
65
|
+
instance/
|
|
66
|
+
.webassets-cache
|
|
67
|
+
|
|
68
|
+
# Scrapy stuff:
|
|
69
|
+
.scrapy
|
|
70
|
+
|
|
71
|
+
# Sphinx documentation
|
|
72
|
+
docs/_build/
|
|
73
|
+
|
|
74
|
+
# PyBuilder
|
|
75
|
+
.pybuilder/
|
|
76
|
+
target/
|
|
77
|
+
|
|
78
|
+
# Jupyter Notebook
|
|
79
|
+
.ipynb_checkpoints
|
|
80
|
+
|
|
81
|
+
# IPython
|
|
82
|
+
profile_default/
|
|
83
|
+
ipython_config.py
|
|
84
|
+
|
|
85
|
+
# pyenv
|
|
86
|
+
# For a library or package, you might want to ignore these files since the code is
|
|
87
|
+
# intended to run in multiple environments; otherwise, check them in:
|
|
88
|
+
# .python-version
|
|
89
|
+
|
|
90
|
+
# pipenv
|
|
91
|
+
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
|
|
92
|
+
# However, in case of collaboration, if having platform-specific dependencies or dependencies
|
|
93
|
+
# having no cross-platform support, pipenv may install dependencies that don't work, or not
|
|
94
|
+
# install all needed dependencies.
|
|
95
|
+
#Pipfile.lock
|
|
96
|
+
|
|
97
|
+
# UV
|
|
98
|
+
# Similar to Pipfile.lock, it is generally recommended to include uv.lock in version control.
|
|
99
|
+
# This is especially recommended for binary packages to ensure reproducibility, and is more
|
|
100
|
+
# commonly ignored for libraries.
|
|
101
|
+
#uv.lock
|
|
102
|
+
|
|
103
|
+
# poetry
|
|
104
|
+
# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
|
|
105
|
+
# This is especially recommended for binary packages to ensure reproducibility, and is more
|
|
106
|
+
# commonly ignored for libraries.
|
|
107
|
+
# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
|
|
108
|
+
#poetry.lock
|
|
109
|
+
|
|
110
|
+
# pdm
|
|
111
|
+
# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
|
|
112
|
+
#pdm.lock
|
|
113
|
+
# pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it
|
|
114
|
+
# in version control.
|
|
115
|
+
# https://pdm.fming.dev/latest/usage/project/#working-with-version-control
|
|
116
|
+
.pdm.toml
|
|
117
|
+
.pdm-python
|
|
118
|
+
.pdm-build/
|
|
119
|
+
|
|
120
|
+
# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
|
|
121
|
+
__pypackages__/
|
|
122
|
+
|
|
123
|
+
# Celery stuff
|
|
124
|
+
celerybeat-schedule
|
|
125
|
+
celerybeat.pid
|
|
126
|
+
|
|
127
|
+
# SageMath parsed files
|
|
128
|
+
*.sage.py
|
|
129
|
+
|
|
130
|
+
# Environments
|
|
131
|
+
.env
|
|
132
|
+
.venv
|
|
133
|
+
env/
|
|
134
|
+
venv/
|
|
135
|
+
ENV/
|
|
136
|
+
env.bak/
|
|
137
|
+
venv.bak/
|
|
138
|
+
|
|
139
|
+
# Spyder project settings
|
|
140
|
+
.spyderproject
|
|
141
|
+
.spyproject
|
|
142
|
+
|
|
143
|
+
# Rope project settings
|
|
144
|
+
.ropeproject
|
|
145
|
+
|
|
146
|
+
# mkdocs documentation
|
|
147
|
+
/site
|
|
148
|
+
|
|
149
|
+
# mypy
|
|
150
|
+
.mypy_cache/
|
|
151
|
+
.dmypy.json
|
|
152
|
+
dmypy.json
|
|
153
|
+
|
|
154
|
+
# Pyre type checker
|
|
155
|
+
.pyre/
|
|
156
|
+
|
|
157
|
+
# pytype static type analyzer
|
|
158
|
+
.pytype/
|
|
159
|
+
|
|
160
|
+
# Cython debug symbols
|
|
161
|
+
cython_debug/
|
|
162
|
+
|
|
163
|
+
# PyCharm
|
|
164
|
+
# JetBrains specific template is maintained in a separate JetBrains.gitignore that can
|
|
165
|
+
# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
|
|
166
|
+
# and can be added to the global gitignore or merged into this file. For a more nuclear
|
|
167
|
+
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
|
|
168
|
+
#.idea/
|
|
169
|
+
|
|
170
|
+
# Ruff stuff:
|
|
171
|
+
.ruff_cache/
|
|
172
|
+
|
|
173
|
+
# PyPI configuration file
|
|
174
|
+
.pypirc
|
|
175
|
+
|
|
176
|
+
# Database files
|
|
177
|
+
*.db
|
|
178
|
+
*.db-shm
|
|
179
|
+
*.db-wai
|
|
180
|
+
*.db-journal*
|
|
181
|
+
*.sqlite
|
|
182
|
+
*.sqlite3
|
|
183
|
+
agents.db
|
|
184
|
+
checkpoint_async.db
|
|
185
|
+
|
|
186
|
+
# Python virtual environment
|
|
187
|
+
.venv/
|
|
188
|
+
venv/
|
|
189
|
+
ENV/
|
|
190
|
+
|
|
191
|
+
# Python cache
|
|
192
|
+
__pycache__/
|
|
193
|
+
*.pyc
|
|
194
|
+
*.pyo
|
|
195
|
+
*.pyd
|
|
196
|
+
|
|
197
|
+
# Poetry
|
|
198
|
+
poetry.lock
|
|
199
|
+
|
|
200
|
+
# Environment variables
|
|
201
|
+
.env
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: idun-agent-schema
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Centralized Pydantic schema library for Idun Agent Engine and Manager
|
|
5
|
+
Project-URL: Homepage, https://github.com/geoffreyharrazi/idun-agent-platform
|
|
6
|
+
Project-URL: Repository, https://github.com/geoffreyharrazi/idun-agent-platform
|
|
7
|
+
Project-URL: Issues, https://github.com/geoffreyharrazi/idun-agent-platform/issues
|
|
8
|
+
Author-email: Idun Team <team@idun.com>
|
|
9
|
+
License-Expression: MIT
|
|
10
|
+
Keywords: fastapi,idun,langgraph,pydantic,schemas
|
|
11
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
12
|
+
Classifier: Programming Language :: Python :: 3
|
|
13
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
15
|
+
Classifier: Typing :: Typed
|
|
16
|
+
Requires-Python: <3.14,>=3.13
|
|
17
|
+
Requires-Dist: pydantic-settings<3.0.0,>=2.7.0
|
|
18
|
+
Requires-Dist: pydantic<3.0.0,>=2.11.7
|
|
19
|
+
Description-Content-Type: text/markdown
|
|
20
|
+
|
|
21
|
+
# Idun Agent Schema
|
|
22
|
+
|
|
23
|
+
Centralized Pydantic schema library shared by Idun Agent Engine and Idun Agent Manager.
|
|
24
|
+
|
|
25
|
+
## Install
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
pip install idun-agent-schema
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
## Usage
|
|
32
|
+
|
|
33
|
+
```python
|
|
34
|
+
from idun_agent_schema.engine import EngineConfig
|
|
35
|
+
from idun_agent_schema.manager.api import AgentCreateRequest
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
This package re-exports stable schema namespaces to avoid breaking existing imports. Prefer importing from this package directly going forward.
|
|
39
|
+
|
|
40
|
+
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
# Idun Agent Schema
|
|
2
|
+
|
|
3
|
+
Centralized Pydantic schema library shared by Idun Agent Engine and Idun Agent Manager.
|
|
4
|
+
|
|
5
|
+
## Install
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
pip install idun-agent-schema
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Usage
|
|
12
|
+
|
|
13
|
+
```python
|
|
14
|
+
from idun_agent_schema.engine import EngineConfig
|
|
15
|
+
from idun_agent_schema.manager.api import AgentCreateRequest
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
This package re-exports stable schema namespaces to avoid breaking existing imports. Prefer importing from this package directly going forward.
|
|
19
|
+
|
|
20
|
+
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "idun-agent-schema"
|
|
3
|
+
version = "0.1.0"
|
|
4
|
+
description = "Centralized Pydantic schema library for Idun Agent Engine and Manager"
|
|
5
|
+
authors = [{ name = "Idun Team", email = "team@idun.com" }]
|
|
6
|
+
readme = "README.md"
|
|
7
|
+
license = "MIT"
|
|
8
|
+
requires-python = ">=3.13,<3.14"
|
|
9
|
+
keywords = ["pydantic", "schemas", "fastapi", "langgraph", "idun"]
|
|
10
|
+
classifiers = [
|
|
11
|
+
"License :: OSI Approved :: MIT License",
|
|
12
|
+
"Programming Language :: Python :: 3",
|
|
13
|
+
"Programming Language :: Python :: 3 :: Only",
|
|
14
|
+
"Programming Language :: Python :: 3.13",
|
|
15
|
+
"Typing :: Typed",
|
|
16
|
+
]
|
|
17
|
+
dependencies = [
|
|
18
|
+
"pydantic>=2.11.7,<3.0.0",
|
|
19
|
+
"pydantic-settings>=2.7.0,<3.0.0",
|
|
20
|
+
]
|
|
21
|
+
|
|
22
|
+
[project.urls]
|
|
23
|
+
Homepage = "https://github.com/geoffreyharrazi/idun-agent-platform"
|
|
24
|
+
Repository = "https://github.com/geoffreyharrazi/idun-agent-platform"
|
|
25
|
+
Issues = "https://github.com/geoffreyharrazi/idun-agent-platform/issues"
|
|
26
|
+
|
|
27
|
+
[build-system]
|
|
28
|
+
requires = ["hatchling>=1.25.0"]
|
|
29
|
+
build-backend = "hatchling.build"
|
|
30
|
+
|
|
31
|
+
[tool.hatch.build.targets.sdist]
|
|
32
|
+
include = [
|
|
33
|
+
"src/idun_agent_schema",
|
|
34
|
+
"src/idun_agent_schema/py.typed",
|
|
35
|
+
]
|
|
36
|
+
|
|
37
|
+
[tool.hatch.build.targets.wheel]
|
|
38
|
+
include = [
|
|
39
|
+
"src/idun_agent_schema",
|
|
40
|
+
"src/idun_agent_schema/py.typed",
|
|
41
|
+
]
|
|
42
|
+
|
|
43
|
+
[tool.hatch.build.targets.wheel.sources]
|
|
44
|
+
"src/idun_agent_schema" = "idun_agent_schema"
|
|
45
|
+
|
|
46
|
+
[tool.ruff]
|
|
47
|
+
target-version = "py313"
|
|
48
|
+
line-length = 88
|
|
49
|
+
|
|
50
|
+
[tool.ruff.lint]
|
|
51
|
+
select = [
|
|
52
|
+
"E", "F", "W", "I", "B", "UP", "N", "SIM", "D"
|
|
53
|
+
]
|
|
54
|
+
extend-ignore = ["E501"]
|
|
55
|
+
|
|
56
|
+
[tool.black]
|
|
57
|
+
line-length = 88
|
|
58
|
+
target-version = ["py313"]
|
|
59
|
+
include = "\\.pyi?$"
|
|
60
|
+
|
|
61
|
+
[tool.mypy]
|
|
62
|
+
python_version = "3.13"
|
|
63
|
+
warn_return_any = true
|
|
64
|
+
warn_unused_ignores = true
|
|
65
|
+
ignore_missing_imports = true
|
|
66
|
+
strict_optional = true
|
|
67
|
+
no_implicit_optional = true
|
|
68
|
+
check_untyped_defs = true
|
|
69
|
+
pretty = true
|
|
70
|
+
show_error_codes = true
|
|
71
|
+
|
|
72
|
+
[tool.pytest.ini_options]
|
|
73
|
+
addopts = "-ra -q --strict-markers --disable-warnings"
|
|
74
|
+
testpaths = ["tests"]
|
|
75
|
+
|
|
76
|
+
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
"""Idun Agent Schema - Centralized Pydantic schemas.
|
|
2
|
+
|
|
3
|
+
Public namespaces:
|
|
4
|
+
- idun_agent_schema.engine: Engine-related schemas
|
|
5
|
+
- idun_agent_schema.manager: Manager-related schemas
|
|
6
|
+
- idun_agent_schema.shared: Shared cross-cutting schemas
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
# Re-export key types for convenience
|
|
10
|
+
from .shared.observability import ObservabilityConfig # noqa: F401
|
|
11
|
+
|
|
12
|
+
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
"""Engine-related schemas."""
|
|
2
|
+
|
|
3
|
+
from .agent import BaseAgentConfig # noqa: F401
|
|
4
|
+
from .api import ChatRequest, ChatResponse # noqa: F401
|
|
5
|
+
from .config import AgentConfig, EngineConfig # noqa: F401
|
|
6
|
+
from .langgraph import ( # noqa: F401
|
|
7
|
+
CheckpointConfig,
|
|
8
|
+
LangGraphAgentConfig,
|
|
9
|
+
SqliteCheckpointConfig,
|
|
10
|
+
)
|
|
11
|
+
from .server import ServerAPIConfig, ServerConfig # noqa: F401
|
|
12
|
+
|
|
13
|
+
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
"""Common agent model definitions (engine)."""
|
|
2
|
+
|
|
3
|
+
from typing import Any
|
|
4
|
+
|
|
5
|
+
from pydantic import BaseModel, Field
|
|
6
|
+
|
|
7
|
+
from idun_agent_schema.shared import ObservabilityConfig
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class BaseAgentConfig(BaseModel):
|
|
11
|
+
"""Base model for agent configurations. Extend for specific frameworks."""
|
|
12
|
+
|
|
13
|
+
name: str | None = Field(default="Unnamed Agent")
|
|
14
|
+
input_schema_definition: dict[str, Any] | None = Field(default_factory=dict)
|
|
15
|
+
output_schema_definition: dict[str, Any] | None = Field(default_factory=dict)
|
|
16
|
+
observability: ObservabilityConfig | None = Field(default=None)
|
|
17
|
+
|
|
18
|
+
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
"""Top-level engine configuration models."""
|
|
2
|
+
|
|
3
|
+
from typing import Literal
|
|
4
|
+
|
|
5
|
+
from pydantic import BaseModel, Field
|
|
6
|
+
|
|
7
|
+
from .agent import BaseAgentConfig
|
|
8
|
+
from .langgraph import LangGraphAgentConfig
|
|
9
|
+
from .server import ServerConfig
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class AgentConfig(BaseModel):
|
|
13
|
+
"""Configuration for agent specification and settings."""
|
|
14
|
+
|
|
15
|
+
type: Literal["langgraph", "ADK", "CREWAI"] = Field(default="langgraph")
|
|
16
|
+
config: BaseAgentConfig | LangGraphAgentConfig = Field(
|
|
17
|
+
default_factory=BaseAgentConfig
|
|
18
|
+
)
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class EngineConfig(BaseModel):
|
|
22
|
+
"""Main engine configuration model for the entire Idun Agent Engine."""
|
|
23
|
+
|
|
24
|
+
server: ServerConfig = Field(default_factory=ServerConfig)
|
|
25
|
+
agent: AgentConfig
|
|
26
|
+
|
|
27
|
+
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
"""Configuration models for LangGraph agents (engine)."""
|
|
2
|
+
|
|
3
|
+
from typing import Any, Literal
|
|
4
|
+
from urllib.parse import urlparse
|
|
5
|
+
|
|
6
|
+
from pydantic import BaseModel, field_validator
|
|
7
|
+
|
|
8
|
+
from .agent import BaseAgentConfig
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class SqliteCheckpointConfig(BaseModel):
|
|
12
|
+
"""Configuration for SQLite checkpointer."""
|
|
13
|
+
|
|
14
|
+
type: Literal["sqlite"]
|
|
15
|
+
db_url: str
|
|
16
|
+
|
|
17
|
+
@field_validator("db_url")
|
|
18
|
+
@classmethod
|
|
19
|
+
def db_url_must_be_sqlite(cls, v: str) -> str:
|
|
20
|
+
if not v.startswith("sqlite:///"):
|
|
21
|
+
raise ValueError("SQLite DB URL must start with 'sqlite:///'")
|
|
22
|
+
return v
|
|
23
|
+
|
|
24
|
+
@property
|
|
25
|
+
def db_path(self) -> str:
|
|
26
|
+
path = urlparse(self.db_url).path
|
|
27
|
+
if self.db_url.startswith("sqlite:///"):
|
|
28
|
+
return path.lstrip("/")
|
|
29
|
+
return path
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
CheckpointConfig = SqliteCheckpointConfig
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
class LangGraphAgentConfig(BaseAgentConfig):
|
|
36
|
+
"""Configuration model for LangGraph agents."""
|
|
37
|
+
|
|
38
|
+
graph_definition: str
|
|
39
|
+
checkpointer: CheckpointConfig | None = None
|
|
40
|
+
store: dict[str, Any] | None = None
|
|
41
|
+
|
|
42
|
+
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
"""Server configuration models (engine)."""
|
|
2
|
+
|
|
3
|
+
from pydantic import BaseModel, Field
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class ServerAPIConfig(BaseModel):
|
|
7
|
+
"""API server configuration."""
|
|
8
|
+
|
|
9
|
+
port: int = 8000
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class ServerConfig(BaseModel):
|
|
13
|
+
"""Configuration for the Engine's universal settings."""
|
|
14
|
+
|
|
15
|
+
api: ServerAPIConfig = Field(default_factory=ServerAPIConfig)
|
|
16
|
+
|
|
17
|
+
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
"""Manager-related schemas."""
|
|
2
|
+
|
|
3
|
+
from .api import ( # noqa: F401
|
|
4
|
+
AgentCreateRequest,
|
|
5
|
+
AgentResponse,
|
|
6
|
+
AgentRunRequest,
|
|
7
|
+
AgentRunResponse,
|
|
8
|
+
AgentRunSummaryResponse,
|
|
9
|
+
AgentStatsResponse,
|
|
10
|
+
AgentSummaryResponse,
|
|
11
|
+
AgentUpdateRequest,
|
|
12
|
+
PaginatedAgentsResponse,
|
|
13
|
+
PaginatedResponse,
|
|
14
|
+
PaginatedRunsResponse,
|
|
15
|
+
)
|
|
16
|
+
from .deps import Principal # noqa: F401
|
|
17
|
+
from .domain import ( # noqa: F401
|
|
18
|
+
AgentEntity,
|
|
19
|
+
AgentFramework,
|
|
20
|
+
AgentRunEntity,
|
|
21
|
+
AgentStatus,
|
|
22
|
+
TenantEntity,
|
|
23
|
+
TenantPlan,
|
|
24
|
+
TenantStatus,
|
|
25
|
+
TenantUserEntity,
|
|
26
|
+
)
|
|
27
|
+
from .dto import ( # noqa: F401
|
|
28
|
+
AgentCreateDTO,
|
|
29
|
+
AgentDeploymentDTO,
|
|
30
|
+
AgentHealthDTO,
|
|
31
|
+
AgentMetricsDTO,
|
|
32
|
+
AgentRunCreateDTO,
|
|
33
|
+
AgentUpdateDTO,
|
|
34
|
+
TenantCreateDTO,
|
|
35
|
+
TenantQuotaDTO,
|
|
36
|
+
TenantUpdateDTO,
|
|
37
|
+
TenantUsageDTO,
|
|
38
|
+
TenantUserCreateDTO,
|
|
39
|
+
TenantUserUpdateDTO,
|
|
40
|
+
)
|
|
41
|
+
from .errors import ProblemDetail # noqa: F401
|
|
42
|
+
from .settings import ( # noqa: F401
|
|
43
|
+
APISettings,
|
|
44
|
+
AuthSettings,
|
|
45
|
+
CelerySettings,
|
|
46
|
+
DatabaseSettings,
|
|
47
|
+
ObservabilitySettings,
|
|
48
|
+
RedisSettings,
|
|
49
|
+
Settings,
|
|
50
|
+
)
|
|
51
|
+
|
|
52
|
+
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
"""Pydantic schemas for Agent Manager API I/O."""
|
|
2
|
+
|
|
3
|
+
from datetime import datetime
|
|
4
|
+
from typing import Any
|
|
5
|
+
from uuid import UUID
|
|
6
|
+
|
|
7
|
+
from pydantic import BaseModel, Field
|
|
8
|
+
|
|
9
|
+
from .domain import AgentFramework, AgentStatus
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class AgentCreateRequest(BaseModel):
|
|
13
|
+
name: str = Field(..., min_length=1, max_length=255)
|
|
14
|
+
description: str | None = Field(None, max_length=1000)
|
|
15
|
+
framework: AgentFramework
|
|
16
|
+
config: dict[str, Any] = Field(default_factory=dict)
|
|
17
|
+
environment_variables: dict[str, str] = Field(default_factory=dict)
|
|
18
|
+
tags: list[str] = Field(default_factory=list)
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class AgentUpdateRequest(BaseModel):
|
|
22
|
+
name: str | None = Field(None, min_length=1, max_length=255)
|
|
23
|
+
description: str | None = Field(None, max_length=1000)
|
|
24
|
+
config: dict[str, Any] | None = None
|
|
25
|
+
environment_variables: dict[str, str] | None = None
|
|
26
|
+
tags: list[str] | None = None
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
class AgentRunRequest(BaseModel):
|
|
30
|
+
input_data: dict[str, Any]
|
|
31
|
+
trace_id: str | None = Field(None, max_length=100)
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
class AgentResponse(BaseModel):
|
|
35
|
+
id: UUID
|
|
36
|
+
name: str
|
|
37
|
+
description: str | None
|
|
38
|
+
framework: AgentFramework
|
|
39
|
+
status: AgentStatus
|
|
40
|
+
config: dict[str, Any]
|
|
41
|
+
environment_variables: dict[str, str]
|
|
42
|
+
version: str
|
|
43
|
+
tags: list[str]
|
|
44
|
+
tenant_id: UUID
|
|
45
|
+
created_at: datetime
|
|
46
|
+
updated_at: datetime
|
|
47
|
+
deployed_at: datetime | None
|
|
48
|
+
total_runs: int
|
|
49
|
+
success_rate: float | None
|
|
50
|
+
avg_response_time_ms: float | None
|
|
51
|
+
|
|
52
|
+
class Config:
|
|
53
|
+
from_attributes = True
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
class AgentSummaryResponse(BaseModel):
|
|
57
|
+
id: UUID
|
|
58
|
+
name: str
|
|
59
|
+
description: str | None
|
|
60
|
+
framework: AgentFramework
|
|
61
|
+
status: AgentStatus
|
|
62
|
+
version: str
|
|
63
|
+
tags: list[str]
|
|
64
|
+
created_at: datetime
|
|
65
|
+
updated_at: datetime
|
|
66
|
+
total_runs: int
|
|
67
|
+
success_rate: float | None
|
|
68
|
+
|
|
69
|
+
class Config:
|
|
70
|
+
from_attributes = True
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
class AgentRunResponse(BaseModel):
|
|
74
|
+
id: UUID
|
|
75
|
+
agent_id: UUID
|
|
76
|
+
tenant_id: UUID
|
|
77
|
+
input_data: dict[str, Any]
|
|
78
|
+
output_data: dict[str, Any] | None
|
|
79
|
+
status: str
|
|
80
|
+
started_at: datetime
|
|
81
|
+
completed_at: datetime | None
|
|
82
|
+
error_message: str | None
|
|
83
|
+
response_time_ms: float | None
|
|
84
|
+
tokens_used: int | None
|
|
85
|
+
cost_usd: float | None
|
|
86
|
+
trace_id: str | None
|
|
87
|
+
span_id: str | None
|
|
88
|
+
|
|
89
|
+
class Config:
|
|
90
|
+
from_attributes = True
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
class AgentRunSummaryResponse(BaseModel):
|
|
94
|
+
id: UUID
|
|
95
|
+
agent_id: UUID
|
|
96
|
+
status: str
|
|
97
|
+
started_at: datetime
|
|
98
|
+
completed_at: datetime | None
|
|
99
|
+
response_time_ms: float | None
|
|
100
|
+
tokens_used: int | None
|
|
101
|
+
cost_usd: float | None
|
|
102
|
+
|
|
103
|
+
class Config:
|
|
104
|
+
from_attributes = True
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
class PaginatedResponse(BaseModel):
|
|
108
|
+
total: int
|
|
109
|
+
limit: int
|
|
110
|
+
offset: int
|
|
111
|
+
has_more: bool
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
class PaginatedAgentsResponse(PaginatedResponse):
|
|
115
|
+
items: list[AgentSummaryResponse]
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
class PaginatedRunsResponse(PaginatedResponse):
|
|
119
|
+
items: list[AgentRunSummaryResponse]
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
class AgentStatsResponse(BaseModel):
|
|
123
|
+
total_agents: int
|
|
124
|
+
active_agents: int
|
|
125
|
+
total_runs_today: int
|
|
126
|
+
total_runs_this_month: int
|
|
127
|
+
avg_success_rate: float | None
|
|
128
|
+
avg_response_time_ms: float | None
|
|
129
|
+
|
|
130
|
+
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
"""Shared dependency models used by FastAPI deps in Manager."""
|
|
2
|
+
|
|
3
|
+
from uuid import UUID
|
|
4
|
+
|
|
5
|
+
from pydantic import BaseModel
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class Principal(BaseModel):
|
|
9
|
+
"""Authenticated caller identity and authorization context."""
|
|
10
|
+
|
|
11
|
+
user_id: str
|
|
12
|
+
tenant_id: UUID
|
|
13
|
+
roles: list[str] = []
|
|
14
|
+
workspace_ids: list[UUID] = []
|
|
15
|
+
|
|
16
|
+
|
|
@@ -0,0 +1,261 @@
|
|
|
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
|
+
if self.status == AgentStatus.DRAFT:
|
|
62
|
+
self.status = AgentStatus.ACTIVE
|
|
63
|
+
self.deployed_at = datetime.now(UTC)
|
|
64
|
+
else:
|
|
65
|
+
raise ValueError(f"Cannot activate agent in {self.status} status")
|
|
66
|
+
|
|
67
|
+
def deactivate(self) -> None:
|
|
68
|
+
if self.status == AgentStatus.ACTIVE:
|
|
69
|
+
self.status = AgentStatus.INACTIVE
|
|
70
|
+
else:
|
|
71
|
+
raise ValueError(f"Cannot deactivate agent in {self.status} status")
|
|
72
|
+
|
|
73
|
+
def update_metrics(self, success: bool, response_time_ms: float) -> None:
|
|
74
|
+
self.total_runs += 1
|
|
75
|
+
|
|
76
|
+
if self.success_rate is None:
|
|
77
|
+
self.success_rate = 1.0 if success else 0.0
|
|
78
|
+
else:
|
|
79
|
+
current_successes = self.success_rate * (self.total_runs - 1)
|
|
80
|
+
if success:
|
|
81
|
+
current_successes += 1
|
|
82
|
+
self.success_rate = current_successes / self.total_runs
|
|
83
|
+
|
|
84
|
+
if self.avg_response_time_ms is None:
|
|
85
|
+
self.avg_response_time_ms = response_time_ms
|
|
86
|
+
else:
|
|
87
|
+
total_time = self.avg_response_time_ms * (self.total_runs - 1)
|
|
88
|
+
self.avg_response_time_ms = (total_time + response_time_ms) / self.total_runs
|
|
89
|
+
|
|
90
|
+
def can_be_deployed(self) -> bool:
|
|
91
|
+
return (
|
|
92
|
+
self.status in [AgentStatus.DRAFT, AgentStatus.INACTIVE]
|
|
93
|
+
and bool(self.name)
|
|
94
|
+
and bool(self.config)
|
|
95
|
+
)
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
class AgentRunEntity(BaseModel):
|
|
99
|
+
"""Agent run domain entity."""
|
|
100
|
+
|
|
101
|
+
id: UUID = Field(default_factory=uuid4)
|
|
102
|
+
agent_id: UUID
|
|
103
|
+
tenant_id: UUID
|
|
104
|
+
|
|
105
|
+
# Input/Output
|
|
106
|
+
input_data: dict[str, Any]
|
|
107
|
+
output_data: dict[str, Any] | None = None
|
|
108
|
+
|
|
109
|
+
# Execution details
|
|
110
|
+
status: str # running, completed, failed
|
|
111
|
+
started_at: datetime
|
|
112
|
+
completed_at: datetime | None = None
|
|
113
|
+
error_message: str | None = None
|
|
114
|
+
|
|
115
|
+
# Performance
|
|
116
|
+
response_time_ms: float | None = None
|
|
117
|
+
tokens_used: int | None = None
|
|
118
|
+
cost_usd: float | None = None
|
|
119
|
+
|
|
120
|
+
# Tracing
|
|
121
|
+
trace_id: str | None = None
|
|
122
|
+
span_id: str | None = None
|
|
123
|
+
|
|
124
|
+
def complete(self, output_data: dict[str, Any], response_time_ms: float) -> None:
|
|
125
|
+
self.status = "completed"
|
|
126
|
+
self.output_data = output_data
|
|
127
|
+
self.completed_at = datetime.now(UTC)
|
|
128
|
+
self.response_time_ms = response_time_ms
|
|
129
|
+
|
|
130
|
+
def fail(self, error_message: str) -> None:
|
|
131
|
+
self.status = "failed"
|
|
132
|
+
self.error_message = error_message
|
|
133
|
+
self.completed_at = datetime.now(UTC)
|
|
134
|
+
|
|
135
|
+
@property
|
|
136
|
+
def is_completed(self) -> bool:
|
|
137
|
+
return self.status in ["completed", "failed"]
|
|
138
|
+
|
|
139
|
+
@property
|
|
140
|
+
def was_successful(self) -> bool:
|
|
141
|
+
return self.status == "completed"
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
class TenantStatus(str, Enum):
|
|
145
|
+
"""Tenant status enumeration."""
|
|
146
|
+
|
|
147
|
+
ACTIVE = "active"
|
|
148
|
+
SUSPENDED = "suspended"
|
|
149
|
+
DEACTIVATED = "deactivated"
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
class TenantPlan(str, Enum):
|
|
153
|
+
"""Tenant subscription plan."""
|
|
154
|
+
|
|
155
|
+
FREE = "free"
|
|
156
|
+
STARTER = "starter"
|
|
157
|
+
PROFESSIONAL = "professional"
|
|
158
|
+
ENTERPRISE = "enterprise"
|
|
159
|
+
|
|
160
|
+
|
|
161
|
+
class TenantEntity(BaseModel):
|
|
162
|
+
"""Tenant domain entity."""
|
|
163
|
+
|
|
164
|
+
id: UUID = Field(default_factory=uuid4)
|
|
165
|
+
name: str = Field(..., min_length=1, max_length=255)
|
|
166
|
+
slug: str = Field(..., min_length=1, max_length=100)
|
|
167
|
+
|
|
168
|
+
# Contact information
|
|
169
|
+
email: str = Field(..., description="Primary contact email")
|
|
170
|
+
website: str | None = Field(None)
|
|
171
|
+
|
|
172
|
+
# Status and plan
|
|
173
|
+
status: TenantStatus = Field(default=TenantStatus.ACTIVE)
|
|
174
|
+
plan: TenantPlan = Field(default=TenantPlan.FREE)
|
|
175
|
+
|
|
176
|
+
# Settings
|
|
177
|
+
settings: dict[str, Any] = Field(default_factory=dict)
|
|
178
|
+
|
|
179
|
+
# Quotas and limits
|
|
180
|
+
max_agents: int = Field(default=5)
|
|
181
|
+
max_runs_per_month: int = Field(default=1000)
|
|
182
|
+
max_storage_mb: int = Field(default=100)
|
|
183
|
+
|
|
184
|
+
# Usage tracking
|
|
185
|
+
current_agents: int = Field(default=0)
|
|
186
|
+
current_runs_this_month: int = Field(default=0)
|
|
187
|
+
current_storage_mb: float = Field(default=0.0)
|
|
188
|
+
|
|
189
|
+
# Timestamps
|
|
190
|
+
created_at: datetime
|
|
191
|
+
updated_at: datetime
|
|
192
|
+
suspended_at: datetime | None = None
|
|
193
|
+
|
|
194
|
+
def can_create_agent(self) -> bool:
|
|
195
|
+
return self.status == TenantStatus.ACTIVE and self.current_agents < self.max_agents
|
|
196
|
+
|
|
197
|
+
def can_run_agent(self) -> bool:
|
|
198
|
+
return self.status == TenantStatus.ACTIVE and self.current_runs_this_month < self.max_runs_per_month
|
|
199
|
+
|
|
200
|
+
def suspend(self, reason: str) -> None:
|
|
201
|
+
self.status = TenantStatus.SUSPENDED
|
|
202
|
+
self.suspended_at = datetime.utcnow()
|
|
203
|
+
self.settings["suspension_reason"] = reason
|
|
204
|
+
|
|
205
|
+
def reactivate(self) -> None:
|
|
206
|
+
if self.status == TenantStatus.SUSPENDED:
|
|
207
|
+
self.status = TenantStatus.ACTIVE
|
|
208
|
+
self.suspended_at = None
|
|
209
|
+
if "suspension_reason" in self.settings:
|
|
210
|
+
del self.settings["suspension_reason"]
|
|
211
|
+
|
|
212
|
+
def upgrade_plan(self, new_plan: TenantPlan) -> None:
|
|
213
|
+
self.plan = new_plan
|
|
214
|
+
if new_plan == TenantPlan.STARTER:
|
|
215
|
+
self.max_agents = 20
|
|
216
|
+
self.max_runs_per_month = 10000
|
|
217
|
+
self.max_storage_mb = 1000
|
|
218
|
+
elif new_plan == TenantPlan.PROFESSIONAL:
|
|
219
|
+
self.max_agents = 100
|
|
220
|
+
self.max_runs_per_month = 100000
|
|
221
|
+
self.max_storage_mb = 10000
|
|
222
|
+
elif new_plan == TenantPlan.ENTERPRISE:
|
|
223
|
+
self.max_agents = 1000
|
|
224
|
+
self.max_runs_per_month = 1000000
|
|
225
|
+
self.max_storage_mb = 100000
|
|
226
|
+
|
|
227
|
+
def increment_usage(self, agents: int = 0, runs: int = 0, storage_mb: float = 0) -> None:
|
|
228
|
+
self.current_agents += agents
|
|
229
|
+
self.current_runs_this_month += runs
|
|
230
|
+
self.current_storage_mb += storage_mb
|
|
231
|
+
|
|
232
|
+
def reset_monthly_usage(self) -> None:
|
|
233
|
+
self.current_runs_this_month = 0
|
|
234
|
+
|
|
235
|
+
|
|
236
|
+
class TenantUserEntity(BaseModel):
|
|
237
|
+
"""Tenant user domain entity for multi-user tenants."""
|
|
238
|
+
|
|
239
|
+
id: UUID = Field(default_factory=uuid4)
|
|
240
|
+
tenant_id: UUID
|
|
241
|
+
user_id: str
|
|
242
|
+
email: str
|
|
243
|
+
|
|
244
|
+
# Role and permissions
|
|
245
|
+
role: str = Field(default="member")
|
|
246
|
+
permissions: list[str] = Field(default_factory=list)
|
|
247
|
+
|
|
248
|
+
# Status
|
|
249
|
+
is_active: bool = Field(default=True)
|
|
250
|
+
|
|
251
|
+
# Timestamps
|
|
252
|
+
joined_at: datetime
|
|
253
|
+
last_active_at: datetime | None = None
|
|
254
|
+
|
|
255
|
+
def has_permission(self, permission: str) -> bool:
|
|
256
|
+
return permission in self.permissions or self.role == "owner"
|
|
257
|
+
|
|
258
|
+
def is_admin(self) -> bool:
|
|
259
|
+
return self.role in ["owner", "admin"]
|
|
260
|
+
|
|
261
|
+
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
"""DTOs for Manager operations."""
|
|
2
|
+
|
|
3
|
+
from datetime import datetime
|
|
4
|
+
from typing import Any
|
|
5
|
+
from uuid import UUID
|
|
6
|
+
|
|
7
|
+
from pydantic import BaseModel, Field
|
|
8
|
+
|
|
9
|
+
from .domain import AgentFramework, TenantPlan
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class AgentCreateDTO(BaseModel):
|
|
13
|
+
name: str = Field(..., min_length=1, max_length=255)
|
|
14
|
+
description: str | None = Field(None, max_length=1000)
|
|
15
|
+
framework: AgentFramework
|
|
16
|
+
config: dict[str, Any] = Field(default_factory=dict)
|
|
17
|
+
environment_variables: dict[str, str] = Field(default_factory=dict)
|
|
18
|
+
tags: list[str] = Field(default_factory=list)
|
|
19
|
+
tenant_id: UUID
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class AgentUpdateDTO(BaseModel):
|
|
23
|
+
name: str | None = Field(None, min_length=1, max_length=255)
|
|
24
|
+
description: str | None = Field(None, max_length=1000)
|
|
25
|
+
config: dict[str, Any] | None = None
|
|
26
|
+
environment_variables: dict[str, str] | None = None
|
|
27
|
+
tags: list[str] | None = None
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
class AgentDeploymentDTO(BaseModel):
|
|
31
|
+
agent_id: UUID
|
|
32
|
+
container_id: str
|
|
33
|
+
endpoint: str
|
|
34
|
+
status: str
|
|
35
|
+
framework: str
|
|
36
|
+
deployed_at: datetime | None = None
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
class AgentHealthDTO(BaseModel):
|
|
40
|
+
agent_id: UUID
|
|
41
|
+
status: str
|
|
42
|
+
uptime: str | None = None
|
|
43
|
+
cpu_usage: str | None = None
|
|
44
|
+
memory_usage: str | None = None
|
|
45
|
+
last_activity: str | None = None
|
|
46
|
+
error: str | None = None
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
class AgentRunCreateDTO(BaseModel):
|
|
50
|
+
agent_id: UUID
|
|
51
|
+
tenant_id: UUID
|
|
52
|
+
input_data: dict[str, Any]
|
|
53
|
+
trace_id: str | None = None
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
class AgentMetricsDTO(BaseModel):
|
|
57
|
+
agent_id: UUID
|
|
58
|
+
total_runs: int
|
|
59
|
+
success_rate: float | None = Field(None, ge=0.0, le=1.0)
|
|
60
|
+
avg_response_time_ms: float | None = Field(None, ge=0)
|
|
61
|
+
last_run_at: datetime | None = None
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
class TenantCreateDTO(BaseModel):
|
|
65
|
+
name: str = Field(..., min_length=1, max_length=255)
|
|
66
|
+
slug: str = Field(..., min_length=1, max_length=100)
|
|
67
|
+
email: str = Field(..., description="Primary contact email")
|
|
68
|
+
website: str | None = None
|
|
69
|
+
plan: TenantPlan = Field(default=TenantPlan.FREE)
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
class TenantUpdateDTO(BaseModel):
|
|
73
|
+
name: str | None = Field(None, min_length=1, max_length=255)
|
|
74
|
+
email: str | None = None
|
|
75
|
+
website: str | None = None
|
|
76
|
+
settings: dict[str, Any] | None = None
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
class TenantUsageDTO(BaseModel):
|
|
80
|
+
tenant_id: UUID
|
|
81
|
+
current_agents: int
|
|
82
|
+
max_agents: int
|
|
83
|
+
current_runs_this_month: int
|
|
84
|
+
max_runs_per_month: int
|
|
85
|
+
current_storage_mb: float
|
|
86
|
+
max_storage_mb: int
|
|
87
|
+
usage_percentage: dict[str, float]
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
class TenantQuotaDTO(BaseModel):
|
|
91
|
+
max_agents: int | None = None
|
|
92
|
+
max_runs_per_month: int | None = None
|
|
93
|
+
max_storage_mb: int | None = None
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
class TenantUserCreateDTO(BaseModel):
|
|
97
|
+
tenant_id: UUID
|
|
98
|
+
user_id: str
|
|
99
|
+
email: str
|
|
100
|
+
role: str = Field(default="member")
|
|
101
|
+
permissions: list[str] = Field(default_factory=list)
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
class TenantUserUpdateDTO(BaseModel):
|
|
105
|
+
role: str | None = None
|
|
106
|
+
permissions: list[str] | None = None
|
|
107
|
+
is_active: bool | None = None
|
|
108
|
+
|
|
109
|
+
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
"""RFC 9457 Problem Details model (shared)."""
|
|
2
|
+
|
|
3
|
+
from typing import Any
|
|
4
|
+
|
|
5
|
+
from pydantic import BaseModel, Field
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class ProblemDetail(BaseModel):
|
|
9
|
+
"""RFC 9457 Problem Details model."""
|
|
10
|
+
|
|
11
|
+
type: str = Field(default="about:blank")
|
|
12
|
+
title: str = Field()
|
|
13
|
+
status: int = Field()
|
|
14
|
+
detail: str | None = Field(default=None)
|
|
15
|
+
instance: str | None = Field(default=None)
|
|
16
|
+
|
|
17
|
+
# Extension members
|
|
18
|
+
timestamp: str | None = Field(default=None)
|
|
19
|
+
request_id: str | None = Field(default=None)
|
|
20
|
+
errors: dict[str, Any] | None = Field(default=None)
|
|
21
|
+
|
|
22
|
+
model_config = {"extra": "allow"}
|
|
23
|
+
|
|
24
|
+
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
"""Application settings schemas using Pydantic Settings v2."""
|
|
2
|
+
|
|
3
|
+
from typing import Literal
|
|
4
|
+
|
|
5
|
+
from pydantic import Field, field_validator
|
|
6
|
+
from pydantic_settings import BaseSettings, SettingsConfigDict
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class DatabaseSettings(BaseSettings):
|
|
10
|
+
url: str = Field(default="postgresql+asyncpg://postgres:postgres@localhost:55432/idun_agents")
|
|
11
|
+
echo: bool = Field(default=False)
|
|
12
|
+
pool_size: int = Field(default=10)
|
|
13
|
+
max_overflow: int = Field(default=20)
|
|
14
|
+
pool_pre_ping: bool = Field(default=True)
|
|
15
|
+
|
|
16
|
+
model_config = SettingsConfigDict(env_prefix="DATABASE_", env_file=".env")
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class RedisSettings(BaseSettings):
|
|
20
|
+
url: str = Field(default="redis://localhost:6379/0")
|
|
21
|
+
max_connections: int = Field(default=20)
|
|
22
|
+
|
|
23
|
+
model_config = SettingsConfigDict(env_prefix="REDIS_", env_file=".env")
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class AuthSettings(BaseSettings):
|
|
27
|
+
provider_type: Literal["okta", "auth0", "entra", "google"] = Field(default="auth0")
|
|
28
|
+
issuer: str = Field(default="")
|
|
29
|
+
client_id: str = Field(default="")
|
|
30
|
+
client_secret: str = Field(default="")
|
|
31
|
+
audience: str | None = Field(default=None)
|
|
32
|
+
redirect_uri: str | None = Field(default=None)
|
|
33
|
+
scopes: list[str] = Field(default_factory=lambda: ["openid", "profile", "email"])
|
|
34
|
+
|
|
35
|
+
allowed_algs: list[str] = Field(default_factory=lambda: ["RS256", "RS512", "ES256"])
|
|
36
|
+
jwks_cache_ttl: int = Field(default=300)
|
|
37
|
+
clock_skew_seconds: int = Field(default=60)
|
|
38
|
+
expected_audiences: list[str] = Field(default_factory=list)
|
|
39
|
+
|
|
40
|
+
claim_user_id_path: list[str] | None = Field(default=None)
|
|
41
|
+
claim_email_path: list[str] | None = Field(default=None)
|
|
42
|
+
claim_roles_paths: list[list[str]] | None = Field(default=None)
|
|
43
|
+
claim_groups_paths: list[list[str]] | None = Field(default=None)
|
|
44
|
+
claim_workspace_ids_paths: list[list[str]] | None = Field(default=None)
|
|
45
|
+
|
|
46
|
+
@field_validator("claim_user_id_path", "claim_email_path", mode="before")
|
|
47
|
+
@classmethod
|
|
48
|
+
def _string_to_list(cls, v):
|
|
49
|
+
if isinstance(v, str) and v.strip().startswith("#"):
|
|
50
|
+
return None
|
|
51
|
+
return v
|
|
52
|
+
|
|
53
|
+
@field_validator(
|
|
54
|
+
"claim_roles_paths",
|
|
55
|
+
"claim_groups_paths",
|
|
56
|
+
"claim_workspace_ids_paths",
|
|
57
|
+
mode="before",
|
|
58
|
+
)
|
|
59
|
+
@classmethod
|
|
60
|
+
def _string_to_list_of_lists(cls, v):
|
|
61
|
+
if isinstance(v, str) and v.strip().startswith("#"):
|
|
62
|
+
return None
|
|
63
|
+
return v
|
|
64
|
+
|
|
65
|
+
model_config = SettingsConfigDict(env_prefix="AUTH_", env_file=".env", extra="ignore")
|
|
66
|
+
|
|
67
|
+
@field_validator("issuer")
|
|
68
|
+
@classmethod
|
|
69
|
+
def validate_issuer(cls, v: str) -> str:
|
|
70
|
+
if not v:
|
|
71
|
+
return v
|
|
72
|
+
if not v.startswith("http"):
|
|
73
|
+
raise ValueError("issuer must be a URL")
|
|
74
|
+
return v
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
class ObservabilitySettings(BaseSettings):
|
|
78
|
+
otel_service_name: str = Field(default="idun-agent-manager")
|
|
79
|
+
otel_exporter_endpoint: str | None = Field(default=None)
|
|
80
|
+
otel_exporter_headers: str | None = Field(default=None)
|
|
81
|
+
log_level: str = Field(default="INFO")
|
|
82
|
+
log_format: str = Field(default="json")
|
|
83
|
+
|
|
84
|
+
model_config = SettingsConfigDict(env_prefix="OTEL_", env_file=".env")
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
class CelerySettings(BaseSettings):
|
|
88
|
+
broker_url: str = Field(default="redis://localhost:6379/1")
|
|
89
|
+
result_backend: str = Field(default="redis://localhost:6379/2")
|
|
90
|
+
task_serializer: str = Field(default="json")
|
|
91
|
+
result_serializer: str = Field(default="json")
|
|
92
|
+
accept_content: list[str] = Field(default=["json"])
|
|
93
|
+
timezone: str = Field(default="UTC")
|
|
94
|
+
|
|
95
|
+
model_config = SettingsConfigDict(env_prefix="CELERY_", env_file=".env")
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
class APISettings(BaseSettings):
|
|
99
|
+
title: str = Field(default="Idun Agent Manager API")
|
|
100
|
+
description: str = Field(default="Modern FastAPI backend for managing AI agents")
|
|
101
|
+
version: str = Field(default="0.1.0")
|
|
102
|
+
docs_url: str = Field(default="/docs")
|
|
103
|
+
redoc_url: str = Field(default="/redoc")
|
|
104
|
+
openapi_url: str = Field(default="/openapi.json")
|
|
105
|
+
cors_origins: list[str] = Field(default=["http://localhost:3000", "http://localhost:8080"])
|
|
106
|
+
cors_methods: list[str] = Field(default=["*"])
|
|
107
|
+
cors_headers: list[str] = Field(default=["*"])
|
|
108
|
+
rate_limit_enabled: bool = Field(default=True)
|
|
109
|
+
rate_limit_requests: int = Field(default=100)
|
|
110
|
+
rate_limit_window: int = Field(default=60)
|
|
111
|
+
|
|
112
|
+
model_config = SettingsConfigDict(env_prefix="API_", env_file=".env", extra="ignore")
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
class Settings(BaseSettings):
|
|
116
|
+
environment: str = Field(default="development")
|
|
117
|
+
debug: bool = Field(default=False)
|
|
118
|
+
testing: bool = Field(default=False)
|
|
119
|
+
host: str = Field(default="0.0.0.0")
|
|
120
|
+
port: int = Field(default=8000)
|
|
121
|
+
workers: int = Field(default=1)
|
|
122
|
+
reload: bool = Field(default=False)
|
|
123
|
+
|
|
124
|
+
database: DatabaseSettings = Field(default_factory=DatabaseSettings)
|
|
125
|
+
redis: RedisSettings = Field(default_factory=RedisSettings)
|
|
126
|
+
auth: AuthSettings = Field(default_factory=AuthSettings)
|
|
127
|
+
observability: ObservabilitySettings = Field(default_factory=ObservabilitySettings)
|
|
128
|
+
celery: CelerySettings = Field(default_factory=CelerySettings)
|
|
129
|
+
api: APISettings = Field(default_factory=APISettings)
|
|
130
|
+
|
|
131
|
+
model_config = SettingsConfigDict(
|
|
132
|
+
env_file=".env",
|
|
133
|
+
env_file_encoding="utf-8",
|
|
134
|
+
env_nested_delimiter="__",
|
|
135
|
+
case_sensitive=False,
|
|
136
|
+
extra="ignore",
|
|
137
|
+
)
|
|
138
|
+
|
|
139
|
+
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
"""Provider-agnostic observability configuration model (shared)."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import os
|
|
6
|
+
from typing import Any
|
|
7
|
+
|
|
8
|
+
from pydantic import BaseModel, Field
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def _resolve_env(value: Any) -> Any:
|
|
12
|
+
"""Resolve environment placeholders in strings.
|
|
13
|
+
|
|
14
|
+
Supports patterns ${VAR} and $VAR. Non-strings are returned unchanged.
|
|
15
|
+
"""
|
|
16
|
+
if isinstance(value, str):
|
|
17
|
+
if value.startswith("${") and value.endswith("}"):
|
|
18
|
+
return os.getenv(value[2:-1])
|
|
19
|
+
if value.startswith("$"):
|
|
20
|
+
return os.getenv(value[1:])
|
|
21
|
+
return value
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class ObservabilityConfig(BaseModel):
|
|
25
|
+
"""Provider-agnostic observability configuration based on Pydantic.
|
|
26
|
+
|
|
27
|
+
Example YAML:
|
|
28
|
+
observability:
|
|
29
|
+
provider: "langfuse" # or "phoenix"
|
|
30
|
+
enabled: true
|
|
31
|
+
options:
|
|
32
|
+
host: ${LANGFUSE_HOST}
|
|
33
|
+
public_key: ${LANGFUSE_PUBLIC_KEY}
|
|
34
|
+
secret_key: ${LANGFUSE_SECRET_KEY}
|
|
35
|
+
run_name: "my-run"
|
|
36
|
+
"""
|
|
37
|
+
|
|
38
|
+
provider: str | None = Field(default=None)
|
|
39
|
+
enabled: bool = Field(default=False)
|
|
40
|
+
options: dict[str, Any] = Field(default_factory=dict)
|
|
41
|
+
|
|
42
|
+
def _resolve_value(self, value: Any) -> Any:
|
|
43
|
+
if isinstance(value, dict):
|
|
44
|
+
return {k: self._resolve_value(v) for k, v in value.items()}
|
|
45
|
+
if isinstance(value, list):
|
|
46
|
+
return [self._resolve_value(v) for v in value]
|
|
47
|
+
return _resolve_env(value)
|
|
48
|
+
|
|
49
|
+
def resolved(self) -> ObservabilityConfig:
|
|
50
|
+
"""Return a copy with env placeholders resolved in options."""
|
|
51
|
+
resolved_options = self._resolve_value(self.options)
|
|
52
|
+
return ObservabilityConfig(
|
|
53
|
+
provider=self.provider,
|
|
54
|
+
enabled=self.enabled,
|
|
55
|
+
options=resolved_options,
|
|
56
|
+
)
|
|
57
|
+
|
|
58
|
+
|