forkflux-api 0.1.0__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.
File without changes
@@ -0,0 +1,21 @@
1
+ from fastapi import Depends, Request
2
+ from forkflux_api.agents.respositories import TargetRoleRepository
3
+ from forkflux_api.agents.services import TargetRoleService
4
+ from forkflux_api.database import get_session
5
+ from sqlalchemy.ext.asyncio import AsyncSession
6
+
7
+
8
+ def get_trace_id(request: Request) -> str:
9
+ return request.state.trace_id
10
+
11
+
12
+ def get_target_role_repo(
13
+ session: AsyncSession = Depends(get_session), trace_id: str = Depends(get_trace_id)
14
+ ) -> TargetRoleRepository:
15
+ return TargetRoleRepository(session=session, trace_id=trace_id)
16
+
17
+
18
+ def get_target_role_service(
19
+ repository: TargetRoleRepository = Depends(get_target_role_repo), trace_id: str = Depends(get_trace_id)
20
+ ) -> TargetRoleService:
21
+ return TargetRoleService(target_role_repo=repository, trace_id=trace_id)
@@ -0,0 +1,19 @@
1
+ from dataclasses import dataclass
2
+
3
+
4
+ @dataclass(slots=True)
5
+ class TargetRoleCreate:
6
+ role_key: str
7
+ role_label: str
8
+
9
+
10
+ @dataclass(slots=True)
11
+ class AgentIdentityCreate:
12
+ agent_label: str
13
+ role_id: int
14
+ tool_family: str | None
15
+
16
+
17
+ @dataclass(slots=True)
18
+ class AgentApiTokenCreate:
19
+ agent_id: int
@@ -0,0 +1,28 @@
1
+ class AgentApiTokenNotFoundError(Exception):
2
+ code = "agent_api_token.not_found"
3
+ msg = "Agent API token not found."
4
+
5
+
6
+ class AgentApiTokenConflictError(Exception):
7
+ code = "agent_api_token.conflict"
8
+ msg = "Agent API token conflicts with existing data constraints."
9
+
10
+
11
+ class AgentIdentityNotFoundError(Exception):
12
+ code = "agent_identity.not_found"
13
+ msg = "Agent identity not found."
14
+
15
+
16
+ class AgentIdentityConflictError(Exception):
17
+ code = "agent_identity.conflict"
18
+ msg = "Agent identity conflicts with existing data constraints."
19
+
20
+
21
+ class TargetRoleConflictError(Exception):
22
+ code = "target_role.conflict"
23
+ msg = "Target role already exists."
24
+
25
+
26
+ class TargetRoleNotFoundError(Exception):
27
+ code = "target_role.not_found"
28
+ msg = "Target role not found."
@@ -0,0 +1,19 @@
1
+ from fastapi import APIRouter, Depends
2
+ from forkflux_api.agents.dependencies import get_target_role_service
3
+ from forkflux_api.agents.models import AgentIdentity
4
+ from forkflux_api.agents.schemas import GetMeResponse, ListRolesResponse
5
+ from forkflux_api.agents.services import TargetRoleService
6
+ from forkflux_api.dependencies import get_current_agent, verify_token
7
+
8
+ router = APIRouter(prefix="/agents", tags=["agents"], dependencies=[Depends(verify_token)])
9
+
10
+
11
+ @router.get("/roles", response_model=list[ListRolesResponse])
12
+ async def list_roles(service: TargetRoleService = Depends(get_target_role_service)):
13
+ roles = await service.get_all_roles()
14
+ return roles
15
+
16
+
17
+ @router.get("/me", response_model=GetMeResponse)
18
+ async def get_me(current_agent: AgentIdentity = Depends(get_current_agent)):
19
+ return current_agent
@@ -0,0 +1,38 @@
1
+ from datetime import datetime
2
+
3
+ from forkflux_api.database import Base, UTCDateTime
4
+ from sqlalchemy import BigInteger, Boolean, ForeignKey, Integer, Text
5
+ from sqlalchemy.orm import Mapped, mapped_column
6
+
7
+ PK_TYPE = BigInteger().with_variant(Integer, "sqlite")
8
+
9
+
10
+ class TargetRole(Base):
11
+ __tablename__ = "target_role"
12
+
13
+ id: Mapped[int] = mapped_column(PK_TYPE, primary_key=True, autoincrement=True)
14
+ role_key: Mapped[str] = mapped_column(Text, nullable=False, unique=True)
15
+ role_label: Mapped[str] = mapped_column(Text, nullable=False)
16
+ created_at: Mapped[datetime] = mapped_column(UTCDateTime(), nullable=False)
17
+
18
+
19
+ class AgentIdentity(Base):
20
+ __tablename__ = "agent_identity"
21
+
22
+ id: Mapped[int] = mapped_column(PK_TYPE, primary_key=True, autoincrement=True)
23
+ agent_label: Mapped[str] = mapped_column(Text, nullable=False)
24
+ role_id: Mapped[int] = mapped_column(ForeignKey("target_role.id"), nullable=False)
25
+ tool_family: Mapped[str | None] = mapped_column(Text, nullable=True)
26
+ created_at: Mapped[datetime] = mapped_column(UTCDateTime(), nullable=False)
27
+
28
+
29
+ class AgentApiToken(Base):
30
+ __tablename__ = "agent_api_token"
31
+
32
+ id: Mapped[int] = mapped_column(PK_TYPE, primary_key=True, autoincrement=True)
33
+ token_hash: Mapped[str] = mapped_column(Text, nullable=False, unique=True)
34
+ agent_id: Mapped[int] = mapped_column(ForeignKey("agent_identity.id"), nullable=False)
35
+ is_active: Mapped[bool] = mapped_column(Boolean, nullable=False)
36
+ created_at: Mapped[datetime] = mapped_column(UTCDateTime(), nullable=False)
37
+ last_used_at: Mapped[datetime | None] = mapped_column(UTCDateTime(), nullable=True)
38
+ revoked_at: Mapped[datetime | None] = mapped_column(UTCDateTime(), nullable=True)
@@ -0,0 +1,150 @@
1
+ from datetime import datetime, timezone
2
+
3
+ import structlog
4
+ from forkflux_api.agents.dto import AgentApiTokenCreate, AgentIdentityCreate, TargetRoleCreate
5
+ from forkflux_api.agents.exceptions import (
6
+ AgentApiTokenConflictError,
7
+ AgentApiTokenNotFoundError,
8
+ AgentIdentityConflictError,
9
+ AgentIdentityNotFoundError,
10
+ TargetRoleConflictError,
11
+ TargetRoleNotFoundError,
12
+ )
13
+ from forkflux_api.agents.models import AgentApiToken, AgentIdentity, TargetRole
14
+ from sqlalchemy import exists, select, update
15
+ from sqlalchemy.exc import IntegrityError
16
+ from sqlalchemy.ext.asyncio import AsyncSession
17
+
18
+
19
+ class TargetRoleRepository:
20
+ def __init__(self, session: AsyncSession, trace_id: str) -> None:
21
+ self._session = session
22
+ self._logger = structlog.get_logger().bind(cls=self.__class__.__name__, trace_id=trace_id)
23
+
24
+ async def list(self) -> list[TargetRole]:
25
+ result = await self._session.execute(select(TargetRole))
26
+ return list(result.scalars().all())
27
+
28
+ async def get_by_role_key(self, role_key: str) -> TargetRole:
29
+ result = await self._session.execute(select(TargetRole).where(TargetRole.role_key == role_key))
30
+ target_role = result.scalar_one_or_none()
31
+ if target_role is None:
32
+ raise TargetRoleNotFoundError
33
+
34
+ return target_role
35
+
36
+ async def exists(self, role_key: str) -> bool:
37
+ log = self._logger.bind(method="exists", role_key=role_key)
38
+ result = await self._session.execute(select(exists().where(TargetRole.role_key == role_key)))
39
+ role_exists = result.scalar_one()
40
+
41
+ if role_exists:
42
+ log.info("target_role_exists_hit")
43
+ else:
44
+ log.info("target_role_exists_miss")
45
+
46
+ return role_exists
47
+
48
+ async def create(self, dto: TargetRoleCreate) -> TargetRole:
49
+ target_role = TargetRole(
50
+ role_key=dto.role_key,
51
+ role_label=dto.role_label,
52
+ created_at=datetime.now(timezone.utc),
53
+ )
54
+
55
+ self._session.add(target_role)
56
+ try:
57
+ await self._session.flush()
58
+ except IntegrityError as err:
59
+ await self._session.rollback()
60
+ raise TargetRoleConflictError from err
61
+
62
+ return target_role
63
+
64
+
65
+ class AgentApiTokenRepository:
66
+ def __init__(self, session: AsyncSession, trace_id: str) -> None:
67
+ self._session = session
68
+ self._logger = structlog.get_logger().bind(cls=self.__class__.__name__, trace_id=trace_id)
69
+
70
+ async def get(self, token_hash: str) -> AgentApiToken:
71
+ result = await self._session.execute(
72
+ select(AgentApiToken).where(
73
+ AgentApiToken.token_hash == token_hash,
74
+ AgentApiToken.is_active.is_(True),
75
+ )
76
+ )
77
+ token = result.scalar_one_or_none()
78
+ if token is None:
79
+ raise AgentApiTokenNotFoundError
80
+
81
+ return token
82
+
83
+ async def create(self, dto: AgentApiTokenCreate, token_hash: str) -> AgentApiToken:
84
+ agent_api_token = AgentApiToken(
85
+ token_hash=token_hash,
86
+ agent_id=dto.agent_id,
87
+ is_active=True,
88
+ created_at=datetime.now(timezone.utc),
89
+ )
90
+
91
+ self._session.add(agent_api_token)
92
+ try:
93
+ await self._session.flush()
94
+ except IntegrityError as err:
95
+ await self._session.rollback()
96
+ raise AgentApiTokenConflictError from err
97
+
98
+ return agent_api_token
99
+
100
+ async def revoke(self, agent_id: int) -> int:
101
+ revoked_at = datetime.now(timezone.utc)
102
+ result = await self._session.execute(
103
+ update(AgentApiToken)
104
+ .where(
105
+ AgentApiToken.agent_id == agent_id,
106
+ AgentApiToken.is_active.is_(True),
107
+ )
108
+ .values(
109
+ is_active=False,
110
+ revoked_at=revoked_at,
111
+ )
112
+ )
113
+ await self._session.flush()
114
+
115
+ return result.rowcount or 0 # type: ignore[attr-defined]
116
+
117
+
118
+ class AgentIdentityRepository:
119
+ def __init__(self, session: AsyncSession, trace_id: str) -> None:
120
+ self._session = session
121
+ self._logger = structlog.get_logger().bind(cls=self.__class__.__name__, trace_id=trace_id)
122
+
123
+ async def list(self) -> list[AgentIdentity]:
124
+ result = await self._session.execute(select(AgentIdentity))
125
+ return list(result.scalars().all())
126
+
127
+ async def get_by_id(self, agent_identity_id: int) -> AgentIdentity:
128
+ result = await self._session.execute(select(AgentIdentity).where(AgentIdentity.id == agent_identity_id))
129
+ agent_identity = result.scalar_one_or_none()
130
+ if agent_identity is None:
131
+ raise AgentIdentityNotFoundError
132
+
133
+ return agent_identity
134
+
135
+ async def create(self, dto: AgentIdentityCreate) -> AgentIdentity:
136
+ agent_identity = AgentIdentity(
137
+ agent_label=dto.agent_label,
138
+ role_id=dto.role_id,
139
+ tool_family=dto.tool_family,
140
+ created_at=datetime.now(timezone.utc),
141
+ )
142
+
143
+ self._session.add(agent_identity)
144
+ try:
145
+ await self._session.flush()
146
+ except IntegrityError as err:
147
+ await self._session.rollback()
148
+ raise AgentIdentityConflictError from err
149
+
150
+ return agent_identity
@@ -0,0 +1,17 @@
1
+ from pydantic import BaseModel, ConfigDict
2
+
3
+
4
+ class ListRolesResponse(BaseModel):
5
+ model_config = ConfigDict(from_attributes=True)
6
+
7
+ role_key: str
8
+ role_label: str
9
+
10
+
11
+ class GetMeResponse(BaseModel):
12
+ model_config = ConfigDict(from_attributes=True)
13
+
14
+ id: int
15
+ agent_label: str
16
+ role_id: int
17
+ tool_family: str | None
@@ -0,0 +1,117 @@
1
+ import hashlib
2
+ import secrets
3
+
4
+ import structlog
5
+ from forkflux_api.agents.dto import AgentApiTokenCreate, AgentIdentityCreate, TargetRoleCreate
6
+ from forkflux_api.agents.models import AgentApiToken, AgentIdentity, TargetRole
7
+ from forkflux_api.agents.respositories import AgentApiTokenRepository, AgentIdentityRepository, TargetRoleRepository
8
+
9
+
10
+ class TargetRoleService:
11
+ def __init__(self, target_role_repo: TargetRoleRepository, trace_id: str) -> None:
12
+ self._logger = structlog.get_logger().bind(cls=self.__class__.__name__, trace_id=trace_id)
13
+ self._target_role_repo = target_role_repo
14
+
15
+ async def get_all_roles(self) -> list[TargetRole]:
16
+ log = self._logger.bind(method="get_all_roles")
17
+ log.info("operation_started")
18
+
19
+ roles = await self._target_role_repo.list()
20
+
21
+ log.info("operation_completed", roles_count=len(roles))
22
+ return roles
23
+
24
+ async def get_by_role_key(self, role_key: str) -> TargetRole:
25
+ log = self._logger.bind(method="get_by_role_key", role_key=role_key)
26
+ log.info("operation_started")
27
+
28
+ role = await self._target_role_repo.get_by_role_key(role_key)
29
+
30
+ log.info("operation_completed")
31
+ return role
32
+
33
+ async def is_role_exists(self, role_key: str) -> bool:
34
+ log = self._logger.bind(method="is_role_exists", role_key=role_key)
35
+ log.info("operation_started")
36
+
37
+ exists = await self._target_role_repo.exists(role_key)
38
+
39
+ log.info("operation_completed", role_exists=exists)
40
+ return exists
41
+
42
+ async def create_role(self, dto: TargetRoleCreate) -> TargetRole:
43
+ log = self._logger.bind(method="create_role", role_key=dto.role_key)
44
+ log.info("operation_started")
45
+
46
+ role = await self._target_role_repo.create(dto)
47
+
48
+ log.info("operation_completed")
49
+ return role
50
+
51
+
52
+ class AgentApiTokenService:
53
+ def __init__(self, agent_api_token_repo: AgentApiTokenRepository, trace_id: str) -> None:
54
+ self._logger = structlog.get_logger().bind(cls=self.__class__.__name__, trace_id=trace_id)
55
+ self._agent_api_token_repo = agent_api_token_repo
56
+
57
+ async def get_token(self, token_hash: str) -> AgentApiToken:
58
+ log = self._logger.bind(method="get_token", token_hash=token_hash)
59
+ log.info("operation_started")
60
+
61
+ token = await self._agent_api_token_repo.get(token_hash)
62
+
63
+ log.info("operation_completed", token_id=token.id, agent_id=token.agent_id)
64
+ return token
65
+
66
+ async def create_token(self, dto: AgentApiTokenCreate) -> str:
67
+ log = self._logger.bind(method="create_token", agent_id=dto.agent_id)
68
+ log.info("operation_started")
69
+
70
+ raw_token = secrets.token_urlsafe(32)
71
+ token_hash = hashlib.sha256(raw_token.encode()).hexdigest()
72
+ await self._agent_api_token_repo.create(dto=dto, token_hash=token_hash)
73
+
74
+ log.info("operation_completed")
75
+ return raw_token
76
+
77
+ async def revoke_token(self, agent_id: int) -> int:
78
+ log = self._logger.bind(method="revoke_token", agent_id=agent_id)
79
+ log.info("operation_started")
80
+
81
+ revoked_count = await self._agent_api_token_repo.revoke(agent_id)
82
+
83
+ log.info("operation_completed", revoked_count=revoked_count)
84
+ return revoked_count
85
+
86
+
87
+ class AgentIdentityService:
88
+ def __init__(self, agent_identity_repo: AgentIdentityRepository, trace_id: str) -> None:
89
+ self._logger = structlog.get_logger().bind(cls=self.__class__.__name__, trace_id=trace_id)
90
+ self._agent_identity_repo = agent_identity_repo
91
+
92
+ async def get_all_agents(self) -> list[AgentIdentity]:
93
+ log = self._logger.bind(method="get_all_agents")
94
+ log.info("operation_started")
95
+
96
+ agents = await self._agent_identity_repo.list()
97
+
98
+ log.info("operation_completed", agents_count=len(agents))
99
+ return agents
100
+
101
+ async def get_by_id(self, agent_identity_id: int) -> AgentIdentity:
102
+ log = self._logger.bind(method="get_by_id", agent_identity_id=agent_identity_id)
103
+ log.info("operation_started")
104
+
105
+ agent = await self._agent_identity_repo.get_by_id(agent_identity_id)
106
+
107
+ log.info("operation_completed")
108
+ return agent
109
+
110
+ async def create_agent(self, dto: AgentIdentityCreate) -> AgentIdentity:
111
+ log = self._logger.bind(method="create_agent", agent_label=dto.agent_label, role_id=dto.role_id)
112
+ log.info("operation_started")
113
+
114
+ agent = await self._agent_identity_repo.create(dto)
115
+
116
+ log.info("operation_completed", agent_identity_id=agent.id)
117
+ return agent
forkflux_api/cli.py ADDED
@@ -0,0 +1,208 @@
1
+ import asyncio
2
+ import logging
3
+ import os
4
+ import pathlib
5
+ import sys
6
+ from functools import wraps
7
+ from uuid import uuid4
8
+
9
+ import structlog
10
+ import typer
11
+ import uvicorn
12
+ from alembic import command
13
+ from alembic.config import Config
14
+ from rich.console import Console
15
+ from rich.table import Table
16
+
17
+ sys.path.insert(0, str(pathlib.Path(__file__).resolve().parent.parent))
18
+
19
+ from forkflux_api.agents.dto import AgentApiTokenCreate, AgentIdentityCreate, TargetRoleCreate
20
+ from forkflux_api.agents.exceptions import (
21
+ AgentApiTokenConflictError,
22
+ AgentIdentityConflictError,
23
+ TargetRoleConflictError,
24
+ TargetRoleNotFoundError,
25
+ )
26
+ from forkflux_api.agents.respositories import AgentApiTokenRepository, AgentIdentityRepository, TargetRoleRepository
27
+ from forkflux_api.agents.services import AgentApiTokenService, AgentIdentityService, TargetRoleService
28
+ from forkflux_api.database import session_manager
29
+
30
+ app = typer.Typer(help="ForkFlux Management CLI")
31
+ console = Console()
32
+
33
+ _CLI_LOGGING_CONFIGURED = False
34
+
35
+ agents_role_app = typer.Typer(help="Agents role management")
36
+ agent_app = typer.Typer(help="Agents management")
37
+
38
+ app.add_typer(agents_role_app, name="agents-role")
39
+ app.add_typer(agent_app, name="agent")
40
+
41
+
42
+ def _configure_cli_logging() -> None:
43
+ """Suppress INFO logs for CLI-invoked services, keep WARNING/ERROR visible."""
44
+ global _CLI_LOGGING_CONFIGURED
45
+
46
+ if _CLI_LOGGING_CONFIGURED:
47
+ return
48
+
49
+ logging.basicConfig(level=logging.WARNING, force=True)
50
+ structlog.configure(wrapper_class=structlog.make_filtering_bound_logger(logging.WARNING))
51
+
52
+ _CLI_LOGGING_CONFIGURED = True
53
+
54
+
55
+ def apply_migrations() -> None:
56
+ console.print("Apply database migrations")
57
+ current_dir = os.path.dirname(__file__)
58
+ alembic_cfg = Config(toml_file="../pyproject.toml")
59
+ alembic_cfg.set_main_option("script_location", os.path.join(current_dir, "migrations"))
60
+ command.upgrade(alembic_cfg, "head")
61
+
62
+
63
+ @app.command(help="Run the server")
64
+ def serve(host: str = "0.0.0.0", port: int = 8080) -> None: # noqa: S104
65
+ apply_migrations()
66
+
67
+ console.print("Starting server...", style="bold green")
68
+ uvicorn.run(
69
+ "forkflux_api.main:app",
70
+ host=host,
71
+ port=port,
72
+ forwarded_allow_ips="*",
73
+ workers=2,
74
+ loop="none" if sys.platform == "win32" else "auto",
75
+ )
76
+
77
+
78
+ @app.command(help="Initialize the database and add some example data")
79
+ def init() -> None:
80
+ apply_migrations()
81
+
82
+ asyncio.run(_init_async())
83
+
84
+
85
+ async def _init_async() -> None:
86
+ _configure_cli_logging()
87
+
88
+ console.print("Lets add 2 roles - developer and QA")
89
+ await add_role.__wrapped__(role_key="developer", role_label="Developer")
90
+ await add_role.__wrapped__(role_key="qa", role_label="QA")
91
+
92
+ console.print("Lets add 2 agents - agent-1 and agent-2")
93
+ await add_agent.__wrapped__(agent_label="agent-1", role_key="developer")
94
+ await add_agent.__wrapped__(agent_label="agent-2", role_key="qa")
95
+
96
+
97
+ @agents_role_app.command("list")
98
+ @lambda f: wraps(f)(lambda *a, **kw: asyncio.run(f(*a, **kw)))
99
+ async def list_roles() -> None:
100
+ _configure_cli_logging()
101
+ trace_id = str(uuid4())
102
+
103
+ async with session_manager() as session:
104
+ repo = TargetRoleRepository(session=session, trace_id=trace_id)
105
+ roles = await TargetRoleService(target_role_repo=repo, trace_id=trace_id).get_all_roles()
106
+
107
+ table = Table("Key", "Label")
108
+ for role in roles:
109
+ table.add_row(role.role_key, role.role_label)
110
+ console.print(table)
111
+
112
+
113
+ @agents_role_app.command("add")
114
+ @lambda f: wraps(f)(lambda *a, **kw: asyncio.run(f(*a, **kw)))
115
+ async def add_role(role_key: str, role_label: str) -> None:
116
+ """
117
+ Adds a new role with the specified key and label.
118
+ """
119
+ _configure_cli_logging()
120
+ trace_id = str(uuid4())
121
+
122
+ async with session_manager() as session:
123
+ try:
124
+ repo = TargetRoleRepository(session=session, trace_id=trace_id)
125
+ dto = TargetRoleCreate(role_key=role_key, role_label=role_label)
126
+ new_role = await TargetRoleService(target_role_repo=repo, trace_id=trace_id).create_role(dto=dto)
127
+ console.print(f"Role {new_role.role_key} created successfully")
128
+ except TargetRoleConflictError:
129
+ console.print(f"Role with key {role_key} already exists", style="bold red")
130
+
131
+
132
+ @agent_app.command("list")
133
+ @lambda f: wraps(f)(lambda *a, **kw: asyncio.run(f(*a, **kw)))
134
+ async def list_agents() -> None:
135
+ _configure_cli_logging()
136
+ trace_id = str(uuid4())
137
+
138
+ async with session_manager() as session:
139
+ agent_repo = AgentIdentityRepository(session=session, trace_id=trace_id)
140
+ agents = await AgentIdentityService(agent_identity_repo=agent_repo, trace_id=trace_id).get_all_agents()
141
+ role_repo = TargetRoleRepository(session=session, trace_id=trace_id)
142
+ roles = await TargetRoleService(target_role_repo=role_repo, trace_id=trace_id).get_all_roles()
143
+
144
+ roles_mapping = {role.id: role.role_key for role in roles}
145
+
146
+ table = Table("ID", "Label", "Role key")
147
+ for agent in agents:
148
+ table.add_row(str(agent.id), agent.agent_label, roles_mapping[agent.role_id])
149
+ console.print(table)
150
+
151
+
152
+ @agent_app.command("add")
153
+ @lambda f: wraps(f)(lambda *a, **kw: asyncio.run(f(*a, **kw)))
154
+ async def add_agent(agent_label: str, role_key: str, tool_family: str | None = None) -> None:
155
+ """
156
+ Adds a new agent with the specified label, role, and tool family (optional).
157
+ """
158
+ _configure_cli_logging()
159
+ trace_id = str(uuid4())
160
+
161
+ async with session_manager() as session:
162
+ try:
163
+ role_repo = TargetRoleRepository(session=session, trace_id=trace_id)
164
+ role = await TargetRoleService(target_role_repo=role_repo, trace_id=trace_id).get_by_role_key(role_key)
165
+ except TargetRoleNotFoundError:
166
+ console.print(f"Role with key {role_key} not found", style="bold red")
167
+ return
168
+
169
+ try:
170
+ agent_repo = AgentIdentityRepository(session=session, trace_id=trace_id)
171
+ agent_dto = AgentIdentityCreate(agent_label=agent_label, role_id=role.id, tool_family=tool_family)
172
+ new_agent = await AgentIdentityService(agent_identity_repo=agent_repo, trace_id=trace_id).create_agent(
173
+ dto=agent_dto
174
+ )
175
+ console.print(f"Agent {new_agent.agent_label} created successfully")
176
+ except AgentIdentityConflictError:
177
+ console.print("Can't create new agent", style="bold red")
178
+ return
179
+
180
+ try:
181
+ token_repo = AgentApiTokenRepository(session=session, trace_id=trace_id)
182
+ token_dto = AgentApiTokenCreate(agent_id=new_agent.id)
183
+ new_token = await AgentApiTokenService(agent_api_token_repo=token_repo, trace_id=trace_id).create_token(
184
+ dto=token_dto
185
+ )
186
+ console.print(f"Token {new_token} for agent {new_agent.agent_label} created successfully")
187
+ except AgentApiTokenConflictError:
188
+ console.print("Can't create new token", style="bold red")
189
+ return
190
+
191
+
192
+ @agent_app.command("revoke-token")
193
+ @lambda f: wraps(f)(lambda *a, **kw: asyncio.run(f(*a, **kw)))
194
+ async def agent_revoke_token(agent_id: int) -> None:
195
+ """
196
+ Revokes the token associated with a specified agent.
197
+ """
198
+ _configure_cli_logging()
199
+ trace_id = str(uuid4())
200
+
201
+ async with session_manager() as session:
202
+ token_repo = AgentApiTokenRepository(session=session, trace_id=trace_id)
203
+ await AgentApiTokenService(agent_api_token_repo=token_repo, trace_id=trace_id).revoke_token(agent_id=agent_id)
204
+ console.print(f"Token for agent {agent_id} revoked successfully")
205
+
206
+
207
+ if __name__ == "__main__":
208
+ app()