diff --git a/api/entrypoints/routers.py b/api/entrypoints/routers.py index 2e1bfbaa4c..06b5191d3b 100644 --- a/api/entrypoints/routers.py +++ b/api/entrypoints/routers.py @@ -137,10 +137,6 @@ from oss.src.core.ai_services.service import AIServicesService from oss.src.apis.fastapi.ai_services.router import AIServicesRouter -from oss.src.dbs.postgres.sessions.states.dbes import SessionStateDBE # noqa: F401 -from oss.src.dbs.postgres.sessions.states.dao import SessionStatesDAO -from oss.src.core.sessions.states.service import SessionStatesService - from oss.src.core.accounts.service import PlatformAdminAccountsService from oss.src.apis.fastapi.accounts.router import PlatformAdminAccountsRouter from oss.src.dbs.postgres.gateway.connections.dao import ConnectionsDAO @@ -178,10 +174,15 @@ from oss.src.tasks.asyncio.sessions.orphan_sweep import orphan_sweep_loop from oss.src.dbs.redis.shared.engine import get_lock_engine +from oss.src.dbs.postgres.sessions.turns.dbes import SessionTurnDBE # noqa: F401 +from oss.src.dbs.postgres.sessions.turns.dao import SessionTurnsDAO +from oss.src.core.sessions.turns.service import SessionTurnsService + # Interactions from oss.src.dbs.postgres.sessions.interactions.dbes import SessionInteractionDBE # noqa: F401 from oss.src.dbs.postgres.sessions.interactions.dao import SessionInteractionsDAO from oss.src.core.sessions.interactions.service import SessionInteractionsService +from oss.src.core.sessions.service import SessionsService from oss.src.tasks.asyncio.sessions.interactions_dispatcher import ( InteractionsDispatcher, ) @@ -549,6 +550,7 @@ async def lifespan(*args, **kwargs): evaluations_dao = EvaluationsDAO(engine=_transactions_engine) folders_dao = FoldersDAO(engine=_transactions_engine) session_streams_dao = SessionStreamsDAO(engine=_transactions_engine) +session_turns_dao = SessionTurnsDAO(engine=_transactions_engine) connections_dao = ConnectionsDAO(engine=_transactions_engine) mounts_dao = MountsDAO(engine=_transactions_engine) @@ -616,6 +618,10 @@ async def lifespan(*args, **kwargs): lock_engine=_lock_engine, ) +session_turns_service = SessionTurnsService( + turns_dao=session_turns_dao, +) + workflows_service = WorkflowsService( workflows_dao=workflows_dao, static_catalog=StaticWorkflowCatalog(), @@ -1026,22 +1032,26 @@ async def _dispatch_detached_run(*, project_id, user_id, request) -> str: ai_services_service=ai_services_service, ) -# SESSION STATES --------------------------------------------------------------- +# SESSIONS --------------------------------------------------------------------- +# Session header rename (name/description) lives on the streams router +# (PUT /sessions/streams/header) via streams_service.set_header. -session_states_dao = SessionStatesDAO(engine=_transactions_engine) - -session_states_service = SessionStatesService( - session_states_dao=session_states_dao, +sessions_service = SessionsService( + streams_service=session_streams_service, + turns_service=session_turns_service, + interactions_service=interactions_service, + mounts_service=mounts_service, ) sessions = SessionsRouter( streams_service=session_streams_service, - states_service=session_states_service, records_service=records_service, interactions_service=interactions_service, workflows_service=workflows_service, session_mounts_service=session_mounts_service, mounts_service=mounts_service, + turns_service=session_turns_service, + sessions_service=sessions_service, respond_task=_interactions_worker.respond_interaction, ) @@ -1479,6 +1489,12 @@ async def _dispatch_detached_run(*, project_id, user_id, request) -> str: tags=["Sessions"], ) +app.include_router( + router=sessions.turns.router, + prefix="/sessions/turns", + tags=["Sessions"], +) + app.include_router( router=platform_admin_accounts.router, prefix="/admin", @@ -1486,8 +1502,7 @@ async def _dispatch_detached_run(*, project_id, user_id, request) -> str: ) app.include_router( - router=sessions.states.router, - prefix="/sessions", + router=sessions.root.router, tags=["Sessions"], ) diff --git a/api/oss/databases/postgres/migrations/core/env.py b/api/oss/databases/postgres/migrations/core/env.py index 93511ab358..219ebdb21d 100644 --- a/api/oss/databases/postgres/migrations/core/env.py +++ b/api/oss/databases/postgres/migrations/core/env.py @@ -22,7 +22,6 @@ import oss.src.dbs.postgres.users.dbes # noqa: F401 import oss.src.dbs.postgres.workflows.dbes # noqa: F401 import oss.src.dbs.postgres.webhooks.dbes # noqa: F401 -import oss.src.dbs.postgres.sessions.states.dbes # noqa: F401 # this is the Alembic Config object, which provides diff --git a/api/oss/databases/postgres/migrations/core_oss/versions/oss000000014_add_session_turns.py b/api/oss/databases/postgres/migrations/core_oss/versions/oss000000014_add_session_turns.py new file mode 100644 index 0000000000..40ff28ba20 --- /dev/null +++ b/api/oss/databases/postgres/migrations/core_oss/versions/oss000000014_add_session_turns.py @@ -0,0 +1,98 @@ +"""add session_turns table + +Revision ID: oss000000014 +Revises: oss000000013 +Create Date: 2026-07-17 00:00:00.000000 + +""" + +from typing import Sequence, Union + +from alembic import op +import sqlalchemy as sa +from sqlalchemy.dialects import postgresql + +revision: str = "oss000000014" +down_revision: Union[str, None] = "oss000000013" +branch_labels: Union[str, Sequence[str], None] = None +depends_on: Union[str, Sequence[str], None] = None + + +def upgrade() -> None: + op.create_table( + "session_turns", + sa.Column("id", sa.UUID(as_uuid=True), nullable=False), + sa.Column("project_id", sa.UUID(as_uuid=True), nullable=False), + sa.Column("session_id", sa.String(), nullable=False), + sa.Column("stream_id", sa.UUID(as_uuid=True), nullable=False), + sa.Column("turn_index", sa.Integer(), nullable=False), + sa.Column("harness_kind", sa.String(), nullable=False), + sa.Column("agent_session_id", sa.String(), nullable=True), + sa.Column("sandbox_id", sa.String(), nullable=True), + sa.Column( + "references", + postgresql.JSONB(none_as_null=True), + nullable=True, + ), + sa.Column("trace_id", sa.UUID(as_uuid=True), nullable=True), + # OTel span id: 64-bit / 16 hex chars, NOT a UUID (128-bit / 32 hex). Text, not UUID. + sa.Column("span_id", sa.String(), nullable=True), + sa.Column("start_time", sa.TIMESTAMP(timezone=True), nullable=True), + sa.Column("end_time", sa.TIMESTAMP(timezone=True), nullable=True), + sa.Column( + "created_at", + sa.TIMESTAMP(timezone=True), + server_default=sa.func.current_timestamp(), + nullable=True, + ), + sa.Column("updated_at", sa.TIMESTAMP(timezone=True), nullable=True), + sa.Column("deleted_at", sa.TIMESTAMP(timezone=True), nullable=True), + sa.Column("created_by_id", sa.UUID(as_uuid=True), nullable=True), + sa.Column("updated_by_id", sa.UUID(as_uuid=True), nullable=True), + sa.Column("deleted_by_id", sa.UUID(as_uuid=True), nullable=True), + sa.ForeignKeyConstraint( + ["project_id"], + ["projects.id"], + ondelete="CASCADE", + ), + sa.ForeignKeyConstraint( + ["project_id", "stream_id"], + ["session_streams.project_id", "session_streams.id"], + ondelete="NO ACTION", + ), + sa.PrimaryKeyConstraint("project_id", "id"), + ) + op.create_index( + "ix_session_turns_project_id_session_id", + "session_turns", + ["project_id", "session_id"], + ) + op.create_index( + "ix_session_turns_project_id_session_id_turn_index", + "session_turns", + ["project_id", "session_id", "turn_index"], + unique=True, + ) + op.create_index( + "ix_session_turns_references", + "session_turns", + ["references"], + postgresql_using="gin", + postgresql_ops={"references": "jsonb_path_ops"}, + ) + + +def downgrade() -> None: + op.drop_index( + "ix_session_turns_references", + table_name="session_turns", + ) + op.drop_index( + "ix_session_turns_project_id_session_id_turn_index", + table_name="session_turns", + ) + op.drop_index( + "ix_session_turns_project_id_session_id", + table_name="session_turns", + ) + op.drop_table("session_turns") diff --git a/api/oss/databases/postgres/migrations/core_oss/versions/oss000000015_add_session_streams_header.py b/api/oss/databases/postgres/migrations/core_oss/versions/oss000000015_add_session_streams_header.py new file mode 100644 index 0000000000..612d606a01 --- /dev/null +++ b/api/oss/databases/postgres/migrations/core_oss/versions/oss000000015_add_session_streams_header.py @@ -0,0 +1,33 @@ +"""add name/description header to session_streams + +Revision ID: oss000000015 +Revises: oss000000014 +Create Date: 2026-07-17 00:00:00.000000 + +""" + +from typing import Sequence, Union + +from alembic import op +import sqlalchemy as sa + +revision: str = "oss000000015" +down_revision: Union[str, None] = "oss000000014" +branch_labels: Union[str, Sequence[str], None] = None +depends_on: Union[str, Sequence[str], None] = None + + +def upgrade() -> None: + op.add_column( + "session_streams", + sa.Column("name", sa.String(), nullable=True), + ) + op.add_column( + "session_streams", + sa.Column("description", sa.String(), nullable=True), + ) + + +def downgrade() -> None: + op.drop_column("session_streams", "description") + op.drop_column("session_streams", "name") diff --git a/api/oss/databases/postgres/migrations/core_oss/versions/oss000000016_add_mounts_agent_id.py b/api/oss/databases/postgres/migrations/core_oss/versions/oss000000016_add_mounts_agent_id.py new file mode 100644 index 0000000000..8415b05eea --- /dev/null +++ b/api/oss/databases/postgres/migrations/core_oss/versions/oss000000016_add_mounts_agent_id.py @@ -0,0 +1,59 @@ +"""add mounts agent_id column, index, and backfill + +Mirrors session_id: a bare, nullable varchar populated only for agent mounts +(session mounts stay agent_id-null), with a partial index for querying "mounts +for agent X" the same way session_id already supports "mounts for session X". + +Backfill: mounts are core DB, so a data migration is allowed here (unlike the +tracing DB). Agent-mount slugs are minted as +`__ag__agent____` (`mint_agent_slug`, +`core/mounts/service.py`) — the artifact id is the raw canonical (lowercase) +UUID, not a hash, so it is deterministically recoverable straight out of the +slug. Session-mount slugs (`__ag__session____`) hash +the session id, so no session_id backfill is possible or attempted here. + +Revision ID: oss000000016 +Revises: oss000000015 +Create Date: 2026-07-17 00:00:00.000000 + +""" + +from typing import Sequence, Union + +from alembic import op +import sqlalchemy as sa + +revision: str = "oss000000016" +down_revision: Union[str, None] = "oss000000015" +branch_labels: Union[str, Sequence[str], None] = None +depends_on: Union[str, Sequence[str], None] = None + +_AGENT_SLUG_PREFIX = "__ag__agent__" + + +def upgrade() -> None: + op.add_column("mounts", sa.Column("agent_id", sa.String(), nullable=True)) + + op.create_index( + "ix_mounts_project_id_agent_id", + "mounts", + ["project_id", "agent_id"], + unique=False, + postgresql_where=sa.text("agent_id IS NOT NULL"), + ) + + conn = op.get_bind() + conn.execute( + sa.text( + f""" + UPDATE mounts + SET agent_id = split_part(substr(slug, {len(_AGENT_SLUG_PREFIX) + 1}), '__', 1) + WHERE left(slug, {len(_AGENT_SLUG_PREFIX)}) = '{_AGENT_SLUG_PREFIX}' + """ + ) + ) + + +def downgrade() -> None: + op.drop_index("ix_mounts_project_id_agent_id", table_name="mounts") + op.drop_column("mounts", "agent_id") diff --git a/api/oss/databases/postgres/migrations/core_oss/versions/oss000000017_drop_session_states.py b/api/oss/databases/postgres/migrations/core_oss/versions/oss000000017_drop_session_states.py new file mode 100644 index 0000000000..364befd38c --- /dev/null +++ b/api/oss/databases/postgres/migrations/core_oss/versions/oss000000017_drop_session_states.py @@ -0,0 +1,61 @@ +"""drop session_states table + +Superseded by the session_streams header (name/description) — the /sessions/states/ +router now reads/writes the merged stream row via streams_service. The standalone +table, its DAO/service, and DBE are orphaned; drop the physical table. + +Revision ID: oss000000017 +Revises: oss000000016 +""" + +from typing import Sequence, Union + +from alembic import op +import sqlalchemy as sa +from sqlalchemy.dialects import postgresql + + +revision: str = "oss000000017" +down_revision: Union[str, None] = "oss000000016" +branch_labels: Union[str, Sequence[str], None] = None +depends_on: Union[str, Sequence[str], None] = None + + +def upgrade() -> None: + op.drop_table("session_states") + + +def downgrade() -> None: + op.create_table( + "session_states", + sa.Column("project_id", sa.UUID(), nullable=False), + sa.Column("id", sa.UUID(), nullable=False), + sa.Column("session_id", sa.String(), nullable=False), + sa.Column("sandbox_id", sa.String(), nullable=True), + sa.Column( + "data", + postgresql.JSON(astext_type=sa.Text()), + nullable=True, + ), + sa.Column("flags", postgresql.JSONB(none_as_null=True), nullable=True), + sa.Column("tags", postgresql.JSONB(none_as_null=True), nullable=True), + sa.Column("meta", postgresql.JSON(none_as_null=True), nullable=True), + sa.Column( + "created_at", + sa.TIMESTAMP(timezone=True), + server_default=sa.text("CURRENT_TIMESTAMP"), + nullable=True, + ), + sa.Column("updated_at", sa.TIMESTAMP(timezone=True), nullable=True), + sa.Column("deleted_at", sa.TIMESTAMP(timezone=True), nullable=True), + sa.Column("created_by_id", sa.UUID(), nullable=True), + sa.Column("updated_by_id", sa.UUID(), nullable=True), + sa.Column("deleted_by_id", sa.UUID(), nullable=True), + sa.ForeignKeyConstraint(["project_id"], ["projects.id"], ondelete="CASCADE"), + sa.PrimaryKeyConstraint("project_id", "id"), + sa.UniqueConstraint( + "project_id", + "session_id", + name="uq_session_states_project_session_id", + ), + ) diff --git a/api/oss/databases/postgres/migrations/tracing_oss/versions/oss000000003_add_span_identity_columns.py b/api/oss/databases/postgres/migrations/tracing_oss/versions/oss000000003_add_span_identity_columns.py new file mode 100644 index 0000000000..bf50595d81 --- /dev/null +++ b/api/oss/databases/postgres/migrations/tracing_oss/versions/oss000000003_add_span_identity_columns.py @@ -0,0 +1,39 @@ +"""add_span_identity_columns + +Revision ID: oss000000003 +Revises: oss000000002 +Create Date: 2026-07-17 12:00:00.000000 + +""" + +from typing import Sequence, Union + +from alembic import op +import sqlalchemy as sa + +# revision identifiers, used by Alembic. +revision: str = "oss000000003" +down_revision: Union[str, None] = "oss000000002" +branch_labels: Union[str, Sequence[str], None] = None +depends_on: Union[str, Sequence[str], None] = None + + +def upgrade() -> None: + op.add_column("spans", sa.Column("session_id", sa.String(), nullable=True)) + op.add_column("spans", sa.Column("user_id", sa.String(), nullable=True)) + op.add_column("spans", sa.Column("agent_id", sa.String(), nullable=True)) + + op.create_index( + "ix_spans_project_id_session_id", + "spans", + ["project_id", "session_id"], + unique=False, + ) + + +def downgrade() -> None: + op.drop_index("ix_spans_project_id_session_id", table_name="spans") + + op.drop_column("spans", "agent_id") + op.drop_column("spans", "user_id") + op.drop_column("spans", "session_id") diff --git a/api/oss/databases/postgres/migrations/tracing_oss/versions/oss000000004_add_records_turn_span.py b/api/oss/databases/postgres/migrations/tracing_oss/versions/oss000000004_add_records_turn_span.py new file mode 100644 index 0000000000..b6ae152f13 --- /dev/null +++ b/api/oss/databases/postgres/migrations/tracing_oss/versions/oss000000004_add_records_turn_span.py @@ -0,0 +1,40 @@ +"""add_records_turn_span + +Revision ID: oss000000004 +Revises: oss000000003 +Create Date: 2026-07-17 12:16:14.000000 + +""" + +from typing import Sequence, Union + +from alembic import op +import sqlalchemy as sa + +# revision identifiers, used by Alembic. +revision: str = "oss000000004" +down_revision: Union[str, None] = "oss000000003" +branch_labels: Union[str, Sequence[str], None] = None +depends_on: Union[str, Sequence[str], None] = None + + +def upgrade() -> None: + # Plain columns, no FK (cross-DB: turns/spans live outside the tracing DB). Both + # nullable and forward-fill only — the tracing DB is never backfilled/data-migrated; + # existing rows carry no turn key to reconstruct one from, so they stay null. + op.add_column("records", sa.Column("turn_id", sa.String(), nullable=True)) + # OTel span id: 64-bit / 16 hex chars, NOT a UUID (128-bit / 32 hex). Text, not UUID. + op.add_column("records", sa.Column("span_id", sa.String(), nullable=True)) + + op.create_index( + "ix_records_project_id_session_id_turn_id", + "records", + ["project_id", "session_id", "turn_id"], + unique=False, + ) + + +def downgrade() -> None: + op.drop_index("ix_records_project_id_session_id_turn_id", table_name="records") + op.drop_column("records", "span_id") + op.drop_column("records", "turn_id") diff --git a/api/oss/src/apis/fastapi/mounts/router.py b/api/oss/src/apis/fastapi/mounts/router.py index 76f01d1001..cc670d8665 100644 --- a/api/oss/src/apis/fastapi/mounts/router.py +++ b/api/oss/src/apis/fastapi/mounts/router.py @@ -298,12 +298,14 @@ async def query_mounts( *, body: MountQueryRequest, session_id: Optional[str] = Query(default=None), + agent_id: Optional[str] = Query(default=None), include_archived: bool = Query(default=False), ) -> MountsResponse: await self._check(request, Permission.VIEW_MOUNTS) mount_query = merge_mount_query( session_id=session_id, + agent_id=agent_id, include_archived=include_archived, body_query=body.mount, ) diff --git a/api/oss/src/apis/fastapi/mounts/utils.py b/api/oss/src/apis/fastapi/mounts/utils.py index 29b09e1a4f..c8279fbba2 100644 --- a/api/oss/src/apis/fastapi/mounts/utils.py +++ b/api/oss/src/apis/fastapi/mounts/utils.py @@ -78,6 +78,7 @@ async def sign_mount_credentials( def merge_mount_query( *, session_id: Optional[str] = None, + agent_id: Optional[str] = None, include_archived: bool = False, body_query: Optional[MountQuery] = None, ) -> MountQuery: @@ -87,6 +88,9 @@ def merge_mount_query( if session_id is not None: base.session_id = session_id + if agent_id is not None: + base.agent_id = agent_id + if include_archived: base.include_archived = include_archived diff --git a/api/oss/src/apis/fastapi/otlp/extractors/adapters/logfire_adapter.py b/api/oss/src/apis/fastapi/otlp/extractors/adapters/logfire_adapter.py index bb3c1df1dd..32ec482ea2 100644 --- a/api/oss/src/apis/fastapi/otlp/extractors/adapters/logfire_adapter.py +++ b/api/oss/src/apis/fastapi/otlp/extractors/adapters/logfire_adapter.py @@ -182,7 +182,8 @@ def _split_agent_messages( ("tool_response", "ag.meta.tool_response"), # System instructions ("gen_ai.system_instructions", "ag.meta.system_instructions"), - # Agent description + # Agent identity/description + ("gen_ai.agent.id", "ag.agent.id"), ("gen_ai.agent.description", "ag.meta.agent.description"), # Provider name ("gen_ai.provider.name", "ag.meta.provider.name"), diff --git a/api/oss/src/apis/fastapi/otlp/extractors/canonical_attributes.py b/api/oss/src/apis/fastapi/otlp/extractors/canonical_attributes.py index 2e71678815..aa5d3f07c8 100644 --- a/api/oss/src/apis/fastapi/otlp/extractors/canonical_attributes.py +++ b/api/oss/src/apis/fastapi/otlp/extractors/canonical_attributes.py @@ -24,6 +24,7 @@ class SpanFeatures(BaseModel): tags: Dict[str, Any] = Field(default_factory=dict) session: Dict[str, Any] = Field(default_factory=dict) user: Dict[str, Any] = Field(default_factory=dict) + agent: Dict[str, Any] = Field(default_factory=dict) class EventData(BaseModel): diff --git a/api/oss/src/apis/fastapi/otlp/extractors/span_data_builders.py b/api/oss/src/apis/fastapi/otlp/extractors/span_data_builders.py index 15260ad1b8..1eaf2d4409 100644 --- a/api/oss/src/apis/fastapi/otlp/extractors/span_data_builders.py +++ b/api/oss/src/apis/fastapi/otlp/extractors/span_data_builders.py @@ -149,6 +149,7 @@ def build( attributes.update(**{f"ag.session.{k}": v for k, v in features.session.items()}) attributes.update(**{f"ag.user.{k}": v for k, v in features.user.items()}) + attributes.update(**{f"ag.agent.{k}": v for k, v in features.agent.items()}) attributes.update(**{f"ag.references.{k}": v for k, v in features.refs.items()}) diff --git a/api/oss/src/apis/fastapi/otlp/utils/serialization.py b/api/oss/src/apis/fastapi/otlp/utils/serialization.py index 7eb5ccac63..8709f7ba68 100644 --- a/api/oss/src/apis/fastapi/otlp/utils/serialization.py +++ b/api/oss/src/apis/fastapi/otlp/utils/serialization.py @@ -15,6 +15,7 @@ "ag.exception.": "exception", "ag.session.": "session", "ag.user.": "user", + "ag.agent.": "agent", } diff --git a/api/oss/src/apis/fastapi/sessions/models.py b/api/oss/src/apis/fastapi/sessions/models.py index dd701fd4af..2d71b3d202 100644 --- a/api/oss/src/apis/fastapi/sessions/models.py +++ b/api/oss/src/apis/fastapi/sessions/models.py @@ -5,10 +5,8 @@ from pydantic import BaseModel, Field from oss.src.core.sessions.streams.dtos import ( - CommandMode, SessionStream, ) -from oss.src.core.sessions.states.dtos import SessionState, SessionStateData from oss.src.core.sessions.records.dtos import SessionRecord from oss.src.core.sessions.interactions.dtos import ( SessionInteraction, @@ -19,92 +17,55 @@ SessionInteractionStatus, ) from oss.src.core.sessions.mounts.dtos import SessionMount, SessionMountQuery -from oss.src.core.shared.dtos import Windowing +from oss.src.core.sessions.turns.dtos import HarnessKind, SessionTurn, SessionTurnQuery +from oss.src.core.shared.dtos import OTelSpanId, Reference, Windowing # --------------------------------------------------------------------------- -# Streams request/response models +# Root session-level request/response models (query/delete/archive/unarchive) # --------------------------------------------------------------------------- -class SessionStreamCommandRequestModel(BaseModel): - session_id: str - prompt: Optional[str] = None - force: bool = False - detached: bool = False +class SessionQueryRequest(BaseModel): + references: Optional[List[Reference]] = None + windowing: Optional[Windowing] = None -class SessionHeartbeatRequestModel(BaseModel): - # project scope comes from the caller's credential, never the body - session_id: str - replica_id: str = Field(min_length=1) - turn_id: Optional[str] = None - is_running: bool = True +class SessionsResponse(BaseModel): + count: int = 0 + sessions: List[SessionStream] = Field(default_factory=list) -class SessionDetachRequestModel(BaseModel): - session_id: str - watcher_id: str +class SessionResponse(BaseModel): + count: int = 0 + session: Optional[SessionStream] = None -class SessionStreamQueryRequestModel(BaseModel): - session_id: Optional[str] = None - is_alive: Optional[bool] = None - is_running: Optional[bool] = None +# --------------------------------------------------------------------------- +# Streams request/response models +# --------------------------------------------------------------------------- -class SessionStreamCommandResponseModel(BaseModel): - mode: CommandMode +class SessionDetachRequest(BaseModel): session_id: str - turn_id: Optional[str] = None - watcher_id: Optional[str] = None - detached: bool = False + watcher_id: str -class SessionStreamResponseModel(BaseModel): - stream: Optional[SessionStream] = None +class SessionStreamQueryRequest(BaseModel): + session_id: Optional[str] = None + is_alive: Optional[bool] = None + is_running: Optional[bool] = None -class SessionHeartbeatResponseModel(BaseModel): - # the replica owning the session after the heartbeat's claim; the runner compares it - # to its own replica_id to refuse serving a session it doesn't own. +class SessionStreamResponse(BaseModel): stream: Optional[SessionStream] = None - replica_id: str -class SessionStreamsResponseModel(BaseModel): +class SessionStreamsResponse(BaseModel): count: int streams: List[SessionStream] -# --------------------------------------------------------------------------- -# States request/response models -# --------------------------------------------------------------------------- - - -class SessionStateResponse(BaseModel): - count: int = Field(default=0) - session_state: Optional[SessionState] = Field(default=None) - - -class SessionStateUpsertRequest(BaseModel): - data: Optional[SessionStateData] = Field( - default=None, - description="Full replacement of the continuity state (resume ids + staleness guard).", - ) - sandbox_id: Optional[str] = Field( - default=None, - description="Remote sandbox id to record alongside the continuity state.", - ) - sandbox_turn_index: Optional[int] = Field( - default=None, - description=( - "the writer's conversation turn index; the pointer write is applied only " - "when it is >= the row's data.latest_turn_index." - ), - ) - - # --------------------------------------------------------------------------- # Records request/response models # --------------------------------------------------------------------------- @@ -194,6 +155,41 @@ class SessionMountsResponse(BaseModel): mounts: List[SessionMount] = Field(default_factory=list) +# --------------------------------------------------------------------------- +# Turns request/response models +# --------------------------------------------------------------------------- + + +class SessionTurnAppendRequest(BaseModel): + # No project_id: scope comes from the caller's credential (request.state). + session_id: str + stream_id: UUID + turn_index: int + harness_kind: HarnessKind + agent_session_id: Optional[str] = None + sandbox_id: Optional[str] = None + references: Optional[List[Reference]] = None + trace_id: Optional[UUID] = None + span_id: Optional[OTelSpanId] = None + start_time: Optional[datetime] = None + end_time: Optional[datetime] = None + + +class SessionTurnQueryRequest(BaseModel): + query: Optional[SessionTurnQuery] = None + windowing: Optional[Windowing] = None + + +class SessionTurnResponse(BaseModel): + count: int = 0 + turn: Optional[SessionTurn] = None + + +class SessionTurnsResponse(BaseModel): + count: int = 0 + turns: List[SessionTurn] = Field(default_factory=list) + + # --------------------------------------------------------------------------- # Admin record ingest model # --------------------------------------------------------------------------- @@ -209,3 +205,7 @@ class SessionRecordIngestRequest(BaseModel): record_type: Optional[str] = None record_source: Optional[str] = None attributes: Optional[Dict[str, Any]] = None + # The turn this record belongs to; span_id bridges to observability when available. + # Both forward-fill only (tracing-DB rule) — absent on producers that predate this. + turn_id: Optional[str] = None + span_id: Optional[OTelSpanId] = None diff --git a/api/oss/src/apis/fastapi/sessions/router.py b/api/oss/src/apis/fastapi/sessions/router.py index 516769f8dd..37958056f3 100644 --- a/api/oss/src/apis/fastapi/sessions/router.py +++ b/api/oss/src/apis/fastapi/sessions/router.py @@ -1,10 +1,19 @@ """Unified sessions API router. -Composes four sub-domain routers: +Composes the sub-domain routers: - SessionStreamsRouter — /sessions/streams/* - - SessionStatesRouter — /sessions/states/ - RecordsRouter — /sessions/records/* - InteractionsRouter — /sessions/interactions/* + - SessionTurnsRouter — /sessions/turns/* + - SessionsRootRouter — /sessions/query, /sessions/ (DELETE), + /sessions/archive, /sessions/unarchive + +peek (S12/E1) is NOT a verb and NOT a server-side aggregate. It is the front-end +composing the individual reads already exposed here: + 1. `POST /sessions/query` (this router) -> a list of session_ids. + 2. Per session_id: `GET /sessions/streams/?session_id=` (fetch the stream), + `POST /sessions/turns/query` (turns), `POST /sessions/records/query` + (records). No overlay/aggregate endpoint exists or is planned. """ import re @@ -25,7 +34,10 @@ # Core domain imports — new paths from oss.src.core.sessions.streams.dtos import ( SessionHeartbeatRequest, + SessionHeartbeatResult, SessionStreamCommandRequest, + SessionStreamCommandResponse, + SessionStreamHeaderEdit, SessionStreamQuery, SessionStreamQueryFlags, ) @@ -37,8 +49,6 @@ SessionStreamNotFound, ) from oss.src.core.sessions.streams.service import SessionStreamsService -from oss.src.core.sessions.states.service import SessionStatesService -from oss.src.core.sessions.states.dtos import SessionStateUpsert from oss.src.core.sessions.records.service import RecordsService from oss.src.core.sessions.records.dtos import SessionRecordEvent from oss.src.core.sessions.records.streaming import publish_record @@ -51,6 +61,10 @@ from oss.src.core.sessions.interactions.types import InteractionNotFound from oss.src.core.sessions.mounts.service import SessionMountsService from oss.src.core.sessions.mounts.dtos import SessionMountQuery +from oss.src.core.sessions.turns.dtos import SessionTurnCreate +from oss.src.core.sessions.turns.service import SessionTurnsService +from oss.src.core.sessions.dtos import SessionQuery +from oss.src.core.sessions.service import SessionsService from oss.src.core.mounts.service import MountsService from oss.src.apis.fastapi.mounts.router import handle_mount_exceptions from oss.src.apis.fastapi.mounts.utils import ( @@ -70,17 +84,10 @@ from oss.src.apis.fastapi.sessions.models import ( # streams - SessionDetachRequestModel, - SessionHeartbeatRequestModel, - SessionHeartbeatResponseModel, - SessionStreamCommandRequestModel, - SessionStreamCommandResponseModel, - SessionStreamQueryRequestModel, - SessionStreamResponseModel, - SessionStreamsResponseModel, - # states - SessionStateResponse, - SessionStateUpsertRequest, + SessionDetachRequest, + SessionStreamQueryRequest, + SessionStreamResponse, + SessionStreamsResponse, # records SessionRecordIngestRequest, SessionRecordQueryRequest, @@ -98,6 +105,15 @@ SessionMountQueryRequest, SessionMountResponse, # noqa: F401 (exported for OpenAPI/single-mount future use) SessionMountsResponse, + # turns + SessionTurnAppendRequest, + SessionTurnQueryRequest, + SessionTurnResponse, + SessionTurnsResponse, + # root session-level ops + SessionQueryRequest, + SessionResponse, + SessionsResponse, ) log = get_module_logger(__name__) @@ -217,13 +233,21 @@ def __init__( tags=["Sessions"], ) + self.router.add_api_route( + "/sessions/streams/header", + self.set_session_stream_header, + methods=["PUT", "POST"], + operation_id="set_session_stream_header", + tags=["Sessions"], + ) + @intercept_exceptions() @_handle_session_exceptions() async def set_session_stream( self, request: Request, - payload: SessionStreamCommandRequestModel, - ) -> SessionStreamCommandResponseModel: + payload: SessionStreamCommandRequest, + ) -> SessionStreamCommandResponse: project_id = request.state.project_id user_id = request.state.user_id @@ -237,22 +261,10 @@ async def set_session_stream( await self._service.check_runner_concurrency_limit(project_id=project_id) - result = await self._service.command( + return await self._service.command( project_id=project_id, user_id=user_id, - request=SessionStreamCommandRequest( - session_id=payload.session_id, - prompt=payload.prompt, - force=payload.force, - detached=payload.detached, - ), - ) - return SessionStreamCommandResponseModel( - mode=result.mode, - session_id=result.session_id, - turn_id=result.turn_id, - watcher_id=result.watcher_id, - detached=result.detached, + request=payload, ) @intercept_exceptions() @@ -261,7 +273,7 @@ async def fetch_session_stream( self, request: Request, session_id: str = Query(...), - ) -> SessionStreamResponseModel: + ) -> SessionStreamResponse: project_id = request.state.project_id user_id = request.state.user_id @@ -277,7 +289,7 @@ async def fetch_session_stream( project_id=UUID(str(project_id)), session_id=session_id, ) - return SessionStreamResponseModel(stream=stream) + return SessionStreamResponse(stream=stream) @intercept_exceptions() @_handle_session_exceptions() @@ -314,7 +326,7 @@ async def delete_session_stream( async def detach_session_stream( self, request: Request, - payload: SessionDetachRequestModel, + payload: SessionDetachRequest, ) -> dict: project_id = request.state.project_id user_id = request.state.user_id @@ -340,8 +352,8 @@ async def detach_session_stream( async def heartbeat_session_stream( self, request: Request, - payload: SessionHeartbeatRequestModel, - ) -> SessionHeartbeatResponseModel: + payload: SessionHeartbeatRequest, + ) -> SessionHeartbeatResult: project_id = request.state.project_id user_id = request.state.user_id @@ -353,18 +365,9 @@ async def heartbeat_session_stream( if not has_permission: raise FORBIDDEN_EXCEPTION - result = await self._service.heartbeat( + return await self._service.heartbeat( project_id=project_id, - request=SessionHeartbeatRequest( - session_id=payload.session_id, - replica_id=payload.replica_id, - turn_id=payload.turn_id, - is_running=payload.is_running, - ), - ) - return SessionHeartbeatResponseModel( - stream=result.stream, - replica_id=result.replica_id, + request=payload, ) @intercept_exceptions() @@ -372,8 +375,8 @@ async def heartbeat_session_stream( async def query_session_streams( self, request: Request, - payload: SessionStreamQueryRequestModel, - ) -> SessionStreamsResponseModel: + payload: SessionStreamQueryRequest, + ) -> SessionStreamsResponse: project_id = request.state.project_id user_id = request.state.user_id @@ -395,70 +398,17 @@ async def query_session_streams( ), ), ) - return SessionStreamsResponseModel(count=len(streams), streams=streams) - - -class SessionStatesRouter: - """States sub-router — /sessions/states/?session_id=...""" - - __test__ = False - - def __init__(self, *, session_states_service: SessionStatesService): - self.session_states_service = session_states_service - self.router = APIRouter() - - self.router.add_api_route( - "/states/", - self.get_session_state, - methods=["GET"], - operation_id="get_state", - status_code=status.HTTP_200_OK, - response_model=SessionStateResponse, - response_model_exclude_none=True, - ) - self.router.add_api_route( - "/states/", - self.set_session_state, - methods=["PUT", "POST"], - operation_id="set_state", - status_code=status.HTTP_200_OK, - response_model=SessionStateResponse, - response_model_exclude_none=True, - ) + return SessionStreamsResponse(count=len(streams), streams=streams) @intercept_exceptions() - async def get_session_state( - self, - request: Request, - *, - session_id: str = Query(...), - ) -> SessionStateResponse: - _validate_session_id_http(session_id) - - if not await check_action_access( - user_uid=request.state.user_id, - project_id=request.state.project_id, - permission=Permission.VIEW_SESSIONS, - ): - raise FORBIDDEN_EXCEPTION - - session_state = await self.session_states_service.get_session_state( - project_id=UUID(request.state.project_id), - session_id=session_id, - ) - return SessionStateResponse( - count=1 if session_state else 0, - session_state=session_state, - ) - - @intercept_exceptions() - async def set_session_state( + @_handle_session_exceptions() + async def set_session_stream_header( self, request: Request, *, - session_state_upsert_request: SessionStateUpsertRequest, + header: SessionStreamHeaderEdit, session_id: str = Query(...), - ) -> SessionStateResponse: + ) -> SessionStreamResponse: _validate_session_id_http(session_id) if not await check_action_access( @@ -468,18 +418,13 @@ async def set_session_state( ): raise FORBIDDEN_EXCEPTION - session_state = await self.session_states_service.set_session_state( + stream = await self._service.set_header( project_id=UUID(request.state.project_id), user_id=UUID(request.state.user_id), session_id=session_id, - upsert=SessionStateUpsert( - **session_state_upsert_request.model_dump(exclude_unset=True) - ), - ) - return SessionStateResponse( - count=1 if session_state else 0, - session_state=session_state, + header=header, ) + return SessionStreamResponse(stream=stream) class RecordsRouter: @@ -584,6 +529,8 @@ async def ingest_record_event( record_type=body.record_type, record_source=body.record_source, attributes=body.attributes, + turn_id=body.turn_id, + span_id=body.span_id, ), ) return {"ok": True} @@ -1085,6 +1032,270 @@ async def download_session_mount_file( ) +class SessionTurnsRouter: + """Turns sub-router — /sessions/turns/*""" + + def __init__(self, *, turns_service: SessionTurnsService) -> None: + self.turns_service = turns_service + self.router = APIRouter() + + self.router.add_api_route( + "/", + self.append_turn, + methods=["POST"], + operation_id="append_turn", + status_code=status.HTTP_200_OK, + response_model=SessionTurnResponse, + response_model_exclude_none=True, + ) + self.router.add_api_route( + "/query", + self.query_turns, + methods=["POST"], + operation_id="query_turns", + status_code=status.HTTP_200_OK, + response_model=SessionTurnsResponse, + response_model_exclude_none=True, + ) + self.router.add_api_route( + "/{turn_id}", + self.fetch_turn, + methods=["GET"], + operation_id="fetch_turn", + status_code=status.HTTP_200_OK, + response_model=SessionTurnResponse, + response_model_exclude_none=True, + ) + + @intercept_exceptions() + async def append_turn( + self, + request: Request, + body: SessionTurnAppendRequest, + ) -> SessionTurnResponse: + project_id: UUID = request.state.project_id + user_id: UUID = request.state.user_id + + if not await check_action_access( + user_uid=str(user_id), + project_id=str(project_id), + permission=Permission.RUN_SESSIONS, + ): + raise FORBIDDEN_EXCEPTION + + turn = await self.turns_service.append_turn( + project_id=project_id, + user_id=user_id, + turn=SessionTurnCreate( + session_id=body.session_id, + stream_id=body.stream_id, + turn_index=body.turn_index, + harness_kind=body.harness_kind, + agent_session_id=body.agent_session_id, + sandbox_id=body.sandbox_id, + references=body.references, + trace_id=body.trace_id, + span_id=body.span_id, + start_time=body.start_time, + end_time=body.end_time, + ), + ) + return SessionTurnResponse(count=1, turn=turn) + + @intercept_exceptions() + async def query_turns( + self, + request: Request, + body: SessionTurnQueryRequest, + ) -> SessionTurnsResponse: + project_id: UUID = request.state.project_id + user_id: UUID = request.state.user_id + + if not await check_action_access( + user_uid=str(user_id), + project_id=str(project_id), + permission=Permission.VIEW_SESSIONS, + ): + raise FORBIDDEN_EXCEPTION + + turns = await self.turns_service.query_turns( + project_id=project_id, + query=body.query, + windowing=body.windowing, + ) + return SessionTurnsResponse(count=len(turns), turns=turns) + + @intercept_exceptions() + async def fetch_turn( + self, + request: Request, + turn_id: UUID, + ) -> SessionTurnResponse: + project_id: UUID = request.state.project_id + user_id: UUID = request.state.user_id + + if not await check_action_access( + user_uid=str(user_id), + project_id=str(project_id), + permission=Permission.VIEW_SESSIONS, + ): + raise FORBIDDEN_EXCEPTION + + turn = await self.turns_service.fetch_turn( + project_id=project_id, + turn_id=turn_id, + ) + return SessionTurnResponse(count=1 if turn else 0, turn=turn) + + +class SessionsRootRouter: + """Root session-level operations — /sessions/query, /sessions/ (DELETE), + /sessions/archive, /sessions/unarchive. + + Orchestrates across facets via `SessionsService`, anchored on `session_id` + (never `stream_id`). RBAC: VIEW_SESSIONS for query, EDIT_SESSIONS for the + three mutations. + """ + + def __init__(self, *, sessions_service: SessionsService) -> None: + self.sessions_service = sessions_service + self.router = APIRouter() + + self.router.add_api_route( + "/sessions/query", + self.query_sessions, + methods=["POST"], + operation_id="query_sessions", + status_code=status.HTTP_200_OK, + response_model=SessionsResponse, + response_model_exclude_none=True, + tags=["Sessions"], + ) + self.router.add_api_route( + "/sessions/", + self.delete_session, + methods=["DELETE"], + operation_id="delete_session", + status_code=status.HTTP_200_OK, + tags=["Sessions"], + ) + self.router.add_api_route( + "/sessions/archive", + self.archive_session, + methods=["POST"], + operation_id="archive_session", + status_code=status.HTTP_200_OK, + response_model=SessionResponse, + response_model_exclude_none=True, + tags=["Sessions"], + ) + self.router.add_api_route( + "/sessions/unarchive", + self.unarchive_session, + methods=["POST"], + operation_id="unarchive_session", + status_code=status.HTTP_200_OK, + response_model=SessionResponse, + response_model_exclude_none=True, + tags=["Sessions"], + ) + + @intercept_exceptions() + async def query_sessions( + self, + request: Request, + body: SessionQueryRequest, + ) -> SessionsResponse: + project_id = request.state.project_id + user_id = request.state.user_id + + if not await check_action_access( + user_uid=str(user_id), + project_id=str(project_id), + permission=Permission.VIEW_SESSIONS, + ): + raise FORBIDDEN_EXCEPTION + + sessions = await self.sessions_service.query_sessions( + project_id=UUID(str(project_id)), + query=SessionQuery(references=body.references), + windowing=body.windowing, + ) + return SessionsResponse(count=len(sessions), sessions=sessions) + + @intercept_exceptions() + async def delete_session( + self, + request: Request, + session_id: str = Query(...), + ) -> dict: + _validate_session_id_http(session_id) + project_id = request.state.project_id + user_id = request.state.user_id + + if not await check_action_access( + user_uid=str(user_id), + project_id=str(project_id), + permission=Permission.EDIT_SESSIONS, + ): + raise FORBIDDEN_EXCEPTION + + await self.sessions_service.delete_session( + project_id=UUID(str(project_id)), + user_id=UUID(str(user_id)), + session_id=session_id, + ) + return {"ok": True} + + @intercept_exceptions() + async def archive_session( + self, + request: Request, + session_id: str = Query(...), + ) -> SessionResponse: + _validate_session_id_http(session_id) + project_id = request.state.project_id + user_id = request.state.user_id + + if not await check_action_access( + user_uid=str(user_id), + project_id=str(project_id), + permission=Permission.EDIT_SESSIONS, + ): + raise FORBIDDEN_EXCEPTION + + session = await self.sessions_service.archive_session( + project_id=UUID(str(project_id)), + user_id=UUID(str(user_id)), + session_id=session_id, + ) + return SessionResponse(count=1 if session else 0, session=session) + + @intercept_exceptions() + async def unarchive_session( + self, + request: Request, + session_id: str = Query(...), + ) -> SessionResponse: + _validate_session_id_http(session_id) + project_id = request.state.project_id + user_id = request.state.user_id + + if not await check_action_access( + user_uid=str(user_id), + project_id=str(project_id), + permission=Permission.EDIT_SESSIONS, + ): + raise FORBIDDEN_EXCEPTION + + session = await self.sessions_service.unarchive_session( + project_id=UUID(str(project_id)), + user_id=UUID(str(user_id)), + session_id=session_id, + ) + return SessionResponse(count=1 if session else 0, session=session) + + # --------------------------------------------------------------------------- # Top-level composer # --------------------------------------------------------------------------- @@ -1095,29 +1306,30 @@ class SessionsRouter: The entrypoint mounts: sessions_router.streams.router → no prefix (paths include /sessions/streams/…) - sessions_router.states.router → prefix /sessions sessions_router.records.router → prefix /sessions/records sessions_router.interactions.router → prefix /sessions/interactions sessions_router.mounts.router → prefix /sessions + sessions_router.turns.router → prefix /sessions/turns + sessions_router.root.router → no prefix (paths include /sessions/query, /sessions/, /sessions/archive, /sessions/unarchive) """ def __init__( self, *, streams_service: SessionStreamsService, - states_service: SessionStatesService, records_service: RecordsService, interactions_service: SessionInteractionsService, workflows_service: WorkflowsService, session_mounts_service: SessionMountsService, mounts_service: MountsService, + turns_service: SessionTurnsService, + sessions_service: SessionsService, respond_task: Optional[Any] = None, ) -> None: self.streams = SessionStreamsRouter( service=streams_service, interactions_service=interactions_service, ) - self.states = SessionStatesRouter(session_states_service=states_service) self.records = RecordsRouter(records_service=records_service) self.interactions = InteractionsRouter( interactions_service=interactions_service, @@ -1128,3 +1340,5 @@ def __init__( session_mounts_service=session_mounts_service, mounts_service=mounts_service, ) + self.turns = SessionTurnsRouter(turns_service=turns_service) + self.root = SessionsRootRouter(sessions_service=sessions_service) diff --git a/api/oss/src/apis/fastapi/tracing/router.py b/api/oss/src/apis/fastapi/tracing/router.py index d84aaf7b40..5ee060c5b4 100644 --- a/api/oss/src/apis/fastapi/tracing/router.py +++ b/api/oss/src/apis/fastapi/tracing/router.py @@ -178,7 +178,7 @@ def __init__( "/sessions/query", self.list_sessions, methods=["POST"], - operation_id="query_sessions", + operation_id="query_sessions_tracing", status_code=status.HTTP_200_OK, response_model=SessionIdsResponse, response_model_exclude_none=True, diff --git a/api/oss/src/core/mounts/dtos.py b/api/oss/src/core/mounts/dtos.py index c3d9309ab0..552f37aa88 100644 --- a/api/oss/src/core/mounts/dtos.py +++ b/api/oss/src/core/mounts/dtos.py @@ -25,6 +25,7 @@ class MountFlags(BaseModel): class Mount(Identifier, Slug, Header, Lifecycle): project_id: UUID session_id: Optional[str] = None + agent_id: Optional[str] = None # data: MountData = Field(default_factory=MountData) # @@ -35,6 +36,7 @@ class Mount(Identifier, Slug, Header, Lifecycle): class MountCreate(Slug, Header): session_id: Optional[str] = None + agent_id: Optional[str] = None # flags: MountFlags = Field(default_factory=MountFlags) tags: Optional[Dict[str, Any]] = None @@ -49,6 +51,7 @@ class MountEdit(Identifier, Header): class MountQuery(BaseModel): session_id: Optional[str] = None + agent_id: Optional[str] = None include_archived: bool = False diff --git a/api/oss/src/core/mounts/interfaces.py b/api/oss/src/core/mounts/interfaces.py index fa26dc530b..45ea77b8b3 100644 --- a/api/oss/src/core/mounts/interfaces.py +++ b/api/oss/src/core/mounts/interfaces.py @@ -85,3 +85,11 @@ async def query_mounts( # windowing: Optional[Windowing] = None, ) -> List[Mount]: ... + + @abstractmethod + async def delete_by_session_id( + self, + *, + project_id: UUID, + session_id: str, + ) -> List[Mount]: ... diff --git a/api/oss/src/core/mounts/service.py b/api/oss/src/core/mounts/service.py index c6f13656b1..4c20c3e161 100644 --- a/api/oss/src/core/mounts/service.py +++ b/api/oss/src/core/mounts/service.py @@ -78,17 +78,25 @@ def mint_session_slug(*, session_id: str, name: str) -> str: return f"{_RESERVED_SLUG_PREFIX}session__{uuid5(_MOUNTS_NAMESPACE, session_id)}__{slugify_mount_name(name)}" -def mint_agent_slug(*, artifact_id: str, name: str) -> str: - """Mint the deterministic reserved slug for an artifact mount. +def mint_agent_id(*, artifact_id: str) -> str: + """Canonicalize an artifact id: UUID-parsed, rendered lowercase. - Artifact IDs are UUID-parsed and rendered lowercase. Sign and query must use - this same derivation byte-identically so they address the same mount. + Shared by `mint_agent_slug` (the slug's id segment) and the mount's queryable + `agent_id` column, so both derive byte-identically from the same artifact id. """ try: - canonical_artifact_id = UUID(str(artifact_id)) + return str(UUID(str(artifact_id))) except (ValueError, TypeError, AttributeError) as e: raise MountArtifactIdInvalid(str(artifact_id)) from e + +def mint_agent_slug(*, artifact_id: str, name: str) -> str: + """Mint the deterministic reserved slug for an artifact mount. + + Artifact IDs are UUID-parsed and rendered lowercase. Sign and query must use + this same derivation byte-identically so they address the same mount. + """ + canonical_artifact_id = mint_agent_id(artifact_id=artifact_id) slug_name = slugify_mount_name(name) return f"{_RESERVED_SLUG_PREFIX}agent__{canonical_artifact_id}__{slug_name}" @@ -252,9 +260,11 @@ async def get_or_create_agent_mount( ) slug_name = slugify_mount_name(name) + canonical_artifact_id = mint_agent_id(artifact_id=artifact_id) mount_create = MountCreate( slug=mint_agent_slug(artifact_id=artifact_id, name=name), name=slug_name, + agent_id=canonical_artifact_id, ) return await self.mounts_dao.upsert_mount( project_id=project_id, @@ -368,6 +378,78 @@ async def query_mounts( windowing=windowing, ) + async def delete_session_mounts( + self, + *, + project_id: UUID, + session_id: str, + ) -> List[Mount]: + """Hard-delete every mount bound to a session, tearing down each mount's + object-store prefix. Explicit + session-aware (S7/F1, WP5): mounts are + semi-independent (optional `session_id`, can outlive a session), so this + is a targeted fan-out, never a blind cascade.""" + mounts = await self.mounts_dao.delete_by_session_id( + project_id=project_id, + session_id=session_id, + ) + if self.mounts_store is not None and self.bucket: + for mount in mounts: + prefix = self._storage_key(project_id=project_id, mount=mount) + await self.mounts_store.delete_prefix( + bucket=self.bucket, + prefix=prefix, + ) + return mounts + + async def archive_session_mounts( + self, + *, + project_id: UUID, + user_id: UUID, + session_id: str, + ) -> List[Mount]: + """Soft-archive every mount bound to a session (reversible; folders + untouched). Session archive fan-out (S7/F2, WP5).""" + mounts = await self.mounts_dao.query_mounts( + project_id=project_id, + mount_query=MountQuery(session_id=session_id, include_archived=False), + ) + archived: List[Mount] = [] + for mount in mounts: + result = await self.mounts_dao.archive_mount( + project_id=project_id, + user_id=user_id, + mount_id=mount.id, + ) + if result is not None: + archived.append(result) + return archived + + async def unarchive_session_mounts( + self, + *, + project_id: UUID, + user_id: UUID, + session_id: str, + ) -> List[Mount]: + """Reverse of `archive_session_mounts`.""" + mounts = await self.mounts_dao.query_mounts( + project_id=project_id, + mount_query=MountQuery(session_id=session_id, include_archived=True), + ) + unarchived: List[Mount] = [] + for mount in mounts: + if mount.deleted_at is None: + continue + result = await self.mounts_dao.unarchive_mount( + project_id=project_id, + user_id=user_id, + mount_id=mount.id, + ) + if result is not None: + unarchived.append(result) + return unarchived + # --- File ops (durable store contents) --------------------------------- # async def _resolve_mount( diff --git a/api/oss/src/core/sessions/dtos.py b/api/oss/src/core/sessions/dtos.py new file mode 100644 index 0000000000..bfcdcb1bb9 --- /dev/null +++ b/api/oss/src/core/sessions/dtos.py @@ -0,0 +1,12 @@ +from typing import List, Optional + +from pydantic import BaseModel + +from oss.src.core.shared.dtos import Reference + + +class SessionQuery(BaseModel): + """Root `/sessions/query` filter: reference-scoped, joined through the turns' + references (WP1's GIN `.contains()`), not denormalized onto the stream row.""" + + references: Optional[List[Reference]] = None diff --git a/api/oss/src/core/sessions/interactions/dtos.py b/api/oss/src/core/sessions/interactions/dtos.py index 91b61d9e71..89e86ad64e 100644 --- a/api/oss/src/core/sessions/interactions/dtos.py +++ b/api/oss/src/core/sessions/interactions/dtos.py @@ -4,7 +4,7 @@ from pydantic import BaseModel -from oss.src.core.shared.dtos import Reference, Selector +from oss.src.core.shared.dtos import Identifier, Lifecycle, Reference, Selector class SessionInteractionKind(str, Enum): @@ -38,16 +38,7 @@ class SessionInteractionQueryFlags(BaseModel): delivered_webhook: Optional[bool] = None -class SessionInteraction(BaseModel): - id: Optional[UUID] = None - # - created_at: Optional[Any] = None - updated_at: Optional[Any] = None - deleted_at: Optional[Any] = None - created_by_id: Optional[UUID] = None - updated_by_id: Optional[UUID] = None - deleted_by_id: Optional[UUID] = None - # +class SessionInteraction(Identifier, Lifecycle): project_id: Optional[UUID] = None session_id: str turn_id: Optional[str] = None diff --git a/api/oss/src/core/sessions/interactions/interfaces.py b/api/oss/src/core/sessions/interactions/interfaces.py index 17a97ffbd4..f66f6e9bb1 100644 --- a/api/oss/src/core/sessions/interactions/interfaces.py +++ b/api/oss/src/core/sessions/interactions/interfaces.py @@ -57,3 +57,11 @@ async def query_interactions( query: Optional[SessionInteractionQuery] = None, windowing: Optional[Windowing] = None, ) -> List[SessionInteraction]: ... + + @abstractmethod + async def delete_by_session_id( + self, + *, + project_id: UUID, + session_id: str, + ) -> int: ... diff --git a/api/oss/src/core/sessions/interactions/service.py b/api/oss/src/core/sessions/interactions/service.py index 3a86c049c7..16130b252d 100644 --- a/api/oss/src/core/sessions/interactions/service.py +++ b/api/oss/src/core/sessions/interactions/service.py @@ -89,3 +89,15 @@ async def query_interactions( query=query, windowing=windowing, ) + + async def delete_by_session_id( + self, + *, + project_id: UUID, + session_id: str, + ) -> int: + """Hard delete every interaction for a session (S7 delete fan-out, WP5).""" + return await self.interactions_dao.delete_by_session_id( + project_id=project_id, + session_id=session_id, + ) diff --git a/api/oss/src/core/sessions/records/dtos.py b/api/oss/src/core/sessions/records/dtos.py index b725b03185..44dd01092f 100644 --- a/api/oss/src/core/sessions/records/dtos.py +++ b/api/oss/src/core/sessions/records/dtos.py @@ -4,6 +4,8 @@ from pydantic import BaseModel +from oss.src.core.shared.dtos import Lifecycle, OTelSpanId + class SessionRecordEvent(BaseModel): project_id: UUID @@ -16,8 +18,12 @@ class SessionRecordEvent(BaseModel): record_source: Optional[str] = None attributes: Optional[Dict[str, Any]] = None + # Forward-fill only (tracing-DB rule): populated on new records, null on old ones. + turn_id: Optional[str] = None + span_id: Optional[OTelSpanId] = None + -class SessionRecord(BaseModel): +class SessionRecord(Lifecycle): record_id: UUID session_id: str @@ -29,7 +35,8 @@ class SessionRecord(BaseModel): record_source: Optional[str] = None attributes: Optional[Dict[str, Any]] = None - created_at: Optional[datetime] = None + turn_id: Optional[str] = None + span_id: Optional[OTelSpanId] = None class SessionRecordQuery(BaseModel): diff --git a/api/oss/src/core/sessions/service.py b/api/oss/src/core/sessions/service.py new file mode 100644 index 0000000000..978409f0f4 --- /dev/null +++ b/api/oss/src/core/sessions/service.py @@ -0,0 +1,146 @@ +"""Root session-level operations: query/list, delete, archive, unarchive. + +Orchestrates across the session facets (streams, turns, interactions, mounts), +anchored on `session_id` — the universal handle. Fan-out NEVER routes through +`stream_id`. Records (tracing DB) are untouched here; tracing retention owns +them. + +peek is NOT a verb and NOT built here: the individual reads (this service's +`query_sessions`, the streams/turns/records fetch-and-query endpoints) are the +whole surface. The front-end composes them — see `apis/fastapi/sessions/router.py` +module docstring for the read-walk. +""" + +from typing import List, Optional +from uuid import UUID + +from oss.src.core.shared.dtos import Reference, Windowing +from oss.src.core.sessions.dtos import SessionQuery +from oss.src.core.sessions.streams.dtos import SessionStream, SessionStreamQuery +from oss.src.core.sessions.streams.service import SessionStreamsService +from oss.src.core.sessions.turns.dtos import SessionTurnQuery +from oss.src.core.sessions.turns.service import SessionTurnsService +from oss.src.core.sessions.interactions.service import SessionInteractionsService +from oss.src.core.mounts.service import MountsService + + +class SessionsService: + def __init__( + self, + *, + streams_service: SessionStreamsService, + turns_service: SessionTurnsService, + interactions_service: SessionInteractionsService, + mounts_service: MountsService, + ) -> None: + self.streams_service = streams_service + self.turns_service = turns_service + self.interactions_service = interactions_service + self.mounts_service = mounts_service + + async def query_sessions( + self, + *, + project_id: UUID, + # + query: Optional[SessionQuery] = None, + windowing: Optional[Windowing] = None, + ) -> List[SessionStream]: + """List/filter sessions, newest -> oldest, windowed. + + Reads the merged stream rows; when `references` is set, first joins the + turns' references (WP1's GIN `.contains()`) to resolve the matching + `session_id`s, then filters the stream query to that set. No + denormalization onto the stream row (B3) — revisit only if the join + proves hot. + """ + session_ids: Optional[List[str]] = None + + references: Optional[List[Reference]] = query.references if query else None + if references: + matching_turns = await self.turns_service.query_turns( + project_id=project_id, + query=SessionTurnQuery(references=references), + ) + session_ids = sorted({turn.session_id for turn in matching_turns}) + if not session_ids: + return [] + + return await self.streams_service.query_streams( + project_id=project_id, + filter=SessionStreamQuery(), + windowing=windowing, + session_ids=session_ids, + ) + + async def delete_session( + self, + *, + project_id: UUID, + user_id: UUID, + session_id: str, + ) -> None: + """Hard delete, `session_id`-scoped fan-out (F1). No DB cascade: + + - session_turns: hard delete (WP1's `delete_by_session_id`). + - session_interactions: hard delete (new — soft-only before this). + - the merged stream row: hard delete (new — `kill` only soft-deletes). + - session-bound mounts: delete the rows + their object-store prefixes + (explicit, session-aware — mounts are semi-independent). + - records: UNTOUCHED — cross-DB, tracing retention owns them. + """ + await self.turns_service.delete_by_session_id( + project_id=project_id, + session_id=session_id, + ) + await self.interactions_service.delete_by_session_id( + project_id=project_id, + session_id=session_id, + ) + await self.mounts_service.delete_session_mounts( + project_id=project_id, + session_id=session_id, + ) + await self.streams_service.hard_delete( + project_id=project_id, + session_id=session_id, + ) + + async def archive_session( + self, + *, + project_id: UUID, + user_id: UUID, + session_id: str, + ) -> Optional[SessionStream]: + """Soft (`deleted_at`) fan-out (F2): archives the stream row and soft- + archives the bound mounts too (reversible); folders untouched.""" + await self.mounts_service.archive_session_mounts( + project_id=project_id, + user_id=user_id, + session_id=session_id, + ) + return await self.streams_service.archive( + project_id=project_id, + session_id=session_id, + ) + + async def unarchive_session( + self, + *, + project_id: UUID, + user_id: UUID, + session_id: str, + ) -> Optional[SessionStream]: + """Reverse of `archive_session`: clears `deleted_at` on the stream row + and un-archives the bound mounts.""" + await self.mounts_service.unarchive_session_mounts( + project_id=project_id, + user_id=user_id, + session_id=session_id, + ) + return await self.streams_service.unarchive( + project_id=project_id, + user_id=user_id, + session_id=session_id, + ) diff --git a/api/oss/src/core/sessions/states/dtos.py b/api/oss/src/core/sessions/states/dtos.py deleted file mode 100644 index adb674dbd3..0000000000 --- a/api/oss/src/core/sessions/states/dtos.py +++ /dev/null @@ -1,94 +0,0 @@ -from typing import Any, Dict, Optional -from uuid import UUID - -from pydantic import BaseModel, Field - -from oss.src.core.shared.dtos import Lifecycle - - -class SessionStateFlags(BaseModel): - pass - - -class HarnessSessionRecord(BaseModel): - """Per-harness resume state. Value shape of `data.harness_sessions[]`.""" - - agent_session_id: Optional[str] = Field( - default=None, - description="This harness's own agentSessionId, fed to session/load on resume.", - ) - turn_index: Optional[int] = Field( - default=None, - description=( - "Conversation turn number this harness last ran at. Load-eligible only " - "when equal to the conversation's latest_turn_index; otherwise this " - "harness's session file is stale (another harness ran since)." - ), - ) - - -class SessionStateData(BaseModel): - """Typed shape of the `data` column: the session's durable continuity state. - - Stored in the existing `data` JSON column (no dedicated columns) — every field is - read and compared in the runner, never queried server-side, so a typed DTO gives the - contract without a schema change. - """ - - latest_agent_session_id: Optional[str] = Field( - default=None, - description=( - "The latest-run harness's agentSessionId; a fast-path mirror of " - "harness_sessions[].agent_session_id." - ), - ) - latest_turn_index: Optional[int] = Field( - default=None, - description="Conversation-level turn counter compared against each harness's " - "turn_index.", - ) - harness_sessions: Optional[Dict[str, HarnessSessionRecord]] = Field( - default=None, - description=( - "Per-harness resume state, keyed by harness id (e.g. 'claude', 'pi'). " - "Durable mirror of the staleness guard." - ), - ) - - -class SessionState(Lifecycle): - id: Optional[UUID] = Field(default=None, description="Own uuid7 pk (state_id).") - project_id: Optional[UUID] = Field(default=None) - session_id: str = Field(description="Bare session correlator (not an FK).") - data: Optional[SessionStateData] = Field( - default=None, - description="Durable continuity state (resume ids + staleness guard).", - ) - sandbox_id: Optional[str] = Field( - default=None, - description="Remote sandbox id — the single source of truth resume pointer.", - ) - flags: SessionStateFlags = Field(default_factory=SessionStateFlags) - tags: Optional[Dict[str, Any]] = Field(default=None) - meta: Optional[Dict[str, Any]] = Field(default=None) - - -class SessionStateUpsert(BaseModel): - data: Optional[SessionStateData] = Field( - default=None, - description=( - "Full replacement of the continuity state. Callers read-modify-write: " - "GET the current row, patch the one harness's entry, PUT the whole data back." - ), - ) - sandbox_id: Optional[str] = Field( - default=None, - description="Remote sandbox id to record alongside the continuity state.", - ) - sandbox_turn_index: Optional[int] = Field( - default=None, - description=( - "the writer's conversation turn index; the pointer write is applied only " - "when it is >= the row's data.latest_turn_index." - ), - ) diff --git a/api/oss/src/core/sessions/states/interfaces.py b/api/oss/src/core/sessions/states/interfaces.py deleted file mode 100644 index 29efa50b51..0000000000 --- a/api/oss/src/core/sessions/states/interfaces.py +++ /dev/null @@ -1,25 +0,0 @@ -from abc import ABC, abstractmethod -from typing import Optional -from uuid import UUID - -from oss.src.core.sessions.states.dtos import SessionState, SessionStateUpsert - - -class SessionStatesDAOInterface(ABC): - @abstractmethod - async def get_session_state( - self, - *, - project_id: UUID, - session_id: str, - ) -> Optional[SessionState]: ... - - @abstractmethod - async def set_session_state( - self, - *, - project_id: UUID, - user_id: UUID, - session_id: str, - upsert: SessionStateUpsert, - ) -> Optional[SessionState]: ... diff --git a/api/oss/src/core/sessions/states/service.py b/api/oss/src/core/sessions/states/service.py deleted file mode 100644 index e3de29ec3f..0000000000 --- a/api/oss/src/core/sessions/states/service.py +++ /dev/null @@ -1,36 +0,0 @@ -from typing import Optional -from uuid import UUID - -from oss.src.core.sessions.states.dtos import SessionState, SessionStateUpsert -from oss.src.core.sessions.states.interfaces import SessionStatesDAOInterface - - -class SessionStatesService: - def __init__(self, *, session_states_dao: SessionStatesDAOInterface): - self.session_states_dao = session_states_dao - - async def get_session_state( - self, - *, - project_id: UUID, - session_id: str, - ) -> Optional[SessionState]: - return await self.session_states_dao.get_session_state( - project_id=project_id, - session_id=session_id, - ) - - async def set_session_state( - self, - *, - project_id: UUID, - user_id: UUID, - session_id: str, - upsert: SessionStateUpsert, - ) -> Optional[SessionState]: - return await self.session_states_dao.set_session_state( - project_id=project_id, - user_id=user_id, - session_id=session_id, - upsert=upsert, - ) diff --git a/api/oss/src/core/sessions/streams/dtos.py b/api/oss/src/core/sessions/streams/dtos.py index 06854bbdf6..2439458e4d 100644 --- a/api/oss/src/core/sessions/streams/dtos.py +++ b/api/oss/src/core/sessions/streams/dtos.py @@ -1,9 +1,12 @@ -from datetime import datetime from enum import Enum from typing import Any, Dict, Optional from uuid import UUID -from pydantic import BaseModel +from pydantic import BaseModel, Field + +from agenta.sdk.models.workflows import WorkflowServiceRequestData + +from oss.src.core.shared.dtos import Header, Identifier, Lifecycle class SessionStreamFlags(BaseModel): @@ -24,16 +27,7 @@ class SessionStreamQueryFlags(BaseModel): is_attached: Optional[bool] = None -class SessionStream(BaseModel): - id: UUID - # - created_at: Optional[datetime] = None - updated_at: Optional[datetime] = None - deleted_at: Optional[datetime] = None - created_by_id: Optional[UUID] = None - updated_by_id: Optional[UUID] = None - deleted_by_id: Optional[UUID] = None - # +class SessionStream(Identifier, Header, Lifecycle): project_id: UUID session_id: str flags: SessionStreamFlags = SessionStreamFlags() @@ -42,7 +36,7 @@ class SessionStream(BaseModel): turn_id: Optional[str] = None -class SessionStreamCreate(BaseModel): +class SessionStreamCreate(Header): session_id: str flags: Optional[SessionStreamFlags] = None tags: Optional[Dict[str, Any]] = None @@ -50,35 +44,46 @@ class SessionStreamCreate(BaseModel): turn_id: Optional[str] = None -class SessionStreamEdit(BaseModel): +class SessionStreamEdit(Header): flags: Optional[SessionStreamFlags] = None tags: Optional[Dict[str, Any]] = None meta: Optional[Dict[str, Any]] = None turn_id: Optional[str] = None +class SessionStreamHeaderEdit(Header): + """The rename edit: a full-PUT of the header fields only. + + Distinct from SessionStreamEdit (used by the flag-mirror/heartbeat paths) so the + liveness-only writes can never carry name/description, and vice versa. + """ + + class SessionStreamQuery(BaseModel): session_id: Optional[str] = None flags: Optional[SessionStreamQueryFlags] = None class CommandMode(str, Enum): - """Derived from the prompt × force matrix.""" + """Derived from the inputs/data × force matrix.""" - send = "send" # prompt + no force → 409 if alive - steer = "steer" # prompt + force → cancel holder, run new - cancel = "cancel" # no prompt + no force → cancel holder - attach = "attach" # no prompt + force → steal attached, watch + send = "send" # inputs + no force → 409 if alive + steer = "steer" # inputs + force → cancel holder, run new + cancel = "cancel" # no inputs + no force → cancel holder + attach = "attach" # no inputs + force → steal attached, watch class SessionStreamCommandRequest(BaseModel): """The set_session_stream edit: a state mutation over the lock/row nest. Runs nothing itself — the runner (execution plane) is the only thing that runs. + `data` mirrors the workflow-invoke shape (`WorkflowServiceRequestData`, keyed on + `.inputs`) so the discriminator aligns with `WorkflowInvokeRequest.data.inputs` + rather than a bespoke `prompt` string. """ session_id: str - prompt: Optional[str] = None + data: Optional[WorkflowServiceRequestData] = None force: bool = False detached: bool = False # fire-and-forget mode @@ -93,7 +98,7 @@ class SessionStreamCommandResponse(BaseModel): class SessionHeartbeatRequest(BaseModel): session_id: str - replica_id: str # the runner CONTAINER (affinity / owner key) + replica_id: str = Field(min_length=1) # the runner CONTAINER (affinity / owner key) turn_id: Optional[str] = None # the current TURN (proves alive-lock ownership) is_running: bool = True @@ -113,7 +118,14 @@ class SessionHeartbeatResult(BaseModel): `stream` is None when a losing replica heartbeats a session that has no row yet: it may not create or stamp one, since that row belongs to the owner. + + `is_current_turn` (W7.4) is False when this turn_id's alive/running lock was gone or + reassigned at the moment of this beat — i.e. a cancel/steer/kill interrupted this turn + since the last heartbeat. The runner's watchdog reads this to abort the in-flight run; + without it a cancel that raced a heartbeat's nx=True re-acquire would silently re-arm the + SAME lock under the SAME turn_id and the interruption would never surface. """ stream: Optional[SessionStream] = None replica_id: str + is_current_turn: bool = True diff --git a/api/oss/src/core/sessions/streams/interfaces.py b/api/oss/src/core/sessions/streams/interfaces.py index 873c13c598..b6f479e04e 100644 --- a/api/oss/src/core/sessions/streams/interfaces.py +++ b/api/oss/src/core/sessions/streams/interfaces.py @@ -6,8 +6,10 @@ SessionStream, SessionStreamCreate, SessionStreamEdit, + SessionStreamHeaderEdit, SessionStreamQuery, ) +from oss.src.core.shared.dtos import Windowing class SessionStreamsDAOInterface(ABC): @@ -28,6 +30,14 @@ async def get_by_session_id( session_id: str, ) -> Optional[SessionStream]: ... + @abstractmethod + async def get_by_session_id_including_archived( + self, + *, + project_id: UUID, + session_id: str, + ) -> Optional[SessionStream]: ... + @abstractmethod async def get_by_id( self, @@ -42,6 +52,8 @@ async def query( *, project_id: UUID, filter: SessionStreamQuery, + windowing: Optional[Windowing] = None, + session_ids: Optional[List[str]] = None, ) -> List[SessionStream]: ... @abstractmethod @@ -54,6 +66,16 @@ async def update( stream: SessionStreamEdit, ) -> Optional[SessionStream]: ... + @abstractmethod + async def update_header( + self, + *, + project_id: UUID, + user_id: Optional[UUID], + session_id: str, + header: SessionStreamHeaderEdit, + ) -> Optional[SessionStream]: ... + @abstractmethod async def delete_by_session_id( self, @@ -62,6 +84,23 @@ async def delete_by_session_id( session_id: str, ) -> bool: ... + @abstractmethod + async def unarchive_by_session_id( + self, + *, + project_id: UUID, + user_id: Optional[UUID], + session_id: str, + ) -> Optional[SessionStream]: ... + + @abstractmethod + async def hard_delete_by_session_id( + self, + *, + project_id: UUID, + session_id: str, + ) -> bool: ... + @abstractmethod async def count_active( self, diff --git a/api/oss/src/core/sessions/streams/runner_client.py b/api/oss/src/core/sessions/streams/runner_client.py new file mode 100644 index 0000000000..45e6689aca --- /dev/null +++ b/api/oss/src/core/sessions/streams/runner_client.py @@ -0,0 +1,62 @@ +"""Direct API -> runner HTTP hop, used only by `kill` (W7.3). + +Everything else in `core/sessions/` reaches the runner only indirectly, through the Redis +coordination plane (the runner heartbeats/reads locks the API wrote) or through the separate +invoke path (`WorkflowsService` -> the Python agent service -> the runner). `kill` is the one +verb that must reach the runner's OWN sandbox-teardown route (`POST /kill` on +`services/runner/src/server.ts`) directly, because dropping the Redis locks alone does not +tear down a warm sandbox — it only removes the coordination-plane bookkeeping. Without this +call, `kill` was Redis/row-only (see `service.py`'s `kill()` before this module existed) and +the runner's session-pool / in-flight sandbox kept running until its own idle TTL expired. + +Same base URL + shared-secret token the Python agent service already uses to reach the runner +(`services/oss/src/agent/config.py`'s `runner_url()`, `AGENTA_RUNNER_TOKEN` on both sides). +Best-effort: `env.runner.internal_url` unset means no direct hop is configured (e.g. a +dev/test composition running the runner as a bare subprocess with no HTTP surface), and any +call failure is swallowed — `kill`'s Redis/row edit must still succeed and be idempotent, and +the runner's own orphan sweep / idle-TTL eviction is the fallback net for a missed signal. +""" + +import httpx + +from oss.src.utils.env import env +from oss.src.utils.logging import get_module_logger + +log = get_module_logger(__name__) + +_KILL_TIMEOUT_SECONDS = 10.0 + + +async def kill_runner_sandbox(*, project_id: str, session_id: str) -> bool: + """POST the runner's `/kill`, scoped to (project_id, session_id). Returns True iff the + call was made and returned 2xx; False otherwise (not configured, network error, non-2xx). + Never raises — kill's Redis/row edit is the source of truth and must not be blocked by + the runner being unreachable. + """ + base_url = env.runner.internal_url + token = env.runner.token + if not base_url or not token: + log.debug( + "kill: no runner internal_url/token configured, skipping direct sandbox teardown" + ) + return False + + url = base_url.rstrip("/") + "/kill" + try: + async with httpx.AsyncClient(timeout=_KILL_TIMEOUT_SECONDS) as client: + response = await client.post( + url, + json={"sessionId": session_id, "projectId": project_id}, + headers={"Authorization": f"Bearer {token}"}, + ) + if response.status_code >= 300: + log.warning( + "kill: runner /kill returned %s for session=%s", + response.status_code, + session_id, + ) + return False + return True + except httpx.HTTPError as e: + log.warning("kill: runner /kill call failed for session=%s: %s", session_id, e) + return False diff --git a/api/oss/src/core/sessions/streams/service.py b/api/oss/src/core/sessions/streams/service.py index 402435202d..db2cf13311 100644 --- a/api/oss/src/core/sessions/streams/service.py +++ b/api/oss/src/core/sessions/streams/service.py @@ -4,11 +4,11 @@ session_streams row. Runs nothing itself: the runner (execution plane) is the only component that runs an agent. -Command matrix (prompt × force): - prompt + no force → SEND (409 if alive) - prompt + force → STEER (cancel holder, start a new turn) - no prompt + no f. → CANCEL (cancel holder, run nothing) - no prompt + force → ATTACH (steal attached, watch the live turn) +Command matrix (inputs/data × force): + inputs + no force → SEND (409 if alive) + inputs + force → STEER (cancel holder, start a new turn) + no inputs + no f. → CANCEL (cancel holder, run nothing) + no inputs + force → ATTACH (steal attached, watch the live turn) detach / kill → explicit lifecycle edits (see methods) """ @@ -47,6 +47,7 @@ SessionStreamCreate, SessionStreamEdit, SessionStreamFlags, + SessionStreamHeaderEdit, SessionStreamQuery, ) from oss.src.core.sessions.streams.types import ( @@ -56,6 +57,8 @@ SessionTurnInUse, ) from oss.src.core.sessions.streams.interfaces import SessionStreamsDAOInterface +from oss.src.core.sessions.streams.runner_client import kill_runner_sandbox +from oss.src.core.shared.dtos import Windowing log = get_module_logger(__name__) @@ -84,13 +87,13 @@ async def command( ) -> SessionStreamCommandResponse: _validate_session_id(request.session_id) - has_prompt = bool(request.prompt and request.prompt.strip()) + has_inputs = bool(request.data and request.data.inputs) - if has_prompt and not request.force: + if has_inputs and not request.force: mode = CommandMode.send - elif has_prompt and request.force: + elif has_inputs and request.force: mode = CommandMode.steer - elif not has_prompt and not request.force: + elif not has_inputs and not request.force: mode = CommandMode.cancel else: mode = CommandMode.attach @@ -203,11 +206,15 @@ async def kill( user_id: Optional[UUID], session_id: str, ) -> bool: - """KILL: collapse the whole nest and end the stream. + """KILL: tear down the sandbox and collapse the whole nest. KILL != CANCEL — cancel + only ends the current turn (the session/sandbox can resume); kill ends the session. Force-clears alive + running + attached + owner in Redis (losing the alive lock is - the runner's existing teardown signal), marks the row ended, and soft-deletes it. - Idempotent: a kill on an already-dead session is a no-op success. + the runner's existing teardown signal for its OWN in-process bookkeeping), calls the + runner's `/kill` directly so the actual sandbox is torn down rather than left to its + own idle-TTL eviction (W7.3 — a bare Redis/row edit is not sandbox teardown), marks the + row ended, and soft-deletes it. Idempotent: a kill on an already-dead session, or one + whose runner replica is unreachable, is still a no-op success (best-effort teardown). """ _validate_session_id(session_id) await force_cancel_alive( @@ -235,6 +242,9 @@ async def kill( session_id=session_id, watcher_id=throwaway, ) + # Best-effort: the Redis/row edit above is authoritative and must not depend on this + # succeeding — see runner_client.kill_runner_sandbox's docstring. + await kill_runner_sandbox(project_id=str(project_id), session_id=session_id) await self._mark_stream_ended( project_id=project_id, user_id=user_id, @@ -269,7 +279,29 @@ async def heartbeat( project_id=project_id, session_id=request.session_id, ) - return SessionHeartbeatResult(stream=stream, replica_id=owner) + return SessionHeartbeatResult( + stream=stream, replica_id=owner, is_current_turn=False + ) + + # True only when this turn_id still (or again, uninterrupted) owns the alive lock at + # the moment of this heartbeat. A cancel/steer/kill deletes the alive key entirely, + # which the nx=True re-acquire below would otherwise silently re-establish under the + # SAME turn_id, masking the interruption from the runner's watchdog (W7.4 — this is + # what `is_current_turn` exists to surface). An absent key is ambiguous by itself: it + # is also the normal state before this turn's VERY FIRST heartbeat (the API's + # `_start_turn` acquire may not have landed yet, or this beat wins a race with it), and + # that is NOT an interruption. Disambiguate with the durable row's `turn_id`: if it + # already recorded THIS turn_id as established (a prior heartbeat's write), the key + # being gone now is something else's doing; if the row shows no turn yet, or a + # different one, this is establishment. + prior_stream = await self._dao.get_by_session_id( + project_id=project_id, + session_id=request.session_id, + ) + turn_was_established = bool( + prior_stream and prior_stream.turn_id == request.turn_id + ) + is_current_turn = True if request.turn_id and request.is_running: # Acquire-then-refresh: the first heartbeat must establish the nest locks @@ -280,6 +312,8 @@ async def heartbeat( session_id=request.session_id, turn_id=request.turn_id, ): + if turn_was_established: + is_current_turn = False await acquire_alive( self._lock, project_id=str(project_id), @@ -292,6 +326,8 @@ async def heartbeat( session_id=request.session_id, turn_id=request.turn_id, ): + if turn_was_established: + is_current_turn = False await acquire_running( self._lock, project_id=str(project_id), @@ -314,10 +350,9 @@ async def heartbeat( is_attached=liveness["attached"], ) - stream = await self._dao.get_by_session_id( - project_id=project_id, - session_id=request.session_id, - ) + # Nothing between `prior_stream`'s fetch above and here mutates the row, so it is + # still the current read — no need to re-fetch. + stream = prior_stream if stream is None: try: @@ -344,6 +379,7 @@ async def heartbeat( return SessionHeartbeatResult( stream=stream, replica_id=owner, + is_current_turn=is_current_turn, ) async def fetch( @@ -372,15 +408,124 @@ async def fetch( stream.flags = flags return stream + async def fetch_header( + self, + *, + project_id: UUID, + session_id: str, + ) -> Optional[SessionStream]: + """Fetch the session header (name/description/flags/lifecycle) — used by + GET /sessions/streams/. Reads the same reconciled row as `fetch`. + """ + return await self.fetch(project_id=project_id, session_id=session_id) + + async def set_header( + self, + *, + project_id: UUID, + user_id: Optional[UUID], + session_id: str, + header: SessionStreamHeaderEdit, + ) -> Optional[SessionStream]: + """The rename edit: full-PUT {name, description} onto the merged stream row. + + Pure DB write — no Redis nest interaction, no flags/turn_id touched. Off the + runner's write path. Creates the row if the session has never heartbeat/run + yet (a caller may name a session before its first turn), mirroring + `_start_turn`'s create-or-update pattern. + """ + _validate_session_id(session_id) + updated = await self._dao.update_header( + project_id=project_id, + user_id=user_id, + session_id=session_id, + header=header, + ) + if updated is not None: + return updated + try: + return await self._dao.create( + project_id=project_id, + user_id=user_id, + stream=SessionStreamCreate( + session_id=session_id, + name=header.name, + description=header.description, + ), + ) + except SessionStreamAlreadyExists: + # A concurrent first touch (heartbeat/rename) won the race; the row now + # exists — apply the header edit onto it. + return await self._dao.update_header( + project_id=project_id, + user_id=user_id, + session_id=session_id, + header=header, + ) + async def query_streams( self, *, project_id: UUID, filter: SessionStreamQuery, + windowing: Optional[Windowing] = None, + session_ids: Optional[List[str]] = None, ) -> List[SessionStream]: if filter.session_id: _validate_session_id(filter.session_id) - return await self._dao.query(project_id=project_id, filter=filter) + return await self._dao.query( + project_id=project_id, + filter=filter, + windowing=windowing, + session_ids=session_ids, + ) + + async def hard_delete( + self, + *, + project_id: UUID, + session_id: str, + ) -> bool: + """Hard delete the merged stream row (S7 delete fan-out, WP5). Distinct + from `kill`, which only soft-deletes via `delete_by_session_id`.""" + _validate_session_id(session_id) + return await self._dao.hard_delete_by_session_id( + project_id=project_id, + session_id=session_id, + ) + + async def archive( + self, + *, + project_id: UUID, + session_id: str, + ) -> Optional[SessionStream]: + """Soft-archive the stream row (S7/F2 archive fan-out, WP5). Returns the + archived row (`deleted_at` set) as the caller's confirmation read.""" + _validate_session_id(session_id) + await self._dao.delete_by_session_id( + project_id=project_id, + session_id=session_id, + ) + return await self._dao.get_by_session_id_including_archived( + project_id=project_id, + session_id=session_id, + ) + + async def unarchive( + self, + *, + project_id: UUID, + user_id: Optional[UUID], + session_id: str, + ) -> Optional[SessionStream]: + """Reverse of `archive`: clears `deleted_at` on the stream row.""" + _validate_session_id(session_id) + return await self._dao.unarchive_by_session_id( + project_id=project_id, + user_id=user_id, + session_id=session_id, + ) async def check_runner_concurrency_limit(self, *, project_id: UUID) -> None: """Raise ConcurrencyLimitExceeded if the per-project limit is reached.""" diff --git a/api/oss/src/core/sessions/states/__init__.py b/api/oss/src/core/sessions/turns/__init__.py similarity index 100% rename from api/oss/src/core/sessions/states/__init__.py rename to api/oss/src/core/sessions/turns/__init__.py diff --git a/api/oss/src/core/sessions/turns/dtos.py b/api/oss/src/core/sessions/turns/dtos.py new file mode 100644 index 0000000000..5c50cbc9a2 --- /dev/null +++ b/api/oss/src/core/sessions/turns/dtos.py @@ -0,0 +1,45 @@ +from datetime import datetime +from typing import List, Optional +from uuid import UUID + +from pydantic import BaseModel + +from agenta.sdk.agents.dtos import HarnessKind + +from oss.src.core.shared.dtos import Identifier, Lifecycle, OTelSpanId, Reference + + +class SessionTurn(Identifier, Lifecycle): + project_id: UUID + session_id: str + stream_id: UUID + turn_index: int + harness_kind: HarnessKind + agent_session_id: Optional[str] = None + sandbox_id: Optional[str] = None + references: Optional[List[Reference]] = None + trace_id: Optional[UUID] = None + span_id: Optional[OTelSpanId] = None + start_time: Optional[datetime] = None + end_time: Optional[datetime] = None + + +class SessionTurnCreate(BaseModel): + session_id: str + stream_id: UUID + turn_index: int + harness_kind: HarnessKind + agent_session_id: Optional[str] = None + sandbox_id: Optional[str] = None + references: Optional[List[Reference]] = None + trace_id: Optional[UUID] = None + span_id: Optional[OTelSpanId] = None + start_time: Optional[datetime] = None + end_time: Optional[datetime] = None + + +class SessionTurnQuery(BaseModel): + session_id: Optional[str] = None + stream_id: Optional[UUID] = None + harness_kind: Optional[HarnessKind] = None + references: Optional[List[Reference]] = None diff --git a/api/oss/src/core/sessions/turns/interfaces.py b/api/oss/src/core/sessions/turns/interfaces.py new file mode 100644 index 0000000000..7d337fc887 --- /dev/null +++ b/api/oss/src/core/sessions/turns/interfaces.py @@ -0,0 +1,67 @@ +from abc import ABC, abstractmethod +from typing import List, Optional +from uuid import UUID + +from oss.src.core.shared.dtos import Windowing +from oss.src.core.sessions.turns.dtos import ( + HarnessKind, + SessionTurn, + SessionTurnCreate, + SessionTurnQuery, +) + + +class SessionTurnsDAOInterface(ABC): + @abstractmethod + async def append( + self, + *, + project_id: UUID, + user_id: Optional[UUID], + # + turn: SessionTurnCreate, + ) -> SessionTurn: ... + + @abstractmethod + async def fetch_turn( + self, + *, + project_id: UUID, + # + turn_id: UUID, + ) -> Optional[SessionTurn]: ... + + @abstractmethod + async def query_turns( + self, + *, + project_id: UUID, + # + query: Optional[SessionTurnQuery] = None, + windowing: Optional[Windowing] = None, + ) -> List[SessionTurn]: ... + + @abstractmethod + async def latest_turn( + self, + *, + project_id: UUID, + session_id: str, + ) -> Optional[SessionTurn]: ... + + @abstractmethod + async def latest_turn_per_harness_kind( + self, + *, + project_id: UUID, + session_id: str, + harness_kind: HarnessKind, + ) -> Optional[SessionTurn]: ... + + @abstractmethod + async def delete_by_session_id( + self, + *, + project_id: UUID, + session_id: str, + ) -> int: ... diff --git a/api/oss/src/core/sessions/turns/service.py b/api/oss/src/core/sessions/turns/service.py new file mode 100644 index 0000000000..6cf4048297 --- /dev/null +++ b/api/oss/src/core/sessions/turns/service.py @@ -0,0 +1,105 @@ +"""Session turns service — one row per turn, the transcript twin of a trace. + +The latest turn's agent_session_id / sandbox_id IS the current resume pointer (a +query, not a stored fold — a late lower-index write can never win ORDER BY +turn_index DESC LIMIT 1). +""" + +from typing import List, Optional +from uuid import UUID + +from oss.src.core.shared.dtos import Windowing +from oss.src.core.sessions.turns.dtos import ( + HarnessKind, + SessionTurn, + SessionTurnCreate, + SessionTurnQuery, +) +from oss.src.core.sessions.turns.interfaces import SessionTurnsDAOInterface + + +class SessionTurnsService: + def __init__( + self, + *, + turns_dao: SessionTurnsDAOInterface, + ) -> None: + self._dao = turns_dao + + async def append_turn( + self, + *, + project_id: UUID, + user_id: Optional[UUID], + # + turn: SessionTurnCreate, + ) -> SessionTurn: + return await self._dao.append( + project_id=project_id, + user_id=user_id, + turn=turn, + ) + + async def fetch_turn( + self, + *, + project_id: UUID, + # + turn_id: UUID, + ) -> Optional[SessionTurn]: + return await self._dao.fetch_turn( + project_id=project_id, + turn_id=turn_id, + ) + + async def query_turns( + self, + *, + project_id: UUID, + # + query: Optional[SessionTurnQuery] = None, + windowing: Optional[Windowing] = None, + ) -> List[SessionTurn]: + return await self._dao.query_turns( + project_id=project_id, + query=query, + windowing=windowing, + ) + + async def latest_turn( + self, + *, + project_id: UUID, + session_id: str, + ) -> Optional[SessionTurn]: + """The resume-read: latest_turn_index / agent_session_id / sandbox_id are this row.""" + return await self._dao.latest_turn( + project_id=project_id, + session_id=session_id, + ) + + async def latest_turn_per_harness_kind( + self, + *, + project_id: UUID, + session_id: str, + harness_kind: HarnessKind, + ) -> Optional[SessionTurn]: + """The per-harness-kind resume-read.""" + return await self._dao.latest_turn_per_harness_kind( + project_id=project_id, + session_id=session_id, + harness_kind=harness_kind, + ) + + async def delete_by_session_id( + self, + *, + project_id: UUID, + session_id: str, + ) -> int: + """Hard delete every turn for a session (S7 delete fan-out, WP5).""" + return await self._dao.delete_by_session_id( + project_id=project_id, + session_id=session_id, + ) diff --git a/api/oss/src/core/sessions/turns/types.py b/api/oss/src/core/sessions/turns/types.py new file mode 100644 index 0000000000..6bdd507414 --- /dev/null +++ b/api/oss/src/core/sessions/turns/types.py @@ -0,0 +1,12 @@ +"""Domain exceptions for session turns.""" + + +class SessionTurnError(Exception): + """Base exception for session turn errors.""" + + +class SessionTurnNotFound(SessionTurnError): + def __init__(self, turn_id: str): + self.turn_id = turn_id + self.message = f"No turn found with id '{turn_id}'." + super().__init__(self.message) diff --git a/api/oss/src/core/shared/dtos.py b/api/oss/src/core/shared/dtos.py index ab3b1ebc1f..3f45cc4264 100644 --- a/api/oss/src/core/shared/dtos.py +++ b/api/oss/src/core/shared/dtos.py @@ -1,8 +1,8 @@ from datetime import datetime, timezone -from typing import Optional +from typing import Annotated, Optional from uuid import UUID -from pydantic import BaseModel, Field +from pydantic import BaseModel, Field, StringConstraints from agenta.sdk.models.shared import ( # noqa: F401 BoolJson, @@ -51,6 +51,12 @@ ) +# An OpenTelemetry span id: 64-bit, exactly 16 hex chars. NOT a UUID (which is 128-bit / +# 32 hex). Typing this as UUID rejected every runner record-ingest with a 422 (16 vs 32 hex). +# Trace ids ARE 128-bit (32 hex) and correctly continue to ride as UUID. +OTelSpanId = Annotated[str, StringConstraints(pattern=r"^[0-9a-fA-F]{16}$")] + + class Status(BaseModel): timestamp: datetime = Field(default_factory=lambda: datetime.now(timezone.utc)) type: Optional[str] = None diff --git a/api/oss/src/core/tracing/dtos.py b/api/oss/src/core/tracing/dtos.py index dfa5b56799..d59afb4a43 100644 --- a/api/oss/src/core/tracing/dtos.py +++ b/api/oss/src/core/tracing/dtos.py @@ -94,6 +94,9 @@ class Fields(str, Enum): UPDATED_BY_ID = "updated_by_id" DELETED_BY_ID = "deleted_by_id" CONTENT = "content" + SESSION_ID = "session_id" + USER_ID = "user_id" + AGENT_ID = "agent_id" class LogicalOperator(str, Enum): diff --git a/api/oss/src/core/tracing/service.py b/api/oss/src/core/tracing/service.py index 39566d6001..a0d06a8c35 100644 --- a/api/oss/src/core/tracing/service.py +++ b/api/oss/src/core/tracing/service.py @@ -23,6 +23,7 @@ from oss.src.core.tracing.utils.trees import ( calculate_and_propagate_metrics_by_trace, infer_and_propagate_trace_type_by_trace, + promote_identity_by_trace, trace_map_to_traces, ) from oss.src.core.tracing.streaming import publish_spans @@ -150,6 +151,14 @@ async def ingest_span_dtos( exc_info=True, ) + try: + span_dtos = promote_identity_by_trace(span_dtos) + except Exception: # pylint: disable=broad-exception-caught + log.error( + "Failed to promote session/user/agent identity; continuing without it", + exc_info=True, + ) + await publish_spans( organization_id=organization_id, project_id=project_id, diff --git a/api/oss/src/core/tracing/utils/filtering.py b/api/oss/src/core/tracing/utils/filtering.py index 1c730239d6..7412f1582a 100644 --- a/api/oss/src/core/tracing/utils/filtering.py +++ b/api/oss/src/core/tracing/utils/filtering.py @@ -533,6 +533,12 @@ def parse_condition( _parse_uuid_field_condition(condition) elif condition.field == Fields.CONTENT: _parse_fts_field_condition(condition) + elif condition.field == Fields.SESSION_ID: + _parse_string_field_condition(condition) + elif condition.field == Fields.USER_ID: + _parse_string_field_condition(condition) + elif condition.field == Fields.AGENT_ID: + _parse_string_field_condition(condition) else: # raise FilteringException( # f"Unsupported condition field '{condition.field}'.", diff --git a/api/oss/src/core/tracing/utils/trees.py b/api/oss/src/core/tracing/utils/trees.py index eca466d41b..adf7181524 100644 --- a/api/oss/src/core/tracing/utils/trees.py +++ b/api/oss/src/core/tracing/utils/trees.py @@ -140,6 +140,38 @@ def infer_and_propagate_trace_type_by_trace( return span_dtos +def promote_identity_by_trace( + span_dtos: List[OTelFlatSpan], +) -> List[OTelFlatSpan]: + """ + Lift session/user/agent identity from the root span's attributes onto its + own session_id/user_id/agent_id columns. + + Root-only: ingestion is span-by-span / partial-batch, so there is no cheap + way to propagate identity from a root to children arriving in a different + request. Children are left untouched (nullable columns). + """ + if not span_dtos: + return span_dtos + + for span_dto in span_dtos: + if span_dto.parent_id is not None: + continue + + attributes = span_dto.attributes or {} + ag = attributes.get("ag") or {} + + session = ag.get("session") or {} + user = ag.get("user") or {} + agent = ag.get("agent") or {} + + span_dto.session_id = session.get("id") if isinstance(session, dict) else None + span_dto.user_id = user.get("id") if isinstance(user, dict) else None + span_dto.agent_id = agent.get("id") if isinstance(agent, dict) else None + + return span_dtos + + def parse_span_idx_to_span_id_tree( span_idx: Dict[str, OTelFlatSpan], ) -> OrderedDict: diff --git a/api/oss/src/dbs/postgres/mounts/dao.py b/api/oss/src/dbs/postgres/mounts/dao.py index d43bc9bd63..6bb8dd3b86 100644 --- a/api/oss/src/dbs/postgres/mounts/dao.py +++ b/api/oss/src/dbs/postgres/mounts/dao.py @@ -2,7 +2,7 @@ from typing import List, Optional from uuid import UUID -from sqlalchemy import select +from sqlalchemy import delete as sa_delete, select from sqlalchemy.exc import IntegrityError from sqlalchemy.dialects.postgresql import insert @@ -226,6 +226,36 @@ async def unarchive_mount( return map_mount_dbe_to_dto(mount_dbe=mount_dbe) + async def delete_by_session_id( + self, + *, + project_id: UUID, + session_id: str, + ) -> List[Mount]: + """Hard delete the mount rows bound to a session. Mounts are semi- + independent (optional `session_id`, may outlive a session) — this is + the explicit, session-scoped fan-out (S7/F1, WP5), not a blind + cascade. Returns the deleted rows so the caller can tear down their + object-store prefixes.""" + async with self.engine.session() as session: + stmt = select(MountDBE).where( + MountDBE.project_id == project_id, + MountDBE.session_id == session_id, + ) + result = await session.execute(stmt) + mount_dbes = list(result.scalars().all()) + mounts = [map_mount_dbe_to_dto(mount_dbe=dbe) for dbe in mount_dbes] + + if mount_dbes: + del_stmt = sa_delete(MountDBE).where( + MountDBE.project_id == project_id, + MountDBE.session_id == session_id, + ) + await session.execute(del_stmt) + await session.commit() + + return mounts + async def query_mounts( self, *, @@ -247,6 +277,9 @@ async def query_mounts( if mount_query.session_id is not None: stmt = stmt.where(MountDBE.session_id == mount_query.session_id) + if mount_query.agent_id is not None: + stmt = stmt.where(MountDBE.agent_id == mount_query.agent_id) + else: stmt = stmt.where(MountDBE.deleted_at.is_(None)) diff --git a/api/oss/src/dbs/postgres/mounts/dbas.py b/api/oss/src/dbs/postgres/mounts/dbas.py index 6ea201466b..464c388514 100644 --- a/api/oss/src/dbs/postgres/mounts/dbas.py +++ b/api/oss/src/dbs/postgres/mounts/dbas.py @@ -31,3 +31,10 @@ class MountDBA( String, nullable=True, ) + + # agent_id is a bare column — not an FK. Populated only for agent mounts, + # mirroring session_id (populated only for session mounts). + agent_id = Column( + String, + nullable=True, + ) diff --git a/api/oss/src/dbs/postgres/mounts/dbes.py b/api/oss/src/dbs/postgres/mounts/dbes.py index 0e6e2c280b..7616f4746a 100644 --- a/api/oss/src/dbs/postgres/mounts/dbes.py +++ b/api/oss/src/dbs/postgres/mounts/dbes.py @@ -41,4 +41,10 @@ class MountDBE(Base, MountDBA): "session_id", postgresql_where=text("session_id IS NOT NULL"), ), + Index( + "ix_mounts_project_id_agent_id", + "project_id", + "agent_id", + postgresql_where=text("agent_id IS NOT NULL"), + ), ) diff --git a/api/oss/src/dbs/postgres/mounts/mappings.py b/api/oss/src/dbs/postgres/mounts/mappings.py index 1ab7f915fd..529ed8899f 100644 --- a/api/oss/src/dbs/postgres/mounts/mappings.py +++ b/api/oss/src/dbs/postgres/mounts/mappings.py @@ -38,6 +38,7 @@ def map_mount_dto_to_dbe_upsert( "deleted_by_id": None, "slug": mount_create.slug, "session_id": mount_create.session_id, + "agent_id": mount_create.agent_id, "name": mount_create.name, "description": mount_create.description, "flags": mount_create.flags.model_dump(), @@ -61,6 +62,7 @@ def map_mount_dto_to_dbe_create( # slug=mount_create.slug, session_id=mount_create.session_id, + agent_id=mount_create.agent_id, # name=mount_create.name, description=mount_create.description, @@ -90,6 +92,7 @@ def map_mount_dbe_to_dto( project_id=mount_dbe.project_id, slug=mount_dbe.slug, session_id=mount_dbe.session_id, + agent_id=mount_dbe.agent_id, # name=mount_dbe.name, description=mount_dbe.description, diff --git a/api/oss/src/dbs/postgres/sessions/interactions/dao.py b/api/oss/src/dbs/postgres/sessions/interactions/dao.py index ab53c880d3..7ec4a66b0d 100644 --- a/api/oss/src/dbs/postgres/sessions/interactions/dao.py +++ b/api/oss/src/dbs/postgres/sessions/interactions/dao.py @@ -2,7 +2,7 @@ from typing import List, Optional from uuid import UUID -from sqlalchemy import func, select, update as sa_update +from sqlalchemy import delete as sa_delete, func, select, update as sa_update from sqlalchemy.exc import IntegrityError from oss.src.core.sessions.interactions.dtos import ( @@ -208,3 +208,20 @@ async def query_interactions( result = await session.execute(stmt) return [map_interaction_dbe_to_dto(dbe) for dbe in result.scalars().all()] + + async def delete_by_session_id( + self, + *, + project_id: UUID, + session_id: str, + ) -> int: + """Hard delete — no soft-delete for interactions today (session-scoped + fan-out, WP5).""" + async with self.engine.session() as session: + stmt = sa_delete(SessionInteractionDBE).where( + SessionInteractionDBE.project_id == project_id, + SessionInteractionDBE.session_id == session_id, + ) + result = await session.execute(stmt) + await session.commit() + return result.rowcount or 0 diff --git a/api/oss/src/dbs/postgres/sessions/records/dao.py b/api/oss/src/dbs/postgres/sessions/records/dao.py index 2ec9b47d37..93ff6ed548 100644 --- a/api/oss/src/dbs/postgres/sessions/records/dao.py +++ b/api/oss/src/dbs/postgres/sessions/records/dao.py @@ -91,6 +91,8 @@ def _upsert_stmt(*, values_list: List[dict]): "record_source": stmt.excluded.record_source, "timestamp": stmt.excluded.timestamp, "attributes": stmt.excluded.attributes, + "turn_id": stmt.excluded.turn_id, + "span_id": stmt.excluded.span_id, }, ).returning(RecordDBE) diff --git a/api/oss/src/dbs/postgres/sessions/records/dbas.py b/api/oss/src/dbs/postgres/sessions/records/dbas.py index 1345bd2bde..200eae44bb 100644 --- a/api/oss/src/dbs/postgres/sessions/records/dbas.py +++ b/api/oss/src/dbs/postgres/sessions/records/dbas.py @@ -4,6 +4,23 @@ from sqlalchemy.dialects.postgresql import JSONB +class RecordTurnSpanDBA: + __abstract__ = True + + # Plain columns, no FK (cross-DB: turns live in the core DB, spans elsewhere in the + # tracing DB). Forward-fill only — the tracing DB is never backfilled/data-migrated, so + # both stay null on rows written before this column existed. + turn_id = Column( + String, + nullable=True, + ) + # OTel span id (16 hex chars), NOT a UUID — stored as text (see OTelSpanId). + span_id = Column( + String, + nullable=True, + ) + + class RecordDBA: __abstract__ = True diff --git a/api/oss/src/dbs/postgres/sessions/records/dbes.py b/api/oss/src/dbs/postgres/sessions/records/dbes.py index 2c312f6e4c..78de94b5df 100644 --- a/api/oss/src/dbs/postgres/sessions/records/dbes.py +++ b/api/oss/src/dbs/postgres/sessions/records/dbes.py @@ -1,7 +1,7 @@ from sqlalchemy import PrimaryKeyConstraint, Index from oss.src.dbs.postgres.shared.base import Base -from oss.src.dbs.postgres.sessions.records.dbas import RecordDBA +from oss.src.dbs.postgres.sessions.records.dbas import RecordDBA, RecordTurnSpanDBA from oss.src.dbs.postgres.shared.dbas import ProjectScopeDBA, LifecycleDBA @@ -10,6 +10,7 @@ class RecordDBE( ProjectScopeDBA, LifecycleDBA, RecordDBA, + RecordTurnSpanDBA, ): __tablename__ = "records" @@ -26,4 +27,10 @@ class RecordDBE( "attributes", postgresql_using="gin", ), + Index( + "ix_records_project_id_session_id_turn_id", + "project_id", + "session_id", + "turn_id", + ), ) diff --git a/api/oss/src/dbs/postgres/sessions/records/mappings.py b/api/oss/src/dbs/postgres/sessions/records/mappings.py index e38a1570de..62420cc97e 100644 --- a/api/oss/src/dbs/postgres/sessions/records/mappings.py +++ b/api/oss/src/dbs/postgres/sessions/records/mappings.py @@ -23,6 +23,8 @@ def map_record_event_to_dbe( record_type=event.record_type, record_source=event.record_source, attributes=event.attributes, + turn_id=event.turn_id, + span_id=event.span_id, ) @@ -36,5 +38,7 @@ def map_record_dbe_to_dto(*, dbe: RecordDBE) -> SessionRecord: record_type=dbe.record_type, record_source=dbe.record_source, attributes=dbe.attributes, + turn_id=dbe.turn_id, + span_id=dbe.span_id, created_at=dbe.created_at, ) diff --git a/api/oss/src/dbs/postgres/sessions/states/dao.py b/api/oss/src/dbs/postgres/sessions/states/dao.py deleted file mode 100644 index 3e2c14843f..0000000000 --- a/api/oss/src/dbs/postgres/sessions/states/dao.py +++ /dev/null @@ -1,128 +0,0 @@ -from datetime import datetime, timezone -from typing import Optional -from uuid import UUID - -import uuid_utils.compat as uuid_utils - -from sqlalchemy import Integer, case, cast, func, select -from sqlalchemy.dialects.postgresql import insert - -from oss.src.utils.logging import get_module_logger -from oss.src.utils.exceptions import suppress_exceptions - -from oss.src.core.sessions.states.interfaces import SessionStatesDAOInterface -from oss.src.core.sessions.states.dtos import ( - SessionState, - SessionStateFlags, - SessionStateUpsert, -) - -from oss.src.dbs.postgres.shared.engine import ( - TransactionsEngine, - get_transactions_engine, -) -from oss.src.dbs.postgres.sessions.states.dbes import SessionStateDBE -from oss.src.dbs.postgres.sessions.states.mappings import dbe_to_dto - -log = get_module_logger(__name__) - - -class SessionStatesDAO(SessionStatesDAOInterface): - def __init__(self, engine: TransactionsEngine = None): - if engine is None: - engine = get_transactions_engine() - self.engine = engine - - @suppress_exceptions() - async def get_session_state( - self, - *, - project_id: UUID, - session_id: str, - ) -> Optional[SessionState]: - async with self.engine.session() as session: - stmt = ( - select(SessionStateDBE) - .filter(SessionStateDBE.project_id == project_id) - .filter(SessionStateDBE.session_id == session_id) - .limit(1) - ) - result = await session.execute(stmt) - dbe = result.scalars().first() - if dbe is None: - return None - return dbe_to_dto(dbe) - - @suppress_exceptions() - async def set_session_state( - self, - *, - project_id: UUID, - user_id: UUID, - session_id: str, - upsert: SessionStateUpsert, - ) -> Optional[SessionState]: - now = datetime.now(timezone.utc) - - data_json = ( - upsert.data.model_dump( - mode="json", - ) - if upsert.data is not None - else None - ) - - values = { - "id": uuid_utils.uuid7(), - "project_id": project_id, - "session_id": session_id, - "data": data_json, - "sandbox_id": upsert.sandbox_id, - "flags": SessionStateFlags().model_dump(mode="json"), - "created_at": now, - "updated_at": None, - "created_by_id": user_id, - "updated_by_id": None, - "deleted_at": None, - "deleted_by_id": None, - } - - stmt = insert(SessionStateDBE).values(**values) - update_values = { - "updated_at": now, - "updated_by_id": user_id, - } - if "data" in upsert.model_fields_set: - update_values["data"] = stmt.excluded.data - guarded_pointer_write = ( - "sandbox_id" in upsert.model_fields_set - and upsert.sandbox_turn_index is not None - ) - if guarded_pointer_write: - pointer_write_allowed = ( - func.coalesce( - cast(SessionStateDBE.data["latest_turn_index"].astext, Integer), - -1, - ) - <= upsert.sandbox_turn_index - ) - update_values["sandbox_id"] = case( - (pointer_write_allowed, stmt.excluded.sandbox_id), - else_=SessionStateDBE.sandbox_id, - ) - elif "sandbox_id" in upsert.model_fields_set: - update_values["sandbox_id"] = stmt.excluded.sandbox_id - - stmt = stmt.on_conflict_do_update( - constraint="uq_session_states_project_session_id", - set_=update_values, - ) - stmt = stmt.returning(SessionStateDBE) - - async with self.engine.session() as db_session: - result = await db_session.execute(stmt) - await db_session.commit() - dbe = result.scalars().first() - if dbe is None: - return None - return dbe_to_dto(dbe) diff --git a/api/oss/src/dbs/postgres/sessions/states/dbes.py b/api/oss/src/dbs/postgres/sessions/states/dbes.py deleted file mode 100644 index b054f16e33..0000000000 --- a/api/oss/src/dbs/postgres/sessions/states/dbes.py +++ /dev/null @@ -1,51 +0,0 @@ -from sqlalchemy import ( - Column, - String, - PrimaryKeyConstraint, - UniqueConstraint, - ForeignKeyConstraint, -) - -from oss.src.dbs.postgres.shared.base import Base -from oss.src.dbs.postgres.shared.dbas import ( - IdentifierDBA, - ProjectScopeDBA, - DataDBA, - FlagsDBA, - LifecycleDBA, - MetaDBA, - TagsDBA, -) - - -class SessionStateDBE( - Base, - ProjectScopeDBA, - IdentifierDBA, - DataDBA, - FlagsDBA, - TagsDBA, - MetaDBA, - LifecycleDBA, -): - __tablename__ = "session_states" - - __table_args__ = ( - PrimaryKeyConstraint("project_id", "id"), - ForeignKeyConstraint( - ["project_id"], - ["projects.id"], - ondelete="CASCADE", - ), - UniqueConstraint( - "project_id", - "session_id", - name="uq_session_states_project_session_id", - ), - ) - - # bare correlator — not an FK; sessions may be external - session_id = Column(String, nullable=False) - - # resume pointer: which sandbox to reconnect (null = no live sandbox) - sandbox_id = Column(String, nullable=True) diff --git a/api/oss/src/dbs/postgres/sessions/states/mappings.py b/api/oss/src/dbs/postgres/sessions/states/mappings.py deleted file mode 100644 index a15712475a..0000000000 --- a/api/oss/src/dbs/postgres/sessions/states/mappings.py +++ /dev/null @@ -1,52 +0,0 @@ -from typing import Optional - -from pydantic import ValidationError - -from oss.src.utils.logging import get_module_logger -from oss.src.core.sessions.states.dtos import ( - SessionState, - SessionStateData, - SessionStateFlags, -) -from oss.src.dbs.postgres.sessions.states.dbes import SessionStateDBE - -log = get_module_logger(__name__) - - -def _parse_state_data(dbe: SessionStateDBE) -> Optional[SessionStateData]: - if not dbe.data: - return None - - try: - return SessionStateData.model_validate(dbe.data) - except ValidationError as e: - log.error( - "[SESSION_STATES] Corrupt session_states.data; degrading to None", - session_id=dbe.session_id, - project_id=dbe.project_id, - raw_data=dbe.data, - error=str(e), - ) - return None - - -def dbe_to_dto(dbe: SessionStateDBE) -> SessionState: - data = _parse_state_data(dbe) - return SessionState( - id=dbe.id, - project_id=dbe.project_id, - session_id=dbe.session_id, - data=data, - sandbox_id=dbe.sandbox_id, - flags=SessionStateFlags.model_validate(dbe.flags) - if dbe.flags - else SessionStateFlags(), - tags=dbe.tags, - meta=dbe.meta, - created_at=dbe.created_at, - updated_at=dbe.updated_at, - deleted_at=dbe.deleted_at, - created_by_id=dbe.created_by_id, - updated_by_id=dbe.updated_by_id, - deleted_by_id=dbe.deleted_by_id, - ) diff --git a/api/oss/src/dbs/postgres/sessions/streams/dao.py b/api/oss/src/dbs/postgres/sessions/streams/dao.py index d2f997c38b..e23f37f2ac 100644 --- a/api/oss/src/dbs/postgres/sessions/streams/dao.py +++ b/api/oss/src/dbs/postgres/sessions/streams/dao.py @@ -2,27 +2,31 @@ from typing import List, Optional from uuid import UUID -from sqlalchemy import func, select +from sqlalchemy import delete as sa_delete, func, select from sqlalchemy.exc import IntegrityError from oss.src.core.sessions.streams.dtos import ( SessionStream, SessionStreamCreate, SessionStreamEdit, + SessionStreamHeaderEdit, SessionStreamQuery, ) from oss.src.core.sessions.streams.interfaces import SessionStreamsDAOInterface from oss.src.core.sessions.streams.types import SessionStreamAlreadyExists +from oss.src.core.shared.dtos import Windowing from oss.src.dbs.postgres.shared.engine import ( TransactionsEngine, get_transactions_engine, ) +from oss.src.dbs.postgres.shared.utils import apply_windowing from oss.src.dbs.postgres.sessions.streams.dbes import SessionStreamDBE from oss.src.dbs.postgres.sessions.streams.mappings import ( map_stream_dbe_to_dto, map_stream_dto_to_dbe_create, map_stream_dto_to_dbe_edit, + map_stream_dto_to_dbe_header_edit, ) @@ -74,6 +78,25 @@ async def get_by_session_id( return None return map_stream_dbe_to_dto(stream_dbe=dbe) + async def get_by_session_id_including_archived( + self, + *, + project_id: UUID, + session_id: str, + ) -> Optional[SessionStream]: + """Like `get_by_session_id`, but also returns a soft-archived row — the + confirmation read for `archive`/`unarchive` (S7/F2, WP5).""" + async with self.engine.session() as session: + stmt = select(SessionStreamDBE).where( + SessionStreamDBE.project_id == project_id, + SessionStreamDBE.session_id == session_id, + ) + result = await session.execute(stmt) + dbe = result.scalar_one_or_none() + if dbe is None: + return None + return map_stream_dbe_to_dto(stream_dbe=dbe) + async def get_by_id( self, *, @@ -97,6 +120,8 @@ async def query( *, project_id: UUID, filter: SessionStreamQuery, + windowing: Optional[Windowing] = None, + session_ids: Optional[List[str]] = None, ) -> List[SessionStream]: async with self.engine.session() as session: stmt = select(SessionStreamDBE).where( @@ -105,13 +130,24 @@ async def query( ) if filter.session_id is not None: stmt = stmt.where(SessionStreamDBE.session_id == filter.session_id) + if session_ids is not None: + stmt = stmt.where(SessionStreamDBE.session_id.in_(session_ids)) if filter.flags is not None: flags_filter = filter.flags.model_dump( exclude_none=True, exclude_unset=True ) if flags_filter: stmt = stmt.where(SessionStreamDBE.flags.contains(flags_filter)) - stmt = stmt.order_by(SessionStreamDBE.created_at.desc()) + if windowing: + stmt = apply_windowing( + stmt=stmt, + DBE=SessionStreamDBE, + attribute="id", + order="descending", + windowing=windowing, + ) + else: + stmt = stmt.order_by(SessionStreamDBE.created_at.desc()) result = await session.execute(stmt) dbes = result.scalars().all() return [map_stream_dbe_to_dto(stream_dbe=dbe) for dbe in dbes] @@ -144,6 +180,34 @@ async def update( await session.refresh(dbe) return map_stream_dbe_to_dto(stream_dbe=dbe) + async def update_header( + self, + *, + project_id: UUID, + user_id: Optional[UUID], + session_id: str, + header: SessionStreamHeaderEdit, + ) -> Optional[SessionStream]: + async with self.engine.session() as session: + stmt = select(SessionStreamDBE).where( + SessionStreamDBE.project_id == project_id, + SessionStreamDBE.session_id == session_id, + SessionStreamDBE.deleted_at.is_(None), + ) + result = await session.execute(stmt) + dbe = result.scalar_one_or_none() + if dbe is None: + return None + map_stream_dto_to_dbe_header_edit( + stream_dbe=dbe, + user_id=user_id, + header=header, + ) + dbe.updated_at = datetime.now(timezone.utc) + await session.commit() + await session.refresh(dbe) + return map_stream_dbe_to_dto(stream_dbe=dbe) + async def delete_by_session_id( self, *, @@ -164,6 +228,49 @@ async def delete_by_session_id( await session.commit() return True + async def unarchive_by_session_id( + self, + *, + project_id: UUID, + user_id: Optional[UUID], + session_id: str, + ) -> Optional[SessionStream]: + """Clear `deleted_at` on the stream row — the reverse of the archive + fan-out's `delete_by_session_id` soft-delete (S7/F2, WP5).""" + async with self.engine.session() as session: + stmt = select(SessionStreamDBE).where( + SessionStreamDBE.project_id == project_id, + SessionStreamDBE.session_id == session_id, + ) + result = await session.execute(stmt) + dbe = result.scalar_one_or_none() + if dbe is None: + return None + dbe.deleted_at = None + dbe.updated_by_id = user_id + dbe.updated_at = datetime.now(timezone.utc) + await session.commit() + await session.refresh(dbe) + return map_stream_dbe_to_dto(stream_dbe=dbe) + + async def hard_delete_by_session_id( + self, + *, + project_id: UUID, + session_id: str, + ) -> bool: + """Hard delete the merged stream row — `kill`/`delete_by_session_id` only + soft-delete; this is new plumbing for the session-scoped hard-delete + fan-out (S7/F1, WP5).""" + async with self.engine.session() as session: + stmt = sa_delete(SessionStreamDBE).where( + SessionStreamDBE.project_id == project_id, + SessionStreamDBE.session_id == session_id, + ) + result = await session.execute(stmt) + await session.commit() + return bool(result.rowcount) + async def count_active( self, *, diff --git a/api/oss/src/dbs/postgres/sessions/streams/dbes.py b/api/oss/src/dbs/postgres/sessions/streams/dbes.py index 00a0ad0602..a22ed8f4f7 100644 --- a/api/oss/src/dbs/postgres/sessions/streams/dbes.py +++ b/api/oss/src/dbs/postgres/sessions/streams/dbes.py @@ -10,6 +10,7 @@ from oss.src.dbs.postgres.shared.base import Base from oss.src.dbs.postgres.shared.dbas import ( FlagsDBA, + HeaderDBA, IdentifierDBA, LifecycleDBA, MetaDBA, @@ -22,18 +23,21 @@ class SessionStreamDBE( Base, IdentifierDBA, ProjectScopeDBA, + HeaderDBA, LifecycleDBA, FlagsDBA, TagsDBA, MetaDBA, ): - """Ephemeral run/liveness facet for a session — the durable mirror of the - Redis nest (alive ⊇ running ⊇ attached). + """The session's one row: identity (name/description) and run/liveness (the + durable mirror of the Redis nest, alive ⊇ running ⊇ attached). 1:1 with session_id per project. Redis is authoritative for the nest bools; this row mirrors them in ``flags`` for durability / orphan sweep / observability. ``updated_at`` (LifecycleDBA) is the heartbeat timestamp — no separate column. - sandbox_id is NOT stored here (it lives in session_states). + ``name``/``description`` (HeaderDBA) are written only on the rename edit, never + on a flag-mirror write, so heartbeats don't churn them. + sandbox_id is NOT stored here (it lives on the latest session_turns row). """ __tablename__ = "session_streams" diff --git a/api/oss/src/dbs/postgres/sessions/streams/mappings.py b/api/oss/src/dbs/postgres/sessions/streams/mappings.py index 00d5a1d7ff..5053fc4091 100644 --- a/api/oss/src/dbs/postgres/sessions/streams/mappings.py +++ b/api/oss/src/dbs/postgres/sessions/streams/mappings.py @@ -6,6 +6,7 @@ SessionStreamCreate, SessionStreamEdit, SessionStreamFlags, + SessionStreamHeaderEdit, ) from oss.src.dbs.postgres.sessions.streams.dbes import SessionStreamDBE @@ -20,6 +21,8 @@ def map_stream_dto_to_dbe_create( project_id=project_id, created_by_id=user_id, session_id=stream.session_id, + name=stream.name, + description=stream.description, flags=stream.flags.model_dump(mode="json") if stream.flags else None, tags=stream.tags, meta=stream.meta, @@ -41,6 +44,8 @@ def map_stream_dbe_to_dto( deleted_by_id=stream_dbe.deleted_by_id, project_id=stream_dbe.project_id, session_id=stream_dbe.session_id, + name=stream_dbe.name, + description=stream_dbe.description, turn_id=stream_dbe.turn_id, flags=SessionStreamFlags.model_validate(stream_dbe.flags) if stream_dbe.flags @@ -57,6 +62,10 @@ def map_stream_dto_to_dbe_edit( stream: SessionStreamEdit, ) -> None: stream_dbe.updated_by_id = user_id + if stream.name is not None: + stream_dbe.name = stream.name + if stream.description is not None: + stream_dbe.description = stream.description if stream.flags is not None: stream_dbe.flags = stream.flags.model_dump(mode="json") if stream.tags is not None: @@ -65,3 +74,17 @@ def map_stream_dto_to_dbe_edit( stream_dbe.meta = stream.meta if stream.turn_id is not None: stream_dbe.turn_id = stream.turn_id + + +def map_stream_dto_to_dbe_header_edit( + *, + stream_dbe: SessionStreamDBE, + user_id: Optional[UUID], + header: SessionStreamHeaderEdit, +) -> None: + """The rename edit: only ever touches name/description — never flags/turn_id.""" + stream_dbe.updated_by_id = user_id + if header.name is not None: + stream_dbe.name = header.name + if header.description is not None: + stream_dbe.description = header.description diff --git a/api/oss/src/dbs/postgres/sessions/states/__init__.py b/api/oss/src/dbs/postgres/sessions/turns/__init__.py similarity index 100% rename from api/oss/src/dbs/postgres/sessions/states/__init__.py rename to api/oss/src/dbs/postgres/sessions/turns/__init__.py diff --git a/api/oss/src/dbs/postgres/sessions/turns/dao.py b/api/oss/src/dbs/postgres/sessions/turns/dao.py new file mode 100644 index 0000000000..cc9f2ce27d --- /dev/null +++ b/api/oss/src/dbs/postgres/sessions/turns/dao.py @@ -0,0 +1,196 @@ +from typing import List, Optional +from uuid import UUID + +from sqlalchemy import delete as sa_delete, select +from sqlalchemy.exc import IntegrityError + +from oss.src.core.sessions.turns.dtos import ( + HarnessKind, + SessionTurn, + SessionTurnCreate, + SessionTurnQuery, +) +from oss.src.core.sessions.turns.interfaces import SessionTurnsDAOInterface +from oss.src.core.shared.dtos import Windowing +from oss.src.core.shared.exceptions import EntityCreationConflict +from oss.src.dbs.postgres.sessions.turns.dbes import SessionTurnDBE +from oss.src.dbs.postgres.sessions.turns.mappings import ( + map_turn_dbe_to_dto, + map_turn_dto_to_dbe_create, +) +from oss.src.dbs.postgres.sessions.turns.utils import query_turn_references +from oss.src.dbs.postgres.shared.engine import ( + TransactionsEngine, + get_transactions_engine, +) +from oss.src.dbs.postgres.shared.utils import apply_windowing + + +class SessionTurnsDAO(SessionTurnsDAOInterface): + def __init__(self, engine: TransactionsEngine = None): + if engine is None: + engine = get_transactions_engine() + self.engine = engine + + async def append( + self, + *, + project_id: UUID, + user_id: Optional[UUID], + # + turn: SessionTurnCreate, + ) -> SessionTurn: + dbe = map_turn_dto_to_dbe_create( + project_id=project_id, + user_id=user_id, + turn=turn, + ) + async with self.engine.session() as session: + try: + session.add(dbe) + await session.commit() + await session.refresh(dbe) + except IntegrityError as e: + await session.rollback() + error_str = str(e.orig) if e.orig else str(e) + if "ix_session_turns_project_id_session_id_turn_index" in error_str: + raise EntityCreationConflict( + entity="Session turn", + message=( + f"Session turn {turn.turn_index} already exists for " + f"session {turn.session_id}." + ), + conflict={ + "session_id": turn.session_id, + "turn_index": turn.turn_index, + }, + ) from e + raise + return map_turn_dbe_to_dto(turn_dbe=dbe) + + async def fetch_turn( + self, + *, + project_id: UUID, + # + turn_id: UUID, + ) -> Optional[SessionTurn]: + async with self.engine.session() as session: + stmt = select(SessionTurnDBE).where( + SessionTurnDBE.project_id == project_id, + SessionTurnDBE.id == turn_id, + ) + result = await session.execute(stmt) + dbe = result.scalar_one_or_none() + if dbe is None: + return None + return map_turn_dbe_to_dto(turn_dbe=dbe) + + async def query_turns( + self, + *, + project_id: UUID, + # + query: Optional[SessionTurnQuery] = None, + windowing: Optional[Windowing] = None, + ) -> List[SessionTurn]: + async with self.engine.session() as session: + stmt = select(SessionTurnDBE).where( + SessionTurnDBE.project_id == project_id, + ) + + if query is not None: + if query.session_id is not None: + stmt = stmt.where( + SessionTurnDBE.session_id == query.session_id, + ) + if query.stream_id is not None: + stmt = stmt.where( + SessionTurnDBE.stream_id == query.stream_id, + ) + if query.harness_kind is not None: + stmt = stmt.where( + SessionTurnDBE.harness_kind == query.harness_kind.value, + ) + if query.references is not None: + turn_references = query_turn_references(query) + if turn_references is not None: + stmt = stmt.where( + SessionTurnDBE.references.contains(turn_references), + ) + + if windowing: + stmt = apply_windowing( + stmt=stmt, + DBE=SessionTurnDBE, + attribute="id", + order="descending", + windowing=windowing, + ) + else: + stmt = stmt.order_by(SessionTurnDBE.created_at.desc()) + + result = await session.execute(stmt) + return [map_turn_dbe_to_dto(turn_dbe=dbe) for dbe in result.scalars().all()] + + async def latest_turn( + self, + *, + project_id: UUID, + session_id: str, + ) -> Optional[SessionTurn]: + async with self.engine.session() as session: + stmt = ( + select(SessionTurnDBE) + .where( + SessionTurnDBE.project_id == project_id, + SessionTurnDBE.session_id == session_id, + ) + .order_by(SessionTurnDBE.turn_index.desc()) + .limit(1) + ) + result = await session.execute(stmt) + dbe = result.scalar_one_or_none() + if dbe is None: + return None + return map_turn_dbe_to_dto(turn_dbe=dbe) + + async def latest_turn_per_harness_kind( + self, + *, + project_id: UUID, + session_id: str, + harness_kind: HarnessKind, + ) -> Optional[SessionTurn]: + async with self.engine.session() as session: + stmt = ( + select(SessionTurnDBE) + .where( + SessionTurnDBE.project_id == project_id, + SessionTurnDBE.session_id == session_id, + SessionTurnDBE.harness_kind == harness_kind.value, + ) + .order_by(SessionTurnDBE.turn_index.desc()) + .limit(1) + ) + result = await session.execute(stmt) + dbe = result.scalar_one_or_none() + if dbe is None: + return None + return map_turn_dbe_to_dto(turn_dbe=dbe) + + async def delete_by_session_id( + self, + *, + project_id: UUID, + session_id: str, + ) -> int: + """Hard delete — no soft-delete for turns (session-scoped fan-out, WP5).""" + async with self.engine.session() as session: + stmt = sa_delete(SessionTurnDBE).where( + SessionTurnDBE.project_id == project_id, + SessionTurnDBE.session_id == session_id, + ) + result = await session.execute(stmt) + await session.commit() + return result.rowcount or 0 diff --git a/api/oss/src/dbs/postgres/sessions/turns/dbas.py b/api/oss/src/dbs/postgres/sessions/turns/dbas.py new file mode 100644 index 0000000000..708338c27d --- /dev/null +++ b/api/oss/src/dbs/postgres/sessions/turns/dbas.py @@ -0,0 +1,41 @@ +from sqlalchemy import Column, Integer, String, TIMESTAMP, UUID +from sqlalchemy.dialects.postgresql import JSONB + +from oss.src.dbs.postgres.shared.dbas import ( + IdentifierDBA, + LifecycleDBA, + ProjectScopeDBA, +) + + +class SessionTurnDBA( + ProjectScopeDBA, + LifecycleDBA, + IdentifierDBA, +): + __abstract__ = True + + # Bare string correlator — NOT an FK (sessions may be external). Spine: NOT NULL. + session_id = Column(String, nullable=False) + + # Spine: NOT NULL — every turn is written with stream_id in hand (from the heartbeat). + stream_id = Column(UUID(as_uuid=True), nullable=False) + + turn_index = Column(Integer, nullable=False) + + # Enum-validated at the DTO (HarnessKind); plain varchar column here. + harness_kind = Column(String, nullable=False) + + agent_session_id = Column(String, nullable=True) + sandbox_id = Column(String, nullable=True) + + # eval_runs pattern: list of {id, slug, version} dicts, GIN jsonb_path_ops, .contains(). + references = Column(JSONB(none_as_null=True), nullable=True) + + # Bridge — nullable. trace_id is a 128-bit OTel trace id (fits UUID); span_id is a + # 64-bit OTel span id (16 hex), NOT a UUID — stored as text (see OTelSpanId). + trace_id = Column(UUID(as_uuid=True), nullable=True) + span_id = Column(String, nullable=True) + + start_time = Column(TIMESTAMP(timezone=True), nullable=True) + end_time = Column(TIMESTAMP(timezone=True), nullable=True) diff --git a/api/oss/src/dbs/postgres/sessions/turns/dbes.py b/api/oss/src/dbs/postgres/sessions/turns/dbes.py new file mode 100644 index 0000000000..4209532575 --- /dev/null +++ b/api/oss/src/dbs/postgres/sessions/turns/dbes.py @@ -0,0 +1,50 @@ +from sqlalchemy import ( + ForeignKeyConstraint, + Index, + PrimaryKeyConstraint, +) + +from oss.src.dbs.postgres.shared.base import Base +from oss.src.dbs.postgres.sessions.turns.dbas import SessionTurnDBA + + +class SessionTurnDBE(Base, SessionTurnDBA): + """One row per turn — the transcript twin of a trace. 1:many by session_id. + + No data/flags/tags/meta: every field is first-class/queryable. Resume state + (agent_session_id/sandbox_id/turn_index) is read off the latest row, not folded. + """ + + __tablename__ = "session_turns" + + __table_args__ = ( + ForeignKeyConstraint( + ["project_id"], + ["projects.id"], + ondelete="CASCADE", + ), + ForeignKeyConstraint( + ["project_id", "stream_id"], + ["session_streams.project_id", "session_streams.id"], + ondelete="NO ACTION", + ), + PrimaryKeyConstraint("project_id", "id"), + Index( + "ix_session_turns_project_id_session_id", + "project_id", + "session_id", + ), + Index( + "ix_session_turns_project_id_session_id_turn_index", + "project_id", + "session_id", + "turn_index", + unique=True, + ), + Index( + "ix_session_turns_references", + "references", + postgresql_using="gin", + postgresql_ops={"references": "jsonb_path_ops"}, + ), + ) diff --git a/api/oss/src/dbs/postgres/sessions/turns/mappings.py b/api/oss/src/dbs/postgres/sessions/turns/mappings.py new file mode 100644 index 0000000000..4a44f655b3 --- /dev/null +++ b/api/oss/src/dbs/postgres/sessions/turns/mappings.py @@ -0,0 +1,82 @@ +from typing import List, Optional +from uuid import UUID + +from oss.src.core.shared.dtos import Reference +from oss.src.core.sessions.turns.dtos import ( + HarnessKind, + SessionTurn, + SessionTurnCreate, +) +from oss.src.dbs.postgres.sessions.turns.dbes import SessionTurnDBE + + +def _references_to_json( + references: Optional[List[Reference]], +) -> Optional[List[dict]]: + if not references: + return None + return [ + reference.model_dump(mode="json", exclude_none=True) for reference in references + ] + + +def _references_from_json( + references: Optional[List[dict]], +) -> Optional[List[Reference]]: + if not references: + return None + return [Reference.model_validate(reference) for reference in references] + + +def map_turn_dto_to_dbe_create( + *, + project_id: UUID, + user_id: Optional[UUID], + # + turn: SessionTurnCreate, +) -> SessionTurnDBE: + return SessionTurnDBE( + project_id=project_id, + created_by_id=user_id, + # + session_id=turn.session_id, + stream_id=turn.stream_id, + turn_index=turn.turn_index, + harness_kind=turn.harness_kind.value, + agent_session_id=turn.agent_session_id, + sandbox_id=turn.sandbox_id, + references=_references_to_json(turn.references), + trace_id=turn.trace_id, + span_id=turn.span_id, + start_time=turn.start_time, + end_time=turn.end_time, + ) + + +def map_turn_dbe_to_dto( + *, + turn_dbe: SessionTurnDBE, +) -> SessionTurn: + return SessionTurn( + id=turn_dbe.id, + # + created_at=turn_dbe.created_at, + updated_at=turn_dbe.updated_at, + deleted_at=turn_dbe.deleted_at, + created_by_id=turn_dbe.created_by_id, + updated_by_id=turn_dbe.updated_by_id, + deleted_by_id=turn_dbe.deleted_by_id, + # + project_id=turn_dbe.project_id, + session_id=turn_dbe.session_id, + stream_id=turn_dbe.stream_id, + turn_index=turn_dbe.turn_index, + harness_kind=HarnessKind(turn_dbe.harness_kind), + agent_session_id=turn_dbe.agent_session_id, + sandbox_id=turn_dbe.sandbox_id, + references=_references_from_json(turn_dbe.references), + trace_id=turn_dbe.trace_id, + span_id=turn_dbe.span_id, + start_time=turn_dbe.start_time, + end_time=turn_dbe.end_time, + ) diff --git a/api/oss/src/dbs/postgres/sessions/turns/utils.py b/api/oss/src/dbs/postgres/sessions/turns/utils.py new file mode 100644 index 0000000000..578dd04fbb --- /dev/null +++ b/api/oss/src/dbs/postgres/sessions/turns/utils.py @@ -0,0 +1,22 @@ +from typing import Any, Dict, List, Optional + +from oss.src.core.sessions.turns.dtos import SessionTurnQuery + + +def query_turn_references( + turn: Optional[SessionTurnQuery] = None, +) -> Optional[List[Any]]: + """eval_runs pattern: flatten to bare {id, slug, version} dicts for .contains().""" + if not turn or not turn.references: + return None + + _references: Dict[Any, Any] = dict() + + for reference in turn.references: + _key = reference.id or reference.slug + _references[_key] = reference.model_dump( + mode="json", + exclude_none=True, + ) + + return list(_references.values()) or None diff --git a/api/oss/src/dbs/postgres/tracing/dao.py b/api/oss/src/dbs/postgres/tracing/dao.py index ee0a11c3c9..52649e7f67 100644 --- a/api/oss/src/dbs/postgres/tracing/dao.py +++ b/api/oss/src/dbs/postgres/tracing/dao.py @@ -889,6 +889,9 @@ async def _query_by_group( # BASE QUERY: Use DISTINCT ON pattern (like query() does for traces) # DISTINCT ON picks one row per identifier based on ORDER BY + # TODO(WP0.7, deferred): SpanDBE.session_id/user_id are now indexed + # root-only columns (see migration oss000000003) — this JSONB path + # scan can become a column scan. Left as-is; not blocking. id_column = SpanDBE.attributes["ag"][group]["id"].as_string() base = ( select( diff --git a/api/oss/src/dbs/postgres/tracing/dbas.py b/api/oss/src/dbs/postgres/tracing/dbas.py index 99ae1d4844..2d90cf84bb 100644 --- a/api/oss/src/dbs/postgres/tracing/dbas.py +++ b/api/oss/src/dbs/postgres/tracing/dbas.py @@ -58,6 +58,21 @@ class SpanDBA: nullable=True, ) + # Root-span-only (parent_id IS NULL); promoted from ag.session/user/agent + # attributes at ingest_span_dtos. Nullable — children never populate these. + session_id = Column( + VARCHAR, + nullable=True, + ) + user_id = Column( + VARCHAR, + nullable=True, + ) + agent_id = Column( + VARCHAR, + nullable=True, + ) + attributes = Column( JSONB(none_as_null=True), nullable=True, diff --git a/api/oss/src/dbs/postgres/tracing/dbes.py b/api/oss/src/dbs/postgres/tracing/dbes.py index af265a8c5a..a2bcf581d2 100644 --- a/api/oss/src/dbs/postgres/tracing/dbes.py +++ b/api/oss/src/dbs/postgres/tracing/dbes.py @@ -40,6 +40,11 @@ class SpanDBE( "project_id", "start_time", ), # for sorting and scrolling + Index( + "ix_spans_project_id_session_id", + "project_id", + "session_id", + ), # for session filtering (root-only column) Index( "ix_spans_project_id_trace_type", "project_id", diff --git a/api/oss/src/dbs/postgres/tracing/mappings.py b/api/oss/src/dbs/postgres/tracing/mappings.py index 6a1117cbc3..101e75ca8f 100644 --- a/api/oss/src/dbs/postgres/tracing/mappings.py +++ b/api/oss/src/dbs/postgres/tracing/mappings.py @@ -56,6 +56,9 @@ def map_span_dbe_to_span_dbe( existing_span_dbe.end_time = new_span_dbe.end_time existing_span_dbe.status_code = new_span_dbe.status_code existing_span_dbe.status_message = new_span_dbe.status_message + existing_span_dbe.session_id = new_span_dbe.session_id + existing_span_dbe.user_id = new_span_dbe.user_id + existing_span_dbe.agent_id = new_span_dbe.agent_id existing_span_dbe.attributes = new_span_dbe.attributes existing_span_dbe.events = new_span_dbe.events existing_span_dbe.links = new_span_dbe.links @@ -119,6 +122,10 @@ def map_span_dbe_to_span_dto( status_code=OTelStatusCode(span_dbe.status_code), status_message=span_dbe.status_message, # + session_id=span_dbe.session_id, + user_id=span_dbe.user_id, + agent_id=span_dbe.agent_id, + # attributes=span_dbe.attributes, # references=references if references else None, @@ -162,6 +169,10 @@ def map_span_dto_to_span_dbe( status_code=span_dto.status_code, status_message=span_dto.status_message, # + session_id=span_dto.session_id, + user_id=span_dto.user_id, + agent_id=span_dto.agent_id, + # attributes=span_dto.attributes, # references=( diff --git a/api/oss/src/dbs/postgres/tracing/utils.py b/api/oss/src/dbs/postgres/tracing/utils.py index dbd2cda916..70c8815453 100644 --- a/api/oss/src/dbs/postgres/tracing/utils.py +++ b/api/oss/src/dbs/postgres/tracing/utils.py @@ -787,6 +787,12 @@ def filter( # pylint:disable=redefined-builtin clauses.extend(_handle_uuid_field(condition)) elif field == Fields.CONTENT: clauses.extend(_handle_fts_field(condition)) + elif field == Fields.SESSION_ID: + clauses.extend(_handle_string_field(condition)) + elif field == Fields.USER_ID: + clauses.extend(_handle_string_field(condition)) + elif field == Fields.AGENT_ID: + clauses.extend(_handle_string_field(condition)) else: # raise FilteringException( # f"Unsupported condition field: {field}", diff --git a/api/oss/src/utils/env.py b/api/oss/src/utils/env.py index c704ca7766..2166bca399 100644 --- a/api/oss/src/utils/env.py +++ b/api/oss/src/utils/env.py @@ -1047,6 +1047,14 @@ class RunnerConfig(BaseModel): default_factory=_default_sandbox_provider_default ) + # Direct API -> runner hop for `kill` (W7.3): the same base URL and shared-secret token + # the Python agent service (services/oss/src/agent/config.py's `runner_url()`) already + # uses to reach the runner's HTTP surface. None/blank means kill only edits the Redis + # nest + row (best-effort; the runner's own orphan sweep/TTL expiry still reclaims the + # sandbox eventually, just not immediately). + internal_url: Optional[str] = os.getenv("AGENTA_RUNNER_INTERNAL_URL") or None + token: Optional[str] = os.getenv("AGENTA_RUNNER_TOKEN") or None + model_config = ConfigDict(extra="ignore") @model_validator(mode="after") diff --git a/api/oss/tests/pytest/acceptance/session_states/test_harness_sessions_roundtrip.py b/api/oss/tests/pytest/acceptance/session_states/test_harness_sessions_roundtrip.py deleted file mode 100644 index ba2b54d6af..0000000000 --- a/api/oss/tests/pytest/acceptance/session_states/test_harness_sessions_roundtrip.py +++ /dev/null @@ -1,118 +0,0 @@ -"""Acceptance tests for the S2/S2b/S4 durable continuity state on /sessions/states/ and -/sessions/mounts/sign (OSS edition). Follows test_session_states_basics.py's setup exactly. - -The continuity fields (latest_agent_session_id / harness_sessions / latest_turn_index) live -inside the existing `data` JSON column, so NO migration is required -- `session_states.data` -already exists. - -Requires a live stack (AGENTA_API_URL/AGENTA_AUTH_KEY) -- see the pytest.ini `acceptance` -marker. -""" - -import uuid - - -class TestHarnessSessionsRoundtrip: - """GET/PUT /sessions/states/ round-trips data.{latest_agent_session_id, - harness_sessions, latest_turn_index}; POST /sessions/mounts/sign accepts - name=claude-projects.""" - - def test_put_get_roundtrips_harness_sessions(self, authed_api): - session_id = str(uuid.uuid4()) - data = { - "latest_agent_session_id": "agent-claude-2", - "harness_sessions": { - "claude": { - "agent_session_id": "agent-claude-2", - "turn_index": 2, - }, - "pi": { - "agent_session_id": "agent-pi-1", - "turn_index": 1, - }, - }, - "latest_turn_index": 2, - } - - put_response = authed_api( - "PUT", - "/sessions/states/", - params={"session_id": session_id}, - json={"data": data}, - ) - assert put_response.status_code == 200 - put_state = put_response.json()["session_state"] - assert put_state["data"]["latest_agent_session_id"] == "agent-claude-2" - assert put_state["data"]["latest_turn_index"] == 2 - assert put_state["data"]["harness_sessions"] == data["harness_sessions"] - - get_response = authed_api( - "GET", "/sessions/states/", params={"session_id": session_id} - ) - assert get_response.status_code == 200 - get_state = get_response.json()["session_state"] - assert get_state["data"]["latest_agent_session_id"] == "agent-claude-2" - assert get_state["data"]["latest_turn_index"] == 2 - assert get_state["data"]["harness_sessions"] == data["harness_sessions"] - - def test_harness_sessions_survive_a_read_modify_write_merge(self, authed_api): - # Mirrors the runner's syncHarnessSessionDurable: GET, splice one harness's entry, - # PUT the whole data back -- the other harness's entry must survive untouched. - session_id = str(uuid.uuid4()) - authed_api( - "PUT", - "/sessions/states/", - params={"session_id": session_id}, - json={ - "data": { - "harness_sessions": { - "pi": { - "agent_session_id": "agent-pi-1", - "turn_index": 1, - }, - }, - "latest_turn_index": 1, - }, - }, - ) - - merged = authed_api( - "PUT", - "/sessions/states/", - params={"session_id": session_id}, - json={ - "data": { - "latest_agent_session_id": "agent-claude-2", - "harness_sessions": { - "pi": { - "agent_session_id": "agent-pi-1", - "turn_index": 1, - }, - "claude": { - "agent_session_id": "agent-claude-2", - "turn_index": 2, - }, - }, - "latest_turn_index": 2, - }, - }, - ) - assert merged.status_code == 200 - state = merged.json()["session_state"] - assert state["data"]["harness_sessions"]["pi"]["turn_index"] == 1 - assert state["data"]["harness_sessions"]["claude"]["turn_index"] == 2 - - def test_mounts_sign_accepts_claude_projects_name(self, authed_api): - session_id = str(uuid.uuid4()) - response = authed_api( - "POST", - "/sessions/mounts/sign", - params={"session_id": session_id, "name": "claude-projects"}, - ) - # 200 with usable credentials, or 503 if the store is not configured in this - # environment -- either is a legitimate outcome; the endpoint must not 4xx/500 on a - # valid `name` value. - assert response.status_code in (200, 503) - if response.status_code == 200: - body = response.json() - assert body["mount"]["name"] == "claude-projects" diff --git a/api/oss/tests/pytest/acceptance/session_states/test_session_states_basics.py b/api/oss/tests/pytest/acceptance/session_states/test_session_states_basics.py deleted file mode 100644 index ee05ffdc0e..0000000000 --- a/api/oss/tests/pytest/acceptance/session_states/test_session_states_basics.py +++ /dev/null @@ -1,299 +0,0 @@ -"""Acceptance tests for /sessions/states/ endpoints (OSS edition).""" - -import uuid - - -class TestSessionStatesBasics: - """GET / PUT / POST /sessions/states/?session_id=... — happy paths.""" - - def test_get_missing_state_returns_empty(self, authed_api): - session_id = str(uuid.uuid4()) - response = authed_api( - "GET", "/sessions/states/", params={"session_id": session_id} - ) - assert response.status_code == 200 - body = response.json() - assert body["count"] == 0 - assert body.get("session_state") is None - - def test_put_creates_state(self, authed_api): - session_id = str(uuid.uuid4()) - payload = { - "data": {"latest_agent_session_id": "agent-abc"}, - "sandbox_id": "sandbox-001", - } - - response = authed_api( - "PUT", - "/sessions/states/", - params={"session_id": session_id}, - json=payload, - ) - assert response.status_code == 200 - body = response.json() - assert body["count"] == 1 - state = body["session_state"] - assert state["session_id"] == session_id - assert state["data"]["latest_agent_session_id"] == "agent-abc" - assert state["sandbox_id"] == "sandbox-001" - - def test_get_returns_persisted_state(self, authed_api): - session_id = str(uuid.uuid4()) - payload = { - "data": {"latest_agent_session_id": "agent-abc"}, - "sandbox_id": "sbx-999", - } - - authed_api( - "PUT", - "/sessions/states/", - params={"session_id": session_id}, - json=payload, - ) - - response = authed_api( - "GET", "/sessions/states/", params={"session_id": session_id} - ) - assert response.status_code == 200 - body = response.json() - assert body["count"] == 1 - assert body["session_state"]["session_id"] == session_id - assert body["session_state"]["sandbox_id"] == "sbx-999" - - def test_put_upserts_on_second_call(self, authed_api): - session_id = str(uuid.uuid4()) - - authed_api( - "PUT", - "/sessions/states/", - params={"session_id": session_id}, - json={"data": {"latest_turn_index": 1}}, - ) - - response = authed_api( - "PUT", - "/sessions/states/", - params={"session_id": session_id}, - json={"data": {"latest_turn_index": 2}, "sandbox_id": "sbx-v2"}, - ) - assert response.status_code == 200 - state = response.json()["session_state"] - assert state["data"]["latest_turn_index"] == 2 - assert state["sandbox_id"] == "sbx-v2" - - # GET should reflect the latest upsert - get_resp = authed_api( - "GET", "/sessions/states/", params={"session_id": session_id} - ) - assert get_resp.json()["session_state"]["data"]["latest_turn_index"] == 2 - - def test_set_sandbox_id_endpoint(self, authed_api): - session_id = str(uuid.uuid4()) - - # create the state first - authed_api( - "PUT", - "/sessions/states/", - params={"session_id": session_id}, - json={"data": {"latest_agent_session_id": "agent-abc"}}, - ) - - # update sandbox_id independently - response = authed_api( - "PUT", - "/sessions/states/", - params={"session_id": session_id}, - json={"sandbox_id": "sbx-new"}, - ) - assert response.status_code == 200 - state = response.json()["session_state"] - assert state["sandbox_id"] == "sbx-new" - assert state["data"] == {"latest_agent_session_id": "agent-abc"} - - def test_set_sandbox_id_clear(self, authed_api): - session_id = str(uuid.uuid4()) - - authed_api( - "PUT", - "/sessions/states/", - params={"session_id": session_id}, - json={"data": {}, "sandbox_id": "sbx-to-clear"}, - ) - - response = authed_api( - "PUT", - "/sessions/states/", - params={"session_id": session_id}, - json={"sandbox_id": None}, - ) - assert response.status_code == 200 - state = response.json()["session_state"] - assert state.get("sandbox_id") is None - - def test_invalid_session_id_rejected(self, authed_api): - # slashes are not allowed - response = authed_api( - "GET", "/sessions/states/", params={"session_id": "foo/bar"} - ) - assert response.status_code == 400 - - def test_invalid_session_id_chars_rejected(self, authed_api): - # spaces are not allowed - response = authed_api( - "GET", "/sessions/states/", params={"session_id": "foo bar"} - ) - assert response.status_code == 400 - - def test_sandbox_id_on_missing_state_creates_state(self, authed_api): - session_id = str(uuid.uuid4()) - response = authed_api( - "PUT", - "/sessions/states/", - params={"session_id": session_id}, - json={"sandbox_id": "sbx-orphan"}, - ) - assert response.status_code == 200 - body = response.json() - assert body["count"] == 1 - state = body["session_state"] - assert state["session_id"] == session_id - assert state["sandbox_id"] == "sbx-orphan" - assert state.get("data") is None - - def test_sandbox_id_update_preserves_existing_data(self, authed_api): - session_id = str(uuid.uuid4()) - authed_api( - "PUT", - "/sessions/states/", - params={"session_id": session_id}, - json={ - "data": { - "latest_turn_index": 1, - "latest_agent_session_id": "agent-abc", - } - }, - ) - - response = authed_api( - "PUT", - "/sessions/states/", - params={"session_id": session_id}, - json={"sandbox_id": "sbx-preserved"}, - ) - - assert response.status_code == 200 - state = response.json()["session_state"] - assert state["sandbox_id"] == "sbx-preserved" - assert state["data"] == { - "latest_turn_index": 1, - "latest_agent_session_id": "agent-abc", - } - - def test_sandbox_id_accepts_runner_post(self, authed_api): - session_id = str(uuid.uuid4()) - response = authed_api( - "POST", - "/sessions/states/", - params={"session_id": session_id}, - json={"sandbox_id": "sbx-runner"}, - ) - - assert response.status_code == 200 - state = response.json()["session_state"] - assert state["session_id"] == session_id - assert state["sandbox_id"] == "sbx-runner" - - def test_guarded_pointer_write_applies_at_latest_turn(self, authed_api): - session_id = str(uuid.uuid4()) - authed_api( - "PUT", - "/sessions/states/", - params={"session_id": session_id}, - json={ - "data": {"latest_turn_index": 2}, - "sandbox_id": "sbx-old", - }, - ) - - response = authed_api( - "PUT", - "/sessions/states/", - params={"session_id": session_id}, - json={ - "sandbox_id": "sbx-new", - "sandbox_turn_index": 2, - }, - ) - - assert response.status_code == 200 - state = response.json()["session_state"] - assert state["sandbox_id"] == "sbx-new" - - def test_stale_guarded_pointer_write_returns_unchanged_row(self, authed_api): - session_id = str(uuid.uuid4()) - authed_api( - "PUT", - "/sessions/states/", - params={"session_id": session_id}, - json={ - "data": {"latest_turn_index": 3}, - "sandbox_id": "sbx-current", - }, - ) - - response = authed_api( - "PUT", - "/sessions/states/", - params={"session_id": session_id}, - json={ - "sandbox_id": "sbx-stale", - "sandbox_turn_index": 2, - }, - ) - - assert response.status_code == 200 - state = response.json()["session_state"] - assert state["sandbox_id"] == "sbx-current" - assert state["data"]["latest_turn_index"] == 3 - - def test_tokenless_pointer_write_remains_unconditional(self, authed_api): - session_id = str(uuid.uuid4()) - authed_api( - "PUT", - "/sessions/states/", - params={"session_id": session_id}, - json={ - "data": {"latest_turn_index": 4}, - "sandbox_id": "sbx-old", - }, - ) - - response = authed_api( - "PUT", - "/sessions/states/", - params={"session_id": session_id}, - json={ - "sandbox_id": "sbx-tokenless", - }, - ) - - assert response.status_code == 200 - state = response.json()["session_state"] - assert state["sandbox_id"] == "sbx-tokenless" - - def test_guarded_pointer_write_creates_missing_row(self, authed_api): - session_id = str(uuid.uuid4()) - response = authed_api( - "PUT", - "/sessions/states/", - params={"session_id": session_id}, - json={ - "sandbox_id": "sbx-first", - "sandbox_turn_index": 7, - }, - ) - - assert response.status_code == 200 - state = response.json()["session_state"] - assert state["sandbox_id"] == "sbx-first" - assert state.get("data") is None diff --git a/api/oss/tests/pytest/acceptance/session_states/__init__.py b/api/oss/tests/pytest/acceptance/sessions/__init__.py similarity index 100% rename from api/oss/tests/pytest/acceptance/session_states/__init__.py rename to api/oss/tests/pytest/acceptance/sessions/__init__.py diff --git a/api/oss/tests/pytest/acceptance/sessions/test_archive_unarchive.py b/api/oss/tests/pytest/acceptance/sessions/test_archive_unarchive.py new file mode 100644 index 0000000000..b0b8300779 --- /dev/null +++ b/api/oss/tests/pytest/acceptance/sessions/test_archive_unarchive.py @@ -0,0 +1,54 @@ +"""Acceptance test for the session archive/unarchive lifecycle routes (OSS edition). + +POST /sessions/archive and POST /sessions/unarchive (session_id as a query param) soft-archive +and restore the merged stream row; /sessions/query must exclude an archived session and return +it again once unarchived. Covers TEST-GAPS.md §"archive / unarchive lifecycle", which had only +service-level coverage and no route test. + +Requires a live stack (AGENTA_API_URL/AGENTA_AUTH_KEY) — see the pytest `acceptance` marker. +""" + +import uuid + + +def _session_ids(query_response) -> set: + return {s["session_id"] for s in query_response.json().get("sessions", [])} + + +class TestSessionArchiveUnarchive: + """A session created via the stream header is queryable, drops out of /sessions/query when + archived, and returns when unarchived.""" + + def test_archive_excludes_then_unarchive_restores(self, authed_api): + session_id = str(uuid.uuid4()) + + # Create the merged stream row (this is what /sessions/query lists). + create = authed_api( + "PUT", + "/sessions/streams/header", + params={"session_id": session_id}, + json={"name": "Archive Me", "description": "lifecycle check"}, + ) + assert create.status_code == 200, create.text + + listed = authed_api("POST", "/sessions/query", json={}) + assert listed.status_code == 200, listed.text + assert session_id in _session_ids(listed) + + archived = authed_api( + "POST", "/sessions/archive", params={"session_id": session_id} + ) + assert archived.status_code == 200, archived.text + + after_archive = authed_api("POST", "/sessions/query", json={}) + assert after_archive.status_code == 200, after_archive.text + assert session_id not in _session_ids(after_archive) + + unarchived = authed_api( + "POST", "/sessions/unarchive", params={"session_id": session_id} + ) + assert unarchived.status_code == 200, unarchived.text + + after_unarchive = authed_api("POST", "/sessions/query", json={}) + assert after_unarchive.status_code == 200, after_unarchive.text + assert session_id in _session_ids(after_unarchive) diff --git a/api/oss/tests/pytest/acceptance/sessions/test_records_ingest_contract.py b/api/oss/tests/pytest/acceptance/sessions/test_records_ingest_contract.py new file mode 100644 index 0000000000..603ecdd099 --- /dev/null +++ b/api/oss/tests/pytest/acceptance/sessions/test_records_ingest_contract.py @@ -0,0 +1,75 @@ +"""Acceptance test for the record-ingest contract on POST /sessions/records/ingest. + +Pins the regression from debug/qa-sessions-rebase/FINDING-record-ingest-422.md: the runner +posts a 16-hex OTel span_id, and the API had typed span_id as UUID (32 hex) — so every +ingest 422'd and session records silently dropped. This drives the endpoint with the body the +runner actually sends and asserts it is accepted (200) and lands as a queryable record whose +span_id round-trips. + +Requires a live stack (AGENTA_API_URL/AGENTA_AUTH_KEY) with the record worker running — see +the pytest `acceptance` marker. +""" + +import time +import uuid + + +class TestRecordIngestContract: + """POST /sessions/records/ingest accepts the runner-shaped body (16-hex span_id) and the + record becomes queryable with the span id intact.""" + + def test_runner_shaped_ingest_is_accepted_and_persists(self, authed_api): + session_id = str(uuid.uuid4()) + # Exactly the shape services/runner/src/sessions/persist.ts posts: a 16-hex OTel span + # id (NOT a UUID), a turn_id, a per-turn record_index, and the event as attributes. + span_id = uuid.uuid4().hex[:16] + turn_id = f"turn-{uuid.uuid4().hex[:8]}" + + ingest = authed_api( + "POST", + "/sessions/records/ingest", + json={ + "session_id": session_id, + "record_index": 0, + "timestamp": "2026-07-18T00:00:00.000Z", + "record_source": "agent", + "record_type": "message", + "attributes": {"type": "message", "text": "hello from the runner"}, + "turn_id": turn_id, + "span_id": span_id, + }, + ) + assert ingest.status_code == 200, ingest.text + assert ingest.json() == {"ok": True} + + # The record flows through Redis -> worker -> DB; poll the query endpoint until it lands. + record = None + deadline = time.time() + 20 + while time.time() < deadline: + query = authed_api( + "POST", "/sessions/records/query", json={"session_id": session_id} + ) + assert query.status_code == 200, query.text + records = query.json().get("records", []) + if records: + record = records[0] + break + time.sleep(1) + + assert record is not None, "ingested record never became queryable" + assert record["turn_id"] == turn_id + assert record["span_id"] == span_id + + def test_a_uuid_span_id_is_rejected(self, authed_api): + """A 32-hex UUID is not a span id; the endpoint must reject it (422), which is exactly + the validation that a 16-hex span id must pass.""" + response = authed_api( + "POST", + "/sessions/records/ingest", + json={ + "session_id": str(uuid.uuid4()), + "record_source": "agent", + "span_id": uuid.uuid4().hex, # 32 hex — a UUID, not a span id + }, + ) + assert response.status_code == 422, response.text diff --git a/api/oss/tests/pytest/acceptance/sessions/test_stream_header_basics.py b/api/oss/tests/pytest/acceptance/sessions/test_stream_header_basics.py new file mode 100644 index 0000000000..ce1edf4763 --- /dev/null +++ b/api/oss/tests/pytest/acceptance/sessions/test_stream_header_basics.py @@ -0,0 +1,106 @@ +"""Acceptance tests for the session header surface on /sessions/streams/ (OSS edition). + +GET /sessions/streams/ reads the merged stream row {name, description, flags, ...}; +PUT/POST /sessions/streams/header is the rename edit, a full-PUT of {name, description}. +""" + +import uuid + + +class TestSessionStreamHeaderBasics: + """GET /sessions/streams/ + PUT/POST /sessions/streams/header — happy paths.""" + + def test_get_missing_stream_returns_empty(self, authed_api): + session_id = str(uuid.uuid4()) + response = authed_api( + "GET", "/sessions/streams/", params={"session_id": session_id} + ) + assert response.status_code == 200 + body = response.json() + assert body.get("stream") is None + + def test_put_renames_and_creates_row(self, authed_api): + session_id = str(uuid.uuid4()) + payload = {"name": "My Session", "description": "A test session."} + + response = authed_api( + "PUT", + "/sessions/streams/header", + params={"session_id": session_id}, + json=payload, + ) + assert response.status_code == 200 + stream = response.json()["stream"] + assert stream["session_id"] == session_id + assert stream["name"] == "My Session" + assert stream["description"] == "A test session." + + def test_get_returns_persisted_rename(self, authed_api): + session_id = str(uuid.uuid4()) + authed_api( + "PUT", + "/sessions/streams/header", + params={"session_id": session_id}, + json={"name": "Persisted Name"}, + ) + + response = authed_api( + "GET", "/sessions/streams/", params={"session_id": session_id} + ) + assert response.status_code == 200 + stream = response.json()["stream"] + assert stream["session_id"] == session_id + assert stream["name"] == "Persisted Name" + + def test_put_upserts_on_second_call(self, authed_api): + session_id = str(uuid.uuid4()) + + authed_api( + "PUT", + "/sessions/streams/header", + params={"session_id": session_id}, + json={"name": "First"}, + ) + + response = authed_api( + "PUT", + "/sessions/streams/header", + params={"session_id": session_id}, + json={"name": "Second", "description": "updated"}, + ) + assert response.status_code == 200 + stream = response.json()["stream"] + assert stream["name"] == "Second" + assert stream["description"] == "updated" + + get_resp = authed_api( + "GET", "/sessions/streams/", params={"session_id": session_id} + ) + assert get_resp.json()["stream"]["name"] == "Second" + + def test_post_also_accepts_the_rename_edit(self, authed_api): + session_id = str(uuid.uuid4()) + response = authed_api( + "POST", + "/sessions/streams/header", + params={"session_id": session_id}, + json={"name": "Via POST"}, + ) + assert response.status_code == 200 + stream = response.json()["stream"] + assert stream["session_id"] == session_id + assert stream["name"] == "Via POST" + + def test_invalid_session_id_rejected(self, authed_api): + # slashes are not allowed (SessionIdInvalid -> 422 via the domain exception handler) + response = authed_api( + "GET", "/sessions/streams/", params={"session_id": "foo/bar"} + ) + assert response.status_code == 422 + + def test_invalid_session_id_chars_rejected(self, authed_api): + # spaces are not allowed (SessionIdInvalid -> 422 via the domain exception handler) + response = authed_api( + "GET", "/sessions/streams/", params={"session_id": "foo bar"} + ) + assert response.status_code == 422 diff --git a/api/oss/tests/pytest/acceptance/sessions/test_stream_header_roundtrip.py b/api/oss/tests/pytest/acceptance/sessions/test_stream_header_roundtrip.py new file mode 100644 index 0000000000..cdf5a4e00e --- /dev/null +++ b/api/oss/tests/pytest/acceptance/sessions/test_stream_header_roundtrip.py @@ -0,0 +1,75 @@ +"""Acceptance tests for the S1/S8 stream header edit on /sessions/streams/header +and /sessions/mounts/sign (OSS edition). + +PUT /sessions/streams/header is the rename edit -- a full-PUT of {name, description} +-- and GET /sessions/streams/ reads it back. + +Requires a live stack (AGENTA_API_URL/AGENTA_AUTH_KEY) -- see the pytest.ini +`acceptance` marker. +""" + +import uuid + + +class TestSessionStreamHeaderRoundtrip: + """GET /sessions/streams/ + PUT /sessions/streams/header round-trip {name, + description}; POST /sessions/mounts/sign accepts name=claude-projects.""" + + def test_rename_persists_and_round_trips_via_get(self, authed_api): + session_id = str(uuid.uuid4()) + + put_response = authed_api( + "PUT", + "/sessions/streams/header", + params={"session_id": session_id}, + json={"name": "Renamed Session", "description": "roundtrip check"}, + ) + assert put_response.status_code == 200 + put_stream = put_response.json()["stream"] + assert put_stream["name"] == "Renamed Session" + assert put_stream["description"] == "roundtrip check" + + get_response = authed_api( + "GET", "/sessions/streams/", params={"session_id": session_id} + ) + assert get_response.status_code == 200 + get_stream = get_response.json()["stream"] + assert get_stream["name"] == "Renamed Session" + assert get_stream["description"] == "roundtrip check" + + def test_rename_is_a_full_put_partial_fields_preserved(self, authed_api): + # A second PUT that sends only `name` must not clear `description` -- + # exclude_unset means an omitted field is untouched, not nulled. + session_id = str(uuid.uuid4()) + authed_api( + "PUT", + "/sessions/streams/header", + params={"session_id": session_id}, + json={"name": "First", "description": "keep me"}, + ) + + response = authed_api( + "PUT", + "/sessions/streams/header", + params={"session_id": session_id}, + json={"name": "Second"}, + ) + assert response.status_code == 200 + stream = response.json()["stream"] + assert stream["name"] == "Second" + assert stream["description"] == "keep me" + + def test_mounts_sign_accepts_claude_projects_name(self, authed_api): + session_id = str(uuid.uuid4()) + response = authed_api( + "POST", + "/sessions/mounts/sign", + params={"session_id": session_id, "name": "claude-projects"}, + ) + # 200 with usable credentials, or 503 if the store is not configured in this + # environment -- either is a legitimate outcome; the endpoint must not 4xx/500 on a + # valid `name` value. + assert response.status_code in (200, 503) + if response.status_code == 200: + body = response.json() + assert body["mount"]["name"] == "claude-projects" diff --git a/api/oss/tests/pytest/unit/mounts/test_agent_id_backfill.py b/api/oss/tests/pytest/unit/mounts/test_agent_id_backfill.py new file mode 100644 index 0000000000..e5c987a1cf --- /dev/null +++ b/api/oss/tests/pytest/unit/mounts/test_agent_id_backfill.py @@ -0,0 +1,59 @@ +"""Backfill parser for the `agent_id` migration (oss000000014_add_mounts_agent_id). + +The migration's backfill is inline SQL (`split_part(substr(slug, N), '__', 1)`), +matching the sibling core_oss data migrations (oss000000010, oss000000011), +which are also plain SQL with no dedicated unit test. This file pins the +parsing invariant in Python so any drift in the slug format (verified against +`mint_agent_slug`, `core/mounts/service.py`) is caught without a live DB: the +SQL and `mint_agent_slug`/`mint_agent_id` must derive the identical id from +the identical slug. The SQL itself was validated against a real Postgres +instance (ephemeral, local) during implementation. +""" + +from uuid import uuid4 + +from oss.src.core.mounts.service import ( + mint_agent_id, + mint_agent_slug, + mint_session_slug, +) + +_AGENT_SLUG_PREFIX = "__ag__agent__" + + +def _parse_agent_id_like_the_migration_sql(slug: str): + """Python mirror of the migration's `split_part(substr(slug, N), '__', 1)`.""" + if not slug.startswith(_AGENT_SLUG_PREFIX): + return None + rest = slug[len(_AGENT_SLUG_PREFIX) :] + return rest.split("__", 1)[0] + + +def test_parser_recovers_canonical_artifact_id_from_agent_slug(): + artifact_id = "A0B1C2D3-E4F5-4678-9ABC-DEF012345678" + slug = mint_agent_slug(artifact_id=artifact_id, name="My Files") + + parsed = _parse_agent_id_like_the_migration_sql(slug) + + assert parsed == mint_agent_id(artifact_id=artifact_id) + assert parsed == "a0b1c2d3-e4f5-4678-9abc-def012345678" + + +def test_parser_recovers_id_regardless_of_mount_name_length(): + artifact_id = str(uuid4()) + for name in ("default", "notes", "a-much-longer-mount-name-here"): + slug = mint_agent_slug(artifact_id=artifact_id, name=name) + assert _parse_agent_id_like_the_migration_sql(slug) == artifact_id + + +def test_parser_returns_none_for_session_mount_slug(): + """Session-mount rows must stay agent_id-null: the session segment is a + uuid5 hash, not the raw id, and the prefix itself does not match.""" + slug = mint_session_slug(session_id="sess-1", name="cwd") + + assert _parse_agent_id_like_the_migration_sql(slug) is None + + +def test_parser_returns_none_for_unreserved_slug(): + assert _parse_agent_id_like_the_migration_sql("datasets") is None + assert _parse_agent_id_like_the_migration_sql("__ag__unrelated__x") is None diff --git a/api/oss/tests/pytest/unit/mounts/test_agent_mounts.py b/api/oss/tests/pytest/unit/mounts/test_agent_mounts.py index 2a10041d64..dec278f996 100644 --- a/api/oss/tests/pytest/unit/mounts/test_agent_mounts.py +++ b/api/oss/tests/pytest/unit/mounts/test_agent_mounts.py @@ -3,8 +3,8 @@ import pytest -from oss.src.core.mounts.dtos import Mount, MountCreate -from oss.src.core.mounts.service import MountsService, mint_agent_slug +from oss.src.core.mounts.dtos import Mount, MountCreate, MountQuery +from oss.src.core.mounts.service import MountsService, mint_agent_id, mint_agent_slug from oss.src.core.mounts.types import ( MountArtifactIdInvalid, MountArtifactNotFound, @@ -47,6 +47,23 @@ async def archive_mount(self, *, project_id, user_id, mount_id): return archived return None + async def query_mounts(self, *, project_id, mount_query=None, windowing=None): + results = [ + mount + for (mount_project_id, _), mount in self.mounts.items() + if mount_project_id == project_id + ] + if mount_query: + if not mount_query.include_archived: + results = [m for m in results if m.deleted_at is None] + if mount_query.session_id is not None: + results = [m for m in results if m.session_id == mount_query.session_id] + if mount_query.agent_id is not None: + results = [m for m in results if m.agent_id == mount_query.agent_id] + else: + results = [m for m in results if m.deleted_at is None] + return results + @staticmethod def _mount(project_id, mount_create): return Mount( @@ -55,6 +72,7 @@ def _mount(project_id, mount_create): slug=mount_create.slug, name=mount_create.name, session_id=mount_create.session_id, + agent_id=mount_create.agent_id, ) @@ -90,6 +108,7 @@ async def test_agent_mount_upsert_is_idempotent(mount_context): ) assert first.id == second.id assert first.session_id is None + assert first.agent_id == mint_agent_id(artifact_id=artifact_id) assert dao.upsert_calls == 2 assert len(dao.mounts) == 1 @@ -254,3 +273,62 @@ async def test_sign_accepts_static_catalog_artifact_without_db_lookup(): assert mount is not None assert workflows.fetch_calls == 0 + + +# --- agent_id column (session_id/agent_id symmetry, WP6) -------------------- # + + +@pytest.mark.asyncio +async def test_new_agent_mount_has_agent_id_matching_slug_segment(mount_context): + """The populate site (get_or_create_agent_mount): agent_id is the same + canonical id minted into the slug's `__ag__agent____...` segment.""" + service, _, project_id, user_id = mount_context + artifact_id = "A0B1C2D3-E4F5-4678-9ABC-DEF012345678" + + mount = await service.get_or_create_agent_mount( + project_id=project_id, user_id=user_id, artifact_id=artifact_id + ) + + slug_id_segment = mount.slug.removeprefix("__ag__agent__").split("__", 1)[0] + assert mount.agent_id == slug_id_segment + assert mount.agent_id == "a0b1c2d3-e4f5-4678-9abc-def012345678" + + +@pytest.mark.asyncio +async def test_session_mount_leaves_agent_id_null(mount_context): + """Session mounts stay agent_id-null — only agent mounts populate it.""" + service, _, project_id, user_id = mount_context + + mount = await service.get_or_create_session_mount( + project_id=project_id, user_id=user_id, session_id="sess-1" + ) + + assert mount.session_id is not None + assert mount.agent_id is None + + +@pytest.mark.asyncio +async def test_query_mounts_filters_by_agent_id(mount_context): + """The mount query DTO/filter mirrors session_id: query mounts for agent X.""" + service, dao, project_id, user_id = mount_context + artifact_a = str(uuid4()) + artifact_b = str(uuid4()) + + mount_a = await service.get_or_create_agent_mount( + project_id=project_id, user_id=user_id, artifact_id=artifact_a + ) + await service.get_or_create_agent_mount( + project_id=project_id, user_id=user_id, artifact_id=artifact_b + ) + await service.get_or_create_session_mount( + project_id=project_id, user_id=user_id, session_id="sess-1" + ) + + results = await service.query_mounts( + project_id=project_id, + mount_query=MountQuery(agent_id=mint_agent_id(artifact_id=artifact_a)), + ) + + assert len(results) == 1 + assert results[0].id == mount_a.id + assert results[0].agent_id == mint_agent_id(artifact_id=artifact_a) diff --git a/api/oss/tests/pytest/unit/otlp/test_logfire_adapter.py b/api/oss/tests/pytest/unit/otlp/test_logfire_adapter.py index 304e5888eb..90ac86a5c8 100644 --- a/api/oss/tests/pytest/unit/otlp/test_logfire_adapter.py +++ b/api/oss/tests/pytest/unit/otlp/test_logfire_adapter.py @@ -715,6 +715,13 @@ def test_provider_name_mapped(self, adapter): assert features.meta["provider.name"] == "openai" + def test_agent_id_mapped_to_agent_feature(self, adapter): + bag = _make_bag({"gen_ai.agent.id": "agent-42"}) + features = SpanFeatures() + adapter.process(bag, features) + + assert features.agent["id"] == "agent-42" + # ── Edge Cases ────────────────────────────────────────────────────── diff --git a/api/oss/tests/pytest/unit/session_states/__init__.py b/api/oss/tests/pytest/unit/session_states/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/api/oss/tests/pytest/unit/session_states/test_harness_sessions_mapping.py b/api/oss/tests/pytest/unit/session_states/test_harness_sessions_mapping.py deleted file mode 100644 index 16e4681683..0000000000 --- a/api/oss/tests/pytest/unit/session_states/test_harness_sessions_mapping.py +++ /dev/null @@ -1,127 +0,0 @@ -"""Unit tests for the S2/S2b durable continuity state on session_states. - -The continuity fields live inside the existing `data` JSON column (no dedicated columns), -typed as `SessionStateData`. Verifies: - - dbe_to_dto hydrates the raw `data` dict into a SessionStateData with a - Dict[str, HarnessSessionRecord]; - - a DBE with no `data` maps to None, not an empty model; - - SessionStateUpsert carries `data` through model_dump(exclude_unset=True) - (mirrors test_records_mapping_upsert.py's style: no DB, no live stack). -""" - -from uuid import UUID - -from oss.src.core.sessions.states.dtos import ( - HarnessSessionRecord, - SessionStateData, - SessionStateUpsert, -) -from oss.src.dbs.postgres.sessions.states.dbes import SessionStateDBE -from oss.src.dbs.postgres.sessions.states.mappings import dbe_to_dto - - -_PROJECT_ID = UUID("00000000-0000-0000-0000-0000000000aa") - - -def _dbe(**over): - base = dict( - project_id=_PROJECT_ID, - session_id="sess-1", - data=None, - sandbox_id=None, - flags=None, - tags=None, - meta=None, - ) - base.update(over) - return SessionStateDBE(**base) - - -def test_dbe_to_dto_hydrates_data_continuity_state(): - dbe = _dbe( - data={ - "latest_agent_session_id": "agent-claude-2", - "harness_sessions": { - "claude": { - "agent_session_id": "agent-claude-2", - "turn_index": 2, - }, - "pi": { - "agent_session_id": "agent-pi-1", - "turn_index": 1, - }, - }, - "latest_turn_index": 2, - }, - ) - - dto = dbe_to_dto(dbe) - - assert dto.data is not None - assert dto.data.latest_agent_session_id == "agent-claude-2" - assert dto.data.latest_turn_index == 2 - assert dto.data.harness_sessions is not None - assert set(dto.data.harness_sessions.keys()) == {"claude", "pi"} - assert isinstance(dto.data.harness_sessions["claude"], HarnessSessionRecord) - assert dto.data.harness_sessions["claude"].agent_session_id == "agent-claude-2" - assert dto.data.harness_sessions["claude"].turn_index == 2 - assert dto.data.harness_sessions["pi"].agent_session_id == "agent-pi-1" - assert dto.data.harness_sessions["pi"].turn_index == 1 - - -def test_dbe_to_dto_no_data_maps_to_none(): - dbe = _dbe() - - dto = dbe_to_dto(dbe) - - assert dto.data is None - - -def test_dbe_to_dto_empty_data_column_maps_to_none(): - # JSON(none_as_null=True): an empty {} column value is falsy, so the mapping's - # `if dbe.data else None` guard must resolve to None, not an empty model. - dbe = _dbe(data={}) - - dto = dbe_to_dto(dbe) - - assert dto.data is None - - -def test_session_state_upsert_carries_data_through_exclude_unset(): - upsert = SessionStateUpsert( - data=SessionStateData( - latest_agent_session_id="agent-claude-3", - harness_sessions={ - "claude": HarnessSessionRecord( - agent_session_id="agent-claude-3", turn_index=3 - ), - }, - latest_turn_index=3, - ), - ) - - dumped = upsert.model_dump(exclude_unset=True) - - assert dumped == { - "data": { - "latest_agent_session_id": "agent-claude-3", - "harness_sessions": { - "claude": { - "agent_session_id": "agent-claude-3", - "turn_index": 3, - }, - }, - "latest_turn_index": 3, - }, - } - - -def test_session_state_upsert_unset_data_is_excluded(): - # A caller that only updates sandbox_id must not accidentally clear `data`: - # exclude_unset=True means an untouched field is simply absent from the dump, not null. - upsert = SessionStateUpsert(sandbox_id="sbx-1") - - dumped = upsert.model_dump(exclude_unset=True) - - assert dumped == {"sandbox_id": "sbx-1"} - assert "data" not in dumped diff --git a/api/oss/tests/pytest/unit/session_states/test_session_id_validation.py b/api/oss/tests/pytest/unit/session_states/test_session_id_validation.py deleted file mode 100644 index 84cbb794c8..0000000000 --- a/api/oss/tests/pytest/unit/session_states/test_session_id_validation.py +++ /dev/null @@ -1,44 +0,0 @@ -"""Unit tests for session_id shape validation.""" - -import pytest -from fastapi import HTTPException - -from oss.src.apis.fastapi.sessions.router import ( - _validate_session_id_http as _validate_session_id, -) - - -class TestSessionIdValidation: - def test_valid_uuid(self): - _validate_session_id("550e8400-e29b-41d4-a716-446655440000") - - def test_valid_slug(self): - _validate_session_id("my-session_01") - - def test_dotted_rejected(self): - with pytest.raises(HTTPException) as exc_info: - _validate_session_id("project.session.123") - assert exc_info.value.status_code == 400 - - def test_empty_rejected(self): - with pytest.raises(HTTPException) as exc_info: - _validate_session_id("") - assert exc_info.value.status_code == 400 - - def test_slash_rejected(self): - with pytest.raises(HTTPException) as exc_info: - _validate_session_id("foo/bar") - assert exc_info.value.status_code == 400 - - def test_space_rejected(self): - with pytest.raises(HTTPException) as exc_info: - _validate_session_id("foo bar") - assert exc_info.value.status_code == 400 - - def test_too_long_rejected(self): - with pytest.raises(HTTPException) as exc_info: - _validate_session_id("a" * 129) - assert exc_info.value.status_code == 400 - - def test_max_length_accepted(self): - _validate_session_id("a" * 128) diff --git a/api/oss/tests/pytest/unit/sessions/conftest.py b/api/oss/tests/pytest/unit/sessions/conftest.py new file mode 100644 index 0000000000..6f4a48387e --- /dev/null +++ b/api/oss/tests/pytest/unit/sessions/conftest.py @@ -0,0 +1,32 @@ +import socket +from functools import lru_cache +from urllib.parse import urlparse + +import pytest + +from oss.src.utils.env import env + + +@lru_cache(maxsize=1) +def _postgres_reachable() -> bool: + """TCP-probe the configured core Postgres once per session. + + The integration DAO tests here need a real Postgres. The default URI points + at the Docker-network host `postgres:5432`, which resolves in-compose/CI but + not on a bare host (`load-env` leaves it commented). Probe rather than error + so a native `py-run-tests --api` skips these instead of failing setup. + """ + parsed = urlparse(env.postgres.uri_core) + host = parsed.hostname or "postgres" + port = parsed.port or 5432 + try: + with socket.create_connection((host, port), timeout=0.5): + return True + except OSError: + return False + + +@pytest.fixture(autouse=True) +def _skip_when_postgres_unreachable(request): + if request.node.get_closest_marker("integration") and not _postgres_reachable(): + pytest.skip("Postgres not reachable — skipping session DAO integration tests") diff --git a/api/oss/tests/pytest/unit/sessions/test_command_matrix_inputs_data.py b/api/oss/tests/pytest/unit/sessions/test_command_matrix_inputs_data.py new file mode 100644 index 0000000000..d260c85e83 --- /dev/null +++ b/api/oss/tests/pytest/unit/sessions/test_command_matrix_inputs_data.py @@ -0,0 +1,240 @@ +"""WP7 (W7.1/E6): the command discriminator is `data.inputs` present-or-not x `force`, +not `prompt`. + +Pins the matrix in `SessionStreamsService.command()` against the renamed +`SessionStreamCommandRequest.data` field (a `WorkflowServiceRequestData`, so the shape +aligns with `WorkflowInvokeRequest.data.inputs` rather than a bespoke string): + + data.inputs present + no force -> send (409 if alive) + data.inputs present + force -> steer (cancel holder, start new) + no data.inputs + no force -> cancel (cancel holder, run nothing) + no data.inputs + force -> attach (steal attached, watch) + +Also covers: an empty-dict `inputs` (falsy) is treated as "no inputs", same as a missing +`data`, matching the old `prompt` field's "blank string == no prompt" rule. +""" + +from typing import Optional +from unittest.mock import patch +from uuid import UUID, uuid4 + +import pytest +import pytest_asyncio + +from agenta.sdk.models.workflows import WorkflowServiceRequestData + +from oss.src.core.sessions.streams.dtos import ( + CommandMode, + SessionStream, + SessionStreamCommandRequest, +) +from oss.src.core.sessions.streams.service import SessionStreamsService +from oss.src.core.sessions.streams.types import SessionTurnInUse + +from unit.sessions.test_project_scoped_locks import _FakeRedis + + +_PROJECT = uuid4() +_USER = uuid4() + + +class _FakeStreamsDAO: + def __init__(self, existing: Optional[SessionStream] = None): + self.row = existing + + async def get_by_session_id(self, *, project_id: UUID, session_id: str): + return self.row + + async def create(self, *, project_id, user_id, stream): + self.row = SessionStream( + id=uuid4(), + project_id=project_id, + session_id=stream.session_id, + flags=stream.flags, + turn_id=stream.turn_id, + ) + return self.row + + async def update(self, *, project_id, user_id, session_id, stream): + prior = self.row + self.row = SessionStream( + id=prior.id if prior else uuid4(), + project_id=project_id, + session_id=session_id, + flags=stream.flags + if stream.flags is not None + else (prior.flags if prior else None), + turn_id=stream.turn_id + if stream.turn_id is not None + else (prior.turn_id if prior else None), + ) + return self.row + + async def delete_by_session_id(self, *, project_id, session_id): + return True + + +@pytest_asyncio.fixture +async def lock_engine(): + from oss.src.dbs.redis.shared.engine import LockEngine + + eng = LockEngine() + with patch.object(eng, "_client", return_value=_FakeRedis()): + yield eng + + +def _service(lock_engine, dao=None): + return SessionStreamsService( + streams_dao=dao or _FakeStreamsDAO(), lock_engine=lock_engine + ) + + +def _session_id() -> str: + return f"session_{uuid4().hex[:12]}" + + +@pytest.mark.asyncio +async def test_inputs_present_no_force_is_send(lock_engine): + svc = _service(lock_engine) + session_id = _session_id() + + result = await svc.command( + project_id=_PROJECT, + user_id=_USER, + request=SessionStreamCommandRequest( + session_id=session_id, + data=WorkflowServiceRequestData(inputs={"messages": ["hi"]}), + force=False, + ), + ) + + assert result.mode == CommandMode.send + assert result.turn_id is not None + + +@pytest.mark.asyncio +async def test_inputs_present_and_force_is_steer(lock_engine): + svc = _service(lock_engine) + session_id = _session_id() + + # Establish a holder first (a send), then steer over it. + await svc.command( + project_id=_PROJECT, + user_id=_USER, + request=SessionStreamCommandRequest( + session_id=session_id, + data=WorkflowServiceRequestData(inputs={"messages": ["first"]}), + force=False, + ), + ) + + result = await svc.command( + project_id=_PROJECT, + user_id=_USER, + request=SessionStreamCommandRequest( + session_id=session_id, + data=WorkflowServiceRequestData(inputs={"messages": ["steer"]}), + force=True, + ), + ) + + assert result.mode == CommandMode.steer + assert result.turn_id is not None + + +@pytest.mark.asyncio +async def test_no_inputs_no_force_is_cancel(lock_engine): + svc = _service(lock_engine) + session_id = _session_id() + + result = await svc.command( + project_id=_PROJECT, + user_id=_USER, + request=SessionStreamCommandRequest( + session_id=session_id, data=None, force=False + ), + ) + + assert result.mode == CommandMode.cancel + + +@pytest.mark.asyncio +async def test_no_inputs_and_force_is_attach(lock_engine): + svc = _service(lock_engine) + session_id = _session_id() + + result = await svc.command( + project_id=_PROJECT, + user_id=_USER, + request=SessionStreamCommandRequest( + session_id=session_id, data=None, force=True + ), + ) + + assert result.mode == CommandMode.attach + assert result.watcher_id is not None + + +@pytest.mark.asyncio +async def test_empty_inputs_dict_counts_as_no_inputs(lock_engine): + """An empty `inputs={}` is falsy, matching the old prompt field's blank-string rule.""" + svc = _service(lock_engine) + session_id = _session_id() + + result = await svc.command( + project_id=_PROJECT, + user_id=_USER, + request=SessionStreamCommandRequest( + session_id=session_id, + data=WorkflowServiceRequestData(inputs={}), + force=False, + ), + ) + + assert result.mode == CommandMode.cancel + + +@pytest.mark.asyncio +async def test_data_present_but_inputs_none_counts_as_no_inputs(lock_engine): + """`data` set but `data.inputs` unset (None) is still "no inputs".""" + svc = _service(lock_engine) + session_id = _session_id() + + result = await svc.command( + project_id=_PROJECT, + user_id=_USER, + request=SessionStreamCommandRequest( + session_id=session_id, + data=WorkflowServiceRequestData(), + force=True, + ), + ) + + assert result.mode == CommandMode.attach + + +@pytest.mark.asyncio +async def test_send_with_inputs_409s_when_already_alive(lock_engine): + svc = _service(lock_engine) + session_id = _session_id() + + await svc.command( + project_id=_PROJECT, + user_id=_USER, + request=SessionStreamCommandRequest( + session_id=session_id, + data=WorkflowServiceRequestData(inputs={"messages": ["first"]}), + force=False, + ), + ) + + with pytest.raises(SessionTurnInUse): + await svc.command( + project_id=_PROJECT, + user_id=_USER, + request=SessionStreamCommandRequest( + session_id=session_id, + data=WorkflowServiceRequestData(inputs={"messages": ["second"]}), + force=False, + ), + ) diff --git a/api/oss/tests/pytest/unit/sessions/test_heartbeat_is_current_turn.py b/api/oss/tests/pytest/unit/sessions/test_heartbeat_is_current_turn.py new file mode 100644 index 0000000000..4f72336691 --- /dev/null +++ b/api/oss/tests/pytest/unit/sessions/test_heartbeat_is_current_turn.py @@ -0,0 +1,177 @@ +"""WP7 (W7.4): the control signal from cancel/steer/kill must reach the runner's heartbeat. + +Before this, `heartbeat()`'s acquire-then-refresh fallback silently re-acquired a lost alive +lock under the SAME turn_id (nx=True is a no-op only when the key is gone) — a cancel/steer/ +kill that raced a heartbeat was invisible to the runner: the beat still looked like a normal +`ok` heartbeat. `is_current_turn` on `SessionHeartbeatResult` surfaces the interruption so the +runner's watchdog can abort the in-flight run (`services/runner/src/sessions/alive.ts`'s +`onInterrupted`). + +Covers: + - a normal heartbeat sequence (no interruption) reports is_current_turn=True throughout; + - a cancel between two heartbeats of the SAME turn_id flips the next beat's + is_current_turn to False (the lock was gone, then silently re-acquired); + - a steer (different turn_id takes the lock) also reports the OLD turn's next beat as + is_current_turn=False, and does not steal the lock back for the old turn; + - a replica that lost the owner claim entirely reports is_current_turn=False. +""" + +from typing import Optional +from unittest.mock import patch +from uuid import UUID, uuid4 + +import pytest +import pytest_asyncio + +from oss.src.core.sessions.streams.dtos import ( + SessionHeartbeatRequest, + SessionStream, +) +from oss.src.core.sessions.streams.service import SessionStreamsService +from oss.src.dbs.redis.sessions.locks import force_cancel_alive, get_alive_owner + +from unit.sessions.test_project_scoped_locks import _FakeRedis + + +_PROJECT = uuid4() +_SESSION = "session_interrupt" + + +class _FakeStreamsDAO: + def __init__(self, existing: Optional[SessionStream] = None): + self.row = existing + + async def get_by_session_id(self, *, project_id: UUID, session_id: str): + return self.row + + async def create(self, *, project_id, user_id, stream): + self.row = SessionStream( + id=uuid4(), + project_id=project_id, + session_id=stream.session_id, + flags=stream.flags, + turn_id=stream.turn_id, + ) + return self.row + + async def update(self, *, project_id, user_id, session_id, stream): + prior = self.row + self.row = SessionStream( + id=prior.id if prior else uuid4(), + project_id=project_id, + session_id=session_id, + flags=stream.flags + if stream.flags is not None + else (prior.flags if prior else None), + turn_id=stream.turn_id + if stream.turn_id is not None + else (prior.turn_id if prior else None), + ) + return self.row + + async def delete_by_session_id(self, *, project_id, session_id): + return True + + +@pytest_asyncio.fixture +async def lock_engine(): + from oss.src.dbs.redis.shared.engine import LockEngine + + eng = LockEngine() + with patch.object(eng, "_client", return_value=_FakeRedis()): + yield eng + + +def _service(lock_engine, dao=None): + return SessionStreamsService( + streams_dao=dao or _FakeStreamsDAO(), lock_engine=lock_engine + ) + + +def _beat(replica: str, turn: str, running: bool = True) -> SessionHeartbeatRequest: + return SessionHeartbeatRequest( + session_id=_SESSION, replica_id=replica, turn_id=turn, is_running=running + ) + + +@pytest.mark.asyncio +async def test_uninterrupted_heartbeats_stay_current(lock_engine): + svc = _service(lock_engine) + + r1 = await svc.heartbeat(project_id=_PROJECT, request=_beat("replica-a", "turn-1")) + r2 = await svc.heartbeat(project_id=_PROJECT, request=_beat("replica-a", "turn-1")) + + assert r1.is_current_turn is True + assert r2.is_current_turn is True + + +@pytest.mark.asyncio +async def test_cancel_between_beats_flips_next_beat_to_not_current(lock_engine): + svc = _service(lock_engine) + + first = await svc.heartbeat( + project_id=_PROJECT, request=_beat("replica-a", "turn-1") + ) + assert first.is_current_turn is True + + # A cancel/kill force-clears the alive lock out from under the still-running turn. + await force_cancel_alive(lock_engine, project_id=str(_PROJECT), session_id=_SESSION) + + second = await svc.heartbeat( + project_id=_PROJECT, request=_beat("replica-a", "turn-1") + ) + + assert second.is_current_turn is False, ( + "a beat after the lock was force-cancelled must report the interruption, even " + "though the nx=True re-acquire silently re-establishes the SAME lock" + ) + # The re-acquire still happens (the session stays alive/reattachable) — only the + # bookkeeping bit changes. + assert ( + await get_alive_owner( + lock_engine, project_id=str(_PROJECT), session_id=_SESSION + ) + == "turn-1" + ) + + +@pytest.mark.asyncio +async def test_steer_flips_the_old_turns_next_beat_to_not_current(lock_engine): + svc = _service(lock_engine) + + await svc.heartbeat(project_id=_PROJECT, request=_beat("replica-a", "turn-1")) + + # Simulate a steer: a new turn steals the alive/running locks (force_cancel + a fresh + # acquire under turn-2), mirroring what command()'s steer branch does. + await force_cancel_alive(lock_engine, project_id=str(_PROJECT), session_id=_SESSION) + await svc.heartbeat(project_id=_PROJECT, request=_beat("replica-a", "turn-2")) + + # The OLD turn's own heartbeat (still in flight on the runner) must see the takeover. + old_turn_beat = await svc.heartbeat( + project_id=_PROJECT, request=_beat("replica-a", "turn-1") + ) + + assert old_turn_beat.is_current_turn is False + # And it must NOT have stolen the lock back for turn-1 (nx=True fails because turn-2 + # already holds it) — the new turn keeps the lock. + assert ( + await get_alive_owner( + lock_engine, project_id=str(_PROJECT), session_id=_SESSION + ) + == "turn-2" + ) + + +@pytest.mark.asyncio +async def test_losing_owner_claim_reports_not_current(lock_engine): + svc = _service(lock_engine) + + await svc.heartbeat(project_id=_PROJECT, request=_beat("replica-a", "turn-1")) + + # A second replica heartbeats the same session; claim_owner never steals, so it loses. + result = await svc.heartbeat( + project_id=_PROJECT, request=_beat("replica-b", "turn-2") + ) + + assert result.is_current_turn is False + assert result.replica_id == "replica-a" diff --git a/api/oss/tests/pytest/unit/sessions/test_kill_runner_teardown.py b/api/oss/tests/pytest/unit/sessions/test_kill_runner_teardown.py new file mode 100644 index 0000000000..aaf495dc6a --- /dev/null +++ b/api/oss/tests/pytest/unit/sessions/test_kill_runner_teardown.py @@ -0,0 +1,122 @@ +"""WP7 (W7.3): kill must reach the runner's sandbox teardown, not just the Redis/row edit. + +Before this, `SessionStreamsService.kill()` only force-cleared the Redis nest and soft-deleted +the stream row — nothing called the runner's `POST /kill` (`services/runner/src/server.ts`), +so a live sandbox kept running until its own idle-TTL eviction. `kill_runner_sandbox` +(core/sessions/streams/runner_client.py) closes that gap; these tests pin: + - kill() calls it exactly once, scoped to (project_id, session_id); + - a runner-client failure never blocks kill's Redis/row edit (best-effort); + - kill remains idempotent when the runner call fails. +""" + +from typing import Optional +from unittest.mock import patch +from uuid import UUID, uuid4 + +import pytest +import pytest_asyncio + +from oss.src.core.sessions.streams.dtos import SessionStream +from oss.src.core.sessions.streams.service import SessionStreamsService + +from unit.sessions.test_project_scoped_locks import _FakeRedis + + +_PROJECT = uuid4() +_USER = uuid4() +_SESSION = "session_kill_teardown" + + +class _FakeStreamsDAO: + def __init__(self, existing: Optional[SessionStream] = None): + self.row = existing + self.deleted = 0 + + async def get_by_session_id(self, *, project_id: UUID, session_id: str): + return self.row + + async def create(self, *, project_id, user_id, stream): + self.row = SessionStream( + id=uuid4(), + project_id=project_id, + session_id=stream.session_id, + flags=stream.flags, + turn_id=stream.turn_id, + ) + return self.row + + async def update(self, *, project_id, user_id, session_id, stream): + prior = self.row + self.row = SessionStream( + id=prior.id if prior else uuid4(), + project_id=project_id, + session_id=session_id, + flags=stream.flags + if stream.flags is not None + else (prior.flags if prior else None), + ) + return self.row + + async def delete_by_session_id(self, *, project_id, session_id): + self.deleted += 1 + return True + + +@pytest_asyncio.fixture +async def lock_engine(): + from oss.src.dbs.redis.shared.engine import LockEngine + + eng = LockEngine() + with patch.object(eng, "_client", return_value=_FakeRedis()): + yield eng + + +@pytest.mark.asyncio +async def test_kill_calls_runner_client_scoped_to_project_and_session(lock_engine): + dao = _FakeStreamsDAO() + svc = SessionStreamsService(streams_dao=dao, lock_engine=lock_engine) + + with patch( + "oss.src.core.sessions.streams.service.kill_runner_sandbox" + ) as mock_kill: + mock_kill.return_value = True + ok = await svc.kill(project_id=_PROJECT, user_id=_USER, session_id=_SESSION) + + assert ok is True + mock_kill.assert_awaited_once_with(project_id=str(_PROJECT), session_id=_SESSION) + assert dao.deleted == 1 + + +@pytest.mark.asyncio +async def test_kill_still_succeeds_when_runner_call_fails(lock_engine): + """Best-effort: the runner being unreachable must not block the Redis/row edit.""" + dao = _FakeStreamsDAO() + svc = SessionStreamsService(streams_dao=dao, lock_engine=lock_engine) + + with patch( + "oss.src.core.sessions.streams.service.kill_runner_sandbox" + ) as mock_kill: + mock_kill.return_value = False + ok = await svc.kill(project_id=_PROJECT, user_id=_USER, session_id=_SESSION) + + assert ok is True, ( + "kill's Redis/row edit must succeed regardless of runner reachability" + ) + assert dao.deleted == 1 + + +@pytest.mark.asyncio +async def test_kill_is_idempotent_across_repeated_calls(lock_engine): + dao = _FakeStreamsDAO() + svc = SessionStreamsService(streams_dao=dao, lock_engine=lock_engine) + + with patch( + "oss.src.core.sessions.streams.service.kill_runner_sandbox" + ) as mock_kill: + mock_kill.return_value = True + first = await svc.kill(project_id=_PROJECT, user_id=_USER, session_id=_SESSION) + second = await svc.kill(project_id=_PROJECT, user_id=_USER, session_id=_SESSION) + + assert first is True + assert second is True + assert mock_kill.await_count == 2 diff --git a/api/oss/tests/pytest/unit/sessions/test_mounts_agent_id_backfill.py b/api/oss/tests/pytest/unit/sessions/test_mounts_agent_id_backfill.py new file mode 100644 index 0000000000..bcf6c4d5e6 --- /dev/null +++ b/api/oss/tests/pytest/unit/sessions/test_mounts_agent_id_backfill.py @@ -0,0 +1,69 @@ +"""Migration test for the mounts.agent_id backfill (oss000000016_add_mounts_agent_id). + +The migration recovers agent_id straight out of an agent-mount slug +(`__ag__agent____`) with +`split_part(substr(slug, 14), '__', 1)`, guarded by +`left(slug, 13) = '__ag__agent__'`. Session-mount slugs +(`__ag__session____`) hash the session id, so they must stay agent_id-null. + +This runs the migration's exact SQL expression against Postgres (no table/FK needed — the +extraction is over literal slugs) so a drift in the expression fails here instead of +silently mis-backfilling. Mirrors TEST-GAPS.md §"mounts.agent_id backfill". +""" + +import uuid + +import pytest +from sqlalchemy import text + +import oss.src.dbs.postgres.shared.engine as engine_module +from oss.src.dbs.postgres.shared.engine import get_transactions_engine + + +pytestmark = pytest.mark.integration + +# Verbatim from the migration: `left(slug, 13) = '__ag__agent__'` guards, and +# `split_part(substr(slug, 14), '__', 1)` extracts. +_BACKFILL_EXPR = ( + "CASE WHEN left(:slug, 13) = '__ag__agent__' " + "THEN split_part(substr(:slug, 14), '__', 1) ELSE NULL END" +) + + +@pytest.fixture(autouse=True) +async def _fresh_engine_per_test(): + engine_module._transactions_engine = None + yield + if engine_module._transactions_engine is not None: + await engine_module._transactions_engine.close() + engine_module._transactions_engine = None + + +async def _extract(slug: str): + engine = get_transactions_engine() + async with engine.session() as session: + result = await session.execute( + text(f"SELECT {_BACKFILL_EXPR} AS agent_id"), {"slug": slug} + ) + return result.scalar_one() + + +async def test_agent_slug_backfills_the_canonical_artifact_id(): + artifact_id = str(uuid.uuid4()) + slug = f"__ag__agent__{artifact_id}__my-agent" + + assert await _extract(slug) == artifact_id + + +async def test_agent_slug_with_underscores_in_name_keeps_only_the_id(): + artifact_id = str(uuid.uuid4()) + # The name segment may itself contain the `__` separator; only the first field is the id. + slug = f"__ag__agent__{artifact_id}__my__weird__name" + + assert await _extract(slug) == artifact_id + + +async def test_session_mount_slug_stays_agent_id_null(): + slug = f"__ag__session__{uuid.uuid4().hex}__claude-projects" + + assert await _extract(slug) is None diff --git a/api/oss/tests/pytest/unit/sessions/test_record_ingest_endpoint.py b/api/oss/tests/pytest/unit/sessions/test_record_ingest_endpoint.py index 10b98ffc46..2ee452d4b4 100644 --- a/api/oss/tests/pytest/unit/sessions/test_record_ingest_endpoint.py +++ b/api/oss/tests/pytest/unit/sessions/test_record_ingest_endpoint.py @@ -100,3 +100,99 @@ async def test_record_ingest_rejects_without_permission(): await router.ingest_record_event(request=request, body=body) assert exc_info.value.status_code == 403 + + +async def test_record_ingest_threads_turn_id_and_span_id(): + records_service = AsyncMock() + router = RecordsRouter(records_service=records_service) + + project_id = uuid4() + user_id = uuid4() + organization_id = uuid4() + session_id = uuid4() + # The runner posts a 16-hex OTel span id, NOT a UUID (regression: it was typed as UUID + # and every ingest 422'd — see FINDING-record-ingest-422.md). + span_id = uuid4().hex[:16] + + body = SessionRecordIngestRequest( + session_id=str(session_id), + record_index=0, + record_source="agent", + attributes={"type": "message"}, + turn_id="turn-abc", + span_id=span_id, + ) + + app = FastAPI() + request = _make_authed_request(app, project_id, user_id, organization_id) + + with ( + patch( + "oss.src.apis.fastapi.sessions.router.check_action_access", + new_callable=AsyncMock, + return_value=True, + ), + patch( + "oss.src.apis.fastapi.sessions.router.publish_record", + new_callable=AsyncMock, + return_value=True, + ) as mock_publish, + ): + await router.ingest_record_event(request=request, body=body) + + event = mock_publish.await_args.kwargs["record_event"] + assert event.turn_id == "turn-abc" + assert event.span_id == span_id + + +async def test_record_ingest_defaults_turn_id_and_span_id_to_none(): + records_service = AsyncMock() + router = RecordsRouter(records_service=records_service) + + project_id = uuid4() + user_id = uuid4() + organization_id = uuid4() + session_id = uuid4() + + body = SessionRecordIngestRequest(session_id=str(session_id)) + + app = FastAPI() + request = _make_authed_request(app, project_id, user_id, organization_id) + + with ( + patch( + "oss.src.apis.fastapi.sessions.router.check_action_access", + new_callable=AsyncMock, + return_value=True, + ), + patch( + "oss.src.apis.fastapi.sessions.router.publish_record", + new_callable=AsyncMock, + return_value=True, + ) as mock_publish, + ): + await router.ingest_record_event(request=request, body=body) + + event = mock_publish.await_args.kwargs["record_event"] + assert event.turn_id is None + assert event.span_id is None + + +def test_ingest_model_accepts_otel_span_id_and_rejects_uuid(): + """Contract pin for the 422 regression: the ingest model accepts the 16-hex OTel span id + the runner actually sends, and rejects a 32-hex UUID (which is what the field used to be + typed as — that mistyping made pydantic reject every real ingest with a 422).""" + from pydantic import ValidationError + + session_id = str(uuid4()) + + # The real runner shape: a 16-hex span id validates. + body = SessionRecordIngestRequest( + session_id=session_id, + span_id="a1b2c3d4e5f6a7b8", + ) + assert body.span_id == "a1b2c3d4e5f6a7b8" + + # A 32-hex UUID string is NOT a span id and must be rejected. + with pytest.raises(ValidationError): + SessionRecordIngestRequest(session_id=session_id, span_id=uuid4().hex) diff --git a/api/oss/tests/pytest/unit/sessions/test_records_mapping_upsert.py b/api/oss/tests/pytest/unit/sessions/test_records_mapping_upsert.py index 7e00300e75..775d146fce 100644 --- a/api/oss/tests/pytest/unit/sessions/test_records_mapping_upsert.py +++ b/api/oss/tests/pytest/unit/sessions/test_records_mapping_upsert.py @@ -44,6 +44,20 @@ def test_absent_record_id_falls_back_to_uuid4(): assert dbe.record_id.version == 4 +def test_turn_id_and_span_id_map_through_to_the_dbe(): + # A 16-hex OTel span id (what the runner sends), NOT a UUID. + span_id = uuid5(_RECORDS_NS, "span").hex[:16] + dbe = map_record_event_to_dbe(event=_event(turn_id="turn-1", span_id=span_id)) + assert dbe.turn_id == "turn-1" + assert dbe.span_id == span_id + + +def test_turn_id_and_span_id_default_to_none(): + dbe = map_record_event_to_dbe(event=_event()) + assert dbe.turn_id is None + assert dbe.span_id is None + + class _FakeResult: def scalars(self): class _S: @@ -117,6 +131,9 @@ async def _cm(): # payload columns are overwritten; the ordinal is not in the update set. assert "attributes" in compiled assert "set record_index" not in compiled + # turn_id/span_id ride the same upsert as the other payload columns. + assert "turn_id" in compiled + assert "span_id" in compiled def test_append_commits_when_it_opens_its_own_session(): diff --git a/api/oss/tests/pytest/unit/sessions/test_records_turn_span_dao.py b/api/oss/tests/pytest/unit/sessions/test_records_turn_span_dao.py new file mode 100644 index 0000000000..54e6113e52 --- /dev/null +++ b/api/oss/tests/pytest/unit/sessions/test_records_turn_span_dao.py @@ -0,0 +1,194 @@ +"""Integration-style tests for records turn_id/span_id tagging against a real Postgres. + +Requires the tracing_oss migration chain applied (through oss000000003_add_records_turn_span) +and POSTGRES_URI_TRACING pointed at that database. Records have no FK to any other table +(cross-DB, plain columns), so no fixture chain is needed beyond project_id/session_id. + +Verifies: + - a new record carries turn_id (and span_id when supplied); + - records group by turn_id (client-side grouping over get_records, since there is no + server-side aggregate endpoint); + - span_id is populated when present on the event, null otherwise; + - old records (turn_id/span_id both null, as if written before this column existed) + are still readable under the session, alongside newer tagged ones. +""" + +import uuid +from datetime import datetime, timezone + +import pytest + +from oss.src.core.sessions.records.dtos import SessionRecordEvent +from oss.src.dbs.postgres.sessions.records.dao import RecordsDAO +import oss.src.dbs.postgres.shared.engine as engine_module +from oss.src.dbs.postgres.shared.engine import get_analytics_engine + + +pytestmark = pytest.mark.integration + + +@pytest.fixture(autouse=True) +async def _fresh_engine_per_test(): + """Each pytest-asyncio test gets its own event loop; the module-level engine + singleton binds its asyncpg pool to the first loop that touches it. Reset it + so every test starts with a pool bound to its own loop.""" + engine_module._analytics_engine = None + yield + if engine_module._analytics_engine is not None: + await engine_module._analytics_engine.close() + engine_module._analytics_engine = None + + +def _ids(): + return uuid.uuid4(), f"records-turn-span-test-{uuid.uuid4().hex[:8]}" + + +async def test_new_record_carries_turn_id_and_span_id(): + project_id, session_id = _ids() + turn_id = f"turn-{uuid.uuid4().hex[:8]}" + # 16-hex OTel span id (runner shape), NOT a UUID. + span_id = uuid.uuid4().hex[:16] + dao = RecordsDAO(engine=get_analytics_engine()) + + record = await dao.append( + event=SessionRecordEvent( + project_id=project_id, + session_id=session_id, + record_index=0, + record_type="message", + record_source="agent", + attributes={"text": "hello"}, + turn_id=turn_id, + span_id=span_id, + ) + ) + + assert record is not None + assert record.turn_id == turn_id + assert record.span_id == span_id + + fetched = await dao.get_records(project_id=project_id, session_id=session_id) + assert len(fetched) == 1 + assert fetched[0].turn_id == turn_id + assert fetched[0].span_id == span_id + + +async def test_span_id_null_when_not_supplied(): + project_id, session_id = _ids() + turn_id = f"turn-{uuid.uuid4().hex[:8]}" + dao = RecordsDAO(engine=get_analytics_engine()) + + record = await dao.append( + event=SessionRecordEvent( + project_id=project_id, + session_id=session_id, + record_index=0, + record_type="message", + record_source="agent", + attributes={"text": "no span here"}, + turn_id=turn_id, + ) + ) + + assert record is not None + assert record.turn_id == turn_id + assert record.span_id is None + + +async def test_records_group_by_turn_id(): + """No server-side aggregate endpoint (E1) — group-by-turn is client-side grouping + over get_records, which returns turn_id on every row for exactly this purpose.""" + project_id, session_id = _ids() + turn_a = f"turn-a-{uuid.uuid4().hex[:8]}" + turn_b = f"turn-b-{uuid.uuid4().hex[:8]}" + dao = RecordsDAO(engine=get_analytics_engine()) + + events = [ + SessionRecordEvent( + project_id=project_id, + session_id=session_id, + record_index=0, + record_type="message", + record_source="user", + attributes={"text": "turn a msg 1"}, + turn_id=turn_a, + ), + SessionRecordEvent( + project_id=project_id, + session_id=session_id, + record_index=1, + record_type="message", + record_source="agent", + attributes={"text": "turn a msg 2"}, + turn_id=turn_a, + ), + SessionRecordEvent( + project_id=project_id, + session_id=session_id, + record_index=0, + record_type="message", + record_source="user", + attributes={"text": "turn b msg 1"}, + turn_id=turn_b, + ), + ] + await dao.append_many(events=events) + + fetched = await dao.get_records(project_id=project_id, session_id=session_id) + assert len(fetched) == 3 + + grouped: dict = {} + for r in fetched: + grouped.setdefault(r.turn_id, []).append(r) + + assert set(grouped.keys()) == {turn_a, turn_b} + assert len(grouped[turn_a]) == 2 + assert len(grouped[turn_b]) == 1 + + +async def test_old_records_with_null_turn_id_still_readable_under_session(): + """Forward-fill only (tracing-DB rule, no backfill): a record written without + turn_id/span_id (simulating a pre-WP4 row) stays null and is still readable + alongside newer tagged records under the same session.""" + project_id, session_id = _ids() + turn_id = f"turn-{uuid.uuid4().hex[:8]}" + dao = RecordsDAO(engine=get_analytics_engine()) + + # "Old" record: no turn_id/span_id at all, as pre-WP4 producers would have sent. + old_record = await dao.append( + event=SessionRecordEvent( + project_id=project_id, + session_id=session_id, + record_index=0, + timestamp=datetime(2026, 1, 1, tzinfo=timezone.utc), + record_type="message", + record_source="user", + attributes={"text": "pre-existing record"}, + ) + ) + # "New" record: carries both. + new_record = await dao.append( + event=SessionRecordEvent( + project_id=project_id, + session_id=session_id, + record_index=1, + record_type="message", + record_source="agent", + attributes={"text": "post-WP4 record"}, + turn_id=turn_id, + span_id=uuid.uuid4().hex[:16], + ) + ) + + assert old_record.turn_id is None + assert old_record.span_id is None + assert new_record.turn_id == turn_id + + fetched = await dao.get_records(project_id=project_id, session_id=session_id) + ids = {r.record_id for r in fetched} + assert old_record.record_id in ids + assert new_record.record_id in ids + + by_id = {r.record_id: r for r in fetched} + assert by_id[old_record.record_id].turn_id is None + assert by_id[new_record.record_id].turn_id == turn_id diff --git a/api/oss/tests/pytest/unit/sessions/test_runner_client_kill.py b/api/oss/tests/pytest/unit/sessions/test_runner_client_kill.py new file mode 100644 index 0000000000..1966fdbeff --- /dev/null +++ b/api/oss/tests/pytest/unit/sessions/test_runner_client_kill.py @@ -0,0 +1,122 @@ +"""WP7 (W7.3): `kill_runner_sandbox`'s own contract — the direct API -> runner `/kill` hop. + +Covers: not configured -> no-op False; success -> True with the right URL/body/headers; +non-2xx -> False; a transport exception -> False (never raises). +""" + +from unittest.mock import patch + +import httpx +import pytest + +from oss.src.core.sessions.streams.runner_client import kill_runner_sandbox + + +class _FakeRunnerEnv: + def __init__(self, internal_url=None, token=None): + self.internal_url = internal_url + self.token = token + + +@pytest.mark.asyncio +async def test_returns_false_when_not_configured(): + with patch("oss.src.core.sessions.streams.runner_client.env") as mock_env: + mock_env.runner = _FakeRunnerEnv(internal_url=None, token=None) + result = await kill_runner_sandbox(project_id="proj-1", session_id="sess-1") + + assert result is False + + +@pytest.mark.asyncio +async def test_posts_to_kill_with_scoped_body_and_bearer_header(): + captured = {} + + class _FakeResponse: + status_code = 200 + + class _FakeClient: + async def __aenter__(self): + return self + + async def __aexit__(self, *exc): + return False + + async def post(self, url, json=None, headers=None): + captured["url"] = url + captured["json"] = json + captured["headers"] = headers + return _FakeResponse() + + with ( + patch("oss.src.core.sessions.streams.runner_client.env") as mock_env, + patch( + "oss.src.core.sessions.streams.runner_client.httpx.AsyncClient", + return_value=_FakeClient(), + ), + ): + mock_env.runner = _FakeRunnerEnv( + internal_url="http://runner:8765", token="shared-secret" + ) + result = await kill_runner_sandbox(project_id="proj-1", session_id="sess-1") + + assert result is True + assert captured["url"] == "http://runner:8765/kill" + assert captured["json"] == {"sessionId": "sess-1", "projectId": "proj-1"} + assert captured["headers"]["Authorization"] == "Bearer shared-secret" + + +@pytest.mark.asyncio +async def test_returns_false_on_non_2xx(): + class _FakeResponse: + status_code = 500 + + class _FakeClient: + async def __aenter__(self): + return self + + async def __aexit__(self, *exc): + return False + + async def post(self, *a, **kw): + return _FakeResponse() + + with ( + patch("oss.src.core.sessions.streams.runner_client.env") as mock_env, + patch( + "oss.src.core.sessions.streams.runner_client.httpx.AsyncClient", + return_value=_FakeClient(), + ), + ): + mock_env.runner = _FakeRunnerEnv( + internal_url="http://runner:8765", token="shared-secret" + ) + result = await kill_runner_sandbox(project_id="proj-1", session_id="sess-1") + + assert result is False + + +@pytest.mark.asyncio +async def test_returns_false_on_transport_error_never_raises(): + class _FakeClient: + async def __aenter__(self): + return self + + async def __aexit__(self, *exc): + return False + + async def post(self, *a, **kw): + raise httpx.ConnectError("connection refused") + + with ( + patch("oss.src.core.sessions.streams.runner_client.env") as mock_env, + patch( + "oss.src.core.sessions.streams.runner_client.httpx.AsyncClient", + return_value=_FakeClient(), + ), + ): + mock_env.runner = _FakeRunnerEnv( + internal_url="http://runner:8765", token="shared-secret" + ) + result = await kill_runner_sandbox(project_id="proj-1", session_id="sess-1") + + assert result is False diff --git a/api/oss/tests/pytest/unit/sessions/test_sessions_root_service.py b/api/oss/tests/pytest/unit/sessions/test_sessions_root_service.py new file mode 100644 index 0000000000..9bb561536d --- /dev/null +++ b/api/oss/tests/pytest/unit/sessions/test_sessions_root_service.py @@ -0,0 +1,332 @@ +"""WP5 (S7/F1/F2/B3): SessionsService — root /sessions operations. + +Unit-level: fakes each sub-service (streams/turns/interactions/mounts) so the +orchestration itself is under test, not the DAOs (those have their own +DAO-level integration coverage, e.g. test_turns_dao.py). Covers: + + - delete_session: exact fan-out order and calls (turns, interactions, mounts, + then the hard stream delete) — records are never touched. + - archive_session / unarchive_session: soft fan-out including bound mounts, + round-trips deleted_at. + - query_sessions: reference filter joins turns -> session_ids, then filters + the stream query; windowed pass-through; no-match short-circuits to []. + - fan-out keys off session_id, never stream_id (asserted via the fakes' + captured call kwargs). +""" + +from typing import List, Optional +from uuid import uuid4 + +import pytest + +from oss.src.core.sessions.dtos import SessionQuery +from oss.src.core.sessions.service import SessionsService +from oss.src.core.sessions.streams.dtos import SessionStream +from oss.src.core.sessions.turns.dtos import HarnessKind, SessionTurn +from oss.src.core.shared.dtos import Reference, Windowing + + +_PROJECT = uuid4() +_USER = uuid4() +_SESSION = "session-wp5-root" + + +def _stream(session_id: str = _SESSION, deleted: bool = False) -> SessionStream: + return SessionStream( + id=uuid4(), + project_id=_PROJECT, + session_id=session_id, + deleted_at="2026-01-01T00:00:00Z" if deleted else None, + ) + + +def _turn(session_id: str, references: Optional[List[Reference]] = None) -> SessionTurn: + return SessionTurn( + id=uuid4(), + project_id=_PROJECT, + session_id=session_id, + stream_id=uuid4(), + turn_index=0, + harness_kind=HarnessKind.PI, + references=references, + ) + + +class _FakeStreamsService: + def __init__(self, row: Optional[SessionStream] = None): + self.row = row + self.hard_delete_calls: list[dict] = [] + self.archive_calls: list[dict] = [] + self.unarchive_calls: list[dict] = [] + self.query_calls: list[dict] = [] + + async def query_streams( + self, *, project_id, filter, windowing=None, session_ids=None + ): + self.query_calls.append( + { + "project_id": project_id, + "session_ids": session_ids, + "windowing": windowing, + } + ) + if session_ids is not None: + return [ + s + for s in ([self.row] if self.row else []) + if s.session_id in session_ids + ] + return [self.row] if self.row else [] + + async def hard_delete(self, *, project_id, session_id): + self.hard_delete_calls.append( + {"project_id": project_id, "session_id": session_id} + ) + return True + + async def archive(self, *, project_id, session_id): + self.archive_calls.append({"project_id": project_id, "session_id": session_id}) + if self.row is not None: + self.row = self.row.model_copy( + update={"deleted_at": "2026-01-01T00:00:00Z"} + ) + return self.row + + async def unarchive(self, *, project_id, user_id, session_id): + self.unarchive_calls.append( + {"project_id": project_id, "user_id": user_id, "session_id": session_id} + ) + if self.row is not None: + self.row = self.row.model_copy(update={"deleted_at": None}) + return self.row + + +class _FakeTurnsService: + def __init__(self, turns: Optional[List[SessionTurn]] = None): + self.turns = turns or [] + self.query_calls: list[dict] = [] + self.delete_calls: list[dict] = [] + + async def query_turns(self, *, project_id, query=None, windowing=None): + self.query_calls.append({"project_id": project_id, "query": query}) + if query and query.references: + wanted = {r.id for r in query.references} + return [ + t + for t in self.turns + if t.references and any(r.id in wanted for r in t.references) + ] + return self.turns + + async def delete_by_session_id(self, *, project_id, session_id): + self.delete_calls.append({"project_id": project_id, "session_id": session_id}) + return len(self.turns) + + +class _FakeInteractionsService: + def __init__(self): + self.delete_calls: list[dict] = [] + + async def delete_by_session_id(self, *, project_id, session_id): + self.delete_calls.append({"project_id": project_id, "session_id": session_id}) + return 1 + + +class _FakeMountsService: + def __init__(self): + self.delete_calls: list[dict] = [] + self.archive_calls: list[dict] = [] + self.unarchive_calls: list[dict] = [] + + async def delete_session_mounts(self, *, project_id, session_id): + self.delete_calls.append({"project_id": project_id, "session_id": session_id}) + return [] + + async def archive_session_mounts(self, *, project_id, user_id, session_id): + self.archive_calls.append( + {"project_id": project_id, "user_id": user_id, "session_id": session_id} + ) + return [] + + async def unarchive_session_mounts(self, *, project_id, user_id, session_id): + self.unarchive_calls.append( + {"project_id": project_id, "user_id": user_id, "session_id": session_id} + ) + return [] + + +def _service( + *, + stream: Optional[SessionStream] = None, + turns: Optional[List[SessionTurn]] = None, +): + streams = _FakeStreamsService(row=stream) + turns_svc = _FakeTurnsService(turns=turns) + interactions = _FakeInteractionsService() + mounts = _FakeMountsService() + svc = SessionsService( + streams_service=streams, + turns_service=turns_svc, + interactions_service=interactions, + mounts_service=mounts, + ) + return svc, streams, turns_svc, interactions, mounts + + +# --------------------------------------------------------------------------- +# delete_session — F1 fan-out +# --------------------------------------------------------------------------- + + +@pytest.mark.asyncio +async def test_delete_session_fans_out_to_turns_interactions_mounts_and_stream(): + svc, streams, turns_svc, interactions, mounts = _service() + + await svc.delete_session(project_id=_PROJECT, user_id=_USER, session_id=_SESSION) + + assert turns_svc.delete_calls == [{"project_id": _PROJECT, "session_id": _SESSION}] + assert interactions.delete_calls == [ + {"project_id": _PROJECT, "session_id": _SESSION} + ] + assert mounts.delete_calls == [{"project_id": _PROJECT, "session_id": _SESSION}] + assert streams.hard_delete_calls == [ + {"project_id": _PROJECT, "session_id": _SESSION} + ] + + +@pytest.mark.asyncio +async def test_delete_session_never_touches_records(): + # No records service is even injected into SessionsService -- the fan-out + # has no path to a records call. This test pins that absence structurally: + # SessionsService's constructor accepts no records_service. + import inspect + + params = inspect.signature(SessionsService.__init__).parameters + assert "records_service" not in params + + +@pytest.mark.asyncio +async def test_delete_session_keys_off_session_id_not_stream_id(): + svc, streams, turns_svc, interactions, mounts = _service() + + await svc.delete_session(project_id=_PROJECT, user_id=_USER, session_id=_SESSION) + + for call in ( + turns_svc.delete_calls[0], + interactions.delete_calls[0], + mounts.delete_calls[0], + streams.hard_delete_calls[0], + ): + assert call["session_id"] == _SESSION + assert "stream_id" not in call + + +# --------------------------------------------------------------------------- +# archive_session / unarchive_session — F2 fan-out +# --------------------------------------------------------------------------- + + +@pytest.mark.asyncio +async def test_archive_session_soft_archives_stream_and_mounts(): + stream = _stream() + svc, streams, _, _, mounts = _service(stream=stream) + + result = await svc.archive_session( + project_id=_PROJECT, user_id=_USER, session_id=_SESSION + ) + + assert result is not None + assert result.deleted_at is not None + assert mounts.archive_calls == [ + {"project_id": _PROJECT, "user_id": _USER, "session_id": _SESSION} + ] + assert streams.archive_calls == [{"project_id": _PROJECT, "session_id": _SESSION}] + + +@pytest.mark.asyncio +async def test_unarchive_session_reverses_archive_round_trip(): + stream = _stream() + svc, streams, _, _, mounts = _service(stream=stream) + + archived = await svc.archive_session( + project_id=_PROJECT, user_id=_USER, session_id=_SESSION + ) + assert archived.deleted_at is not None + + unarchived = await svc.unarchive_session( + project_id=_PROJECT, user_id=_USER, session_id=_SESSION + ) + + assert unarchived is not None + assert unarchived.deleted_at is None + assert mounts.unarchive_calls == [ + {"project_id": _PROJECT, "user_id": _USER, "session_id": _SESSION} + ] + assert streams.unarchive_calls == [ + {"project_id": _PROJECT, "user_id": _USER, "session_id": _SESSION} + ] + + +# --------------------------------------------------------------------------- +# query_sessions — B3 reference join, windowed +# --------------------------------------------------------------------------- + + +@pytest.mark.asyncio +async def test_query_sessions_no_filter_returns_all_streams(): + stream = _stream() + svc, streams, _, _, _ = _service(stream=stream) + + result = await svc.query_sessions(project_id=_PROJECT) + + assert result == [stream] + assert streams.query_calls[0]["session_ids"] is None + + +@pytest.mark.asyncio +async def test_query_sessions_filters_by_references_via_turns_join(): + stream = _stream() + target_ref = Reference(id=uuid4(), slug="target-workflow", version="v1") + other_ref = Reference(id=uuid4(), slug="other-workflow", version="v1") + turns = [ + _turn(_SESSION, references=[target_ref]), + _turn("some-other-session", references=[other_ref]), + ] + svc, streams, turns_svc, _, _ = _service(stream=stream, turns=turns) + + result = await svc.query_sessions( + project_id=_PROJECT, + query=SessionQuery(references=[target_ref]), + ) + + assert len(result) == 1 + assert result[0].session_id == _SESSION + # the join resolved to session_ids, not references, at the stream query boundary + assert streams.query_calls[0]["session_ids"] == [_SESSION] + + +@pytest.mark.asyncio +async def test_query_sessions_no_matching_reference_short_circuits_empty(): + stream = _stream() + unmatched_ref = Reference(id=uuid4(), slug="nope", version="v1") + svc, streams, turns_svc, _, _ = _service(stream=stream, turns=[]) + + result = await svc.query_sessions( + project_id=_PROJECT, + query=SessionQuery(references=[unmatched_ref]), + ) + + assert result == [] + # never even reaches the stream query -- no session_ids to filter by + assert streams.query_calls == [] + + +@pytest.mark.asyncio +async def test_query_sessions_passes_windowing_through(): + stream = _stream() + svc, streams, _, _, _ = _service(stream=stream) + windowing = Windowing(order="descending", limit=10) + + await svc.query_sessions(project_id=_PROJECT, windowing=windowing) + + assert streams.query_calls[0]["windowing"] is windowing diff --git a/api/oss/tests/pytest/unit/sessions/test_stream_header_merge.py b/api/oss/tests/pytest/unit/sessions/test_stream_header_merge.py new file mode 100644 index 0000000000..cb1e46b1c2 --- /dev/null +++ b/api/oss/tests/pytest/unit/sessions/test_stream_header_merge.py @@ -0,0 +1,383 @@ +"""WP2 (S1/S8): the session header (name/description) lives on the stream row. + +Covers: + - the header edit (rename) persists through the DAO mapping and round-trips + via the DBE->DTO mapping (no live DB — mirrors test_records_mapping_upsert.py's + style); + - a heartbeat / flag-mirror write (`SessionStreamEdit`, used by heartbeat/ + detach/kill/_start_turn/_mirror_flags) never carries name/description, so it + cannot clobber a prior rename — the stated guard against write amplification; + - flags still mirror the Redis nest through a heartbeat, unaffected by the + header columns being present on the same row. +""" + +from typing import Optional +from unittest.mock import patch +from uuid import UUID, uuid4 + +import pytest +import pytest_asyncio + +from oss.src.core.sessions.streams.dtos import ( + SessionHeartbeatRequest, + SessionStream, + SessionStreamEdit, + SessionStreamHeaderEdit, +) +from oss.src.core.sessions.streams.service import SessionStreamsService +from oss.src.dbs.postgres.sessions.streams.dbes import SessionStreamDBE +from oss.src.dbs.postgres.sessions.streams.mappings import ( + map_stream_dbe_to_dto, + map_stream_dto_to_dbe_header_edit, +) + +from unit.sessions.test_project_scoped_locks import _FakeRedis + + +_PROJECT = uuid4() +_SESSION = "session_header_merge" + + +def _dbe(**over) -> SessionStreamDBE: + base = dict( + id=uuid4(), + project_id=_PROJECT, + session_id=_SESSION, + name=None, + description=None, + flags=None, + tags=None, + meta=None, + turn_id=None, + ) + base.update(over) + return SessionStreamDBE(**base) + + +# --------------------------------------------------------------------------- +# Mapping-level: the header edit only ever touches name/description. +# --------------------------------------------------------------------------- + + +def test_header_edit_sets_name_and_description(): + dbe = _dbe() + + map_stream_dto_to_dbe_header_edit( + stream_dbe=dbe, + user_id=None, + header=SessionStreamHeaderEdit(name="My Session", description="a desc"), + ) + + assert dbe.name == "My Session" + assert dbe.description == "a desc" + + +def test_header_edit_partial_field_preserves_the_other(): + # A rename PUT that sends only `name` must not null `description` (full-PUT + # semantics here means "PUT the header fields you own", not "PUT everything or + # get nulled" -- exclude_unset upstream keeps the unsent field out of the DTO). + dbe = _dbe(name="Old Name", description="Old description") + + map_stream_dto_to_dbe_header_edit( + stream_dbe=dbe, + user_id=None, + header=SessionStreamHeaderEdit(name="New Name"), + ) + + assert dbe.name == "New Name" + assert dbe.description == "Old description" + + +def test_header_edit_does_not_touch_flags_or_turn_id(): + dbe = _dbe(flags={"is_alive": True, "is_running": True}, turn_id="turn-1") + + map_stream_dto_to_dbe_header_edit( + stream_dbe=dbe, + user_id=None, + header=SessionStreamHeaderEdit(name="Renamed"), + ) + + assert dbe.flags == {"is_alive": True, "is_running": True} + assert dbe.turn_id == "turn-1" + + +def test_dbe_to_dto_round_trips_header_fields(): + dbe = _dbe(name="Round Trip", description="round trip desc") + + dto = map_stream_dbe_to_dto(stream_dbe=dbe) + + assert dto.name == "Round Trip" + assert dto.description == "round trip desc" + + +def test_flags_only_edit_never_carries_header_fields(): + # SessionStreamEdit (the DTO every flag-mirror write uses) has name/description + # fields for symmetry, but a flag-only construction must leave them unset -- + # so a heartbeat that builds SessionStreamEdit(flags=...) alone cannot clobber + # a prior rename even if some future edit accidentally forwarded model_dump(). + edit = SessionStreamEdit(flags=None) + dumped = edit.model_dump(exclude_unset=True) + assert "name" not in dumped + assert "description" not in dumped + + +# --------------------------------------------------------------------------- +# Service-level: heartbeat/flag-mirror paths never clobber name/description; +# flags still mirror the nest. +# --------------------------------------------------------------------------- + + +class _FakeStreamsDAO: + """Records every write's fields so a test can assert what a heartbeat touches. + + Mimics the real DAO's create/update contract but keeps a single in-memory row, + including name/description, so a rename set once must survive later flag-only + writes coming from the service (heartbeat/detach/kill/_mirror_flags). + """ + + def __init__(self, existing: Optional[SessionStream] = None): + self.row = existing + self.creates = 0 + self.updates = 0 + self.header_updates = 0 + + async def get_by_session_id(self, *, project_id: UUID, session_id: str): + return self.row + + async def create(self, *, project_id, user_id, stream): + self.creates += 1 + kwargs = dict( + id=uuid4(), + project_id=project_id, + session_id=stream.session_id, + name=stream.name, + description=stream.description, + turn_id=stream.turn_id, + ) + if stream.flags is not None: + kwargs["flags"] = stream.flags + self.row = SessionStream(**kwargs) + return self.row + + async def update(self, *, project_id, user_id, session_id, stream): + self.updates += 1 + # Mirror the real DAO's partial-update semantics: only overwrite a field + # when the edit actually sets it. + prior = self.row + self.row = SessionStream( + id=prior.id if prior else uuid4(), + project_id=project_id, + session_id=session_id, + name=stream.name + if stream.name is not None + else (prior.name if prior else None), + description=( + stream.description + if stream.description is not None + else (prior.description if prior else None) + ), + flags=stream.flags + if stream.flags is not None + else (prior.flags if prior else None), + turn_id=stream.turn_id + if stream.turn_id is not None + else (prior.turn_id if prior else None), + ) + return self.row + + async def update_header(self, *, project_id, user_id, session_id, header): + # Mirrors the real DAO: a select-then-update against a missing row returns + # None rather than creating one — the service's set_header falls back to + # `create` for that case. + if self.row is None: + return None + self.header_updates += 1 + prior = self.row + self.row = SessionStream( + id=prior.id, + project_id=project_id, + session_id=session_id, + name=header.name if header.name is not None else prior.name, + description=( + header.description + if header.description is not None + else prior.description + ), + turn_id=prior.turn_id, + flags=prior.flags, + ) + return self.row + + async def delete_by_session_id(self, *, project_id, session_id): + return True + + +@pytest_asyncio.fixture +async def lock_engine(): + from oss.src.dbs.redis.shared.engine import LockEngine + + eng = LockEngine() + with patch.object(eng, "_client", return_value=_FakeRedis()): + yield eng + + +def _service(lock_engine, dao): + return SessionStreamsService(streams_dao=dao, lock_engine=lock_engine) + + +def _beat(replica: str, turn: str, running: bool = True) -> SessionHeartbeatRequest: + return SessionHeartbeatRequest( + session_id=_SESSION, replica_id=replica, turn_id=turn, is_running=running + ) + + +@pytest.mark.asyncio +async def test_rename_survives_a_heartbeat_flag_mirror(lock_engine): + dao = _FakeStreamsDAO() + svc = _service(lock_engine, dao) + + # Rename first (the header edit). + await svc.set_header( + project_id=_PROJECT, + user_id=None, + session_id=_SESSION, + header=SessionStreamHeaderEdit(name="Keep Me", description="stays put"), + ) + assert dao.row.name == "Keep Me" + + # A heartbeat comes in and mirrors flags/turn_id -- it must not touch the header. + result = await svc.heartbeat( + project_id=_PROJECT, request=_beat("replica-a", "turn-a") + ) + + assert result.stream.name == "Keep Me" + assert result.stream.description == "stays put" + assert dao.row.name == "Keep Me" + assert dao.row.description == "stays put" + + +@pytest.mark.asyncio +async def test_heartbeat_still_mirrors_the_nest_flags(lock_engine): + dao = _FakeStreamsDAO() + svc = _service(lock_engine, dao) + + result = await svc.heartbeat( + project_id=_PROJECT, request=_beat("replica-a", "turn-a") + ) + + assert result.stream.flags.is_alive is True + assert result.stream.flags.is_running is True + assert dao.creates == 1 + + +@pytest.mark.asyncio +async def test_detach_flag_mirror_does_not_touch_header(lock_engine): + dao = _FakeStreamsDAO() + svc = _service(lock_engine, dao) + + await svc.set_header( + project_id=_PROJECT, + user_id=None, + session_id=_SESSION, + header=SessionStreamHeaderEdit(name="Named Before Detach"), + ) + await svc.heartbeat(project_id=_PROJECT, request=_beat("replica-a", "turn-a")) + + await svc.detach( + project_id=_PROJECT, user_id=None, session_id=_SESSION, watcher_id="w-1" + ) + + assert dao.row.name == "Named Before Detach" + + +@pytest.mark.asyncio +async def test_kill_flag_mirror_does_not_touch_header(lock_engine): + dao = _FakeStreamsDAO() + svc = _service(lock_engine, dao) + + await svc.set_header( + project_id=_PROJECT, + user_id=None, + session_id=_SESSION, + header=SessionStreamHeaderEdit(name="Named Before Kill"), + ) + await svc.heartbeat(project_id=_PROJECT, request=_beat("replica-a", "turn-a")) + + await svc.kill(project_id=_PROJECT, user_id=None, session_id=_SESSION) + + assert dao.row.name == "Named Before Kill" + + +@pytest.mark.asyncio +async def test_set_header_never_calls_the_flags_update_path(lock_engine): + dao = _FakeStreamsDAO( + existing=SessionStream( + id=uuid4(), project_id=_PROJECT, session_id=_SESSION, name="X" + ) + ) + svc = _service(lock_engine, dao) + + await svc.set_header( + project_id=_PROJECT, + user_id=None, + session_id=_SESSION, + header=SessionStreamHeaderEdit(name="Y"), + ) + + assert dao.header_updates == 1 + assert dao.updates == 0 + assert dao.creates == 0 + + +@pytest.mark.asyncio +async def test_rename_before_any_turn_creates_the_row(lock_engine): + # A caller may name a session before it has ever heartbeat/run -- update_header + # finds no row (mirrors the real DAO's select-then-None), so set_header falls + # back to create. + dao = _FakeStreamsDAO() + svc = _service(lock_engine, dao) + + stream = await svc.set_header( + project_id=_PROJECT, + user_id=None, + session_id=_SESSION, + header=SessionStreamHeaderEdit(name="Named Pre-Turn"), + ) + + assert stream.name == "Named Pre-Turn" + assert dao.creates == 1 + assert dao.header_updates == 0 + + +@pytest.mark.asyncio +async def test_rename_race_falls_back_to_header_update_on_concurrent_create( + lock_engine, +): + # A concurrent first-touch (e.g. a heartbeat) can win the create between + # set_header's update_header miss and its own create call; the DAO surfaces + # that as SessionStreamAlreadyExists, and set_header must retry as an update + # rather than raising. + from oss.src.core.sessions.streams.types import SessionStreamAlreadyExists + + class _RacyDAO(_FakeStreamsDAO): + async def create(self, *, project_id, user_id, stream): + self.row = SessionStream( + id=uuid4(), + project_id=project_id, + session_id=stream.session_id, + name=None, + ) + raise SessionStreamAlreadyExists(session_id=stream.session_id) + + dao = _RacyDAO() + svc = _service(lock_engine, dao) + + stream = await svc.set_header( + project_id=_PROJECT, + user_id=None, + session_id=_SESSION, + header=SessionStreamHeaderEdit(name="Won The Retry"), + ) + + assert stream.name == "Won The Retry" + assert dao.header_updates == 1 diff --git a/api/oss/tests/pytest/unit/sessions/test_turns_dao.py b/api/oss/tests/pytest/unit/sessions/test_turns_dao.py new file mode 100644 index 0000000000..aaae16f656 --- /dev/null +++ b/api/oss/tests/pytest/unit/sessions/test_turns_dao.py @@ -0,0 +1,430 @@ +"""Integration-style tests for SessionTurnsDAO against a real Postgres. + +Requires the core_oss migration chain applied and POSTGRES_URI_CORE pointed at that +database. Exercises: append, references GIN `.contains()` filtering, windowed +newest->oldest listing, latest_turn / latest_turn_per_harness resolution, and +hard-delete-by-session — the behaviors a mock cannot faithfully stand in for. +""" + +import uuid +from datetime import datetime, timezone + +import pytest +from sqlalchemy import text + +from oss.src.core.sessions.turns.dtos import ( + HarnessKind, + SessionTurnCreate, + SessionTurnQuery, +) +from oss.src.core.shared.dtos import Reference, Windowing +import oss.src.models.db_models # noqa: F401 +from oss.src.dbs.postgres.sessions.streams.dbes import SessionStreamDBE # noqa: F401 +from oss.src.dbs.postgres.sessions.turns.dao import SessionTurnsDAO +import oss.src.dbs.postgres.shared.engine as engine_module +from oss.src.dbs.postgres.shared.engine import get_transactions_engine + + +pytestmark = pytest.mark.integration + + +@pytest.fixture(autouse=True) +async def _fresh_engine_per_test(): + """Each pytest-asyncio test gets its own event loop; the module-level engine + singleton binds its asyncpg pool to the first loop that touches it. Reset it + so every test starts with a pool bound to its own loop.""" + engine_module._transactions_engine = None + yield + if engine_module._transactions_engine is not None: + await engine_module._transactions_engine.close() + engine_module._transactions_engine = None + + +@pytest.fixture +async def project_and_stream(): + """Provision the minimal FK chain: user -> org -> workspace -> project -> stream.""" + engine = get_transactions_engine() + + user_id = uuid.uuid4() + org_id = uuid.uuid4() + workspace_id = uuid.uuid4() + project_id = uuid.uuid4() + stream_id = uuid.uuid4() + session_id = f"turns-dao-test-{uuid.uuid4().hex[:8]}" + + async with engine.session() as session: + await session.execute( + text( + "INSERT INTO users (id, uid, username, email) " + "VALUES (:id, :uid, :username, :email)" + ), + { + "id": user_id, + "uid": str(user_id), + "username": "turns-dao-test", + "email": f"turns-dao-test-{user_id.hex[:8]}@example.com", + }, + ) + await session.execute( + text( + "INSERT INTO organizations (id, name, owner_id) " + "VALUES (:id, :name, :owner_id)" + ), + {"id": org_id, "name": "turns-dao-test-org", "owner_id": user_id}, + ) + await session.execute( + text( + "INSERT INTO workspaces (id, name, organization_id) " + "VALUES (:id, :name, :organization_id)" + ), + { + "id": workspace_id, + "name": "turns-dao-test-ws", + "organization_id": org_id, + }, + ) + await session.execute( + text( + "INSERT INTO projects (id, project_name, workspace_id, organization_id) " + "VALUES (:id, :project_name, :workspace_id, :organization_id)" + ), + { + "id": project_id, + "project_name": "turns-dao-test-project", + "workspace_id": workspace_id, + "organization_id": org_id, + }, + ) + await session.execute( + text( + "INSERT INTO session_streams (id, project_id, session_id) " + "VALUES (:id, :project_id, :session_id)" + ), + {"id": stream_id, "project_id": project_id, "session_id": session_id}, + ) + await session.commit() + + yield { + "project_id": project_id, + "stream_id": stream_id, + "session_id": session_id, + "user_id": user_id, + } + + async with engine.session() as session: + await session.execute( + text("DELETE FROM session_turns WHERE project_id = :project_id"), + {"project_id": project_id}, + ) + await session.execute( + text("DELETE FROM session_streams WHERE project_id = :project_id"), + {"project_id": project_id}, + ) + await session.execute( + text("DELETE FROM projects WHERE id = :id"), {"id": project_id} + ) + await session.execute( + text("DELETE FROM workspaces WHERE id = :id"), {"id": workspace_id} + ) + await session.execute( + text("DELETE FROM organizations WHERE id = :id"), {"id": org_id} + ) + await session.execute(text("DELETE FROM users WHERE id = :id"), {"id": user_id}) + await session.commit() + + +@pytest.fixture +def dao(): + return SessionTurnsDAO(engine=get_transactions_engine()) + + +async def test_append_turn_persists_and_sets_created_by_id(dao, project_and_stream): + """W1.6: append a turn — round-trips every field, and created_by_id is the caller.""" + project_id = project_and_stream["project_id"] + stream_id = project_and_stream["stream_id"] + session_id = project_and_stream["session_id"] + user_id = project_and_stream["user_id"] + + workflow_ref = Reference(id=uuid.uuid4(), slug="my-workflow", version="v1") + + # span_id is a 16-hex OTel span id (runner shape), NOT a UUID; trace_id is a 32-hex + # OTel trace id that still fits UUID. + span_id = uuid.uuid4().hex[:16] + turn = await dao.append( + project_id=project_id, + user_id=user_id, + turn=SessionTurnCreate( + session_id=session_id, + stream_id=stream_id, + turn_index=0, + harness_kind=HarnessKind.PI, + agent_session_id="agent-sess-abc", + sandbox_id="sandbox-1", + references=[workflow_ref], + trace_id=uuid.uuid4(), + span_id=span_id, + start_time=datetime.now(timezone.utc), + ), + ) + + assert turn.id is not None + assert turn.session_id == session_id + assert turn.stream_id == stream_id + assert turn.turn_index == 0 + assert turn.harness_kind == HarnessKind.PI + assert turn.agent_session_id == "agent-sess-abc" + assert turn.sandbox_id == "sandbox-1" + assert turn.references == [workflow_ref] + assert turn.span_id == span_id + # jp's requirement: append_turn must populate created_by_id from the caller. + assert turn.created_by_id == user_id + + fetched = await dao.fetch_turn(project_id=project_id, turn_id=turn.id) + assert fetched is not None + assert fetched.id == turn.id + assert fetched.references == [workflow_ref] + assert fetched.span_id == span_id + + +async def test_multi_turn_appends_persist_with_incrementing_index_and_stable_stream( + dao, project_and_stream +): + """Two turns on one session persist as two rows with incrementing turn_index and the + same stream_id (the session_streams row id) — the durable-turns invariant a live + two-turn run relies on. Covers the E2E gap that no test asserted (TEST-GAPS.md §turns).""" + project_id = project_and_stream["project_id"] + stream_id = project_and_stream["stream_id"] + session_id = project_and_stream["session_id"] + + for turn_index in (0, 1): + await dao.append( + project_id=project_id, + user_id=None, + turn=SessionTurnCreate( + session_id=session_id, + stream_id=stream_id, + turn_index=turn_index, + harness_kind=HarnessKind.PI, + ), + ) + + results = await dao.query_turns( + project_id=project_id, + query=SessionTurnQuery(session_id=session_id), + ) + + assert len(results) == 2 + assert {t.turn_index for t in results} == {0, 1} + # Every turn in a session shares the one session_streams row id. + assert {t.stream_id for t in results} == {stream_id} + + +async def test_query_turns_filters_by_references_gin_contains(dao, project_and_stream): + """W1.6: query by references uses the eval_runs GIN `.contains()` pattern.""" + project_id = project_and_stream["project_id"] + stream_id = project_and_stream["stream_id"] + session_id = project_and_stream["session_id"] + "-refs" + + async with get_transactions_engine().session() as session: + await session.execute( + text( + "INSERT INTO session_streams (id, project_id, session_id) " + "VALUES (:id, :project_id, :session_id)" + ), + {"id": uuid.uuid4(), "project_id": project_id, "session_id": session_id}, + ) + await session.commit() + + target_ref = Reference(id=uuid.uuid4(), slug="target-workflow", version="v1") + other_ref = Reference(id=uuid.uuid4(), slug="other-workflow", version="v1") + + matching = await dao.append( + project_id=project_id, + user_id=None, + turn=SessionTurnCreate( + session_id=session_id, + stream_id=stream_id, + turn_index=0, + harness_kind=HarnessKind.PI, + references=[target_ref], + ), + ) + await dao.append( + project_id=project_id, + user_id=None, + turn=SessionTurnCreate( + session_id=session_id, + stream_id=stream_id, + turn_index=1, + harness_kind=HarnessKind.PI, + references=[other_ref], + ), + ) + + results = await dao.query_turns( + project_id=project_id, + query=SessionTurnQuery(session_id=session_id, references=[target_ref]), + ) + + assert len(results) == 1 + assert results[0].id == matching.id + + +async def test_query_turns_windowed_newest_to_oldest(dao, project_and_stream): + """W1.6: windowed list, newest -> oldest.""" + project_id = project_and_stream["project_id"] + stream_id = project_and_stream["stream_id"] + session_id = project_and_stream["session_id"] + "-window" + + async with get_transactions_engine().session() as session: + await session.execute( + text( + "INSERT INTO session_streams (id, project_id, session_id) " + "VALUES (:id, :project_id, :session_id)" + ), + {"id": uuid.uuid4(), "project_id": project_id, "session_id": session_id}, + ) + await session.commit() + + created_ids = [] + for i in range(3): + turn = await dao.append( + project_id=project_id, + user_id=None, + turn=SessionTurnCreate( + session_id=session_id, + stream_id=stream_id, + turn_index=i, + harness_kind=HarnessKind.PI, + ), + ) + created_ids.append(turn.id) + + results = await dao.query_turns( + project_id=project_id, + query=SessionTurnQuery(session_id=session_id), + windowing=Windowing(order="descending"), + ) + + assert [r.id for r in results] == list(reversed(created_ids)) + + +async def test_latest_turn_and_latest_turn_per_harness(dao, project_and_stream): + """W1.6: latest_turn / latest_turn_per_harness — the runner's resume-read (WP3).""" + project_id = project_and_stream["project_id"] + stream_id = project_and_stream["stream_id"] + session_id = project_and_stream["session_id"] + "-latest" + + async with get_transactions_engine().session() as session: + await session.execute( + text( + "INSERT INTO session_streams (id, project_id, session_id) " + "VALUES (:id, :project_id, :session_id)" + ), + {"id": uuid.uuid4(), "project_id": project_id, "session_id": session_id}, + ) + await session.commit() + + await dao.append( + project_id=project_id, + user_id=None, + turn=SessionTurnCreate( + session_id=session_id, + stream_id=stream_id, + turn_index=0, + harness_kind=HarnessKind.PI, + agent_session_id="pi-core-sess-0", + sandbox_id="sandbox-0", + ), + ) + await dao.append( + project_id=project_id, + user_id=None, + turn=SessionTurnCreate( + session_id=session_id, + stream_id=stream_id, + turn_index=1, + harness_kind=HarnessKind.CLAUDE, + agent_session_id="claude-sess-1", + sandbox_id="sandbox-1", + ), + ) + latest_pi_core = await dao.append( + project_id=project_id, + user_id=None, + turn=SessionTurnCreate( + session_id=session_id, + stream_id=stream_id, + turn_index=2, + harness_kind=HarnessKind.PI, + agent_session_id="pi-core-sess-2", + sandbox_id="sandbox-2", + ), + ) + + # A late-arriving lower-index write for a stale turn must never win — the + # resume pointer is ORDER BY turn_index DESC, not insertion order. + latest_overall = await dao.append( + project_id=project_id, + user_id=None, + turn=SessionTurnCreate( + session_id=session_id, + stream_id=stream_id, + turn_index=3, + harness_kind=HarnessKind.CLAUDE, + agent_session_id="claude-sess-3", + sandbox_id="sandbox-3", + ), + ) + + latest = await dao.latest_turn(project_id=project_id, session_id=session_id) + assert latest is not None + assert latest.id == latest_overall.id + assert latest.turn_index == 3 + assert latest.sandbox_id == "sandbox-3" + + latest_for_pi_core = await dao.latest_turn_per_harness_kind( + project_id=project_id, session_id=session_id, harness_kind=HarnessKind.PI + ) + assert latest_for_pi_core is not None + assert latest_for_pi_core.id == latest_pi_core.id + assert latest_for_pi_core.agent_session_id == "pi-core-sess-2" + + +async def test_delete_by_session_id_hard_deletes(dao, project_and_stream): + """W1.6: hard-delete-by-session (WP5's fan-out).""" + project_id = project_and_stream["project_id"] + stream_id = project_and_stream["stream_id"] + session_id = project_and_stream["session_id"] + "-delete" + + async with get_transactions_engine().session() as session: + await session.execute( + text( + "INSERT INTO session_streams (id, project_id, session_id) " + "VALUES (:id, :project_id, :session_id)" + ), + {"id": uuid.uuid4(), "project_id": project_id, "session_id": session_id}, + ) + await session.commit() + + for i in range(2): + await dao.append( + project_id=project_id, + user_id=None, + turn=SessionTurnCreate( + session_id=session_id, + stream_id=stream_id, + turn_index=i, + harness_kind=HarnessKind.PI, + ), + ) + + deleted_count = await dao.delete_by_session_id( + project_id=project_id, session_id=session_id + ) + assert deleted_count == 2 + + remaining = await dao.query_turns( + project_id=project_id, query=SessionTurnQuery(session_id=session_id) + ) + assert remaining == [] diff --git a/api/oss/tests/pytest/unit/sessions/test_turns_dao_conflict.py b/api/oss/tests/pytest/unit/sessions/test_turns_dao_conflict.py new file mode 100644 index 0000000000..2a266f1d28 --- /dev/null +++ b/api/oss/tests/pytest/unit/sessions/test_turns_dao_conflict.py @@ -0,0 +1,77 @@ +"""A duplicate session turn maps to HTTP 409 instead of an uncaught database error.""" + +from contextlib import asynccontextmanager +from uuid import uuid4 + +import pytest +from sqlalchemy.exc import IntegrityError + +from oss.src.core.sessions.turns.dtos import HarnessKind, SessionTurnCreate +from oss.src.dbs.postgres.sessions.turns.dao import SessionTurnsDAO +from oss.src.utils.exceptions import ConflictException, intercept_exceptions + + +class _FakeSession: + def __init__(self): + self.rollback_count = 0 + + def add(self, _dbe): + pass + + async def commit(self): + raise IntegrityError( + "INSERT INTO session_turns ...", + {}, + Exception( + "duplicate key value violates unique constraint " + '"ix_session_turns_project_id_session_id_turn_index"' + ), + ) + + async def refresh(self, _dbe): + raise AssertionError("a failed insert must not refresh") + + async def rollback(self): + self.rollback_count += 1 + + +class _FakeEngine: + def __init__(self): + self.session_handle = _FakeSession() + + @asynccontextmanager + async def session(self): + yield self.session_handle + + +@pytest.mark.anyio +async def test_duplicate_turn_returns_409_and_rolls_back(anyio_backend): + assert anyio_backend == "asyncio" + engine = _FakeEngine() + dao = SessionTurnsDAO(engine=engine) + session_id = "sess-duplicate-1" + turn = SessionTurnCreate( + session_id=session_id, + stream_id=uuid4(), + turn_index=3, + harness_kind=HarnessKind.PI, + ) + + @intercept_exceptions(verbose=False) + async def append_turn(): + return await dao.append(project_id=uuid4(), user_id=None, turn=turn) + + with pytest.raises(ConflictException) as exc_info: + await append_turn() + + assert engine.session_handle.rollback_count == 1 + assert exc_info.value.status_code == 409 + assert exc_info.value.detail == { + "message": f"Session turn 3 already exists for session {session_id}.", + "conflict": {"session_id": session_id, "turn_index": 3}, + } + + +@pytest.fixture +def anyio_backend(): + return "asyncio" diff --git a/api/oss/tests/pytest/unit/sessions/test_wp5_dao_fanout.py b/api/oss/tests/pytest/unit/sessions/test_wp5_dao_fanout.py new file mode 100644 index 0000000000..810933bde4 --- /dev/null +++ b/api/oss/tests/pytest/unit/sessions/test_wp5_dao_fanout.py @@ -0,0 +1,420 @@ +"""Integration-style tests for the new WP5 DAO plumbing against a real Postgres. + +Requires the core_oss migration chain applied and POSTGRES_URI_CORE pointed at that +database (same fixture shape as test_turns_dao.py). Exercises the DAO methods the +delete/archive/unarchive fan-out is built on, and were "new plumbing" per the brief +(everything else was soft-delete-only before this WP): + + - SessionInteractionsDAO.delete_by_session_id — hard delete (was soft-only via + cancel_session_pending's status flip). + - SessionStreamsDAO.hard_delete_by_session_id — hard delete (kill only soft- + deletes via delete_by_session_id). + - SessionStreamsDAO.unarchive_by_session_id / get_by_session_id_including_archived + — the archive round-trip's reverse + confirmation read. + - MountsDAO.delete_by_session_id — hard delete of session-bound mount rows, + returning the deleted rows (so the service can tear down their prefixes). +""" + +import uuid + +import pytest +from sqlalchemy import text + +from oss.src.core.sessions.interactions.dtos import ( + SessionInteractionCreate, + SessionInteractionKind, + SessionInteractionQuery, +) +from oss.src.core.mounts.dtos import MountCreate +import oss.src.models.db_models # noqa: F401 +from oss.src.dbs.postgres.sessions.streams.dbes import SessionStreamDBE # noqa: F401 +from oss.src.dbs.postgres.sessions.streams.dao import SessionStreamsDAO +from oss.src.dbs.postgres.sessions.interactions.dao import SessionInteractionsDAO +from oss.src.dbs.postgres.mounts.dao import MountsDAO +import oss.src.dbs.postgres.shared.engine as engine_module +from oss.src.dbs.postgres.shared.engine import get_transactions_engine + + +pytestmark = pytest.mark.integration + + +@pytest.fixture(autouse=True) +async def _fresh_engine_per_test(): + engine_module._transactions_engine = None + yield + if engine_module._transactions_engine is not None: + await engine_module._transactions_engine.close() + engine_module._transactions_engine = None + + +@pytest.fixture +async def project(): + """Provision the minimal FK chain: user -> org -> workspace -> project.""" + engine = get_transactions_engine() + + user_id = uuid.uuid4() + org_id = uuid.uuid4() + workspace_id = uuid.uuid4() + project_id = uuid.uuid4() + + async with engine.session() as session: + await session.execute( + text( + "INSERT INTO users (id, uid, username, email) " + "VALUES (:id, :uid, :username, :email)" + ), + { + "id": user_id, + "uid": str(user_id), + "username": "wp5-fanout-test", + "email": f"wp5-fanout-test-{user_id.hex[:8]}@example.com", + }, + ) + await session.execute( + text( + "INSERT INTO organizations (id, name, owner_id) " + "VALUES (:id, :name, :owner_id)" + ), + {"id": org_id, "name": "wp5-fanout-test-org", "owner_id": user_id}, + ) + await session.execute( + text( + "INSERT INTO workspaces (id, name, organization_id) " + "VALUES (:id, :name, :organization_id)" + ), + { + "id": workspace_id, + "name": "wp5-fanout-test-ws", + "organization_id": org_id, + }, + ) + await session.execute( + text( + "INSERT INTO projects (id, project_name, workspace_id, organization_id) " + "VALUES (:id, :project_name, :workspace_id, :organization_id)" + ), + { + "id": project_id, + "project_name": "wp5-fanout-test-project", + "workspace_id": workspace_id, + "organization_id": org_id, + }, + ) + await session.commit() + + yield {"project_id": project_id, "user_id": user_id} + + async with engine.session() as session: + for table in ( + "mounts", + "session_interactions", + "session_turns", + "session_streams", + ): + await session.execute( + text(f"DELETE FROM {table} WHERE project_id = :project_id"), + {"project_id": project_id}, + ) + await session.execute( + text("DELETE FROM projects WHERE id = :id"), {"id": project_id} + ) + await session.execute( + text("DELETE FROM workspaces WHERE id = :id"), {"id": workspace_id} + ) + await session.execute( + text("DELETE FROM organizations WHERE id = :id"), {"id": org_id} + ) + await session.execute(text("DELETE FROM users WHERE id = :id"), {"id": user_id}) + await session.commit() + + +@pytest.fixture +def streams_dao(): + return SessionStreamsDAO(engine=get_transactions_engine()) + + +@pytest.fixture +def interactions_dao(): + return SessionInteractionsDAO(engine=get_transactions_engine()) + + +@pytest.fixture +def mounts_dao(): + return MountsDAO(engine=get_transactions_engine()) + + +# --------------------------------------------------------------------------- +# SessionInteractionsDAO.delete_by_session_id — new hard delete +# --------------------------------------------------------------------------- + + +async def test_interactions_delete_by_session_id_hard_deletes( + interactions_dao, project +): + project_id = project["project_id"] + session_id = f"wp5-interactions-{uuid.uuid4().hex[:8]}" + + for i in range(2): + await interactions_dao.create_interaction( + project_id=project_id, + user_id=None, + interaction=SessionInteractionCreate( + project_id=project_id, + session_id=session_id, + token=f"token-{i}", + kind=SessionInteractionKind.user_approval, + ), + ) + + deleted_count = await interactions_dao.delete_by_session_id( + project_id=project_id, session_id=session_id + ) + assert deleted_count == 2 + + remaining = await interactions_dao.query_interactions( + project_id=project_id, + query=SessionInteractionQuery(session_id=session_id), + ) + assert remaining == [] + + +async def test_interactions_delete_by_session_id_scoped_to_session( + interactions_dao, project +): + """Deleting one session's interactions must not touch another session's rows.""" + project_id = project["project_id"] + session_a = f"wp5-interactions-a-{uuid.uuid4().hex[:8]}" + session_b = f"wp5-interactions-b-{uuid.uuid4().hex[:8]}" + + await interactions_dao.create_interaction( + project_id=project_id, + user_id=None, + interaction=SessionInteractionCreate( + project_id=project_id, + session_id=session_a, + token="token-a", + kind=SessionInteractionKind.user_approval, + ), + ) + await interactions_dao.create_interaction( + project_id=project_id, + user_id=None, + interaction=SessionInteractionCreate( + project_id=project_id, + session_id=session_b, + token="token-b", + kind=SessionInteractionKind.user_approval, + ), + ) + + deleted_count = await interactions_dao.delete_by_session_id( + project_id=project_id, session_id=session_a + ) + assert deleted_count == 1 + + remaining_b = await interactions_dao.query_interactions( + project_id=project_id, + query=SessionInteractionQuery(session_id=session_b), + ) + assert len(remaining_b) == 1 + + +# --------------------------------------------------------------------------- +# SessionStreamsDAO.hard_delete_by_session_id — new hard delete +# --------------------------------------------------------------------------- + + +async def test_streams_hard_delete_by_session_id(streams_dao, project): + project_id = project["project_id"] + session_id = f"wp5-streams-hard-{uuid.uuid4().hex[:8]}" + + async with get_transactions_engine().session() as session: + await session.execute( + text( + "INSERT INTO session_streams (id, project_id, session_id) " + "VALUES (:id, :project_id, :session_id)" + ), + {"id": uuid.uuid4(), "project_id": project_id, "session_id": session_id}, + ) + await session.commit() + + deleted = await streams_dao.hard_delete_by_session_id( + project_id=project_id, session_id=session_id + ) + assert deleted is True + + # Hard-deleted: not even visible to the archived-inclusive read. + row = await streams_dao.get_by_session_id_including_archived( + project_id=project_id, session_id=session_id + ) + assert row is None + + +async def test_streams_hard_delete_is_distinct_from_soft_kill_delete( + streams_dao, project +): + """kill's delete_by_session_id (soft) leaves the row queryable with deleted_at + set; hard_delete_by_session_id actually removes it. Same session, two paths.""" + project_id = project["project_id"] + session_id = f"wp5-streams-soft-vs-hard-{uuid.uuid4().hex[:8]}" + + async with get_transactions_engine().session() as session: + await session.execute( + text( + "INSERT INTO session_streams (id, project_id, session_id) " + "VALUES (:id, :project_id, :session_id)" + ), + {"id": uuid.uuid4(), "project_id": project_id, "session_id": session_id}, + ) + await session.commit() + + soft_deleted = await streams_dao.delete_by_session_id( + project_id=project_id, session_id=session_id + ) + assert soft_deleted is True + + still_there = await streams_dao.get_by_session_id_including_archived( + project_id=project_id, session_id=session_id + ) + assert still_there is not None + assert still_there.deleted_at is not None + + hard_deleted = await streams_dao.hard_delete_by_session_id( + project_id=project_id, session_id=session_id + ) + assert hard_deleted is True + + gone = await streams_dao.get_by_session_id_including_archived( + project_id=project_id, session_id=session_id + ) + assert gone is None + + +# --------------------------------------------------------------------------- +# SessionStreamsDAO archive/unarchive round trip +# --------------------------------------------------------------------------- + + +async def test_streams_archive_unarchive_round_trip(streams_dao, project): + project_id = project["project_id"] + session_id = f"wp5-streams-archive-{uuid.uuid4().hex[:8]}" + + async with get_transactions_engine().session() as session: + await session.execute( + text( + "INSERT INTO session_streams (id, project_id, session_id) " + "VALUES (:id, :project_id, :session_id)" + ), + {"id": uuid.uuid4(), "project_id": project_id, "session_id": session_id}, + ) + await session.commit() + + archived = await streams_dao.delete_by_session_id( + project_id=project_id, session_id=session_id + ) + assert archived is True + + # Not visible via the normal (non-archived) read. + normal_read = await streams_dao.get_by_session_id( + project_id=project_id, session_id=session_id + ) + assert normal_read is None + + # Visible via the archived-inclusive read, with deleted_at set. + archived_row = await streams_dao.get_by_session_id_including_archived( + project_id=project_id, session_id=session_id + ) + assert archived_row is not None + assert archived_row.deleted_at is not None + + unarchived_row = await streams_dao.unarchive_by_session_id( + project_id=project_id, user_id=None, session_id=session_id + ) + assert unarchived_row is not None + assert unarchived_row.deleted_at is None + + # Now visible again via the normal read. + normal_read_again = await streams_dao.get_by_session_id( + project_id=project_id, session_id=session_id + ) + assert normal_read_again is not None + assert normal_read_again.deleted_at is None + + +# --------------------------------------------------------------------------- +# MountsDAO.delete_by_session_id — new hard delete of session-bound mounts +# --------------------------------------------------------------------------- + + +async def test_mounts_delete_by_session_id_hard_deletes_and_returns_rows( + mounts_dao, project +): + project_id = project["project_id"] + user_id = project["user_id"] + session_id = f"wp5-mounts-{uuid.uuid4().hex[:8]}" + + mount = await mounts_dao.create_mount( + project_id=project_id, + user_id=user_id, + mount_create=MountCreate( + slug=f"wp5-mount-{uuid.uuid4().hex[:8]}", + name="cwd", + session_id=session_id, + ), + ) + + deleted_mounts = await mounts_dao.delete_by_session_id( + project_id=project_id, session_id=session_id + ) + assert len(deleted_mounts) == 1 + assert deleted_mounts[0].id == mount.id + + fetched = await mounts_dao.fetch_mount(project_id=project_id, mount_id=mount.id) + assert fetched is None + + +async def test_mounts_delete_by_session_id_scoped_to_session(mounts_dao, project): + """A mount bound to a different session must survive another session's delete.""" + project_id = project["project_id"] + user_id = project["user_id"] + session_a = f"wp5-mounts-a-{uuid.uuid4().hex[:8]}" + session_b = f"wp5-mounts-b-{uuid.uuid4().hex[:8]}" + + mount_a = await mounts_dao.create_mount( + project_id=project_id, + user_id=user_id, + mount_create=MountCreate( + slug=f"wp5-mount-a-{uuid.uuid4().hex[:8]}", + name="cwd", + session_id=session_a, + ), + ) + mount_b = await mounts_dao.create_mount( + project_id=project_id, + user_id=user_id, + mount_create=MountCreate( + slug=f"wp5-mount-b-{uuid.uuid4().hex[:8]}", + name="cwd", + session_id=session_b, + ), + ) + + deleted_mounts = await mounts_dao.delete_by_session_id( + project_id=project_id, session_id=session_a + ) + assert [m.id for m in deleted_mounts] == [mount_a.id] + + still_there = await mounts_dao.fetch_mount( + project_id=project_id, mount_id=mount_b.id + ) + assert still_there is not None + + +async def test_mounts_delete_by_session_id_no_mounts_returns_empty(mounts_dao, project): + project_id = project["project_id"] + session_id = f"wp5-mounts-none-{uuid.uuid4().hex[:8]}" + + deleted_mounts = await mounts_dao.delete_by_session_id( + project_id=project_id, session_id=session_id + ) + assert deleted_mounts == [] diff --git a/api/oss/tests/pytest/unit/sessions/test_wp5_mount_teardown.py b/api/oss/tests/pytest/unit/sessions/test_wp5_mount_teardown.py new file mode 100644 index 0000000000..02a8cfc665 --- /dev/null +++ b/api/oss/tests/pytest/unit/sessions/test_wp5_mount_teardown.py @@ -0,0 +1,216 @@ +"""WP5 (S7/F1/F2): MountsService session-scoped teardown/archive fan-out. + +Unit-level: fakes MountsDAOInterface + ObjectStore so the orchestration (which +DAO calls happen, which store prefixes get torn down) is pinned without a real +DB or store. delete_session_mounts must call ObjectStore.delete_prefix once per +deleted mount, using the same `_storage_key` prefix the mount's own file ops +use. archive/unarchive_session_mounts must only touch mount rows (soft), never +the store. +""" + +from datetime import datetime, timezone +from typing import List, Optional +from uuid import UUID, uuid4 + +import pytest + +from oss.src.core.mounts.dtos import Mount, MountCreate, MountEdit, MountQuery +from oss.src.core.mounts.service import MountsService + + +_PROJECT = uuid4() +_USER = uuid4() +_SESSION = "session-wp5-mounts" +_BUCKET = "test-bucket" + + +def _mount(*, session_id: Optional[str], slug: str, deleted: bool = False) -> Mount: + return Mount( + id=uuid4(), + project_id=_PROJECT, + session_id=session_id, + slug=slug, + name=slug, + deleted_at=datetime.now(timezone.utc) if deleted else None, + ) + + +class _FakeMountsDAO: + def __init__(self, mounts: Optional[List[Mount]] = None): + self.mounts = {m.id: m for m in (mounts or [])} + self.archive_calls: list[UUID] = [] + self.unarchive_calls: list[UUID] = [] + self.deleted_session_ids: list[str] = [] + + async def create_mount( + self, *, project_id, user_id, mount_create: MountCreate + ) -> Mount: + raise NotImplementedError + + async def upsert_mount( + self, *, project_id, user_id, mount_create: MountCreate + ) -> Mount: + raise NotImplementedError + + async def fetch_mount(self, *, project_id, mount_id) -> Optional[Mount]: + return self.mounts.get(mount_id) + + async def fetch_mount_by_slug(self, *, project_id, slug) -> Optional[Mount]: + raise NotImplementedError + + async def edit_mount( + self, *, project_id, user_id, mount_edit: MountEdit + ) -> Optional[Mount]: + raise NotImplementedError + + async def archive_mount(self, *, project_id, user_id, mount_id) -> Optional[Mount]: + self.archive_calls.append(mount_id) + mount = self.mounts.get(mount_id) + if mount is None: + return None + mount = mount.model_copy(update={"deleted_at": datetime.now(timezone.utc)}) + self.mounts[mount_id] = mount + return mount + + async def unarchive_mount( + self, *, project_id, user_id, mount_id + ) -> Optional[Mount]: + self.unarchive_calls.append(mount_id) + mount = self.mounts.get(mount_id) + if mount is None: + return None + mount = mount.model_copy(update={"deleted_at": None}) + self.mounts[mount_id] = mount + return mount + + async def query_mounts( + self, *, project_id, mount_query: Optional[MountQuery] = None, windowing=None + ) -> List[Mount]: + rows = list(self.mounts.values()) + if mount_query: + if mount_query.session_id is not None: + rows = [m for m in rows if m.session_id == mount_query.session_id] + if not mount_query.include_archived: + rows = [m for m in rows if m.deleted_at is None] + return rows + + async def delete_by_session_id(self, *, project_id, session_id) -> List[Mount]: + self.deleted_session_ids.append(session_id) + matched = [m for m in self.mounts.values() if m.session_id == session_id] + for m in matched: + del self.mounts[m.id] + return matched + + +class _FakeObjectStore: + def __init__(self): + self.delete_prefix_calls: list[dict] = [] + + async def delete_prefix(self, *, bucket: str, prefix: str) -> int: + self.delete_prefix_calls.append({"bucket": bucket, "prefix": prefix}) + return 3 + + +def _service(mounts: List[Mount]): + dao = _FakeMountsDAO(mounts) + store = _FakeObjectStore() + svc = MountsService(mounts_dao=dao, mounts_store=store, bucket=_BUCKET) + return svc, dao, store + + +# --------------------------------------------------------------------------- +# delete_session_mounts — hard delete + object-store prefix teardown +# --------------------------------------------------------------------------- + + +@pytest.mark.asyncio +async def test_delete_session_mounts_deletes_rows_and_object_store_prefixes(): + mount = _mount(session_id=_SESSION, slug="s1") + svc, dao, store = _service([mount]) + + deleted = await svc.delete_session_mounts(project_id=_PROJECT, session_id=_SESSION) + + assert deleted == [mount] + assert dao.deleted_session_ids == [_SESSION] + assert len(store.delete_prefix_calls) == 1 + call = store.delete_prefix_calls[0] + assert call["bucket"] == _BUCKET + assert call["prefix"] == f"mounts/{_PROJECT}/{mount.id}/" + + +@pytest.mark.asyncio +async def test_delete_session_mounts_tears_down_every_bound_mount(): + mount_a = _mount(session_id=_SESSION, slug="cwd") + mount_b = _mount(session_id=_SESSION, slug="claude-projects") + other = _mount(session_id="other-session", slug="cwd-other") + svc, dao, store = _service([mount_a, mount_b, other]) + + deleted = await svc.delete_session_mounts(project_id=_PROJECT, session_id=_SESSION) + + assert {m.id for m in deleted} == {mount_a.id, mount_b.id} + assert len(store.delete_prefix_calls) == 2 + # the untouched session's mount survives + assert other.id in dao.mounts + + +@pytest.mark.asyncio +async def test_delete_session_mounts_no_bound_mounts_is_a_noop(): + svc, dao, store = _service([]) + + deleted = await svc.delete_session_mounts(project_id=_PROJECT, session_id=_SESSION) + + assert deleted == [] + assert store.delete_prefix_calls == [] + + +# --------------------------------------------------------------------------- +# archive_session_mounts / unarchive_session_mounts — soft, reversible +# --------------------------------------------------------------------------- + + +@pytest.mark.asyncio +async def test_archive_session_mounts_soft_archives_and_never_touches_store(): + mount = _mount(session_id=_SESSION, slug="cwd") + svc, dao, store = _service([mount]) + + archived = await svc.archive_session_mounts( + project_id=_PROJECT, user_id=_USER, session_id=_SESSION + ) + + assert len(archived) == 1 + assert archived[0].deleted_at is not None + assert dao.archive_calls == [mount.id] + assert store.delete_prefix_calls == [] + # the row still exists (soft) -- not removed from the DAO's backing map + assert mount.id in dao.mounts + + +@pytest.mark.asyncio +async def test_unarchive_session_mounts_reverses_archive_round_trip(): + mount = _mount(session_id=_SESSION, slug="cwd") + svc, dao, store = _service([mount]) + + await svc.archive_session_mounts( + project_id=_PROJECT, user_id=_USER, session_id=_SESSION + ) + unarchived = await svc.unarchive_session_mounts( + project_id=_PROJECT, user_id=_USER, session_id=_SESSION + ) + + assert len(unarchived) == 1 + assert unarchived[0].deleted_at is None + assert dao.unarchive_calls == [mount.id] + assert store.delete_prefix_calls == [] + + +@pytest.mark.asyncio +async def test_archive_session_mounts_only_touches_bound_mounts(): + bound = _mount(session_id=_SESSION, slug="cwd") + unbound = _mount(session_id=None, slug="agent-mount") + svc, dao, _ = _service([bound, unbound]) + + await svc.archive_session_mounts( + project_id=_PROJECT, user_id=_USER, session_id=_SESSION + ) + + assert dao.archive_calls == [bound.id] diff --git a/api/oss/tests/pytest/unit/sessions/test_wp5_root_router.py b/api/oss/tests/pytest/unit/sessions/test_wp5_root_router.py new file mode 100644 index 0000000000..331ba147dc --- /dev/null +++ b/api/oss/tests/pytest/unit/sessions/test_wp5_root_router.py @@ -0,0 +1,236 @@ +"""WP5 (S7): SessionsRootRouter — /sessions/query, DELETE /sessions/, +/sessions/archive, /sessions/unarchive. + +Mirrors test_record_ingest_endpoint.py's pattern: a mock Request + +patched check_action_access, so RBAC gating and service delegation are +pinned without a live app/DB. Covers: + + - query_sessions: VIEW_SESSIONS gate; delegates to SessionsService.query_sessions + with the parsed SessionQuery + windowing. + - delete_session: EDIT_SESSIONS gate; delegates to SessionsService.delete_session + keyed by session_id (the query param), not stream_id. + - archive_session / unarchive_session: EDIT_SESSIONS gate; delegate to the + matching SessionsService methods. + - every mutation rejects without permission. +""" + +from uuid import uuid4 +from unittest.mock import AsyncMock, patch + +import pytest +from fastapi import FastAPI, HTTPException, Request + +from oss.src.apis.fastapi.sessions.router import SessionsRootRouter +from oss.src.apis.fastapi.sessions.models import SessionQueryRequest +from oss.src.core.shared.dtos import Reference, Windowing + + +def _make_authed_request(app: FastAPI, project_id, user_id, method="POST") -> Request: + scope = { + "type": "http", + "method": method, + "path": "/sessions/", + "headers": [], + "app": app, + } + request = Request(scope) + request.state.project_id = str(project_id) + request.state.user_id = str(user_id) + return request + + +def _patched_access(allowed: bool): + return patch( + "oss.src.apis.fastapi.sessions.router.check_action_access", + new_callable=AsyncMock, + return_value=allowed, + ) + + +# --------------------------------------------------------------------------- +# query_sessions +# --------------------------------------------------------------------------- + + +async def test_query_sessions_delegates_to_service(): + sessions_service = AsyncMock() + sessions_service.query_sessions.return_value = [] + router = SessionsRootRouter(sessions_service=sessions_service) + + project_id = uuid4() + user_id = uuid4() + app = FastAPI() + request = _make_authed_request(app, project_id, user_id) + + target_ref = Reference(id=uuid4(), slug="wf", version="v1") + body = SessionQueryRequest( + references=[target_ref], windowing=Windowing(order="descending") + ) + + with _patched_access(True): + result = await router.query_sessions(request=request, body=body) + + assert result.count == 0 + sessions_service.query_sessions.assert_awaited_once() + call_kwargs = sessions_service.query_sessions.await_args.kwargs + assert call_kwargs["project_id"] == project_id + assert call_kwargs["query"].references == [target_ref] + assert call_kwargs["windowing"].order == "descending" + + +async def test_query_sessions_rejects_without_view_permission(): + sessions_service = AsyncMock() + router = SessionsRootRouter(sessions_service=sessions_service) + + project_id = uuid4() + user_id = uuid4() + app = FastAPI() + request = _make_authed_request(app, project_id, user_id) + + with _patched_access(False): + with pytest.raises(HTTPException) as exc_info: + await router.query_sessions(request=request, body=SessionQueryRequest()) + + assert exc_info.value.status_code == 403 + sessions_service.query_sessions.assert_not_awaited() + + +# --------------------------------------------------------------------------- +# delete_session +# --------------------------------------------------------------------------- + + +async def test_delete_session_delegates_to_service_keyed_by_session_id(): + sessions_service = AsyncMock() + router = SessionsRootRouter(sessions_service=sessions_service) + + project_id = uuid4() + user_id = uuid4() + app = FastAPI() + request = _make_authed_request(app, project_id, user_id, method="DELETE") + + with _patched_access(True): + result = await router.delete_session(request=request, session_id="sess-1") + + assert result == {"ok": True} + sessions_service.delete_session.assert_awaited_once() + call_kwargs = sessions_service.delete_session.await_args.kwargs + assert call_kwargs["project_id"] == project_id + assert call_kwargs["session_id"] == "sess-1" + assert "stream_id" not in call_kwargs + + +async def test_delete_session_rejects_without_edit_permission(): + sessions_service = AsyncMock() + router = SessionsRootRouter(sessions_service=sessions_service) + + project_id = uuid4() + user_id = uuid4() + app = FastAPI() + request = _make_authed_request(app, project_id, user_id, method="DELETE") + + with _patched_access(False): + with pytest.raises(HTTPException) as exc_info: + await router.delete_session(request=request, session_id="sess-1") + + assert exc_info.value.status_code == 403 + sessions_service.delete_session.assert_not_awaited() + + +async def test_delete_session_rejects_invalid_session_id(): + sessions_service = AsyncMock() + router = SessionsRootRouter(sessions_service=sessions_service) + + project_id = uuid4() + user_id = uuid4() + app = FastAPI() + request = _make_authed_request(app, project_id, user_id, method="DELETE") + + with pytest.raises(HTTPException) as exc_info: + await router.delete_session(request=request, session_id="../etc/passwd") + + assert exc_info.value.status_code == 400 + sessions_service.delete_session.assert_not_awaited() + + +# --------------------------------------------------------------------------- +# archive_session / unarchive_session +# --------------------------------------------------------------------------- + + +async def test_archive_session_delegates_to_service(): + from oss.src.core.sessions.streams.dtos import SessionStream + + sessions_service = AsyncMock() + project_id = uuid4() + user_id = uuid4() + sessions_service.archive_session.return_value = SessionStream( + id=uuid4(), project_id=project_id, session_id="sess-1" + ) + router = SessionsRootRouter(sessions_service=sessions_service) + + app = FastAPI() + request = _make_authed_request(app, project_id, user_id) + + with _patched_access(True): + result = await router.archive_session(request=request, session_id="sess-1") + + assert result.count == 1 + sessions_service.archive_session.assert_awaited_once() + call_kwargs = sessions_service.archive_session.await_args.kwargs + assert call_kwargs["project_id"] == project_id + assert call_kwargs["user_id"] == user_id + assert call_kwargs["session_id"] == "sess-1" + + +async def test_archive_session_rejects_without_edit_permission(): + sessions_service = AsyncMock() + router = SessionsRootRouter(sessions_service=sessions_service) + + project_id = uuid4() + user_id = uuid4() + app = FastAPI() + request = _make_authed_request(app, project_id, user_id) + + with _patched_access(False): + with pytest.raises(HTTPException) as exc_info: + await router.archive_session(request=request, session_id="sess-1") + + assert exc_info.value.status_code == 403 + sessions_service.archive_session.assert_not_awaited() + + +async def test_unarchive_session_delegates_to_service(): + sessions_service = AsyncMock() + sessions_service.unarchive_session.return_value = None + router = SessionsRootRouter(sessions_service=sessions_service) + + project_id = uuid4() + user_id = uuid4() + app = FastAPI() + request = _make_authed_request(app, project_id, user_id) + + with _patched_access(True): + result = await router.unarchive_session(request=request, session_id="sess-1") + + assert result.count == 0 + sessions_service.unarchive_session.assert_awaited_once() + call_kwargs = sessions_service.unarchive_session.await_args.kwargs + assert call_kwargs["session_id"] == "sess-1" + + +async def test_unarchive_session_rejects_without_edit_permission(): + sessions_service = AsyncMock() + router = SessionsRootRouter(sessions_service=sessions_service) + + project_id = uuid4() + user_id = uuid4() + app = FastAPI() + request = _make_authed_request(app, project_id, user_id) + + with _patched_access(False): + with pytest.raises(HTTPException) as exc_info: + await router.unarchive_session(request=request, session_id="sess-1") + + assert exc_info.value.status_code == 403 + sessions_service.unarchive_session.assert_not_awaited() diff --git a/api/oss/tests/pytest/unit/tracing/test_dao_ingest_identity_columns.py b/api/oss/tests/pytest/unit/tracing/test_dao_ingest_identity_columns.py new file mode 100644 index 0000000000..cf82c27ba9 --- /dev/null +++ b/api/oss/tests/pytest/unit/tracing/test_dao_ingest_identity_columns.py @@ -0,0 +1,103 @@ +"""Unit test for TracingDAO.ingest persisting the promoted session/user/agent +identity columns. + +No live DB: a fake AsyncSession captures the compiled upsert statement so the +DAO's ON CONFLICT DO UPDATE values can be asserted without Postgres. See +docs/designs/testing/testing.boundaries.specs.md, boundary 3. +""" + +from contextlib import asynccontextmanager +from datetime import datetime, timezone +from uuid import uuid4 + +import pytest + +from oss.src.core.tracing.dtos import OTelFlatSpan +from oss.src.dbs.postgres.tracing.dao import TracingDAO + + +class _FakeResult: + pass + + +class _FakeSession: + def __init__(self, executed: list): + self._executed = executed + + async def execute(self, stmt): + self._executed.append(stmt) + return _FakeResult() + + async def commit(self): + pass + + +class _FakeEngine: + def __init__(self): + self.executed: list = [] + + @asynccontextmanager + async def session(self): + yield _FakeSession(self.executed) + + +def _flat_span( + *, span_id: str, parent_id=None, session_id=None, user_id=None, agent_id=None +): + now = datetime.now(timezone.utc) + return OTelFlatSpan( + trace_id=uuid4().hex, + span_id=span_id, + parent_id=parent_id, + span_name="span", + start_time=now, + end_time=now, + session_id=session_id, + user_id=user_id, + agent_id=agent_id, + ) + + +@pytest.mark.anyio +async def test_ingest_root_span_persists_identity_columns(anyio_backend): + assert anyio_backend == "asyncio" + engine = _FakeEngine() + dao = TracingDAO(engine=engine) + + root = _flat_span( + span_id=uuid4().hex, + session_id="sess-1", + user_id="user-1", + agent_id="agent-1", + ) + + await dao.ingest(project_id=uuid4(), user_id=uuid4(), span_dtos=[root]) + + assert len(engine.executed) == 1 + values = engine.executed[0].compile().params + + assert values["session_id"] == "sess-1" + assert values["user_id"] == "user-1" + assert values["agent_id"] == "agent-1" + + +@pytest.mark.anyio +async def test_ingest_child_span_leaves_identity_columns_null(anyio_backend): + assert anyio_backend == "asyncio" + engine = _FakeEngine() + dao = TracingDAO(engine=engine) + + child = _flat_span(span_id=uuid4().hex, parent_id=uuid4().hex) + + await dao.ingest(project_id=uuid4(), user_id=uuid4(), span_dtos=[child]) + + values = engine.executed[0].compile().params + + assert values["session_id"] is None + assert values["user_id"] is None + assert values["agent_id"] is None + + +@pytest.fixture +def anyio_backend(): + return "asyncio" diff --git a/api/oss/tests/pytest/unit/tracing/utils/test_trees.py b/api/oss/tests/pytest/unit/tracing/utils/test_trees.py index 5414693054..be94c669da 100644 --- a/api/oss/tests/pytest/unit/tracing/utils/test_trees.py +++ b/api/oss/tests/pytest/unit/tracing/utils/test_trees.py @@ -18,6 +18,7 @@ infer_and_propagate_trace_type_by_trace, parse_span_dtos_to_span_idx, parse_span_idx_to_span_id_tree, + promote_identity_by_trace, trace_map_to_traces, traces_to_trace_map, ) @@ -43,6 +44,9 @@ def _span( errors: int = 0, start_offset_s: int = 0, span_type: SpanType = SpanType.TASK, + session_id: str | None = None, + user_id: str | None = None, + agent_id: str | None = None, ) -> OTelFlatSpan: total_tokens = prompt_tokens + completion_tokens total_cost = prompt_cost + completion_cost @@ -65,6 +69,18 @@ def _span( if errors: metrics["errors"] = {"incremental": errors} + ag_attributes = { + "data": {"parameters": {"model": "gpt-4o-mini"}}, + "meta": {"response": {"model": "gpt-4o-mini"}}, + "metrics": metrics, + } + if session_id is not None: + ag_attributes["session"] = {"id": session_id} + if user_id is not None: + ag_attributes["user"] = {"id": user_id} + if agent_id is not None: + ag_attributes["agent"] = {"id": agent_id} + return OTelFlatSpan( trace_id=trace_id, span_id=span_id, @@ -73,13 +89,7 @@ def _span( span_type=span_type, start_time=datetime(2024, 1, 1, 0, 0, start_offset_s, tzinfo=timezone.utc), links=links, - attributes={ - "ag": { - "data": {"parameters": {"model": "gpt-4o-mini"}}, - "meta": {"response": {"model": "gpt-4o-mini"}}, - "metrics": metrics, - } - }, + attributes={"ag": ag_attributes}, ) @@ -365,3 +375,59 @@ def test_trace_map_to_traces_and_back_and_get_span_helpers(): assert get_span_from_trace(trace, ROOT_UUID) is not None assert get_span_from_trace(trace, CHILD_A_UUID) is not None assert get_span_from_trace(trace, str(UUID(int=1))) is None + + +def test_promote_identity_by_trace_lifts_root_only(): + root = _span( + span_id=ROOT_UUID, + span_name="root", + session_id="sess-1", + user_id="user-1", + agent_id="agent-1", + ) + child = _span( + span_id=CHILD_A_UUID, + parent_id=ROOT_UUID, + span_name="child", + start_offset_s=1, + ) + + out = promote_identity_by_trace([root, child]) + out_idx = {span.span_id: span for span in out} + + assert out_idx[ROOT_UUID].session_id == "sess-1" + assert out_idx[ROOT_UUID].user_id == "user-1" + assert out_idx[ROOT_UUID].agent_id == "agent-1" + + assert out_idx[CHILD_A_UUID].session_id is None + assert out_idx[CHILD_A_UUID].user_id is None + assert out_idx[CHILD_A_UUID].agent_id is None + + +def test_promote_identity_by_trace_is_per_trace_and_ignores_missing_attrs(): + trace_a = "trace-a" + trace_b = "trace-b" + + root_a = _span( + span_id="root-a", + span_name="root", + trace_id=trace_a, + session_id="sess-a", + ) + root_b = _span( + span_id="root-b", + span_name="root", + trace_id=trace_b, + ) + + out = promote_identity_by_trace([root_a, root_b]) + out_idx = {span.span_id: span for span in out} + + assert out_idx["root-a"].session_id == "sess-a" + assert out_idx["root-b"].session_id is None + assert out_idx["root-b"].user_id is None + assert out_idx["root-b"].agent_id is None + + +def test_promote_identity_by_trace_empty_list_is_noop(): + assert promote_identity_by_trace([]) == [] diff --git a/api/pyproject.toml b/api/pyproject.toml index e819845530..0aea7d0ca1 100644 --- a/api/pyproject.toml +++ b/api/pyproject.toml @@ -21,6 +21,7 @@ dependencies = [ "sqlalchemy>=2,<3", "sqlalchemy-utils>=0.42,<0.43", "sqlalchemy-json>=0.7,<0.8", + "greenlet>=3,<4", "asyncpg>=0.31,<0.32", "alembic>=1,<2", "orjson>=3,<4", diff --git a/api/uv.lock b/api/uv.lock index 501b226f23..234f3f6910 100644 --- a/api/uv.lock +++ b/api/uv.lock @@ -285,6 +285,7 @@ dependencies = [ { name = "dnspython" }, { name = "fastapi" }, { name = "genson" }, + { name = "greenlet" }, { name = "gunicorn" }, { name = "httpx" }, { name = "jsonschema" }, @@ -336,6 +337,7 @@ requires-dist = [ { name = "dnspython", specifier = ">=2,<3" }, { name = "fastapi", specifier = ">=0.139,<0.140" }, { name = "genson", specifier = ">=1,<2" }, + { name = "greenlet", specifier = ">=3,<4" }, { name = "gunicorn", specifier = ">=26,<27" }, { name = "httpx", specifier = ">=0.28,<0.29" }, { name = "jsonschema", specifier = ">=4,<5" }, diff --git a/clients/python/agenta_client/__init__.py b/clients/python/agenta_client/__init__.py index 724cc3192e..e9360262bc 100644 --- a/clients/python/agenta_client/__init__.py +++ b/clients/python/agenta_client/__init__.py @@ -5,7 +5,7 @@ import typing from importlib import import_module if typing.TYPE_CHECKING: - from .types import AdminAccountCreateOptions, AdminAccountRead, AdminAccountsCreate, AdminAccountsDelete, AdminAccountsDeleteTarget, AdminAccountsResponse, AdminApiKeyCreate, AdminApiKeyResponse, AdminDeleteResponse, AdminDeletedEntities, AdminDeletedEntity, AdminOrganizationCreate, AdminOrganizationMembershipCreate, AdminOrganizationMembershipRead, AdminOrganizationRead, AdminProjectCreate, AdminProjectMembershipCreate, AdminProjectMembershipRead, AdminProjectRead, AdminSimpleAccountCreate, AdminSimpleAccountDeleteEntry, AdminSimpleAccountRead, AdminSimpleAccountsApiKeysCreate, AdminSimpleAccountsCreate, AdminSimpleAccountsDelete, AdminSimpleAccountsOrganizationsCreate, AdminSimpleAccountsOrganizationsMembershipsCreate, AdminSimpleAccountsOrganizationsTransferOwnership, AdminSimpleAccountsOrganizationsTransferOwnershipIncludeProjects, AdminSimpleAccountsOrganizationsTransferOwnershipIncludeProjectsZero, AdminSimpleAccountsOrganizationsTransferOwnershipIncludeWorkspaces, AdminSimpleAccountsOrganizationsTransferOwnershipIncludeWorkspacesZero, AdminSimpleAccountsOrganizationsTransferOwnershipResponse, AdminSimpleAccountsProjectsCreate, AdminSimpleAccountsProjectsMembershipsCreate, AdminSimpleAccountsResponse, AdminSimpleAccountsUsersCreate, AdminSimpleAccountsUsersIdentitiesCreate, AdminSimpleAccountsUsersResetPassword, AdminSimpleAccountsWorkspacesCreate, AdminSimpleAccountsWorkspacesMembershipsCreate, AdminStructuredError, AdminSubscriptionCreate, AdminSubscriptionRead, AdminUserCreate, AdminUserIdentityCreate, AdminUserIdentityRead, AdminUserIdentityReadStatus, AdminUserRead, AdminWorkspaceCreate, AdminWorkspaceMembershipCreate, AdminWorkspaceMembershipRead, AdminWorkspaceRead, AgentTemplateOverlay, Analytics, AnalyticsResponse, Annotation, AnnotationCreate, AnnotationCreateLinks, AnnotationEdit, AnnotationEditLinks, AnnotationLinkResponse, AnnotationLinks, AnnotationQuery, AnnotationQueryLinks, AnnotationResponse, AnnotationsResponse, Application, ApplicationArtifactFlags, ApplicationArtifactQueryFlags, ApplicationCatalogPreset, ApplicationCatalogPresetResponse, ApplicationCatalogPresetsResponse, ApplicationCatalogTemplate, ApplicationCatalogTemplateResponse, ApplicationCatalogTemplatesResponse, ApplicationCatalogType, ApplicationCatalogTypesResponse, ApplicationCreate, ApplicationEdit, ApplicationFlags, ApplicationQuery, ApplicationResponse, ApplicationRevisionCommit, ApplicationRevisionCreate, ApplicationRevisionDataInput, ApplicationRevisionDataInputHeadersValue, ApplicationRevisionDataInputRuntime, ApplicationRevisionDataOutput, ApplicationRevisionDataOutputHeadersValue, ApplicationRevisionDataOutputRuntime, ApplicationRevisionEdit, ApplicationRevisionFlags, ApplicationRevisionInput, ApplicationRevisionOutput, ApplicationRevisionQuery, ApplicationRevisionQueryFlags, ApplicationRevisionResolveResponse, ApplicationRevisionResponse, ApplicationRevisionsLog, ApplicationRevisionsResponse, ApplicationVariant, ApplicationVariantCreate, ApplicationVariantEdit, ApplicationVariantFlags, ApplicationVariantFork, ApplicationVariantResponse, ApplicationVariantsResponse, ApplicationsResponse, BodyConfigsFetchVariantsConfigsFetchPost, Bucket, BuiltinToolConfig, BuiltinToolConfigPermission, CapabilitiesResult, Capability, CapabilityConnection, CapabilityGuidance, CollectStatusResponse, CommandMode, ComparisonOperator, Condition, ConditionOperator, ConditionOptions, ConditionValue, ConfigResponseModel, ConnectAffordance, ConnectionRequirement, CustomModelSettingsDto, CustomProviderDto, CustomProviderKind, CustomProviderSettingsDto, CustomSecretDto, CustomSecretFormat, CustomSecretSettingsDto, CustomSecretSettingsDtoContent, CustomSecretSettingsDtoContentOneValue, DictOperator, DiscoverResponse, DiscoverResponseMethodsValue, DiscoveredAlternative, DiscoveredTool, DiscoveredToolType, DiscoveredTriggerAlternative, DiscoveredTriggerEvent, DiscoveredTriggerEventType, EntityRef, Environment, EnvironmentCreate, EnvironmentEdit, EnvironmentFlags, EnvironmentQueryFlags, EnvironmentResponse, EnvironmentRevisionCommit, EnvironmentRevisionCreate, EnvironmentRevisionData, EnvironmentRevisionDelta, EnvironmentRevisionEdit, EnvironmentRevisionInput, EnvironmentRevisionOutput, EnvironmentRevisionResolveResponse, EnvironmentRevisionResponse, EnvironmentRevisionsLog, EnvironmentRevisionsResponse, EnvironmentVariant, EnvironmentVariantCreate, EnvironmentVariantEdit, EnvironmentVariantFork, EnvironmentVariantResponse, EnvironmentVariantsResponse, EnvironmentsResponse, ErrorPolicy, EvaluationMetrics, EvaluationMetricsCreate, EvaluationMetricsIdsResponse, EvaluationMetricsQuery, EvaluationMetricsQueryScenarioIds, EvaluationMetricsQueryTimestamps, EvaluationMetricsRefresh, EvaluationMetricsResponse, EvaluationMetricsSetRequest, EvaluationQueue, EvaluationQueueCreate, EvaluationQueueData, EvaluationQueueEdit, EvaluationQueueFlags, EvaluationQueueIdResponse, EvaluationQueueIdsResponse, EvaluationQueueQuery, EvaluationQueueQueryFlags, EvaluationQueueResponse, EvaluationQueueScenariosQuery, EvaluationQueuesResponse, EvaluationResult, EvaluationResultCreate, EvaluationResultIdResponse, EvaluationResultIdsResponse, EvaluationResultQuery, EvaluationResultResponse, EvaluationResultsResponse, EvaluationResultsSetRequest, EvaluationRun, EvaluationRunCreate, EvaluationRunDataConcurrency, EvaluationRunDataInput, EvaluationRunDataMapping, EvaluationRunDataMappingColumn, EvaluationRunDataMappingStep, EvaluationRunDataOutput, EvaluationRunDataStepInput, EvaluationRunDataStepInputKey, EvaluationRunDataStepInputOrigin, EvaluationRunDataStepInputType, EvaluationRunDataStepOutput, EvaluationRunDataStepOutputOrigin, EvaluationRunDataStepOutputType, EvaluationRunEdit, EvaluationRunFlags, EvaluationRunIdResponse, EvaluationRunIdsRequest, EvaluationRunIdsResponse, EvaluationRunQuery, EvaluationRunQueryFlags, EvaluationRunResponse, EvaluationRunsResponse, EvaluationScenario, EvaluationScenarioCreate, EvaluationScenarioEdit, EvaluationScenarioIdResponse, EvaluationScenarioIdsResponse, EvaluationScenarioQuery, EvaluationScenarioResponse, EvaluationScenariosResponse, EvaluationStatus, Evaluator, EvaluatorArtifactFlags, EvaluatorArtifactQueryFlags, EvaluatorCatalogPreset, EvaluatorCatalogPresetResponse, EvaluatorCatalogPresetsResponse, EvaluatorCatalogTemplate, EvaluatorCatalogTemplateResponse, EvaluatorCatalogTemplatesResponse, EvaluatorCatalogType, EvaluatorCatalogTypesResponse, EvaluatorCreate, EvaluatorEdit, EvaluatorFlags, EvaluatorQuery, EvaluatorResponse, EvaluatorRevisionCommit, EvaluatorRevisionCreate, EvaluatorRevisionDataInput, EvaluatorRevisionDataInputHeadersValue, EvaluatorRevisionDataInputRuntime, EvaluatorRevisionDataOutput, EvaluatorRevisionDataOutputHeadersValue, EvaluatorRevisionDataOutputRuntime, EvaluatorRevisionEdit, EvaluatorRevisionFlags, EvaluatorRevisionInput, EvaluatorRevisionOutput, EvaluatorRevisionQuery, EvaluatorRevisionQueryFlags, EvaluatorRevisionResolveResponse, EvaluatorRevisionResponse, EvaluatorRevisionsLog, EvaluatorRevisionsResponse, EvaluatorTemplate, EvaluatorTemplatesResponse, EvaluatorVariant, EvaluatorVariantCreate, EvaluatorVariantEdit, EvaluatorVariantFlags, EvaluatorVariantFork, EvaluatorVariantResponse, EvaluatorVariantsResponse, EvaluatorsResponse, Event, EventQuery, EventType, EventsQueryResponse, ExistenceOperator, FilteringInput, FilteringInputConditionsItem, FilteringOutput, FilteringOutputConditionsItem, Focus, Folder, FolderCreate, FolderEdit, FolderIdResponse, FolderKind, FolderQuery, FolderQueryKinds, FolderResponse, FoldersResponse, Format, Formatting, FullJsonInput, FullJsonOutput, GatewayToolConfig, GatewayToolConfigPermission, HarnessSessionRecord, Header, HttpValidationError, InviteRequest, Invocation, InvocationCreate, InvocationCreateLinks, InvocationEdit, InvocationEditLinks, InvocationLinkResponse, InvocationLinks, InvocationQuery, InvocationQueryLinks, InvocationResponse, InvocationsResponse, JsonSchemasInput, JsonSchemasOutput, LabelJsonInput, LabelJsonOutput, LegacyLifecycleDto, ListApiKeysResponse, ListOperator, ListOptions, LogicalOperator, MetricSpec, MetricType, MetricsBucket, Mount, MountCreate, MountCredentials, MountCredentialsResponse, MountData, MountEdit, MountFileDeletedResponse, MountFileWrittenResponse, MountFlags, MountFolderCreatedResponse, MountQuery, MountResponse, MountsResponse, NumericOperator, OTelEventInput, OTelEventInputTimestamp, OTelEventOutput, OTelEventOutputTimestamp, OTelHashInput, OTelHashOutput, OTelLinkInput, OTelLinkOutput, OTelLinksResponse, OTelReferenceInput, OTelReferenceOutput, OTelSpanKind, OTelStatusCode, OTelTracingRequest, OTelTracingResponse, OldAnalyticsResponse, Organization, OrganizationDetails, OrganizationDomainResponse, OrganizationProviderResponse, OrganizationUpdate, Permission, PlaygroundBuildKitContext, ProjectsResponse, QueriesResponse, Query, QueryCreate, QueryEdit, QueryFlags, QueryQueryFlags, QueryResponse, QueryRevision, QueryRevisionCommit, QueryRevisionCreate, QueryRevisionDataInput, QueryRevisionDataOutput, QueryRevisionEdit, QueryRevisionQuery, QueryRevisionResponse, QueryRevisionsLog, QueryRevisionsResponse, QueryVariant, QueryVariantCreate, QueryVariantEdit, QueryVariantFork, QueryVariantQuery, QueryVariantResponse, QueryVariantsResponse, Reference, ReferenceRequestModelInput, ReferenceRequestModelOutput, RequestType, ResolutionInfo, ResolvedTool, RetrievalInfo, SecretDto, SecretDtoData, SecretKind, SecretResponseDto, SecretResponseDtoData, Selector, SessionHeartbeatResponseModel, SessionIdsResponse, SessionInteraction, SessionInteractionData, SessionInteractionFlags, SessionInteractionKind, SessionInteractionQuery, SessionInteractionQueryFlags, SessionInteractionResponse, SessionInteractionStatus, SessionInteractionsResponse, SessionMount, SessionMountQuery, SessionMountsResponse, SessionRecord, SessionRecordResponse, SessionRecordsQueryResponse, SessionState, SessionStateData, SessionStateFlags, SessionStateResponse, SessionStateUpsertRequest, SessionStream, SessionStreamCommandResponseModel, SessionStreamFlags, SessionStreamResponseModel, SessionStreamsResponseModel, SimpleApplication, SimpleApplicationAdditionalContext, SimpleApplicationCreate, SimpleApplicationDataInput, SimpleApplicationDataInputHeadersValue, SimpleApplicationDataInputRuntime, SimpleApplicationDataOutput, SimpleApplicationDataOutputHeadersValue, SimpleApplicationDataOutputRuntime, SimpleApplicationEdit, SimpleApplicationFlags, SimpleApplicationQuery, SimpleApplicationQueryFlags, SimpleApplicationResponse, SimpleApplicationsResponse, SimpleEnvironment, SimpleEnvironmentCreate, SimpleEnvironmentEdit, SimpleEnvironmentQuery, SimpleEnvironmentResponse, SimpleEnvironmentsResponse, SimpleEvaluation, SimpleEvaluationCreate, SimpleEvaluationData, SimpleEvaluationDataApplicationSteps, SimpleEvaluationDataApplicationStepsOneValue, SimpleEvaluationDataEvaluatorSteps, SimpleEvaluationDataEvaluatorStepsOneValue, SimpleEvaluationDataQuerySteps, SimpleEvaluationDataQueryStepsOneValue, SimpleEvaluationDataTestsetSteps, SimpleEvaluationDataTestsetStepsOneValue, SimpleEvaluationEdit, SimpleEvaluationIdResponse, SimpleEvaluationQuery, SimpleEvaluationResponse, SimpleEvaluationsResponse, SimpleEvaluator, SimpleEvaluatorCreate, SimpleEvaluatorDataInput, SimpleEvaluatorDataInputHeadersValue, SimpleEvaluatorDataInputRuntime, SimpleEvaluatorDataOutput, SimpleEvaluatorDataOutputHeadersValue, SimpleEvaluatorDataOutputRuntime, SimpleEvaluatorEdit, SimpleEvaluatorFlags, SimpleEvaluatorQuery, SimpleEvaluatorQueryFlags, SimpleEvaluatorResponse, SimpleEvaluatorsResponse, SimpleQueriesResponse, SimpleQuery, SimpleQueryCreate, SimpleQueryEdit, SimpleQueryQuery, SimpleQueryResponse, SimpleQueue, SimpleQueueCreate, SimpleQueueData, SimpleQueueDataEvaluators, SimpleQueueDataEvaluatorsOneValue, SimpleQueueIdResponse, SimpleQueueIdsResponse, SimpleQueueKind, SimpleQueueQuery, SimpleQueueResponse, SimpleQueueScenariosQuery, SimpleQueueScenariosResponse, SimpleQueueSettings, SimpleQueuesResponse, SimpleTestset, SimpleTestsetCreate, SimpleTestsetEdit, SimpleTestsetQuery, SimpleTestsetResponse, SimpleTestsetsResponse, SimpleTrace, SimpleTraceChannel, SimpleTraceCreate, SimpleTraceCreateLinks, SimpleTraceEdit, SimpleTraceEditLinks, SimpleTraceKind, SimpleTraceLinkResponse, SimpleTraceLinks, SimpleTraceOrigin, SimpleTraceQuery, SimpleTraceQueryLinks, SimpleTraceReferences, SimpleTraceResponse, SimpleTracesResponse, SimpleWorkflow, SimpleWorkflowCreate, SimpleWorkflowDataInput, SimpleWorkflowDataInputHeadersValue, SimpleWorkflowDataInputRuntime, SimpleWorkflowDataOutput, SimpleWorkflowDataOutputHeadersValue, SimpleWorkflowDataOutputRuntime, SimpleWorkflowEdit, SimpleWorkflowFlags, SimpleWorkflowQuery, SimpleWorkflowQueryFlags, SimpleWorkflowResponse, SimpleWorkflowsResponse, SpanInput, SpanInputEndTime, SpanInputStartTime, SpanOutput, SpanOutputEndTime, SpanOutputStartTime, SpanResponse, SpanType, SpansNodeInput, SpansNodeInputEndTime, SpansNodeInputSpansValue, SpansNodeInputStartTime, SpansNodeOutput, SpansNodeOutputEndTime, SpansNodeOutputSpansValue, SpansNodeOutputStartTime, SpansResponse, SpansTreeInput, SpansTreeInputSpansValue, SpansTreeOutput, SpansTreeOutputSpansValue, SsoProviderDto, SsoProviderInfo, SsoProviderSettingsDto, SsoProviders, StandardProviderDto, StandardProviderKind, StandardProviderSettingsDto, Status, StringOperator, TestcaseInput, TestcaseOutput, TestcaseResponse, TestcasesResponse, Testset, TestsetCreate, TestsetEdit, TestsetFlags, TestsetQuery, TestsetResponse, TestsetRevision, TestsetRevisionCommit, TestsetRevisionCreate, TestsetRevisionDataInput, TestsetRevisionDataOutput, TestsetRevisionDelta, TestsetRevisionDeltaColumns, TestsetRevisionDeltaRows, TestsetRevisionEdit, TestsetRevisionQuery, TestsetRevisionResponse, TestsetRevisionsLog, TestsetRevisionsResponse, TestsetVariant, TestsetVariantCreate, TestsetVariantEdit, TestsetVariantFork, TestsetVariantQuery, TestsetVariantResponse, TestsetVariantsResponse, TestsetsResponse, TextOptions, ToolAuthScheme, ToolCallData, ToolCallFunction, ToolCallResponse, ToolCatalogAction, ToolCatalogActionDetails, ToolCatalogActionResponse, ToolCatalogActionResponseAction, ToolCatalogActionsResponse, ToolCatalogActionsResponseActionsItem, ToolCatalogCategoriesResponse, ToolCatalogCategory, ToolCatalogIntegration, ToolCatalogIntegrationDetails, ToolCatalogIntegrationResponse, ToolCatalogIntegrationResponseIntegration, ToolCatalogIntegrationsResponse, ToolCatalogIntegrationsResponseIntegrationsItem, ToolCatalogProvider, ToolCatalogProviderDetails, ToolCatalogProviderResponse, ToolCatalogProviderResponseProvider, ToolCatalogProvidersResponse, ToolCatalogProvidersResponseProvidersItem, ToolConnection, ToolConnectionCreate, ToolConnectionCreateData, ToolConnectionResponse, ToolConnectionState, ToolConnectionStatus, ToolConnectionsResponse, ToolProviderKind, ToolResolveResponse, ToolResult, ToolResultData, TraceIdResponse, TraceIdsResponse, TraceInput, TraceInputSpansValue, TraceOutput, TraceOutputSpansValue, TraceRequest, TraceResponse, TraceType, TracesRequest, TracesResponse, TracingQuery, TriggerAuthScheme, TriggerCapabilitiesResult, TriggerCapability, TriggerCapabilityConnection, TriggerCatalogEvent, TriggerCatalogEventDetails, TriggerCatalogEventResponse, TriggerCatalogEventsResponse, TriggerCatalogIntegration, TriggerCatalogIntegrationResponse, TriggerCatalogIntegrationsResponse, TriggerCatalogProvider, TriggerCatalogProviderResponse, TriggerCatalogProvidersResponse, TriggerConnectAffordance, TriggerConnection, TriggerConnectionCreate, TriggerConnectionCreateData, TriggerConnectionRequirement, TriggerConnectionResponse, TriggerConnectionStatus, TriggerConnectionsResponse, TriggerDeliveriesResponse, TriggerDelivery, TriggerDeliveryData, TriggerDeliveryQuery, TriggerDeliveryResponse, TriggerDiscoveryConnectionState, TriggerDiscoveryGuidance, TriggerEventAck, TriggerProviderKind, TriggerSchedule, TriggerScheduleCreate, TriggerScheduleData, TriggerScheduleDataInputsFields, TriggerScheduleEdit, TriggerScheduleFlags, TriggerScheduleQuery, TriggerScheduleResponse, TriggerSchedulesResponse, TriggerSubscription, TriggerSubscriptionCreate, TriggerSubscriptionCreateRequest, TriggerSubscriptionData, TriggerSubscriptionDataInputsFields, TriggerSubscriptionEdit, TriggerSubscriptionFlags, TriggerSubscriptionQuery, TriggerSubscriptionResponse, TriggerSubscriptionsResponse, UserIdsResponse, ValidationError, ValidationErrorLocItem, WebhookDeliveriesResponse, WebhookDelivery, WebhookDeliveryCreate, WebhookDeliveryData, WebhookDeliveryQuery, WebhookDeliveryResponse, WebhookDeliveryResponseInfo, WebhookEventType, WebhookProviderDto, WebhookProviderSettingsDto, WebhookSubscription, WebhookSubscriptionCreate, WebhookSubscriptionData, WebhookSubscriptionDataAuthMode, WebhookSubscriptionEdit, WebhookSubscriptionFlags, WebhookSubscriptionQuery, WebhookSubscriptionResponse, WebhookSubscriptionsResponse, Windowing, WindowingOrder, Workflow, WorkflowArtifactFlags, WorkflowCatalogFlags, WorkflowCatalogHarness, WorkflowCatalogHarnessResponse, WorkflowCatalogHarnessesResponse, WorkflowCatalogPreset, WorkflowCatalogPresetResponse, WorkflowCatalogPresetsResponse, WorkflowCatalogTemplate, WorkflowCatalogTemplateResponse, WorkflowCatalogTemplatesResponse, WorkflowCatalogType, WorkflowCatalogTypeResponse, WorkflowCatalogTypesResponse, WorkflowCreate, WorkflowEdit, WorkflowFlags, WorkflowResponse, WorkflowRevisionCommit, WorkflowRevisionCreate, WorkflowRevisionDataInput, WorkflowRevisionDataInputHeadersValue, WorkflowRevisionDataInputRuntime, WorkflowRevisionDataOutput, WorkflowRevisionDataOutputHeadersValue, WorkflowRevisionDataOutputRuntime, WorkflowRevisionDelta, WorkflowRevisionEdit, WorkflowRevisionFlags, WorkflowRevisionInput, WorkflowRevisionOutput, WorkflowRevisionResolveResponse, WorkflowRevisionResponse, WorkflowRevisionsLog, WorkflowRevisionsResponse, WorkflowVariant, WorkflowVariantCreate, WorkflowVariantEdit, WorkflowVariantFlags, WorkflowVariantFork, WorkflowVariantResponse, WorkflowVariantsResponse, WorkflowsResponse, Workspace, WorkspaceMemberResponse, WorkspacePermission, WorkspaceResponse + from .types import AdminAccountCreateOptions, AdminAccountRead, AdminAccountsCreate, AdminAccountsDelete, AdminAccountsDeleteTarget, AdminAccountsResponse, AdminApiKeyCreate, AdminApiKeyResponse, AdminDeleteResponse, AdminDeletedEntities, AdminDeletedEntity, AdminOrganizationCreate, AdminOrganizationMembershipCreate, AdminOrganizationMembershipRead, AdminOrganizationRead, AdminProjectCreate, AdminProjectMembershipCreate, AdminProjectMembershipRead, AdminProjectRead, AdminSimpleAccountCreate, AdminSimpleAccountDeleteEntry, AdminSimpleAccountRead, AdminSimpleAccountsApiKeysCreate, AdminSimpleAccountsCreate, AdminSimpleAccountsDelete, AdminSimpleAccountsOrganizationsCreate, AdminSimpleAccountsOrganizationsMembershipsCreate, AdminSimpleAccountsOrganizationsTransferOwnership, AdminSimpleAccountsOrganizationsTransferOwnershipIncludeProjects, AdminSimpleAccountsOrganizationsTransferOwnershipIncludeProjectsZero, AdminSimpleAccountsOrganizationsTransferOwnershipIncludeWorkspaces, AdminSimpleAccountsOrganizationsTransferOwnershipIncludeWorkspacesZero, AdminSimpleAccountsOrganizationsTransferOwnershipResponse, AdminSimpleAccountsProjectsCreate, AdminSimpleAccountsProjectsMembershipsCreate, AdminSimpleAccountsResponse, AdminSimpleAccountsUsersCreate, AdminSimpleAccountsUsersIdentitiesCreate, AdminSimpleAccountsUsersResetPassword, AdminSimpleAccountsWorkspacesCreate, AdminSimpleAccountsWorkspacesMembershipsCreate, AdminStructuredError, AdminSubscriptionCreate, AdminSubscriptionRead, AdminUserCreate, AdminUserIdentityCreate, AdminUserIdentityRead, AdminUserIdentityReadStatus, AdminUserRead, AdminWorkspaceCreate, AdminWorkspaceMembershipCreate, AdminWorkspaceMembershipRead, AdminWorkspaceRead, AgentTemplateOverlay, Analytics, AnalyticsResponse, Annotation, AnnotationCreate, AnnotationCreateLinks, AnnotationEdit, AnnotationEditLinks, AnnotationLinkResponse, AnnotationLinks, AnnotationQuery, AnnotationQueryLinks, AnnotationResponse, AnnotationsResponse, Application, ApplicationArtifactFlags, ApplicationArtifactQueryFlags, ApplicationCatalogPreset, ApplicationCatalogPresetResponse, ApplicationCatalogPresetsResponse, ApplicationCatalogTemplate, ApplicationCatalogTemplateResponse, ApplicationCatalogTemplatesResponse, ApplicationCatalogType, ApplicationCatalogTypesResponse, ApplicationCreate, ApplicationEdit, ApplicationFlags, ApplicationQuery, ApplicationResponse, ApplicationRevisionCommit, ApplicationRevisionCreate, ApplicationRevisionDataInput, ApplicationRevisionDataInputHeadersValue, ApplicationRevisionDataInputRuntime, ApplicationRevisionDataOutput, ApplicationRevisionDataOutputHeadersValue, ApplicationRevisionDataOutputRuntime, ApplicationRevisionEdit, ApplicationRevisionFlags, ApplicationRevisionInput, ApplicationRevisionOutput, ApplicationRevisionQuery, ApplicationRevisionQueryFlags, ApplicationRevisionResolveResponse, ApplicationRevisionResponse, ApplicationRevisionsLog, ApplicationRevisionsResponse, ApplicationVariant, ApplicationVariantCreate, ApplicationVariantEdit, ApplicationVariantFlags, ApplicationVariantFork, ApplicationVariantResponse, ApplicationVariantsResponse, ApplicationsResponse, BodyConfigsFetchVariantsConfigsFetchPost, Bucket, BuiltinToolConfig, BuiltinToolConfigPermission, CapabilitiesResult, Capability, CapabilityConnection, CapabilityGuidance, CollectStatusResponse, CommandMode, ComparisonOperator, Condition, ConditionOperator, ConditionOptions, ConditionValue, ConfigResponseModel, ConnectAffordance, ConnectionRequirement, CustomModelSettingsDto, CustomProviderDto, CustomProviderKind, CustomProviderSettingsDto, CustomSecretDto, CustomSecretFormat, CustomSecretSettingsDto, CustomSecretSettingsDtoContent, CustomSecretSettingsDtoContentOneValue, DictOperator, DiscoverResponse, DiscoverResponseMethodsValue, DiscoveredAlternative, DiscoveredTool, DiscoveredToolType, DiscoveredTriggerAlternative, DiscoveredTriggerEvent, DiscoveredTriggerEventType, EntityRef, Environment, EnvironmentCreate, EnvironmentEdit, EnvironmentFlags, EnvironmentQueryFlags, EnvironmentResponse, EnvironmentRevisionCommit, EnvironmentRevisionCreate, EnvironmentRevisionData, EnvironmentRevisionDelta, EnvironmentRevisionEdit, EnvironmentRevisionInput, EnvironmentRevisionOutput, EnvironmentRevisionResolveResponse, EnvironmentRevisionResponse, EnvironmentRevisionsLog, EnvironmentRevisionsResponse, EnvironmentVariant, EnvironmentVariantCreate, EnvironmentVariantEdit, EnvironmentVariantFork, EnvironmentVariantResponse, EnvironmentVariantsResponse, EnvironmentsResponse, ErrorPolicy, EvaluationMetrics, EvaluationMetricsCreate, EvaluationMetricsIdsResponse, EvaluationMetricsQuery, EvaluationMetricsQueryScenarioIds, EvaluationMetricsQueryTimestamps, EvaluationMetricsRefresh, EvaluationMetricsResponse, EvaluationMetricsSetRequest, EvaluationQueue, EvaluationQueueCreate, EvaluationQueueData, EvaluationQueueEdit, EvaluationQueueFlags, EvaluationQueueIdResponse, EvaluationQueueIdsResponse, EvaluationQueueQuery, EvaluationQueueQueryFlags, EvaluationQueueResponse, EvaluationQueueScenariosQuery, EvaluationQueuesResponse, EvaluationResult, EvaluationResultCreate, EvaluationResultIdResponse, EvaluationResultIdsResponse, EvaluationResultQuery, EvaluationResultResponse, EvaluationResultsResponse, EvaluationResultsSetRequest, EvaluationRun, EvaluationRunCreate, EvaluationRunDataConcurrency, EvaluationRunDataInput, EvaluationRunDataMapping, EvaluationRunDataMappingColumn, EvaluationRunDataMappingStep, EvaluationRunDataOutput, EvaluationRunDataStepInput, EvaluationRunDataStepInputKey, EvaluationRunDataStepInputOrigin, EvaluationRunDataStepInputType, EvaluationRunDataStepOutput, EvaluationRunDataStepOutputOrigin, EvaluationRunDataStepOutputType, EvaluationRunEdit, EvaluationRunFlags, EvaluationRunIdResponse, EvaluationRunIdsRequest, EvaluationRunIdsResponse, EvaluationRunQuery, EvaluationRunQueryFlags, EvaluationRunResponse, EvaluationRunsResponse, EvaluationScenario, EvaluationScenarioCreate, EvaluationScenarioEdit, EvaluationScenarioIdResponse, EvaluationScenarioIdsResponse, EvaluationScenarioQuery, EvaluationScenarioResponse, EvaluationScenariosResponse, EvaluationStatus, Evaluator, EvaluatorArtifactFlags, EvaluatorArtifactQueryFlags, EvaluatorCatalogPreset, EvaluatorCatalogPresetResponse, EvaluatorCatalogPresetsResponse, EvaluatorCatalogTemplate, EvaluatorCatalogTemplateResponse, EvaluatorCatalogTemplatesResponse, EvaluatorCatalogType, EvaluatorCatalogTypesResponse, EvaluatorCreate, EvaluatorEdit, EvaluatorFlags, EvaluatorQuery, EvaluatorResponse, EvaluatorRevisionCommit, EvaluatorRevisionCreate, EvaluatorRevisionDataInput, EvaluatorRevisionDataInputHeadersValue, EvaluatorRevisionDataInputRuntime, EvaluatorRevisionDataOutput, EvaluatorRevisionDataOutputHeadersValue, EvaluatorRevisionDataOutputRuntime, EvaluatorRevisionEdit, EvaluatorRevisionFlags, EvaluatorRevisionInput, EvaluatorRevisionOutput, EvaluatorRevisionQuery, EvaluatorRevisionQueryFlags, EvaluatorRevisionResolveResponse, EvaluatorRevisionResponse, EvaluatorRevisionsLog, EvaluatorRevisionsResponse, EvaluatorTemplate, EvaluatorTemplatesResponse, EvaluatorVariant, EvaluatorVariantCreate, EvaluatorVariantEdit, EvaluatorVariantFlags, EvaluatorVariantFork, EvaluatorVariantResponse, EvaluatorVariantsResponse, EvaluatorsResponse, Event, EventQuery, EventType, EventsQueryResponse, ExistenceOperator, FilteringInput, FilteringInputConditionsItem, FilteringOutput, FilteringOutputConditionsItem, Focus, Folder, FolderCreate, FolderEdit, FolderIdResponse, FolderKind, FolderQuery, FolderQueryKinds, FolderResponse, FoldersResponse, Format, Formatting, FullJsonInput, FullJsonOutput, GatewayToolConfig, GatewayToolConfigPermission, HarnessKind, Header, HttpValidationError, InviteRequest, Invocation, InvocationCreate, InvocationCreateLinks, InvocationEdit, InvocationEditLinks, InvocationLinkResponse, InvocationLinks, InvocationQuery, InvocationQueryLinks, InvocationResponse, InvocationsResponse, JsonSchemasInput, JsonSchemasOutput, LabelJsonInput, LabelJsonOutput, LegacyLifecycleDto, ListApiKeysResponse, ListOperator, ListOptions, LogicalOperator, MetricSpec, MetricType, MetricsBucket, Mount, MountCreate, MountCredentials, MountCredentialsResponse, MountData, MountEdit, MountFileDeletedResponse, MountFileWrittenResponse, MountFlags, MountFolderCreatedResponse, MountQuery, MountResponse, MountsResponse, NumericOperator, OTelEventInput, OTelEventInputTimestamp, OTelEventOutput, OTelEventOutputTimestamp, OTelHashInput, OTelHashOutput, OTelLinkInput, OTelLinkOutput, OTelLinksResponse, OTelReferenceInput, OTelReferenceOutput, OTelSpanKind, OTelStatusCode, OTelTracingRequest, OTelTracingResponse, OldAnalyticsResponse, Organization, OrganizationDetails, OrganizationDomainResponse, OrganizationProviderResponse, OrganizationUpdate, Permission, PlaygroundBuildKitContext, ProjectsResponse, QueriesResponse, Query, QueryCreate, QueryEdit, QueryFlags, QueryQueryFlags, QueryResponse, QueryRevision, QueryRevisionCommit, QueryRevisionCreate, QueryRevisionDataInput, QueryRevisionDataOutput, QueryRevisionEdit, QueryRevisionQuery, QueryRevisionResponse, QueryRevisionsLog, QueryRevisionsResponse, QueryVariant, QueryVariantCreate, QueryVariantEdit, QueryVariantFork, QueryVariantQuery, QueryVariantResponse, QueryVariantsResponse, Reference, ReferenceRequestModelInput, ReferenceRequestModelOutput, RequestType, ResolutionInfo, ResolvedTool, RetrievalInfo, SecretDto, SecretDtoData, SecretKind, SecretResponseDto, SecretResponseDtoData, Selector, SessionHeartbeatResult, SessionIdsResponse, SessionInteraction, SessionInteractionData, SessionInteractionFlags, SessionInteractionKind, SessionInteractionQuery, SessionInteractionQueryFlags, SessionInteractionResponse, SessionInteractionStatus, SessionInteractionsResponse, SessionMount, SessionMountQuery, SessionMountsResponse, SessionRecord, SessionRecordResponse, SessionRecordsQueryResponse, SessionResponse, SessionStream, SessionStreamCommandResponse, SessionStreamFlags, SessionStreamHeaderEdit, SessionStreamResponse, SessionStreamsResponse, SessionTurn, SessionTurnQuery, SessionTurnResponse, SessionTurnsResponse, SessionsResponse, SimpleApplication, SimpleApplicationAdditionalContext, SimpleApplicationCreate, SimpleApplicationDataInput, SimpleApplicationDataInputHeadersValue, SimpleApplicationDataInputRuntime, SimpleApplicationDataOutput, SimpleApplicationDataOutputHeadersValue, SimpleApplicationDataOutputRuntime, SimpleApplicationEdit, SimpleApplicationFlags, SimpleApplicationQuery, SimpleApplicationQueryFlags, SimpleApplicationResponse, SimpleApplicationsResponse, SimpleEnvironment, SimpleEnvironmentCreate, SimpleEnvironmentEdit, SimpleEnvironmentQuery, SimpleEnvironmentResponse, SimpleEnvironmentsResponse, SimpleEvaluation, SimpleEvaluationCreate, SimpleEvaluationData, SimpleEvaluationDataApplicationSteps, SimpleEvaluationDataApplicationStepsOneValue, SimpleEvaluationDataEvaluatorSteps, SimpleEvaluationDataEvaluatorStepsOneValue, SimpleEvaluationDataQuerySteps, SimpleEvaluationDataQueryStepsOneValue, SimpleEvaluationDataTestsetSteps, SimpleEvaluationDataTestsetStepsOneValue, SimpleEvaluationEdit, SimpleEvaluationIdResponse, SimpleEvaluationQuery, SimpleEvaluationResponse, SimpleEvaluationsResponse, SimpleEvaluator, SimpleEvaluatorCreate, SimpleEvaluatorDataInput, SimpleEvaluatorDataInputHeadersValue, SimpleEvaluatorDataInputRuntime, SimpleEvaluatorDataOutput, SimpleEvaluatorDataOutputHeadersValue, SimpleEvaluatorDataOutputRuntime, SimpleEvaluatorEdit, SimpleEvaluatorFlags, SimpleEvaluatorQuery, SimpleEvaluatorQueryFlags, SimpleEvaluatorResponse, SimpleEvaluatorsResponse, SimpleQueriesResponse, SimpleQuery, SimpleQueryCreate, SimpleQueryEdit, SimpleQueryQuery, SimpleQueryResponse, SimpleQueue, SimpleQueueCreate, SimpleQueueData, SimpleQueueDataEvaluators, SimpleQueueDataEvaluatorsOneValue, SimpleQueueIdResponse, SimpleQueueIdsResponse, SimpleQueueKind, SimpleQueueQuery, SimpleQueueResponse, SimpleQueueScenariosQuery, SimpleQueueScenariosResponse, SimpleQueueSettings, SimpleQueuesResponse, SimpleTestset, SimpleTestsetCreate, SimpleTestsetEdit, SimpleTestsetQuery, SimpleTestsetResponse, SimpleTestsetsResponse, SimpleTrace, SimpleTraceChannel, SimpleTraceCreate, SimpleTraceCreateLinks, SimpleTraceEdit, SimpleTraceEditLinks, SimpleTraceKind, SimpleTraceLinkResponse, SimpleTraceLinks, SimpleTraceOrigin, SimpleTraceQuery, SimpleTraceQueryLinks, SimpleTraceReferences, SimpleTraceResponse, SimpleTracesResponse, SimpleWorkflow, SimpleWorkflowCreate, SimpleWorkflowDataInput, SimpleWorkflowDataInputHeadersValue, SimpleWorkflowDataInputRuntime, SimpleWorkflowDataOutput, SimpleWorkflowDataOutputHeadersValue, SimpleWorkflowDataOutputRuntime, SimpleWorkflowEdit, SimpleWorkflowFlags, SimpleWorkflowQuery, SimpleWorkflowQueryFlags, SimpleWorkflowResponse, SimpleWorkflowsResponse, SpanInput, SpanInputEndTime, SpanInputStartTime, SpanOutput, SpanOutputEndTime, SpanOutputStartTime, SpanResponse, SpanType, SpansNodeInput, SpansNodeInputEndTime, SpansNodeInputSpansValue, SpansNodeInputStartTime, SpansNodeOutput, SpansNodeOutputEndTime, SpansNodeOutputSpansValue, SpansNodeOutputStartTime, SpansResponse, SpansTreeInput, SpansTreeInputSpansValue, SpansTreeOutput, SpansTreeOutputSpansValue, SsoProviderDto, SsoProviderInfo, SsoProviderSettingsDto, SsoProviders, StandardProviderDto, StandardProviderKind, StandardProviderSettingsDto, Status, StringOperator, TestcaseInput, TestcaseOutput, TestcaseResponse, TestcasesResponse, Testset, TestsetCreate, TestsetEdit, TestsetFlags, TestsetQuery, TestsetResponse, TestsetRevision, TestsetRevisionCommit, TestsetRevisionCreate, TestsetRevisionDataInput, TestsetRevisionDataOutput, TestsetRevisionDelta, TestsetRevisionDeltaColumns, TestsetRevisionDeltaRows, TestsetRevisionEdit, TestsetRevisionQuery, TestsetRevisionResponse, TestsetRevisionsLog, TestsetRevisionsResponse, TestsetVariant, TestsetVariantCreate, TestsetVariantEdit, TestsetVariantFork, TestsetVariantQuery, TestsetVariantResponse, TestsetVariantsResponse, TestsetsResponse, TextOptions, ToolAuthScheme, ToolCallData, ToolCallFunction, ToolCallResponse, ToolCatalogAction, ToolCatalogActionDetails, ToolCatalogActionResponse, ToolCatalogActionResponseAction, ToolCatalogActionsResponse, ToolCatalogActionsResponseActionsItem, ToolCatalogCategoriesResponse, ToolCatalogCategory, ToolCatalogIntegration, ToolCatalogIntegrationDetails, ToolCatalogIntegrationResponse, ToolCatalogIntegrationResponseIntegration, ToolCatalogIntegrationsResponse, ToolCatalogIntegrationsResponseIntegrationsItem, ToolCatalogProvider, ToolCatalogProviderDetails, ToolCatalogProviderResponse, ToolCatalogProviderResponseProvider, ToolCatalogProvidersResponse, ToolCatalogProvidersResponseProvidersItem, ToolConnection, ToolConnectionCreate, ToolConnectionCreateData, ToolConnectionResponse, ToolConnectionState, ToolConnectionStatus, ToolConnectionsResponse, ToolProviderKind, ToolResolveResponse, ToolResult, ToolResultData, TraceIdResponse, TraceIdsResponse, TraceInput, TraceInputSpansValue, TraceOutput, TraceOutputSpansValue, TraceRequest, TraceResponse, TraceType, TracesRequest, TracesResponse, TracingQuery, TriggerAuthScheme, TriggerCapabilitiesResult, TriggerCapability, TriggerCapabilityConnection, TriggerCatalogEvent, TriggerCatalogEventDetails, TriggerCatalogEventResponse, TriggerCatalogEventsResponse, TriggerCatalogIntegration, TriggerCatalogIntegrationResponse, TriggerCatalogIntegrationsResponse, TriggerCatalogProvider, TriggerCatalogProviderResponse, TriggerCatalogProvidersResponse, TriggerConnectAffordance, TriggerConnection, TriggerConnectionCreate, TriggerConnectionCreateData, TriggerConnectionRequirement, TriggerConnectionResponse, TriggerConnectionStatus, TriggerConnectionsResponse, TriggerDeliveriesResponse, TriggerDelivery, TriggerDeliveryData, TriggerDeliveryQuery, TriggerDeliveryResponse, TriggerDiscoveryConnectionState, TriggerDiscoveryGuidance, TriggerEventAck, TriggerProviderKind, TriggerSchedule, TriggerScheduleCreate, TriggerScheduleData, TriggerScheduleDataInputsFields, TriggerScheduleEdit, TriggerScheduleFlags, TriggerScheduleQuery, TriggerScheduleResponse, TriggerSchedulesResponse, TriggerSubscription, TriggerSubscriptionCreate, TriggerSubscriptionCreateRequest, TriggerSubscriptionData, TriggerSubscriptionDataInputsFields, TriggerSubscriptionEdit, TriggerSubscriptionFlags, TriggerSubscriptionQuery, TriggerSubscriptionResponse, TriggerSubscriptionsResponse, UserIdsResponse, ValidationError, ValidationErrorLocItem, WebhookDeliveriesResponse, WebhookDelivery, WebhookDeliveryCreate, WebhookDeliveryData, WebhookDeliveryQuery, WebhookDeliveryResponse, WebhookDeliveryResponseInfo, WebhookEventType, WebhookProviderDto, WebhookProviderSettingsDto, WebhookSubscription, WebhookSubscriptionCreate, WebhookSubscriptionData, WebhookSubscriptionDataAuthMode, WebhookSubscriptionEdit, WebhookSubscriptionFlags, WebhookSubscriptionQuery, WebhookSubscriptionResponse, WebhookSubscriptionsResponse, Windowing, WindowingOrder, Workflow, WorkflowArtifactFlags, WorkflowCatalogFlags, WorkflowCatalogHarness, WorkflowCatalogHarnessResponse, WorkflowCatalogHarnessesResponse, WorkflowCatalogPreset, WorkflowCatalogPresetResponse, WorkflowCatalogPresetsResponse, WorkflowCatalogTemplate, WorkflowCatalogTemplateResponse, WorkflowCatalogTemplatesResponse, WorkflowCatalogType, WorkflowCatalogTypeResponse, WorkflowCatalogTypesResponse, WorkflowCreate, WorkflowEdit, WorkflowFlags, WorkflowRequestData, WorkflowResponse, WorkflowRevisionCommit, WorkflowRevisionCreate, WorkflowRevisionDataInput, WorkflowRevisionDataInputHeadersValue, WorkflowRevisionDataInputRuntime, WorkflowRevisionDataOutput, WorkflowRevisionDataOutputHeadersValue, WorkflowRevisionDataOutputRuntime, WorkflowRevisionDelta, WorkflowRevisionEdit, WorkflowRevisionFlags, WorkflowRevisionInput, WorkflowRevisionOutput, WorkflowRevisionResolveResponse, WorkflowRevisionResponse, WorkflowRevisionsLog, WorkflowRevisionsResponse, WorkflowVariant, WorkflowVariantCreate, WorkflowVariantEdit, WorkflowVariantFlags, WorkflowVariantFork, WorkflowVariantResponse, WorkflowVariantsResponse, WorkflowsResponse, Workspace, WorkspaceMemberResponse, WorkspacePermission, WorkspaceResponse from .errors import UnprocessableEntityError from . import access, annotations, applications, billing, environments, evaluations, evaluators, events, folders, invocations, keys, legacy, mounts, organizations, projects, queries, secrets, sessions, status, testcases, testsets, tools, traces, triggers, users, webhooks, workflows, workspaces from .applications import QueryApplicationVariantsRequestOrder @@ -20,7 +20,7 @@ from .traces import QuerySpansAnalyticsRequestNewest, QuerySpansAnalyticsRequestOldest from .webhooks import WebhookSubscriptionTestRequestSubscription from .workflows import QueryWorkflowRevisionsRequestOrder, QueryWorkflowVariantsRequestOrder, QueryWorkflowsRequestOrder -_dynamic_imports: typing.Dict[str, str] = {"AdminAccountCreateOptions": ".types", "AdminAccountRead": ".types", "AdminAccountsCreate": ".types", "AdminAccountsDelete": ".types", "AdminAccountsDeleteTarget": ".types", "AdminAccountsResponse": ".types", "AdminApiKeyCreate": ".types", "AdminApiKeyResponse": ".types", "AdminDeleteResponse": ".types", "AdminDeletedEntities": ".types", "AdminDeletedEntity": ".types", "AdminOrganizationCreate": ".types", "AdminOrganizationMembershipCreate": ".types", "AdminOrganizationMembershipRead": ".types", "AdminOrganizationRead": ".types", "AdminProjectCreate": ".types", "AdminProjectMembershipCreate": ".types", "AdminProjectMembershipRead": ".types", "AdminProjectRead": ".types", "AdminSimpleAccountCreate": ".types", "AdminSimpleAccountDeleteEntry": ".types", "AdminSimpleAccountRead": ".types", "AdminSimpleAccountsApiKeysCreate": ".types", "AdminSimpleAccountsCreate": ".types", "AdminSimpleAccountsDelete": ".types", "AdminSimpleAccountsOrganizationsCreate": ".types", "AdminSimpleAccountsOrganizationsMembershipsCreate": ".types", "AdminSimpleAccountsOrganizationsTransferOwnership": ".types", "AdminSimpleAccountsOrganizationsTransferOwnershipIncludeProjects": ".types", "AdminSimpleAccountsOrganizationsTransferOwnershipIncludeProjectsZero": ".types", "AdminSimpleAccountsOrganizationsTransferOwnershipIncludeWorkspaces": ".types", "AdminSimpleAccountsOrganizationsTransferOwnershipIncludeWorkspacesZero": ".types", "AdminSimpleAccountsOrganizationsTransferOwnershipResponse": ".types", "AdminSimpleAccountsProjectsCreate": ".types", "AdminSimpleAccountsProjectsMembershipsCreate": ".types", "AdminSimpleAccountsResponse": ".types", "AdminSimpleAccountsUsersCreate": ".types", "AdminSimpleAccountsUsersIdentitiesCreate": ".types", "AdminSimpleAccountsUsersResetPassword": ".types", "AdminSimpleAccountsWorkspacesCreate": ".types", "AdminSimpleAccountsWorkspacesMembershipsCreate": ".types", "AdminStructuredError": ".types", "AdminSubscriptionCreate": ".types", "AdminSubscriptionRead": ".types", "AdminUserCreate": ".types", "AdminUserIdentityCreate": ".types", "AdminUserIdentityRead": ".types", "AdminUserIdentityReadStatus": ".types", "AdminUserRead": ".types", "AdminWorkspaceCreate": ".types", "AdminWorkspaceMembershipCreate": ".types", "AdminWorkspaceMembershipRead": ".types", "AdminWorkspaceRead": ".types", "AgentTemplateOverlay": ".types", "AgentaApi": ".client", "AgentaApiEnvironment": ".environment", "Analytics": ".types", "AnalyticsResponse": ".types", "Annotation": ".types", "AnnotationCreate": ".types", "AnnotationCreateLinks": ".types", "AnnotationEdit": ".types", "AnnotationEditLinks": ".types", "AnnotationLinkResponse": ".types", "AnnotationLinks": ".types", "AnnotationQuery": ".types", "AnnotationQueryLinks": ".types", "AnnotationResponse": ".types", "AnnotationsResponse": ".types", "Application": ".types", "ApplicationArtifactFlags": ".types", "ApplicationArtifactQueryFlags": ".types", "ApplicationCatalogPreset": ".types", "ApplicationCatalogPresetResponse": ".types", "ApplicationCatalogPresetsResponse": ".types", "ApplicationCatalogTemplate": ".types", "ApplicationCatalogTemplateResponse": ".types", "ApplicationCatalogTemplatesResponse": ".types", "ApplicationCatalogType": ".types", "ApplicationCatalogTypesResponse": ".types", "ApplicationCreate": ".types", "ApplicationEdit": ".types", "ApplicationFlags": ".types", "ApplicationQuery": ".types", "ApplicationResponse": ".types", "ApplicationRevisionCommit": ".types", "ApplicationRevisionCreate": ".types", "ApplicationRevisionDataInput": ".types", "ApplicationRevisionDataInputHeadersValue": ".types", "ApplicationRevisionDataInputRuntime": ".types", "ApplicationRevisionDataOutput": ".types", "ApplicationRevisionDataOutputHeadersValue": ".types", "ApplicationRevisionDataOutputRuntime": ".types", "ApplicationRevisionEdit": ".types", "ApplicationRevisionFlags": ".types", "ApplicationRevisionInput": ".types", "ApplicationRevisionOutput": ".types", "ApplicationRevisionQuery": ".types", "ApplicationRevisionQueryFlags": ".types", "ApplicationRevisionResolveResponse": ".types", "ApplicationRevisionResponse": ".types", "ApplicationRevisionsLog": ".types", "ApplicationRevisionsResponse": ".types", "ApplicationVariant": ".types", "ApplicationVariantCreate": ".types", "ApplicationVariantEdit": ".types", "ApplicationVariantFlags": ".types", "ApplicationVariantFork": ".types", "ApplicationVariantResponse": ".types", "ApplicationVariantsResponse": ".types", "ApplicationsResponse": ".types", "AsyncAgentaApi": ".client", "BodyConfigsFetchVariantsConfigsFetchPost": ".types", "Bucket": ".types", "BuiltinToolConfig": ".types", "BuiltinToolConfigPermission": ".types", "CapabilitiesResult": ".types", "Capability": ".types", "CapabilityConnection": ".types", "CapabilityGuidance": ".types", "CollectStatusResponse": ".types", "CommandMode": ".types", "ComparisonOperator": ".types", "Condition": ".types", "ConditionOperator": ".types", "ConditionOptions": ".types", "ConditionValue": ".types", "ConfigResponseModel": ".types", "ConnectAffordance": ".types", "ConnectionRequirement": ".types", "CreateSimpleTestsetFromFileRequestFileType": ".testsets", "CreateTestsetRevisionFromFileRequestFileType": ".testsets", "CustomModelSettingsDto": ".types", "CustomProviderDto": ".types", "CustomProviderKind": ".types", "CustomProviderSettingsDto": ".types", "CustomSecretDto": ".types", "CustomSecretFormat": ".types", "CustomSecretSettingsDto": ".types", "CustomSecretSettingsDtoContent": ".types", "CustomSecretSettingsDtoContentOneValue": ".types", "DictOperator": ".types", "DiscoverResponse": ".types", "DiscoverResponseMethodsValue": ".types", "DiscoveredAlternative": ".types", "DiscoveredTool": ".types", "DiscoveredToolType": ".types", "DiscoveredTriggerAlternative": ".types", "DiscoveredTriggerEvent": ".types", "DiscoveredTriggerEventType": ".types", "EditSimpleTestsetFromFileRequestFileType": ".testsets", "EntityRef": ".types", "Environment": ".types", "EnvironmentCreate": ".types", "EnvironmentEdit": ".types", "EnvironmentFlags": ".types", "EnvironmentQueryFlags": ".types", "EnvironmentResponse": ".types", "EnvironmentRevisionCommit": ".types", "EnvironmentRevisionCreate": ".types", "EnvironmentRevisionData": ".types", "EnvironmentRevisionDelta": ".types", "EnvironmentRevisionEdit": ".types", "EnvironmentRevisionInput": ".types", "EnvironmentRevisionOutput": ".types", "EnvironmentRevisionResolveResponse": ".types", "EnvironmentRevisionResponse": ".types", "EnvironmentRevisionsLog": ".types", "EnvironmentRevisionsResponse": ".types", "EnvironmentVariant": ".types", "EnvironmentVariantCreate": ".types", "EnvironmentVariantEdit": ".types", "EnvironmentVariantFork": ".types", "EnvironmentVariantResponse": ".types", "EnvironmentVariantsResponse": ".types", "EnvironmentsResponse": ".types", "ErrorPolicy": ".types", "EvaluationMetrics": ".types", "EvaluationMetricsCreate": ".types", "EvaluationMetricsIdsResponse": ".types", "EvaluationMetricsQuery": ".types", "EvaluationMetricsQueryScenarioIds": ".types", "EvaluationMetricsQueryTimestamps": ".types", "EvaluationMetricsRefresh": ".types", "EvaluationMetricsResponse": ".types", "EvaluationMetricsSetRequest": ".types", "EvaluationQueue": ".types", "EvaluationQueueCreate": ".types", "EvaluationQueueData": ".types", "EvaluationQueueEdit": ".types", "EvaluationQueueFlags": ".types", "EvaluationQueueIdResponse": ".types", "EvaluationQueueIdsResponse": ".types", "EvaluationQueueQuery": ".types", "EvaluationQueueQueryFlags": ".types", "EvaluationQueueResponse": ".types", "EvaluationQueueScenariosQuery": ".types", "EvaluationQueuesResponse": ".types", "EvaluationResult": ".types", "EvaluationResultCreate": ".types", "EvaluationResultIdResponse": ".types", "EvaluationResultIdsResponse": ".types", "EvaluationResultQuery": ".types", "EvaluationResultResponse": ".types", "EvaluationResultsResponse": ".types", "EvaluationResultsSetRequest": ".types", "EvaluationRun": ".types", "EvaluationRunCreate": ".types", "EvaluationRunDataConcurrency": ".types", "EvaluationRunDataInput": ".types", "EvaluationRunDataMapping": ".types", "EvaluationRunDataMappingColumn": ".types", "EvaluationRunDataMappingStep": ".types", "EvaluationRunDataOutput": ".types", "EvaluationRunDataStepInput": ".types", "EvaluationRunDataStepInputKey": ".types", "EvaluationRunDataStepInputOrigin": ".types", "EvaluationRunDataStepInputType": ".types", "EvaluationRunDataStepOutput": ".types", "EvaluationRunDataStepOutputOrigin": ".types", "EvaluationRunDataStepOutputType": ".types", "EvaluationRunEdit": ".types", "EvaluationRunFlags": ".types", "EvaluationRunIdResponse": ".types", "EvaluationRunIdsRequest": ".types", "EvaluationRunIdsResponse": ".types", "EvaluationRunQuery": ".types", "EvaluationRunQueryFlags": ".types", "EvaluationRunResponse": ".types", "EvaluationRunsResponse": ".types", "EvaluationScenario": ".types", "EvaluationScenarioCreate": ".types", "EvaluationScenarioEdit": ".types", "EvaluationScenarioIdResponse": ".types", "EvaluationScenarioIdsResponse": ".types", "EvaluationScenarioQuery": ".types", "EvaluationScenarioResponse": ".types", "EvaluationScenariosResponse": ".types", "EvaluationStatus": ".types", "Evaluator": ".types", "EvaluatorArtifactFlags": ".types", "EvaluatorArtifactQueryFlags": ".types", "EvaluatorCatalogPreset": ".types", "EvaluatorCatalogPresetResponse": ".types", "EvaluatorCatalogPresetsResponse": ".types", "EvaluatorCatalogTemplate": ".types", "EvaluatorCatalogTemplateResponse": ".types", "EvaluatorCatalogTemplatesResponse": ".types", "EvaluatorCatalogType": ".types", "EvaluatorCatalogTypesResponse": ".types", "EvaluatorCreate": ".types", "EvaluatorEdit": ".types", "EvaluatorFlags": ".types", "EvaluatorQuery": ".types", "EvaluatorResponse": ".types", "EvaluatorRevisionCommit": ".types", "EvaluatorRevisionCreate": ".types", "EvaluatorRevisionDataInput": ".types", "EvaluatorRevisionDataInputHeadersValue": ".types", "EvaluatorRevisionDataInputRuntime": ".types", "EvaluatorRevisionDataOutput": ".types", "EvaluatorRevisionDataOutputHeadersValue": ".types", "EvaluatorRevisionDataOutputRuntime": ".types", "EvaluatorRevisionEdit": ".types", "EvaluatorRevisionFlags": ".types", "EvaluatorRevisionInput": ".types", "EvaluatorRevisionOutput": ".types", "EvaluatorRevisionQuery": ".types", "EvaluatorRevisionQueryFlags": ".types", "EvaluatorRevisionResolveResponse": ".types", "EvaluatorRevisionResponse": ".types", "EvaluatorRevisionsLog": ".types", "EvaluatorRevisionsResponse": ".types", "EvaluatorTemplate": ".types", "EvaluatorTemplatesResponse": ".types", "EvaluatorVariant": ".types", "EvaluatorVariantCreate": ".types", "EvaluatorVariantEdit": ".types", "EvaluatorVariantFlags": ".types", "EvaluatorVariantFork": ".types", "EvaluatorVariantResponse": ".types", "EvaluatorVariantsResponse": ".types", "EvaluatorsResponse": ".types", "Event": ".types", "EventQuery": ".types", "EventType": ".types", "EventsQueryResponse": ".types", "ExistenceOperator": ".types", "FetchLegacyAnalyticsRequestNewest": ".legacy", "FetchLegacyAnalyticsRequestOldest": ".legacy", "FetchSimpleTestsetToFileRequestFileType": ".testsets", "FetchTestsetRevisionToFileRequestFileType": ".testsets", "FilteringInput": ".types", "FilteringInputConditionsItem": ".types", "FilteringOutput": ".types", "FilteringOutputConditionsItem": ".types", "Focus": ".types", "Folder": ".types", "FolderCreate": ".types", "FolderEdit": ".types", "FolderIdResponse": ".types", "FolderKind": ".types", "FolderQuery": ".types", "FolderQueryKinds": ".types", "FolderResponse": ".types", "FoldersResponse": ".types", "Format": ".types", "Formatting": ".types", typing.Any: ".types", typing.Any: ".types", "GatewayToolConfig": ".types", "GatewayToolConfigPermission": ".types", "HarnessSessionRecord": ".types", "Header": ".types", "HttpValidationError": ".types", "InviteRequest": ".types", "Invocation": ".types", "InvocationCreate": ".types", "InvocationCreateLinks": ".types", "InvocationEdit": ".types", "InvocationEditLinks": ".types", "InvocationLinkResponse": ".types", "InvocationLinks": ".types", "InvocationQuery": ".types", "InvocationQueryLinks": ".types", "InvocationResponse": ".types", "InvocationsResponse": ".types", "JsonSchemasInput": ".types", "JsonSchemasOutput": ".types", typing.Any: ".types", typing.Any: ".types", "LegacyLifecycleDto": ".types", "ListApiKeysResponse": ".types", "ListOperator": ".types", "ListOptions": ".types", "LogicalOperator": ".types", "MetricSpec": ".types", "MetricType": ".types", "MetricsBucket": ".types", "Mount": ".types", "MountCreate": ".types", "MountCredentials": ".types", "MountCredentialsResponse": ".types", "MountData": ".types", "MountEdit": ".types", "MountFileDeletedResponse": ".types", "MountFileWrittenResponse": ".types", "MountFlags": ".types", "MountFolderCreatedResponse": ".types", "MountQuery": ".types", "MountResponse": ".types", "MountsResponse": ".types", "NumericOperator": ".types", "OTelEventInput": ".types", "OTelEventInputTimestamp": ".types", "OTelEventOutput": ".types", "OTelEventOutputTimestamp": ".types", "OTelHashInput": ".types", "OTelHashOutput": ".types", "OTelLinkInput": ".types", "OTelLinkOutput": ".types", "OTelLinksResponse": ".types", "OTelReferenceInput": ".types", "OTelReferenceOutput": ".types", "OTelSpanKind": ".types", "OTelStatusCode": ".types", "OTelTracingRequest": ".types", "OTelTracingResponse": ".types", "OldAnalyticsResponse": ".types", "Organization": ".types", "OrganizationDetails": ".types", "OrganizationDomainResponse": ".types", "OrganizationProviderResponse": ".types", "OrganizationUpdate": ".types", "Permission": ".types", "PlaygroundBuildKitContext": ".types", "ProjectsResponse": ".types", "QueriesResponse": ".types", "Query": ".types", "QueryApplicationVariantsRequestOrder": ".applications", "QueryCreate": ".types", "QueryEdit": ".types", "QueryEnvironmentRevisionsRequestOrder": ".environments", "QueryEnvironmentVariantsRequestOrder": ".environments", "QueryEnvironmentsRequestOrder": ".environments", "QueryEvaluatorVariantsRequestOrder": ".evaluators", "QueryFlags": ".types", "QueryQueriesRequestOrder": ".queries", "QueryQueryFlags": ".types", "QueryResponse": ".types", "QueryRevision": ".types", "QueryRevisionCommit": ".types", "QueryRevisionCreate": ".types", "QueryRevisionDataInput": ".types", "QueryRevisionDataOutput": ".types", "QueryRevisionEdit": ".types", "QueryRevisionQuery": ".types", "QueryRevisionResponse": ".types", "QueryRevisionsLog": ".types", "QueryRevisionsResponse": ".types", "QuerySpansAnalyticsRequestNewest": ".traces", "QuerySpansAnalyticsRequestOldest": ".traces", "QueryVariant": ".types", "QueryVariantCreate": ".types", "QueryVariantEdit": ".types", "QueryVariantFork": ".types", "QueryVariantQuery": ".types", "QueryVariantResponse": ".types", "QueryVariantsResponse": ".types", "QueryWorkflowRevisionsRequestOrder": ".workflows", "QueryWorkflowVariantsRequestOrder": ".workflows", "QueryWorkflowsRequestOrder": ".workflows", "Reference": ".types", "ReferenceRequestModelInput": ".types", "ReferenceRequestModelOutput": ".types", "RequestType": ".types", "ResolutionInfo": ".types", "ResolvedTool": ".types", "RetrievalInfo": ".types", "SecretDto": ".types", "SecretDtoData": ".types", "SecretKind": ".types", "SecretResponseDto": ".types", "SecretResponseDtoData": ".types", "Selector": ".types", "SessionHeartbeatResponseModel": ".types", "SessionIdsResponse": ".types", "SessionInteraction": ".types", "SessionInteractionData": ".types", "SessionInteractionFlags": ".types", "SessionInteractionKind": ".types", "SessionInteractionQuery": ".types", "SessionInteractionQueryFlags": ".types", "SessionInteractionResponse": ".types", "SessionInteractionStatus": ".types", "SessionInteractionsResponse": ".types", "SessionMount": ".types", "SessionMountQuery": ".types", "SessionMountsResponse": ".types", "SessionRecord": ".types", "SessionRecordResponse": ".types", "SessionRecordsQueryResponse": ".types", "SessionState": ".types", "SessionStateData": ".types", "SessionStateFlags": ".types", "SessionStateResponse": ".types", "SessionStateUpsertRequest": ".types", "SessionStream": ".types", "SessionStreamCommandResponseModel": ".types", "SessionStreamFlags": ".types", "SessionStreamResponseModel": ".types", "SessionStreamsResponseModel": ".types", "SimpleApplication": ".types", "SimpleApplicationAdditionalContext": ".types", "SimpleApplicationCreate": ".types", "SimpleApplicationDataInput": ".types", "SimpleApplicationDataInputHeadersValue": ".types", "SimpleApplicationDataInputRuntime": ".types", "SimpleApplicationDataOutput": ".types", "SimpleApplicationDataOutputHeadersValue": ".types", "SimpleApplicationDataOutputRuntime": ".types", "SimpleApplicationEdit": ".types", "SimpleApplicationFlags": ".types", "SimpleApplicationQuery": ".types", "SimpleApplicationQueryFlags": ".types", "SimpleApplicationResponse": ".types", "SimpleApplicationsResponse": ".types", "SimpleEnvironment": ".types", "SimpleEnvironmentCreate": ".types", "SimpleEnvironmentEdit": ".types", "SimpleEnvironmentQuery": ".types", "SimpleEnvironmentResponse": ".types", "SimpleEnvironmentsResponse": ".types", "SimpleEvaluation": ".types", "SimpleEvaluationCreate": ".types", "SimpleEvaluationData": ".types", "SimpleEvaluationDataApplicationSteps": ".types", "SimpleEvaluationDataApplicationStepsOneValue": ".types", "SimpleEvaluationDataEvaluatorSteps": ".types", "SimpleEvaluationDataEvaluatorStepsOneValue": ".types", "SimpleEvaluationDataQuerySteps": ".types", "SimpleEvaluationDataQueryStepsOneValue": ".types", "SimpleEvaluationDataTestsetSteps": ".types", "SimpleEvaluationDataTestsetStepsOneValue": ".types", "SimpleEvaluationEdit": ".types", "SimpleEvaluationIdResponse": ".types", "SimpleEvaluationQuery": ".types", "SimpleEvaluationResponse": ".types", "SimpleEvaluationsResponse": ".types", "SimpleEvaluator": ".types", "SimpleEvaluatorCreate": ".types", "SimpleEvaluatorDataInput": ".types", "SimpleEvaluatorDataInputHeadersValue": ".types", "SimpleEvaluatorDataInputRuntime": ".types", "SimpleEvaluatorDataOutput": ".types", "SimpleEvaluatorDataOutputHeadersValue": ".types", "SimpleEvaluatorDataOutputRuntime": ".types", "SimpleEvaluatorEdit": ".types", "SimpleEvaluatorFlags": ".types", "SimpleEvaluatorQuery": ".types", "SimpleEvaluatorQueryFlags": ".types", "SimpleEvaluatorResponse": ".types", "SimpleEvaluatorsResponse": ".types", "SimpleQueriesResponse": ".types", "SimpleQuery": ".types", "SimpleQueryCreate": ".types", "SimpleQueryEdit": ".types", "SimpleQueryQuery": ".types", "SimpleQueryResponse": ".types", "SimpleQueue": ".types", "SimpleQueueCreate": ".types", "SimpleQueueData": ".types", "SimpleQueueDataEvaluators": ".types", "SimpleQueueDataEvaluatorsOneValue": ".types", "SimpleQueueIdResponse": ".types", "SimpleQueueIdsResponse": ".types", "SimpleQueueKind": ".types", "SimpleQueueQuery": ".types", "SimpleQueueResponse": ".types", "SimpleQueueScenariosQuery": ".types", "SimpleQueueScenariosResponse": ".types", "SimpleQueueSettings": ".types", "SimpleQueuesResponse": ".types", "SimpleTestset": ".types", "SimpleTestsetCreate": ".types", "SimpleTestsetEdit": ".types", "SimpleTestsetQuery": ".types", "SimpleTestsetResponse": ".types", "SimpleTestsetsResponse": ".types", "SimpleTrace": ".types", "SimpleTraceChannel": ".types", "SimpleTraceCreate": ".types", "SimpleTraceCreateLinks": ".types", "SimpleTraceEdit": ".types", "SimpleTraceEditLinks": ".types", "SimpleTraceKind": ".types", "SimpleTraceLinkResponse": ".types", "SimpleTraceLinks": ".types", "SimpleTraceOrigin": ".types", "SimpleTraceQuery": ".types", "SimpleTraceQueryLinks": ".types", "SimpleTraceReferences": ".types", "SimpleTraceResponse": ".types", "SimpleTracesResponse": ".types", "SimpleWorkflow": ".types", "SimpleWorkflowCreate": ".types", "SimpleWorkflowDataInput": ".types", "SimpleWorkflowDataInputHeadersValue": ".types", "SimpleWorkflowDataInputRuntime": ".types", "SimpleWorkflowDataOutput": ".types", "SimpleWorkflowDataOutputHeadersValue": ".types", "SimpleWorkflowDataOutputRuntime": ".types", "SimpleWorkflowEdit": ".types", "SimpleWorkflowFlags": ".types", "SimpleWorkflowQuery": ".types", "SimpleWorkflowQueryFlags": ".types", "SimpleWorkflowResponse": ".types", "SimpleWorkflowsResponse": ".types", "SpanInput": ".types", "SpanInputEndTime": ".types", "SpanInputStartTime": ".types", "SpanOutput": ".types", "SpanOutputEndTime": ".types", "SpanOutputStartTime": ".types", "SpanResponse": ".types", "SpanType": ".types", "SpansNodeInput": ".types", "SpansNodeInputEndTime": ".types", "SpansNodeInputSpansValue": ".types", "SpansNodeInputStartTime": ".types", "SpansNodeOutput": ".types", "SpansNodeOutputEndTime": ".types", "SpansNodeOutputSpansValue": ".types", "SpansNodeOutputStartTime": ".types", "SpansResponse": ".types", "SpansTreeInput": ".types", "SpansTreeInputSpansValue": ".types", "SpansTreeOutput": ".types", "SpansTreeOutputSpansValue": ".types", "SsoProviderDto": ".types", "SsoProviderInfo": ".types", "SsoProviderSettingsDto": ".types", "SsoProviders": ".types", "StandardProviderDto": ".types", "StandardProviderKind": ".types", "StandardProviderSettingsDto": ".types", "Status": ".types", "StringOperator": ".types", "TestcaseInput": ".types", "TestcaseOutput": ".types", "TestcaseResponse": ".types", "TestcasesResponse": ".types", "Testset": ".types", "TestsetCreate": ".types", "TestsetEdit": ".types", "TestsetFlags": ".types", "TestsetQuery": ".types", "TestsetResponse": ".types", "TestsetRevision": ".types", "TestsetRevisionCommit": ".types", "TestsetRevisionCreate": ".types", "TestsetRevisionDataInput": ".types", "TestsetRevisionDataOutput": ".types", "TestsetRevisionDelta": ".types", "TestsetRevisionDeltaColumns": ".types", "TestsetRevisionDeltaRows": ".types", "TestsetRevisionEdit": ".types", "TestsetRevisionQuery": ".types", "TestsetRevisionResponse": ".types", "TestsetRevisionsLog": ".types", "TestsetRevisionsResponse": ".types", "TestsetVariant": ".types", "TestsetVariantCreate": ".types", "TestsetVariantEdit": ".types", "TestsetVariantFork": ".types", "TestsetVariantQuery": ".types", "TestsetVariantResponse": ".types", "TestsetVariantsResponse": ".types", "TestsetsResponse": ".types", "TextOptions": ".types", "ToolAuthScheme": ".types", "ToolCallData": ".types", "ToolCallFunction": ".types", "ToolCallResponse": ".types", "ToolCatalogAction": ".types", "ToolCatalogActionDetails": ".types", "ToolCatalogActionResponse": ".types", "ToolCatalogActionResponseAction": ".types", "ToolCatalogActionsResponse": ".types", "ToolCatalogActionsResponseActionsItem": ".types", "ToolCatalogCategoriesResponse": ".types", "ToolCatalogCategory": ".types", "ToolCatalogIntegration": ".types", "ToolCatalogIntegrationDetails": ".types", "ToolCatalogIntegrationResponse": ".types", "ToolCatalogIntegrationResponseIntegration": ".types", "ToolCatalogIntegrationsResponse": ".types", "ToolCatalogIntegrationsResponseIntegrationsItem": ".types", "ToolCatalogProvider": ".types", "ToolCatalogProviderDetails": ".types", "ToolCatalogProviderResponse": ".types", "ToolCatalogProviderResponseProvider": ".types", "ToolCatalogProvidersResponse": ".types", "ToolCatalogProvidersResponseProvidersItem": ".types", "ToolConnection": ".types", "ToolConnectionCreate": ".types", "ToolConnectionCreateData": ".types", "ToolConnectionResponse": ".types", "ToolConnectionState": ".types", "ToolConnectionStatus": ".types", "ToolConnectionsResponse": ".types", "ToolProviderKind": ".types", "ToolResolveRequestToolsItem": ".tools", "ToolResolveRequestToolsItem_Builtin": ".tools", "ToolResolveRequestToolsItem_Gateway": ".tools", "ToolResolveResponse": ".types", "ToolResult": ".types", "ToolResultData": ".types", "TraceIdResponse": ".types", "TraceIdsResponse": ".types", "TraceInput": ".types", "TraceInputSpansValue": ".types", "TraceOutput": ".types", "TraceOutputSpansValue": ".types", "TraceRequest": ".types", "TraceResponse": ".types", "TraceType": ".types", "TracesRequest": ".types", "TracesResponse": ".types", "TracingQuery": ".types", "TriggerAuthScheme": ".types", "TriggerCapabilitiesResult": ".types", "TriggerCapability": ".types", "TriggerCapabilityConnection": ".types", "TriggerCatalogEvent": ".types", "TriggerCatalogEventDetails": ".types", "TriggerCatalogEventResponse": ".types", "TriggerCatalogEventsResponse": ".types", "TriggerCatalogIntegration": ".types", "TriggerCatalogIntegrationResponse": ".types", "TriggerCatalogIntegrationsResponse": ".types", "TriggerCatalogProvider": ".types", "TriggerCatalogProviderResponse": ".types", "TriggerCatalogProvidersResponse": ".types", "TriggerConnectAffordance": ".types", "TriggerConnection": ".types", "TriggerConnectionCreate": ".types", "TriggerConnectionCreateData": ".types", "TriggerConnectionRequirement": ".types", "TriggerConnectionResponse": ".types", "TriggerConnectionStatus": ".types", "TriggerConnectionsResponse": ".types", "TriggerDeliveriesResponse": ".types", "TriggerDelivery": ".types", "TriggerDeliveryData": ".types", "TriggerDeliveryQuery": ".types", "TriggerDeliveryResponse": ".types", "TriggerDiscoveryConnectionState": ".types", "TriggerDiscoveryGuidance": ".types", "TriggerEventAck": ".types", "TriggerProviderKind": ".types", "TriggerSchedule": ".types", "TriggerScheduleCreate": ".types", "TriggerScheduleData": ".types", "TriggerScheduleDataInputsFields": ".types", "TriggerScheduleEdit": ".types", "TriggerScheduleFlags": ".types", "TriggerScheduleQuery": ".types", "TriggerScheduleResponse": ".types", "TriggerSchedulesResponse": ".types", "TriggerSubscription": ".types", "TriggerSubscriptionCreate": ".types", "TriggerSubscriptionCreateRequest": ".types", "TriggerSubscriptionData": ".types", "TriggerSubscriptionDataInputsFields": ".types", "TriggerSubscriptionEdit": ".types", "TriggerSubscriptionFlags": ".types", "TriggerSubscriptionQuery": ".types", "TriggerSubscriptionResponse": ".types", "TriggerSubscriptionsResponse": ".types", "UnprocessableEntityError": ".errors", "UserIdsResponse": ".types", "ValidationError": ".types", "ValidationErrorLocItem": ".types", "WebhookDeliveriesResponse": ".types", "WebhookDelivery": ".types", "WebhookDeliveryCreate": ".types", "WebhookDeliveryData": ".types", "WebhookDeliveryQuery": ".types", "WebhookDeliveryResponse": ".types", "WebhookDeliveryResponseInfo": ".types", "WebhookEventType": ".types", "WebhookProviderDto": ".types", "WebhookProviderSettingsDto": ".types", "WebhookSubscription": ".types", "WebhookSubscriptionCreate": ".types", "WebhookSubscriptionData": ".types", "WebhookSubscriptionDataAuthMode": ".types", "WebhookSubscriptionEdit": ".types", "WebhookSubscriptionFlags": ".types", "WebhookSubscriptionQuery": ".types", "WebhookSubscriptionResponse": ".types", "WebhookSubscriptionTestRequestSubscription": ".webhooks", "WebhookSubscriptionsResponse": ".types", "Windowing": ".types", "WindowingOrder": ".types", "Workflow": ".types", "WorkflowArtifactFlags": ".types", "WorkflowCatalogFlags": ".types", "WorkflowCatalogHarness": ".types", "WorkflowCatalogHarnessResponse": ".types", "WorkflowCatalogHarnessesResponse": ".types", "WorkflowCatalogPreset": ".types", "WorkflowCatalogPresetResponse": ".types", "WorkflowCatalogPresetsResponse": ".types", "WorkflowCatalogTemplate": ".types", "WorkflowCatalogTemplateResponse": ".types", "WorkflowCatalogTemplatesResponse": ".types", "WorkflowCatalogType": ".types", "WorkflowCatalogTypeResponse": ".types", "WorkflowCatalogTypesResponse": ".types", "WorkflowCreate": ".types", "WorkflowEdit": ".types", "WorkflowFlags": ".types", "WorkflowResponse": ".types", "WorkflowRevisionCommit": ".types", "WorkflowRevisionCreate": ".types", "WorkflowRevisionDataInput": ".types", "WorkflowRevisionDataInputHeadersValue": ".types", "WorkflowRevisionDataInputRuntime": ".types", "WorkflowRevisionDataOutput": ".types", "WorkflowRevisionDataOutputHeadersValue": ".types", "WorkflowRevisionDataOutputRuntime": ".types", "WorkflowRevisionDelta": ".types", "WorkflowRevisionEdit": ".types", "WorkflowRevisionFlags": ".types", "WorkflowRevisionInput": ".types", "WorkflowRevisionOutput": ".types", "WorkflowRevisionResolveResponse": ".types", "WorkflowRevisionResponse": ".types", "WorkflowRevisionsLog": ".types", "WorkflowRevisionsResponse": ".types", "WorkflowVariant": ".types", "WorkflowVariantCreate": ".types", "WorkflowVariantEdit": ".types", "WorkflowVariantFlags": ".types", "WorkflowVariantFork": ".types", "WorkflowVariantResponse": ".types", "WorkflowVariantsResponse": ".types", "WorkflowsResponse": ".types", "Workspace": ".types", "WorkspaceMemberResponse": ".types", "WorkspacePermission": ".types", "WorkspaceResponse": ".types", "access": ".access", "annotations": ".annotations", "applications": ".applications", "billing": ".billing", "environments": ".environments", "evaluations": ".evaluations", "evaluators": ".evaluators", "events": ".events", "folders": ".folders", "invocations": ".invocations", "keys": ".keys", "legacy": ".legacy", "mounts": ".mounts", "organizations": ".organizations", "projects": ".projects", "queries": ".queries", "secrets": ".secrets", "sessions": ".sessions", "status": ".status", "testcases": ".testcases", "testsets": ".testsets", "tools": ".tools", "traces": ".traces", "triggers": ".triggers", "users": ".users", "webhooks": ".webhooks", "workflows": ".workflows", "workspaces": ".workspaces"} +_dynamic_imports: typing.Dict[str, str] = {"AdminAccountCreateOptions": ".types", "AdminAccountRead": ".types", "AdminAccountsCreate": ".types", "AdminAccountsDelete": ".types", "AdminAccountsDeleteTarget": ".types", "AdminAccountsResponse": ".types", "AdminApiKeyCreate": ".types", "AdminApiKeyResponse": ".types", "AdminDeleteResponse": ".types", "AdminDeletedEntities": ".types", "AdminDeletedEntity": ".types", "AdminOrganizationCreate": ".types", "AdminOrganizationMembershipCreate": ".types", "AdminOrganizationMembershipRead": ".types", "AdminOrganizationRead": ".types", "AdminProjectCreate": ".types", "AdminProjectMembershipCreate": ".types", "AdminProjectMembershipRead": ".types", "AdminProjectRead": ".types", "AdminSimpleAccountCreate": ".types", "AdminSimpleAccountDeleteEntry": ".types", "AdminSimpleAccountRead": ".types", "AdminSimpleAccountsApiKeysCreate": ".types", "AdminSimpleAccountsCreate": ".types", "AdminSimpleAccountsDelete": ".types", "AdminSimpleAccountsOrganizationsCreate": ".types", "AdminSimpleAccountsOrganizationsMembershipsCreate": ".types", "AdminSimpleAccountsOrganizationsTransferOwnership": ".types", "AdminSimpleAccountsOrganizationsTransferOwnershipIncludeProjects": ".types", "AdminSimpleAccountsOrganizationsTransferOwnershipIncludeProjectsZero": ".types", "AdminSimpleAccountsOrganizationsTransferOwnershipIncludeWorkspaces": ".types", "AdminSimpleAccountsOrganizationsTransferOwnershipIncludeWorkspacesZero": ".types", "AdminSimpleAccountsOrganizationsTransferOwnershipResponse": ".types", "AdminSimpleAccountsProjectsCreate": ".types", "AdminSimpleAccountsProjectsMembershipsCreate": ".types", "AdminSimpleAccountsResponse": ".types", "AdminSimpleAccountsUsersCreate": ".types", "AdminSimpleAccountsUsersIdentitiesCreate": ".types", "AdminSimpleAccountsUsersResetPassword": ".types", "AdminSimpleAccountsWorkspacesCreate": ".types", "AdminSimpleAccountsWorkspacesMembershipsCreate": ".types", "AdminStructuredError": ".types", "AdminSubscriptionCreate": ".types", "AdminSubscriptionRead": ".types", "AdminUserCreate": ".types", "AdminUserIdentityCreate": ".types", "AdminUserIdentityRead": ".types", "AdminUserIdentityReadStatus": ".types", "AdminUserRead": ".types", "AdminWorkspaceCreate": ".types", "AdminWorkspaceMembershipCreate": ".types", "AdminWorkspaceMembershipRead": ".types", "AdminWorkspaceRead": ".types", "AgentTemplateOverlay": ".types", "AgentaApi": ".client", "AgentaApiEnvironment": ".environment", "Analytics": ".types", "AnalyticsResponse": ".types", "Annotation": ".types", "AnnotationCreate": ".types", "AnnotationCreateLinks": ".types", "AnnotationEdit": ".types", "AnnotationEditLinks": ".types", "AnnotationLinkResponse": ".types", "AnnotationLinks": ".types", "AnnotationQuery": ".types", "AnnotationQueryLinks": ".types", "AnnotationResponse": ".types", "AnnotationsResponse": ".types", "Application": ".types", "ApplicationArtifactFlags": ".types", "ApplicationArtifactQueryFlags": ".types", "ApplicationCatalogPreset": ".types", "ApplicationCatalogPresetResponse": ".types", "ApplicationCatalogPresetsResponse": ".types", "ApplicationCatalogTemplate": ".types", "ApplicationCatalogTemplateResponse": ".types", "ApplicationCatalogTemplatesResponse": ".types", "ApplicationCatalogType": ".types", "ApplicationCatalogTypesResponse": ".types", "ApplicationCreate": ".types", "ApplicationEdit": ".types", "ApplicationFlags": ".types", "ApplicationQuery": ".types", "ApplicationResponse": ".types", "ApplicationRevisionCommit": ".types", "ApplicationRevisionCreate": ".types", "ApplicationRevisionDataInput": ".types", "ApplicationRevisionDataInputHeadersValue": ".types", "ApplicationRevisionDataInputRuntime": ".types", "ApplicationRevisionDataOutput": ".types", "ApplicationRevisionDataOutputHeadersValue": ".types", "ApplicationRevisionDataOutputRuntime": ".types", "ApplicationRevisionEdit": ".types", "ApplicationRevisionFlags": ".types", "ApplicationRevisionInput": ".types", "ApplicationRevisionOutput": ".types", "ApplicationRevisionQuery": ".types", "ApplicationRevisionQueryFlags": ".types", "ApplicationRevisionResolveResponse": ".types", "ApplicationRevisionResponse": ".types", "ApplicationRevisionsLog": ".types", "ApplicationRevisionsResponse": ".types", "ApplicationVariant": ".types", "ApplicationVariantCreate": ".types", "ApplicationVariantEdit": ".types", "ApplicationVariantFlags": ".types", "ApplicationVariantFork": ".types", "ApplicationVariantResponse": ".types", "ApplicationVariantsResponse": ".types", "ApplicationsResponse": ".types", "AsyncAgentaApi": ".client", "BodyConfigsFetchVariantsConfigsFetchPost": ".types", "Bucket": ".types", "BuiltinToolConfig": ".types", "BuiltinToolConfigPermission": ".types", "CapabilitiesResult": ".types", "Capability": ".types", "CapabilityConnection": ".types", "CapabilityGuidance": ".types", "CollectStatusResponse": ".types", "CommandMode": ".types", "ComparisonOperator": ".types", "Condition": ".types", "ConditionOperator": ".types", "ConditionOptions": ".types", "ConditionValue": ".types", "ConfigResponseModel": ".types", "ConnectAffordance": ".types", "ConnectionRequirement": ".types", "CreateSimpleTestsetFromFileRequestFileType": ".testsets", "CreateTestsetRevisionFromFileRequestFileType": ".testsets", "CustomModelSettingsDto": ".types", "CustomProviderDto": ".types", "CustomProviderKind": ".types", "CustomProviderSettingsDto": ".types", "CustomSecretDto": ".types", "CustomSecretFormat": ".types", "CustomSecretSettingsDto": ".types", "CustomSecretSettingsDtoContent": ".types", "CustomSecretSettingsDtoContentOneValue": ".types", "DictOperator": ".types", "DiscoverResponse": ".types", "DiscoverResponseMethodsValue": ".types", "DiscoveredAlternative": ".types", "DiscoveredTool": ".types", "DiscoveredToolType": ".types", "DiscoveredTriggerAlternative": ".types", "DiscoveredTriggerEvent": ".types", "DiscoveredTriggerEventType": ".types", "EditSimpleTestsetFromFileRequestFileType": ".testsets", "EntityRef": ".types", "Environment": ".types", "EnvironmentCreate": ".types", "EnvironmentEdit": ".types", "EnvironmentFlags": ".types", "EnvironmentQueryFlags": ".types", "EnvironmentResponse": ".types", "EnvironmentRevisionCommit": ".types", "EnvironmentRevisionCreate": ".types", "EnvironmentRevisionData": ".types", "EnvironmentRevisionDelta": ".types", "EnvironmentRevisionEdit": ".types", "EnvironmentRevisionInput": ".types", "EnvironmentRevisionOutput": ".types", "EnvironmentRevisionResolveResponse": ".types", "EnvironmentRevisionResponse": ".types", "EnvironmentRevisionsLog": ".types", "EnvironmentRevisionsResponse": ".types", "EnvironmentVariant": ".types", "EnvironmentVariantCreate": ".types", "EnvironmentVariantEdit": ".types", "EnvironmentVariantFork": ".types", "EnvironmentVariantResponse": ".types", "EnvironmentVariantsResponse": ".types", "EnvironmentsResponse": ".types", "ErrorPolicy": ".types", "EvaluationMetrics": ".types", "EvaluationMetricsCreate": ".types", "EvaluationMetricsIdsResponse": ".types", "EvaluationMetricsQuery": ".types", "EvaluationMetricsQueryScenarioIds": ".types", "EvaluationMetricsQueryTimestamps": ".types", "EvaluationMetricsRefresh": ".types", "EvaluationMetricsResponse": ".types", "EvaluationMetricsSetRequest": ".types", "EvaluationQueue": ".types", "EvaluationQueueCreate": ".types", "EvaluationQueueData": ".types", "EvaluationQueueEdit": ".types", "EvaluationQueueFlags": ".types", "EvaluationQueueIdResponse": ".types", "EvaluationQueueIdsResponse": ".types", "EvaluationQueueQuery": ".types", "EvaluationQueueQueryFlags": ".types", "EvaluationQueueResponse": ".types", "EvaluationQueueScenariosQuery": ".types", "EvaluationQueuesResponse": ".types", "EvaluationResult": ".types", "EvaluationResultCreate": ".types", "EvaluationResultIdResponse": ".types", "EvaluationResultIdsResponse": ".types", "EvaluationResultQuery": ".types", "EvaluationResultResponse": ".types", "EvaluationResultsResponse": ".types", "EvaluationResultsSetRequest": ".types", "EvaluationRun": ".types", "EvaluationRunCreate": ".types", "EvaluationRunDataConcurrency": ".types", "EvaluationRunDataInput": ".types", "EvaluationRunDataMapping": ".types", "EvaluationRunDataMappingColumn": ".types", "EvaluationRunDataMappingStep": ".types", "EvaluationRunDataOutput": ".types", "EvaluationRunDataStepInput": ".types", "EvaluationRunDataStepInputKey": ".types", "EvaluationRunDataStepInputOrigin": ".types", "EvaluationRunDataStepInputType": ".types", "EvaluationRunDataStepOutput": ".types", "EvaluationRunDataStepOutputOrigin": ".types", "EvaluationRunDataStepOutputType": ".types", "EvaluationRunEdit": ".types", "EvaluationRunFlags": ".types", "EvaluationRunIdResponse": ".types", "EvaluationRunIdsRequest": ".types", "EvaluationRunIdsResponse": ".types", "EvaluationRunQuery": ".types", "EvaluationRunQueryFlags": ".types", "EvaluationRunResponse": ".types", "EvaluationRunsResponse": ".types", "EvaluationScenario": ".types", "EvaluationScenarioCreate": ".types", "EvaluationScenarioEdit": ".types", "EvaluationScenarioIdResponse": ".types", "EvaluationScenarioIdsResponse": ".types", "EvaluationScenarioQuery": ".types", "EvaluationScenarioResponse": ".types", "EvaluationScenariosResponse": ".types", "EvaluationStatus": ".types", "Evaluator": ".types", "EvaluatorArtifactFlags": ".types", "EvaluatorArtifactQueryFlags": ".types", "EvaluatorCatalogPreset": ".types", "EvaluatorCatalogPresetResponse": ".types", "EvaluatorCatalogPresetsResponse": ".types", "EvaluatorCatalogTemplate": ".types", "EvaluatorCatalogTemplateResponse": ".types", "EvaluatorCatalogTemplatesResponse": ".types", "EvaluatorCatalogType": ".types", "EvaluatorCatalogTypesResponse": ".types", "EvaluatorCreate": ".types", "EvaluatorEdit": ".types", "EvaluatorFlags": ".types", "EvaluatorQuery": ".types", "EvaluatorResponse": ".types", "EvaluatorRevisionCommit": ".types", "EvaluatorRevisionCreate": ".types", "EvaluatorRevisionDataInput": ".types", "EvaluatorRevisionDataInputHeadersValue": ".types", "EvaluatorRevisionDataInputRuntime": ".types", "EvaluatorRevisionDataOutput": ".types", "EvaluatorRevisionDataOutputHeadersValue": ".types", "EvaluatorRevisionDataOutputRuntime": ".types", "EvaluatorRevisionEdit": ".types", "EvaluatorRevisionFlags": ".types", "EvaluatorRevisionInput": ".types", "EvaluatorRevisionOutput": ".types", "EvaluatorRevisionQuery": ".types", "EvaluatorRevisionQueryFlags": ".types", "EvaluatorRevisionResolveResponse": ".types", "EvaluatorRevisionResponse": ".types", "EvaluatorRevisionsLog": ".types", "EvaluatorRevisionsResponse": ".types", "EvaluatorTemplate": ".types", "EvaluatorTemplatesResponse": ".types", "EvaluatorVariant": ".types", "EvaluatorVariantCreate": ".types", "EvaluatorVariantEdit": ".types", "EvaluatorVariantFlags": ".types", "EvaluatorVariantFork": ".types", "EvaluatorVariantResponse": ".types", "EvaluatorVariantsResponse": ".types", "EvaluatorsResponse": ".types", "Event": ".types", "EventQuery": ".types", "EventType": ".types", "EventsQueryResponse": ".types", "ExistenceOperator": ".types", "FetchLegacyAnalyticsRequestNewest": ".legacy", "FetchLegacyAnalyticsRequestOldest": ".legacy", "FetchSimpleTestsetToFileRequestFileType": ".testsets", "FetchTestsetRevisionToFileRequestFileType": ".testsets", "FilteringInput": ".types", "FilteringInputConditionsItem": ".types", "FilteringOutput": ".types", "FilteringOutputConditionsItem": ".types", "Focus": ".types", "Folder": ".types", "FolderCreate": ".types", "FolderEdit": ".types", "FolderIdResponse": ".types", "FolderKind": ".types", "FolderQuery": ".types", "FolderQueryKinds": ".types", "FolderResponse": ".types", "FoldersResponse": ".types", "Format": ".types", "Formatting": ".types", typing.Any: ".types", typing.Any: ".types", "GatewayToolConfig": ".types", "GatewayToolConfigPermission": ".types", "HarnessKind": ".types", "Header": ".types", "HttpValidationError": ".types", "InviteRequest": ".types", "Invocation": ".types", "InvocationCreate": ".types", "InvocationCreateLinks": ".types", "InvocationEdit": ".types", "InvocationEditLinks": ".types", "InvocationLinkResponse": ".types", "InvocationLinks": ".types", "InvocationQuery": ".types", "InvocationQueryLinks": ".types", "InvocationResponse": ".types", "InvocationsResponse": ".types", "JsonSchemasInput": ".types", "JsonSchemasOutput": ".types", typing.Any: ".types", typing.Any: ".types", "LegacyLifecycleDto": ".types", "ListApiKeysResponse": ".types", "ListOperator": ".types", "ListOptions": ".types", "LogicalOperator": ".types", "MetricSpec": ".types", "MetricType": ".types", "MetricsBucket": ".types", "Mount": ".types", "MountCreate": ".types", "MountCredentials": ".types", "MountCredentialsResponse": ".types", "MountData": ".types", "MountEdit": ".types", "MountFileDeletedResponse": ".types", "MountFileWrittenResponse": ".types", "MountFlags": ".types", "MountFolderCreatedResponse": ".types", "MountQuery": ".types", "MountResponse": ".types", "MountsResponse": ".types", "NumericOperator": ".types", "OTelEventInput": ".types", "OTelEventInputTimestamp": ".types", "OTelEventOutput": ".types", "OTelEventOutputTimestamp": ".types", "OTelHashInput": ".types", "OTelHashOutput": ".types", "OTelLinkInput": ".types", "OTelLinkOutput": ".types", "OTelLinksResponse": ".types", "OTelReferenceInput": ".types", "OTelReferenceOutput": ".types", "OTelSpanKind": ".types", "OTelStatusCode": ".types", "OTelTracingRequest": ".types", "OTelTracingResponse": ".types", "OldAnalyticsResponse": ".types", "Organization": ".types", "OrganizationDetails": ".types", "OrganizationDomainResponse": ".types", "OrganizationProviderResponse": ".types", "OrganizationUpdate": ".types", "Permission": ".types", "PlaygroundBuildKitContext": ".types", "ProjectsResponse": ".types", "QueriesResponse": ".types", "Query": ".types", "QueryApplicationVariantsRequestOrder": ".applications", "QueryCreate": ".types", "QueryEdit": ".types", "QueryEnvironmentRevisionsRequestOrder": ".environments", "QueryEnvironmentVariantsRequestOrder": ".environments", "QueryEnvironmentsRequestOrder": ".environments", "QueryEvaluatorVariantsRequestOrder": ".evaluators", "QueryFlags": ".types", "QueryQueriesRequestOrder": ".queries", "QueryQueryFlags": ".types", "QueryResponse": ".types", "QueryRevision": ".types", "QueryRevisionCommit": ".types", "QueryRevisionCreate": ".types", "QueryRevisionDataInput": ".types", "QueryRevisionDataOutput": ".types", "QueryRevisionEdit": ".types", "QueryRevisionQuery": ".types", "QueryRevisionResponse": ".types", "QueryRevisionsLog": ".types", "QueryRevisionsResponse": ".types", "QuerySpansAnalyticsRequestNewest": ".traces", "QuerySpansAnalyticsRequestOldest": ".traces", "QueryVariant": ".types", "QueryVariantCreate": ".types", "QueryVariantEdit": ".types", "QueryVariantFork": ".types", "QueryVariantQuery": ".types", "QueryVariantResponse": ".types", "QueryVariantsResponse": ".types", "QueryWorkflowRevisionsRequestOrder": ".workflows", "QueryWorkflowVariantsRequestOrder": ".workflows", "QueryWorkflowsRequestOrder": ".workflows", "Reference": ".types", "ReferenceRequestModelInput": ".types", "ReferenceRequestModelOutput": ".types", "RequestType": ".types", "ResolutionInfo": ".types", "ResolvedTool": ".types", "RetrievalInfo": ".types", "SecretDto": ".types", "SecretDtoData": ".types", "SecretKind": ".types", "SecretResponseDto": ".types", "SecretResponseDtoData": ".types", "Selector": ".types", "SessionHeartbeatResult": ".types", "SessionIdsResponse": ".types", "SessionInteraction": ".types", "SessionInteractionData": ".types", "SessionInteractionFlags": ".types", "SessionInteractionKind": ".types", "SessionInteractionQuery": ".types", "SessionInteractionQueryFlags": ".types", "SessionInteractionResponse": ".types", "SessionInteractionStatus": ".types", "SessionInteractionsResponse": ".types", "SessionMount": ".types", "SessionMountQuery": ".types", "SessionMountsResponse": ".types", "SessionRecord": ".types", "SessionRecordResponse": ".types", "SessionRecordsQueryResponse": ".types", "SessionResponse": ".types", "SessionStream": ".types", "SessionStreamCommandResponse": ".types", "SessionStreamFlags": ".types", "SessionStreamHeaderEdit": ".types", "SessionStreamResponse": ".types", "SessionStreamsResponse": ".types", "SessionTurn": ".types", "SessionTurnQuery": ".types", "SessionTurnResponse": ".types", "SessionTurnsResponse": ".types", "SessionsResponse": ".types", "SimpleApplication": ".types", "SimpleApplicationAdditionalContext": ".types", "SimpleApplicationCreate": ".types", "SimpleApplicationDataInput": ".types", "SimpleApplicationDataInputHeadersValue": ".types", "SimpleApplicationDataInputRuntime": ".types", "SimpleApplicationDataOutput": ".types", "SimpleApplicationDataOutputHeadersValue": ".types", "SimpleApplicationDataOutputRuntime": ".types", "SimpleApplicationEdit": ".types", "SimpleApplicationFlags": ".types", "SimpleApplicationQuery": ".types", "SimpleApplicationQueryFlags": ".types", "SimpleApplicationResponse": ".types", "SimpleApplicationsResponse": ".types", "SimpleEnvironment": ".types", "SimpleEnvironmentCreate": ".types", "SimpleEnvironmentEdit": ".types", "SimpleEnvironmentQuery": ".types", "SimpleEnvironmentResponse": ".types", "SimpleEnvironmentsResponse": ".types", "SimpleEvaluation": ".types", "SimpleEvaluationCreate": ".types", "SimpleEvaluationData": ".types", "SimpleEvaluationDataApplicationSteps": ".types", "SimpleEvaluationDataApplicationStepsOneValue": ".types", "SimpleEvaluationDataEvaluatorSteps": ".types", "SimpleEvaluationDataEvaluatorStepsOneValue": ".types", "SimpleEvaluationDataQuerySteps": ".types", "SimpleEvaluationDataQueryStepsOneValue": ".types", "SimpleEvaluationDataTestsetSteps": ".types", "SimpleEvaluationDataTestsetStepsOneValue": ".types", "SimpleEvaluationEdit": ".types", "SimpleEvaluationIdResponse": ".types", "SimpleEvaluationQuery": ".types", "SimpleEvaluationResponse": ".types", "SimpleEvaluationsResponse": ".types", "SimpleEvaluator": ".types", "SimpleEvaluatorCreate": ".types", "SimpleEvaluatorDataInput": ".types", "SimpleEvaluatorDataInputHeadersValue": ".types", "SimpleEvaluatorDataInputRuntime": ".types", "SimpleEvaluatorDataOutput": ".types", "SimpleEvaluatorDataOutputHeadersValue": ".types", "SimpleEvaluatorDataOutputRuntime": ".types", "SimpleEvaluatorEdit": ".types", "SimpleEvaluatorFlags": ".types", "SimpleEvaluatorQuery": ".types", "SimpleEvaluatorQueryFlags": ".types", "SimpleEvaluatorResponse": ".types", "SimpleEvaluatorsResponse": ".types", "SimpleQueriesResponse": ".types", "SimpleQuery": ".types", "SimpleQueryCreate": ".types", "SimpleQueryEdit": ".types", "SimpleQueryQuery": ".types", "SimpleQueryResponse": ".types", "SimpleQueue": ".types", "SimpleQueueCreate": ".types", "SimpleQueueData": ".types", "SimpleQueueDataEvaluators": ".types", "SimpleQueueDataEvaluatorsOneValue": ".types", "SimpleQueueIdResponse": ".types", "SimpleQueueIdsResponse": ".types", "SimpleQueueKind": ".types", "SimpleQueueQuery": ".types", "SimpleQueueResponse": ".types", "SimpleQueueScenariosQuery": ".types", "SimpleQueueScenariosResponse": ".types", "SimpleQueueSettings": ".types", "SimpleQueuesResponse": ".types", "SimpleTestset": ".types", "SimpleTestsetCreate": ".types", "SimpleTestsetEdit": ".types", "SimpleTestsetQuery": ".types", "SimpleTestsetResponse": ".types", "SimpleTestsetsResponse": ".types", "SimpleTrace": ".types", "SimpleTraceChannel": ".types", "SimpleTraceCreate": ".types", "SimpleTraceCreateLinks": ".types", "SimpleTraceEdit": ".types", "SimpleTraceEditLinks": ".types", "SimpleTraceKind": ".types", "SimpleTraceLinkResponse": ".types", "SimpleTraceLinks": ".types", "SimpleTraceOrigin": ".types", "SimpleTraceQuery": ".types", "SimpleTraceQueryLinks": ".types", "SimpleTraceReferences": ".types", "SimpleTraceResponse": ".types", "SimpleTracesResponse": ".types", "SimpleWorkflow": ".types", "SimpleWorkflowCreate": ".types", "SimpleWorkflowDataInput": ".types", "SimpleWorkflowDataInputHeadersValue": ".types", "SimpleWorkflowDataInputRuntime": ".types", "SimpleWorkflowDataOutput": ".types", "SimpleWorkflowDataOutputHeadersValue": ".types", "SimpleWorkflowDataOutputRuntime": ".types", "SimpleWorkflowEdit": ".types", "SimpleWorkflowFlags": ".types", "SimpleWorkflowQuery": ".types", "SimpleWorkflowQueryFlags": ".types", "SimpleWorkflowResponse": ".types", "SimpleWorkflowsResponse": ".types", "SpanInput": ".types", "SpanInputEndTime": ".types", "SpanInputStartTime": ".types", "SpanOutput": ".types", "SpanOutputEndTime": ".types", "SpanOutputStartTime": ".types", "SpanResponse": ".types", "SpanType": ".types", "SpansNodeInput": ".types", "SpansNodeInputEndTime": ".types", "SpansNodeInputSpansValue": ".types", "SpansNodeInputStartTime": ".types", "SpansNodeOutput": ".types", "SpansNodeOutputEndTime": ".types", "SpansNodeOutputSpansValue": ".types", "SpansNodeOutputStartTime": ".types", "SpansResponse": ".types", "SpansTreeInput": ".types", "SpansTreeInputSpansValue": ".types", "SpansTreeOutput": ".types", "SpansTreeOutputSpansValue": ".types", "SsoProviderDto": ".types", "SsoProviderInfo": ".types", "SsoProviderSettingsDto": ".types", "SsoProviders": ".types", "StandardProviderDto": ".types", "StandardProviderKind": ".types", "StandardProviderSettingsDto": ".types", "Status": ".types", "StringOperator": ".types", "TestcaseInput": ".types", "TestcaseOutput": ".types", "TestcaseResponse": ".types", "TestcasesResponse": ".types", "Testset": ".types", "TestsetCreate": ".types", "TestsetEdit": ".types", "TestsetFlags": ".types", "TestsetQuery": ".types", "TestsetResponse": ".types", "TestsetRevision": ".types", "TestsetRevisionCommit": ".types", "TestsetRevisionCreate": ".types", "TestsetRevisionDataInput": ".types", "TestsetRevisionDataOutput": ".types", "TestsetRevisionDelta": ".types", "TestsetRevisionDeltaColumns": ".types", "TestsetRevisionDeltaRows": ".types", "TestsetRevisionEdit": ".types", "TestsetRevisionQuery": ".types", "TestsetRevisionResponse": ".types", "TestsetRevisionsLog": ".types", "TestsetRevisionsResponse": ".types", "TestsetVariant": ".types", "TestsetVariantCreate": ".types", "TestsetVariantEdit": ".types", "TestsetVariantFork": ".types", "TestsetVariantQuery": ".types", "TestsetVariantResponse": ".types", "TestsetVariantsResponse": ".types", "TestsetsResponse": ".types", "TextOptions": ".types", "ToolAuthScheme": ".types", "ToolCallData": ".types", "ToolCallFunction": ".types", "ToolCallResponse": ".types", "ToolCatalogAction": ".types", "ToolCatalogActionDetails": ".types", "ToolCatalogActionResponse": ".types", "ToolCatalogActionResponseAction": ".types", "ToolCatalogActionsResponse": ".types", "ToolCatalogActionsResponseActionsItem": ".types", "ToolCatalogCategoriesResponse": ".types", "ToolCatalogCategory": ".types", "ToolCatalogIntegration": ".types", "ToolCatalogIntegrationDetails": ".types", "ToolCatalogIntegrationResponse": ".types", "ToolCatalogIntegrationResponseIntegration": ".types", "ToolCatalogIntegrationsResponse": ".types", "ToolCatalogIntegrationsResponseIntegrationsItem": ".types", "ToolCatalogProvider": ".types", "ToolCatalogProviderDetails": ".types", "ToolCatalogProviderResponse": ".types", "ToolCatalogProviderResponseProvider": ".types", "ToolCatalogProvidersResponse": ".types", "ToolCatalogProvidersResponseProvidersItem": ".types", "ToolConnection": ".types", "ToolConnectionCreate": ".types", "ToolConnectionCreateData": ".types", "ToolConnectionResponse": ".types", "ToolConnectionState": ".types", "ToolConnectionStatus": ".types", "ToolConnectionsResponse": ".types", "ToolProviderKind": ".types", "ToolResolveRequestToolsItem": ".tools", "ToolResolveRequestToolsItem_Builtin": ".tools", "ToolResolveRequestToolsItem_Gateway": ".tools", "ToolResolveResponse": ".types", "ToolResult": ".types", "ToolResultData": ".types", "TraceIdResponse": ".types", "TraceIdsResponse": ".types", "TraceInput": ".types", "TraceInputSpansValue": ".types", "TraceOutput": ".types", "TraceOutputSpansValue": ".types", "TraceRequest": ".types", "TraceResponse": ".types", "TraceType": ".types", "TracesRequest": ".types", "TracesResponse": ".types", "TracingQuery": ".types", "TriggerAuthScheme": ".types", "TriggerCapabilitiesResult": ".types", "TriggerCapability": ".types", "TriggerCapabilityConnection": ".types", "TriggerCatalogEvent": ".types", "TriggerCatalogEventDetails": ".types", "TriggerCatalogEventResponse": ".types", "TriggerCatalogEventsResponse": ".types", "TriggerCatalogIntegration": ".types", "TriggerCatalogIntegrationResponse": ".types", "TriggerCatalogIntegrationsResponse": ".types", "TriggerCatalogProvider": ".types", "TriggerCatalogProviderResponse": ".types", "TriggerCatalogProvidersResponse": ".types", "TriggerConnectAffordance": ".types", "TriggerConnection": ".types", "TriggerConnectionCreate": ".types", "TriggerConnectionCreateData": ".types", "TriggerConnectionRequirement": ".types", "TriggerConnectionResponse": ".types", "TriggerConnectionStatus": ".types", "TriggerConnectionsResponse": ".types", "TriggerDeliveriesResponse": ".types", "TriggerDelivery": ".types", "TriggerDeliveryData": ".types", "TriggerDeliveryQuery": ".types", "TriggerDeliveryResponse": ".types", "TriggerDiscoveryConnectionState": ".types", "TriggerDiscoveryGuidance": ".types", "TriggerEventAck": ".types", "TriggerProviderKind": ".types", "TriggerSchedule": ".types", "TriggerScheduleCreate": ".types", "TriggerScheduleData": ".types", "TriggerScheduleDataInputsFields": ".types", "TriggerScheduleEdit": ".types", "TriggerScheduleFlags": ".types", "TriggerScheduleQuery": ".types", "TriggerScheduleResponse": ".types", "TriggerSchedulesResponse": ".types", "TriggerSubscription": ".types", "TriggerSubscriptionCreate": ".types", "TriggerSubscriptionCreateRequest": ".types", "TriggerSubscriptionData": ".types", "TriggerSubscriptionDataInputsFields": ".types", "TriggerSubscriptionEdit": ".types", "TriggerSubscriptionFlags": ".types", "TriggerSubscriptionQuery": ".types", "TriggerSubscriptionResponse": ".types", "TriggerSubscriptionsResponse": ".types", "UnprocessableEntityError": ".errors", "UserIdsResponse": ".types", "ValidationError": ".types", "ValidationErrorLocItem": ".types", "WebhookDeliveriesResponse": ".types", "WebhookDelivery": ".types", "WebhookDeliveryCreate": ".types", "WebhookDeliveryData": ".types", "WebhookDeliveryQuery": ".types", "WebhookDeliveryResponse": ".types", "WebhookDeliveryResponseInfo": ".types", "WebhookEventType": ".types", "WebhookProviderDto": ".types", "WebhookProviderSettingsDto": ".types", "WebhookSubscription": ".types", "WebhookSubscriptionCreate": ".types", "WebhookSubscriptionData": ".types", "WebhookSubscriptionDataAuthMode": ".types", "WebhookSubscriptionEdit": ".types", "WebhookSubscriptionFlags": ".types", "WebhookSubscriptionQuery": ".types", "WebhookSubscriptionResponse": ".types", "WebhookSubscriptionTestRequestSubscription": ".webhooks", "WebhookSubscriptionsResponse": ".types", "Windowing": ".types", "WindowingOrder": ".types", "Workflow": ".types", "WorkflowArtifactFlags": ".types", "WorkflowCatalogFlags": ".types", "WorkflowCatalogHarness": ".types", "WorkflowCatalogHarnessResponse": ".types", "WorkflowCatalogHarnessesResponse": ".types", "WorkflowCatalogPreset": ".types", "WorkflowCatalogPresetResponse": ".types", "WorkflowCatalogPresetsResponse": ".types", "WorkflowCatalogTemplate": ".types", "WorkflowCatalogTemplateResponse": ".types", "WorkflowCatalogTemplatesResponse": ".types", "WorkflowCatalogType": ".types", "WorkflowCatalogTypeResponse": ".types", "WorkflowCatalogTypesResponse": ".types", "WorkflowCreate": ".types", "WorkflowEdit": ".types", "WorkflowFlags": ".types", "WorkflowRequestData": ".types", "WorkflowResponse": ".types", "WorkflowRevisionCommit": ".types", "WorkflowRevisionCreate": ".types", "WorkflowRevisionDataInput": ".types", "WorkflowRevisionDataInputHeadersValue": ".types", "WorkflowRevisionDataInputRuntime": ".types", "WorkflowRevisionDataOutput": ".types", "WorkflowRevisionDataOutputHeadersValue": ".types", "WorkflowRevisionDataOutputRuntime": ".types", "WorkflowRevisionDelta": ".types", "WorkflowRevisionEdit": ".types", "WorkflowRevisionFlags": ".types", "WorkflowRevisionInput": ".types", "WorkflowRevisionOutput": ".types", "WorkflowRevisionResolveResponse": ".types", "WorkflowRevisionResponse": ".types", "WorkflowRevisionsLog": ".types", "WorkflowRevisionsResponse": ".types", "WorkflowVariant": ".types", "WorkflowVariantCreate": ".types", "WorkflowVariantEdit": ".types", "WorkflowVariantFlags": ".types", "WorkflowVariantFork": ".types", "WorkflowVariantResponse": ".types", "WorkflowVariantsResponse": ".types", "WorkflowsResponse": ".types", "Workspace": ".types", "WorkspaceMemberResponse": ".types", "WorkspacePermission": ".types", "WorkspaceResponse": ".types", "access": ".access", "annotations": ".annotations", "applications": ".applications", "billing": ".billing", "environments": ".environments", "evaluations": ".evaluations", "evaluators": ".evaluators", "events": ".events", "folders": ".folders", "invocations": ".invocations", "keys": ".keys", "legacy": ".legacy", "mounts": ".mounts", "organizations": ".organizations", "projects": ".projects", "queries": ".queries", "secrets": ".secrets", "sessions": ".sessions", "status": ".status", "testcases": ".testcases", "testsets": ".testsets", "tools": ".tools", "traces": ".traces", "triggers": ".triggers", "users": ".users", "webhooks": ".webhooks", "workflows": ".workflows", "workspaces": ".workspaces"} def __getattr__(attr_name: str) -> typing.Any: module_name = _dynamic_imports.get(attr_name) if module_name is None: @@ -38,4 +38,4 @@ def __getattr__(attr_name: str) -> typing.Any: def __dir__(): lazy_attrs = list(_dynamic_imports.keys()) return sorted(lazy_attrs) -__all__ = ["AdminAccountCreateOptions", "AdminAccountRead", "AdminAccountsCreate", "AdminAccountsDelete", "AdminAccountsDeleteTarget", "AdminAccountsResponse", "AdminApiKeyCreate", "AdminApiKeyResponse", "AdminDeleteResponse", "AdminDeletedEntities", "AdminDeletedEntity", "AdminOrganizationCreate", "AdminOrganizationMembershipCreate", "AdminOrganizationMembershipRead", "AdminOrganizationRead", "AdminProjectCreate", "AdminProjectMembershipCreate", "AdminProjectMembershipRead", "AdminProjectRead", "AdminSimpleAccountCreate", "AdminSimpleAccountDeleteEntry", "AdminSimpleAccountRead", "AdminSimpleAccountsApiKeysCreate", "AdminSimpleAccountsCreate", "AdminSimpleAccountsDelete", "AdminSimpleAccountsOrganizationsCreate", "AdminSimpleAccountsOrganizationsMembershipsCreate", "AdminSimpleAccountsOrganizationsTransferOwnership", "AdminSimpleAccountsOrganizationsTransferOwnershipIncludeProjects", "AdminSimpleAccountsOrganizationsTransferOwnershipIncludeProjectsZero", "AdminSimpleAccountsOrganizationsTransferOwnershipIncludeWorkspaces", "AdminSimpleAccountsOrganizationsTransferOwnershipIncludeWorkspacesZero", "AdminSimpleAccountsOrganizationsTransferOwnershipResponse", "AdminSimpleAccountsProjectsCreate", "AdminSimpleAccountsProjectsMembershipsCreate", "AdminSimpleAccountsResponse", "AdminSimpleAccountsUsersCreate", "AdminSimpleAccountsUsersIdentitiesCreate", "AdminSimpleAccountsUsersResetPassword", "AdminSimpleAccountsWorkspacesCreate", "AdminSimpleAccountsWorkspacesMembershipsCreate", "AdminStructuredError", "AdminSubscriptionCreate", "AdminSubscriptionRead", "AdminUserCreate", "AdminUserIdentityCreate", "AdminUserIdentityRead", "AdminUserIdentityReadStatus", "AdminUserRead", "AdminWorkspaceCreate", "AdminWorkspaceMembershipCreate", "AdminWorkspaceMembershipRead", "AdminWorkspaceRead", "AgentTemplateOverlay", "AgentaApi", "AgentaApiEnvironment", "Analytics", "AnalyticsResponse", "Annotation", "AnnotationCreate", "AnnotationCreateLinks", "AnnotationEdit", "AnnotationEditLinks", "AnnotationLinkResponse", "AnnotationLinks", "AnnotationQuery", "AnnotationQueryLinks", "AnnotationResponse", "AnnotationsResponse", "Application", "ApplicationArtifactFlags", "ApplicationArtifactQueryFlags", "ApplicationCatalogPreset", "ApplicationCatalogPresetResponse", "ApplicationCatalogPresetsResponse", "ApplicationCatalogTemplate", "ApplicationCatalogTemplateResponse", "ApplicationCatalogTemplatesResponse", "ApplicationCatalogType", "ApplicationCatalogTypesResponse", "ApplicationCreate", "ApplicationEdit", "ApplicationFlags", "ApplicationQuery", "ApplicationResponse", "ApplicationRevisionCommit", "ApplicationRevisionCreate", "ApplicationRevisionDataInput", "ApplicationRevisionDataInputHeadersValue", "ApplicationRevisionDataInputRuntime", "ApplicationRevisionDataOutput", "ApplicationRevisionDataOutputHeadersValue", "ApplicationRevisionDataOutputRuntime", "ApplicationRevisionEdit", "ApplicationRevisionFlags", "ApplicationRevisionInput", "ApplicationRevisionOutput", "ApplicationRevisionQuery", "ApplicationRevisionQueryFlags", "ApplicationRevisionResolveResponse", "ApplicationRevisionResponse", "ApplicationRevisionsLog", "ApplicationRevisionsResponse", "ApplicationVariant", "ApplicationVariantCreate", "ApplicationVariantEdit", "ApplicationVariantFlags", "ApplicationVariantFork", "ApplicationVariantResponse", "ApplicationVariantsResponse", "ApplicationsResponse", "AsyncAgentaApi", "BodyConfigsFetchVariantsConfigsFetchPost", "Bucket", "BuiltinToolConfig", "BuiltinToolConfigPermission", "CapabilitiesResult", "Capability", "CapabilityConnection", "CapabilityGuidance", "CollectStatusResponse", "CommandMode", "ComparisonOperator", "Condition", "ConditionOperator", "ConditionOptions", "ConditionValue", "ConfigResponseModel", "ConnectAffordance", "ConnectionRequirement", "CreateSimpleTestsetFromFileRequestFileType", "CreateTestsetRevisionFromFileRequestFileType", "CustomModelSettingsDto", "CustomProviderDto", "CustomProviderKind", "CustomProviderSettingsDto", "CustomSecretDto", "CustomSecretFormat", "CustomSecretSettingsDto", "CustomSecretSettingsDtoContent", "CustomSecretSettingsDtoContentOneValue", "DictOperator", "DiscoverResponse", "DiscoverResponseMethodsValue", "DiscoveredAlternative", "DiscoveredTool", "DiscoveredToolType", "DiscoveredTriggerAlternative", "DiscoveredTriggerEvent", "DiscoveredTriggerEventType", "EditSimpleTestsetFromFileRequestFileType", "EntityRef", "Environment", "EnvironmentCreate", "EnvironmentEdit", "EnvironmentFlags", "EnvironmentQueryFlags", "EnvironmentResponse", "EnvironmentRevisionCommit", "EnvironmentRevisionCreate", "EnvironmentRevisionData", "EnvironmentRevisionDelta", "EnvironmentRevisionEdit", "EnvironmentRevisionInput", "EnvironmentRevisionOutput", "EnvironmentRevisionResolveResponse", "EnvironmentRevisionResponse", "EnvironmentRevisionsLog", "EnvironmentRevisionsResponse", "EnvironmentVariant", "EnvironmentVariantCreate", "EnvironmentVariantEdit", "EnvironmentVariantFork", "EnvironmentVariantResponse", "EnvironmentVariantsResponse", "EnvironmentsResponse", "ErrorPolicy", "EvaluationMetrics", "EvaluationMetricsCreate", "EvaluationMetricsIdsResponse", "EvaluationMetricsQuery", "EvaluationMetricsQueryScenarioIds", "EvaluationMetricsQueryTimestamps", "EvaluationMetricsRefresh", "EvaluationMetricsResponse", "EvaluationMetricsSetRequest", "EvaluationQueue", "EvaluationQueueCreate", "EvaluationQueueData", "EvaluationQueueEdit", "EvaluationQueueFlags", "EvaluationQueueIdResponse", "EvaluationQueueIdsResponse", "EvaluationQueueQuery", "EvaluationQueueQueryFlags", "EvaluationQueueResponse", "EvaluationQueueScenariosQuery", "EvaluationQueuesResponse", "EvaluationResult", "EvaluationResultCreate", "EvaluationResultIdResponse", "EvaluationResultIdsResponse", "EvaluationResultQuery", "EvaluationResultResponse", "EvaluationResultsResponse", "EvaluationResultsSetRequest", "EvaluationRun", "EvaluationRunCreate", "EvaluationRunDataConcurrency", "EvaluationRunDataInput", "EvaluationRunDataMapping", "EvaluationRunDataMappingColumn", "EvaluationRunDataMappingStep", "EvaluationRunDataOutput", "EvaluationRunDataStepInput", "EvaluationRunDataStepInputKey", "EvaluationRunDataStepInputOrigin", "EvaluationRunDataStepInputType", "EvaluationRunDataStepOutput", "EvaluationRunDataStepOutputOrigin", "EvaluationRunDataStepOutputType", "EvaluationRunEdit", "EvaluationRunFlags", "EvaluationRunIdResponse", "EvaluationRunIdsRequest", "EvaluationRunIdsResponse", "EvaluationRunQuery", "EvaluationRunQueryFlags", "EvaluationRunResponse", "EvaluationRunsResponse", "EvaluationScenario", "EvaluationScenarioCreate", "EvaluationScenarioEdit", "EvaluationScenarioIdResponse", "EvaluationScenarioIdsResponse", "EvaluationScenarioQuery", "EvaluationScenarioResponse", "EvaluationScenariosResponse", "EvaluationStatus", "Evaluator", "EvaluatorArtifactFlags", "EvaluatorArtifactQueryFlags", "EvaluatorCatalogPreset", "EvaluatorCatalogPresetResponse", "EvaluatorCatalogPresetsResponse", "EvaluatorCatalogTemplate", "EvaluatorCatalogTemplateResponse", "EvaluatorCatalogTemplatesResponse", "EvaluatorCatalogType", "EvaluatorCatalogTypesResponse", "EvaluatorCreate", "EvaluatorEdit", "EvaluatorFlags", "EvaluatorQuery", "EvaluatorResponse", "EvaluatorRevisionCommit", "EvaluatorRevisionCreate", "EvaluatorRevisionDataInput", "EvaluatorRevisionDataInputHeadersValue", "EvaluatorRevisionDataInputRuntime", "EvaluatorRevisionDataOutput", "EvaluatorRevisionDataOutputHeadersValue", "EvaluatorRevisionDataOutputRuntime", "EvaluatorRevisionEdit", "EvaluatorRevisionFlags", "EvaluatorRevisionInput", "EvaluatorRevisionOutput", "EvaluatorRevisionQuery", "EvaluatorRevisionQueryFlags", "EvaluatorRevisionResolveResponse", "EvaluatorRevisionResponse", "EvaluatorRevisionsLog", "EvaluatorRevisionsResponse", "EvaluatorTemplate", "EvaluatorTemplatesResponse", "EvaluatorVariant", "EvaluatorVariantCreate", "EvaluatorVariantEdit", "EvaluatorVariantFlags", "EvaluatorVariantFork", "EvaluatorVariantResponse", "EvaluatorVariantsResponse", "EvaluatorsResponse", "Event", "EventQuery", "EventType", "EventsQueryResponse", "ExistenceOperator", "FetchLegacyAnalyticsRequestNewest", "FetchLegacyAnalyticsRequestOldest", "FetchSimpleTestsetToFileRequestFileType", "FetchTestsetRevisionToFileRequestFileType", "FilteringInput", "FilteringInputConditionsItem", "FilteringOutput", "FilteringOutputConditionsItem", "Focus", "Folder", "FolderCreate", "FolderEdit", "FolderIdResponse", "FolderKind", "FolderQuery", "FolderQueryKinds", "FolderResponse", "FoldersResponse", "Format", "Formatting", typing.Any, typing.Any, "GatewayToolConfig", "GatewayToolConfigPermission", "HarnessSessionRecord", "Header", "HttpValidationError", "InviteRequest", "Invocation", "InvocationCreate", "InvocationCreateLinks", "InvocationEdit", "InvocationEditLinks", "InvocationLinkResponse", "InvocationLinks", "InvocationQuery", "InvocationQueryLinks", "InvocationResponse", "InvocationsResponse", "JsonSchemasInput", "JsonSchemasOutput", typing.Any, typing.Any, "LegacyLifecycleDto", "ListApiKeysResponse", "ListOperator", "ListOptions", "LogicalOperator", "MetricSpec", "MetricType", "MetricsBucket", "Mount", "MountCreate", "MountCredentials", "MountCredentialsResponse", "MountData", "MountEdit", "MountFileDeletedResponse", "MountFileWrittenResponse", "MountFlags", "MountFolderCreatedResponse", "MountQuery", "MountResponse", "MountsResponse", "NumericOperator", "OTelEventInput", "OTelEventInputTimestamp", "OTelEventOutput", "OTelEventOutputTimestamp", "OTelHashInput", "OTelHashOutput", "OTelLinkInput", "OTelLinkOutput", "OTelLinksResponse", "OTelReferenceInput", "OTelReferenceOutput", "OTelSpanKind", "OTelStatusCode", "OTelTracingRequest", "OTelTracingResponse", "OldAnalyticsResponse", "Organization", "OrganizationDetails", "OrganizationDomainResponse", "OrganizationProviderResponse", "OrganizationUpdate", "Permission", "PlaygroundBuildKitContext", "ProjectsResponse", "QueriesResponse", "Query", "QueryApplicationVariantsRequestOrder", "QueryCreate", "QueryEdit", "QueryEnvironmentRevisionsRequestOrder", "QueryEnvironmentVariantsRequestOrder", "QueryEnvironmentsRequestOrder", "QueryEvaluatorVariantsRequestOrder", "QueryFlags", "QueryQueriesRequestOrder", "QueryQueryFlags", "QueryResponse", "QueryRevision", "QueryRevisionCommit", "QueryRevisionCreate", "QueryRevisionDataInput", "QueryRevisionDataOutput", "QueryRevisionEdit", "QueryRevisionQuery", "QueryRevisionResponse", "QueryRevisionsLog", "QueryRevisionsResponse", "QuerySpansAnalyticsRequestNewest", "QuerySpansAnalyticsRequestOldest", "QueryVariant", "QueryVariantCreate", "QueryVariantEdit", "QueryVariantFork", "QueryVariantQuery", "QueryVariantResponse", "QueryVariantsResponse", "QueryWorkflowRevisionsRequestOrder", "QueryWorkflowVariantsRequestOrder", "QueryWorkflowsRequestOrder", "Reference", "ReferenceRequestModelInput", "ReferenceRequestModelOutput", "RequestType", "ResolutionInfo", "ResolvedTool", "RetrievalInfo", "SecretDto", "SecretDtoData", "SecretKind", "SecretResponseDto", "SecretResponseDtoData", "Selector", "SessionHeartbeatResponseModel", "SessionIdsResponse", "SessionInteraction", "SessionInteractionData", "SessionInteractionFlags", "SessionInteractionKind", "SessionInteractionQuery", "SessionInteractionQueryFlags", "SessionInteractionResponse", "SessionInteractionStatus", "SessionInteractionsResponse", "SessionMount", "SessionMountQuery", "SessionMountsResponse", "SessionRecord", "SessionRecordResponse", "SessionRecordsQueryResponse", "SessionState", "SessionStateData", "SessionStateFlags", "SessionStateResponse", "SessionStateUpsertRequest", "SessionStream", "SessionStreamCommandResponseModel", "SessionStreamFlags", "SessionStreamResponseModel", "SessionStreamsResponseModel", "SimpleApplication", "SimpleApplicationAdditionalContext", "SimpleApplicationCreate", "SimpleApplicationDataInput", "SimpleApplicationDataInputHeadersValue", "SimpleApplicationDataInputRuntime", "SimpleApplicationDataOutput", "SimpleApplicationDataOutputHeadersValue", "SimpleApplicationDataOutputRuntime", "SimpleApplicationEdit", "SimpleApplicationFlags", "SimpleApplicationQuery", "SimpleApplicationQueryFlags", "SimpleApplicationResponse", "SimpleApplicationsResponse", "SimpleEnvironment", "SimpleEnvironmentCreate", "SimpleEnvironmentEdit", "SimpleEnvironmentQuery", "SimpleEnvironmentResponse", "SimpleEnvironmentsResponse", "SimpleEvaluation", "SimpleEvaluationCreate", "SimpleEvaluationData", "SimpleEvaluationDataApplicationSteps", "SimpleEvaluationDataApplicationStepsOneValue", "SimpleEvaluationDataEvaluatorSteps", "SimpleEvaluationDataEvaluatorStepsOneValue", "SimpleEvaluationDataQuerySteps", "SimpleEvaluationDataQueryStepsOneValue", "SimpleEvaluationDataTestsetSteps", "SimpleEvaluationDataTestsetStepsOneValue", "SimpleEvaluationEdit", "SimpleEvaluationIdResponse", "SimpleEvaluationQuery", "SimpleEvaluationResponse", "SimpleEvaluationsResponse", "SimpleEvaluator", "SimpleEvaluatorCreate", "SimpleEvaluatorDataInput", "SimpleEvaluatorDataInputHeadersValue", "SimpleEvaluatorDataInputRuntime", "SimpleEvaluatorDataOutput", "SimpleEvaluatorDataOutputHeadersValue", "SimpleEvaluatorDataOutputRuntime", "SimpleEvaluatorEdit", "SimpleEvaluatorFlags", "SimpleEvaluatorQuery", "SimpleEvaluatorQueryFlags", "SimpleEvaluatorResponse", "SimpleEvaluatorsResponse", "SimpleQueriesResponse", "SimpleQuery", "SimpleQueryCreate", "SimpleQueryEdit", "SimpleQueryQuery", "SimpleQueryResponse", "SimpleQueue", "SimpleQueueCreate", "SimpleQueueData", "SimpleQueueDataEvaluators", "SimpleQueueDataEvaluatorsOneValue", "SimpleQueueIdResponse", "SimpleQueueIdsResponse", "SimpleQueueKind", "SimpleQueueQuery", "SimpleQueueResponse", "SimpleQueueScenariosQuery", "SimpleQueueScenariosResponse", "SimpleQueueSettings", "SimpleQueuesResponse", "SimpleTestset", "SimpleTestsetCreate", "SimpleTestsetEdit", "SimpleTestsetQuery", "SimpleTestsetResponse", "SimpleTestsetsResponse", "SimpleTrace", "SimpleTraceChannel", "SimpleTraceCreate", "SimpleTraceCreateLinks", "SimpleTraceEdit", "SimpleTraceEditLinks", "SimpleTraceKind", "SimpleTraceLinkResponse", "SimpleTraceLinks", "SimpleTraceOrigin", "SimpleTraceQuery", "SimpleTraceQueryLinks", "SimpleTraceReferences", "SimpleTraceResponse", "SimpleTracesResponse", "SimpleWorkflow", "SimpleWorkflowCreate", "SimpleWorkflowDataInput", "SimpleWorkflowDataInputHeadersValue", "SimpleWorkflowDataInputRuntime", "SimpleWorkflowDataOutput", "SimpleWorkflowDataOutputHeadersValue", "SimpleWorkflowDataOutputRuntime", "SimpleWorkflowEdit", "SimpleWorkflowFlags", "SimpleWorkflowQuery", "SimpleWorkflowQueryFlags", "SimpleWorkflowResponse", "SimpleWorkflowsResponse", "SpanInput", "SpanInputEndTime", "SpanInputStartTime", "SpanOutput", "SpanOutputEndTime", "SpanOutputStartTime", "SpanResponse", "SpanType", "SpansNodeInput", "SpansNodeInputEndTime", "SpansNodeInputSpansValue", "SpansNodeInputStartTime", "SpansNodeOutput", "SpansNodeOutputEndTime", "SpansNodeOutputSpansValue", "SpansNodeOutputStartTime", "SpansResponse", "SpansTreeInput", "SpansTreeInputSpansValue", "SpansTreeOutput", "SpansTreeOutputSpansValue", "SsoProviderDto", "SsoProviderInfo", "SsoProviderSettingsDto", "SsoProviders", "StandardProviderDto", "StandardProviderKind", "StandardProviderSettingsDto", "Status", "StringOperator", "TestcaseInput", "TestcaseOutput", "TestcaseResponse", "TestcasesResponse", "Testset", "TestsetCreate", "TestsetEdit", "TestsetFlags", "TestsetQuery", "TestsetResponse", "TestsetRevision", "TestsetRevisionCommit", "TestsetRevisionCreate", "TestsetRevisionDataInput", "TestsetRevisionDataOutput", "TestsetRevisionDelta", "TestsetRevisionDeltaColumns", "TestsetRevisionDeltaRows", "TestsetRevisionEdit", "TestsetRevisionQuery", "TestsetRevisionResponse", "TestsetRevisionsLog", "TestsetRevisionsResponse", "TestsetVariant", "TestsetVariantCreate", "TestsetVariantEdit", "TestsetVariantFork", "TestsetVariantQuery", "TestsetVariantResponse", "TestsetVariantsResponse", "TestsetsResponse", "TextOptions", "ToolAuthScheme", "ToolCallData", "ToolCallFunction", "ToolCallResponse", "ToolCatalogAction", "ToolCatalogActionDetails", "ToolCatalogActionResponse", "ToolCatalogActionResponseAction", "ToolCatalogActionsResponse", "ToolCatalogActionsResponseActionsItem", "ToolCatalogCategoriesResponse", "ToolCatalogCategory", "ToolCatalogIntegration", "ToolCatalogIntegrationDetails", "ToolCatalogIntegrationResponse", "ToolCatalogIntegrationResponseIntegration", "ToolCatalogIntegrationsResponse", "ToolCatalogIntegrationsResponseIntegrationsItem", "ToolCatalogProvider", "ToolCatalogProviderDetails", "ToolCatalogProviderResponse", "ToolCatalogProviderResponseProvider", "ToolCatalogProvidersResponse", "ToolCatalogProvidersResponseProvidersItem", "ToolConnection", "ToolConnectionCreate", "ToolConnectionCreateData", "ToolConnectionResponse", "ToolConnectionState", "ToolConnectionStatus", "ToolConnectionsResponse", "ToolProviderKind", "ToolResolveRequestToolsItem", "ToolResolveRequestToolsItem_Builtin", "ToolResolveRequestToolsItem_Gateway", "ToolResolveResponse", "ToolResult", "ToolResultData", "TraceIdResponse", "TraceIdsResponse", "TraceInput", "TraceInputSpansValue", "TraceOutput", "TraceOutputSpansValue", "TraceRequest", "TraceResponse", "TraceType", "TracesRequest", "TracesResponse", "TracingQuery", "TriggerAuthScheme", "TriggerCapabilitiesResult", "TriggerCapability", "TriggerCapabilityConnection", "TriggerCatalogEvent", "TriggerCatalogEventDetails", "TriggerCatalogEventResponse", "TriggerCatalogEventsResponse", "TriggerCatalogIntegration", "TriggerCatalogIntegrationResponse", "TriggerCatalogIntegrationsResponse", "TriggerCatalogProvider", "TriggerCatalogProviderResponse", "TriggerCatalogProvidersResponse", "TriggerConnectAffordance", "TriggerConnection", "TriggerConnectionCreate", "TriggerConnectionCreateData", "TriggerConnectionRequirement", "TriggerConnectionResponse", "TriggerConnectionStatus", "TriggerConnectionsResponse", "TriggerDeliveriesResponse", "TriggerDelivery", "TriggerDeliveryData", "TriggerDeliveryQuery", "TriggerDeliveryResponse", "TriggerDiscoveryConnectionState", "TriggerDiscoveryGuidance", "TriggerEventAck", "TriggerProviderKind", "TriggerSchedule", "TriggerScheduleCreate", "TriggerScheduleData", "TriggerScheduleDataInputsFields", "TriggerScheduleEdit", "TriggerScheduleFlags", "TriggerScheduleQuery", "TriggerScheduleResponse", "TriggerSchedulesResponse", "TriggerSubscription", "TriggerSubscriptionCreate", "TriggerSubscriptionCreateRequest", "TriggerSubscriptionData", "TriggerSubscriptionDataInputsFields", "TriggerSubscriptionEdit", "TriggerSubscriptionFlags", "TriggerSubscriptionQuery", "TriggerSubscriptionResponse", "TriggerSubscriptionsResponse", "UnprocessableEntityError", "UserIdsResponse", "ValidationError", "ValidationErrorLocItem", "WebhookDeliveriesResponse", "WebhookDelivery", "WebhookDeliveryCreate", "WebhookDeliveryData", "WebhookDeliveryQuery", "WebhookDeliveryResponse", "WebhookDeliveryResponseInfo", "WebhookEventType", "WebhookProviderDto", "WebhookProviderSettingsDto", "WebhookSubscription", "WebhookSubscriptionCreate", "WebhookSubscriptionData", "WebhookSubscriptionDataAuthMode", "WebhookSubscriptionEdit", "WebhookSubscriptionFlags", "WebhookSubscriptionQuery", "WebhookSubscriptionResponse", "WebhookSubscriptionTestRequestSubscription", "WebhookSubscriptionsResponse", "Windowing", "WindowingOrder", "Workflow", "WorkflowArtifactFlags", "WorkflowCatalogFlags", "WorkflowCatalogHarness", "WorkflowCatalogHarnessResponse", "WorkflowCatalogHarnessesResponse", "WorkflowCatalogPreset", "WorkflowCatalogPresetResponse", "WorkflowCatalogPresetsResponse", "WorkflowCatalogTemplate", "WorkflowCatalogTemplateResponse", "WorkflowCatalogTemplatesResponse", "WorkflowCatalogType", "WorkflowCatalogTypeResponse", "WorkflowCatalogTypesResponse", "WorkflowCreate", "WorkflowEdit", "WorkflowFlags", "WorkflowResponse", "WorkflowRevisionCommit", "WorkflowRevisionCreate", "WorkflowRevisionDataInput", "WorkflowRevisionDataInputHeadersValue", "WorkflowRevisionDataInputRuntime", "WorkflowRevisionDataOutput", "WorkflowRevisionDataOutputHeadersValue", "WorkflowRevisionDataOutputRuntime", "WorkflowRevisionDelta", "WorkflowRevisionEdit", "WorkflowRevisionFlags", "WorkflowRevisionInput", "WorkflowRevisionOutput", "WorkflowRevisionResolveResponse", "WorkflowRevisionResponse", "WorkflowRevisionsLog", "WorkflowRevisionsResponse", "WorkflowVariant", "WorkflowVariantCreate", "WorkflowVariantEdit", "WorkflowVariantFlags", "WorkflowVariantFork", "WorkflowVariantResponse", "WorkflowVariantsResponse", "WorkflowsResponse", "Workspace", "WorkspaceMemberResponse", "WorkspacePermission", "WorkspaceResponse", "access", "annotations", "applications", "billing", "environments", "evaluations", "evaluators", "events", "folders", "invocations", "keys", "legacy", "mounts", "organizations", "projects", "queries", "secrets", "sessions", "status", "testcases", "testsets", "tools", "traces", "triggers", "users", "webhooks", "workflows", "workspaces"] +__all__ = ["AdminAccountCreateOptions", "AdminAccountRead", "AdminAccountsCreate", "AdminAccountsDelete", "AdminAccountsDeleteTarget", "AdminAccountsResponse", "AdminApiKeyCreate", "AdminApiKeyResponse", "AdminDeleteResponse", "AdminDeletedEntities", "AdminDeletedEntity", "AdminOrganizationCreate", "AdminOrganizationMembershipCreate", "AdminOrganizationMembershipRead", "AdminOrganizationRead", "AdminProjectCreate", "AdminProjectMembershipCreate", "AdminProjectMembershipRead", "AdminProjectRead", "AdminSimpleAccountCreate", "AdminSimpleAccountDeleteEntry", "AdminSimpleAccountRead", "AdminSimpleAccountsApiKeysCreate", "AdminSimpleAccountsCreate", "AdminSimpleAccountsDelete", "AdminSimpleAccountsOrganizationsCreate", "AdminSimpleAccountsOrganizationsMembershipsCreate", "AdminSimpleAccountsOrganizationsTransferOwnership", "AdminSimpleAccountsOrganizationsTransferOwnershipIncludeProjects", "AdminSimpleAccountsOrganizationsTransferOwnershipIncludeProjectsZero", "AdminSimpleAccountsOrganizationsTransferOwnershipIncludeWorkspaces", "AdminSimpleAccountsOrganizationsTransferOwnershipIncludeWorkspacesZero", "AdminSimpleAccountsOrganizationsTransferOwnershipResponse", "AdminSimpleAccountsProjectsCreate", "AdminSimpleAccountsProjectsMembershipsCreate", "AdminSimpleAccountsResponse", "AdminSimpleAccountsUsersCreate", "AdminSimpleAccountsUsersIdentitiesCreate", "AdminSimpleAccountsUsersResetPassword", "AdminSimpleAccountsWorkspacesCreate", "AdminSimpleAccountsWorkspacesMembershipsCreate", "AdminStructuredError", "AdminSubscriptionCreate", "AdminSubscriptionRead", "AdminUserCreate", "AdminUserIdentityCreate", "AdminUserIdentityRead", "AdminUserIdentityReadStatus", "AdminUserRead", "AdminWorkspaceCreate", "AdminWorkspaceMembershipCreate", "AdminWorkspaceMembershipRead", "AdminWorkspaceRead", "AgentTemplateOverlay", "AgentaApi", "AgentaApiEnvironment", "Analytics", "AnalyticsResponse", "Annotation", "AnnotationCreate", "AnnotationCreateLinks", "AnnotationEdit", "AnnotationEditLinks", "AnnotationLinkResponse", "AnnotationLinks", "AnnotationQuery", "AnnotationQueryLinks", "AnnotationResponse", "AnnotationsResponse", "Application", "ApplicationArtifactFlags", "ApplicationArtifactQueryFlags", "ApplicationCatalogPreset", "ApplicationCatalogPresetResponse", "ApplicationCatalogPresetsResponse", "ApplicationCatalogTemplate", "ApplicationCatalogTemplateResponse", "ApplicationCatalogTemplatesResponse", "ApplicationCatalogType", "ApplicationCatalogTypesResponse", "ApplicationCreate", "ApplicationEdit", "ApplicationFlags", "ApplicationQuery", "ApplicationResponse", "ApplicationRevisionCommit", "ApplicationRevisionCreate", "ApplicationRevisionDataInput", "ApplicationRevisionDataInputHeadersValue", "ApplicationRevisionDataInputRuntime", "ApplicationRevisionDataOutput", "ApplicationRevisionDataOutputHeadersValue", "ApplicationRevisionDataOutputRuntime", "ApplicationRevisionEdit", "ApplicationRevisionFlags", "ApplicationRevisionInput", "ApplicationRevisionOutput", "ApplicationRevisionQuery", "ApplicationRevisionQueryFlags", "ApplicationRevisionResolveResponse", "ApplicationRevisionResponse", "ApplicationRevisionsLog", "ApplicationRevisionsResponse", "ApplicationVariant", "ApplicationVariantCreate", "ApplicationVariantEdit", "ApplicationVariantFlags", "ApplicationVariantFork", "ApplicationVariantResponse", "ApplicationVariantsResponse", "ApplicationsResponse", "AsyncAgentaApi", "BodyConfigsFetchVariantsConfigsFetchPost", "Bucket", "BuiltinToolConfig", "BuiltinToolConfigPermission", "CapabilitiesResult", "Capability", "CapabilityConnection", "CapabilityGuidance", "CollectStatusResponse", "CommandMode", "ComparisonOperator", "Condition", "ConditionOperator", "ConditionOptions", "ConditionValue", "ConfigResponseModel", "ConnectAffordance", "ConnectionRequirement", "CreateSimpleTestsetFromFileRequestFileType", "CreateTestsetRevisionFromFileRequestFileType", "CustomModelSettingsDto", "CustomProviderDto", "CustomProviderKind", "CustomProviderSettingsDto", "CustomSecretDto", "CustomSecretFormat", "CustomSecretSettingsDto", "CustomSecretSettingsDtoContent", "CustomSecretSettingsDtoContentOneValue", "DictOperator", "DiscoverResponse", "DiscoverResponseMethodsValue", "DiscoveredAlternative", "DiscoveredTool", "DiscoveredToolType", "DiscoveredTriggerAlternative", "DiscoveredTriggerEvent", "DiscoveredTriggerEventType", "EditSimpleTestsetFromFileRequestFileType", "EntityRef", "Environment", "EnvironmentCreate", "EnvironmentEdit", "EnvironmentFlags", "EnvironmentQueryFlags", "EnvironmentResponse", "EnvironmentRevisionCommit", "EnvironmentRevisionCreate", "EnvironmentRevisionData", "EnvironmentRevisionDelta", "EnvironmentRevisionEdit", "EnvironmentRevisionInput", "EnvironmentRevisionOutput", "EnvironmentRevisionResolveResponse", "EnvironmentRevisionResponse", "EnvironmentRevisionsLog", "EnvironmentRevisionsResponse", "EnvironmentVariant", "EnvironmentVariantCreate", "EnvironmentVariantEdit", "EnvironmentVariantFork", "EnvironmentVariantResponse", "EnvironmentVariantsResponse", "EnvironmentsResponse", "ErrorPolicy", "EvaluationMetrics", "EvaluationMetricsCreate", "EvaluationMetricsIdsResponse", "EvaluationMetricsQuery", "EvaluationMetricsQueryScenarioIds", "EvaluationMetricsQueryTimestamps", "EvaluationMetricsRefresh", "EvaluationMetricsResponse", "EvaluationMetricsSetRequest", "EvaluationQueue", "EvaluationQueueCreate", "EvaluationQueueData", "EvaluationQueueEdit", "EvaluationQueueFlags", "EvaluationQueueIdResponse", "EvaluationQueueIdsResponse", "EvaluationQueueQuery", "EvaluationQueueQueryFlags", "EvaluationQueueResponse", "EvaluationQueueScenariosQuery", "EvaluationQueuesResponse", "EvaluationResult", "EvaluationResultCreate", "EvaluationResultIdResponse", "EvaluationResultIdsResponse", "EvaluationResultQuery", "EvaluationResultResponse", "EvaluationResultsResponse", "EvaluationResultsSetRequest", "EvaluationRun", "EvaluationRunCreate", "EvaluationRunDataConcurrency", "EvaluationRunDataInput", "EvaluationRunDataMapping", "EvaluationRunDataMappingColumn", "EvaluationRunDataMappingStep", "EvaluationRunDataOutput", "EvaluationRunDataStepInput", "EvaluationRunDataStepInputKey", "EvaluationRunDataStepInputOrigin", "EvaluationRunDataStepInputType", "EvaluationRunDataStepOutput", "EvaluationRunDataStepOutputOrigin", "EvaluationRunDataStepOutputType", "EvaluationRunEdit", "EvaluationRunFlags", "EvaluationRunIdResponse", "EvaluationRunIdsRequest", "EvaluationRunIdsResponse", "EvaluationRunQuery", "EvaluationRunQueryFlags", "EvaluationRunResponse", "EvaluationRunsResponse", "EvaluationScenario", "EvaluationScenarioCreate", "EvaluationScenarioEdit", "EvaluationScenarioIdResponse", "EvaluationScenarioIdsResponse", "EvaluationScenarioQuery", "EvaluationScenarioResponse", "EvaluationScenariosResponse", "EvaluationStatus", "Evaluator", "EvaluatorArtifactFlags", "EvaluatorArtifactQueryFlags", "EvaluatorCatalogPreset", "EvaluatorCatalogPresetResponse", "EvaluatorCatalogPresetsResponse", "EvaluatorCatalogTemplate", "EvaluatorCatalogTemplateResponse", "EvaluatorCatalogTemplatesResponse", "EvaluatorCatalogType", "EvaluatorCatalogTypesResponse", "EvaluatorCreate", "EvaluatorEdit", "EvaluatorFlags", "EvaluatorQuery", "EvaluatorResponse", "EvaluatorRevisionCommit", "EvaluatorRevisionCreate", "EvaluatorRevisionDataInput", "EvaluatorRevisionDataInputHeadersValue", "EvaluatorRevisionDataInputRuntime", "EvaluatorRevisionDataOutput", "EvaluatorRevisionDataOutputHeadersValue", "EvaluatorRevisionDataOutputRuntime", "EvaluatorRevisionEdit", "EvaluatorRevisionFlags", "EvaluatorRevisionInput", "EvaluatorRevisionOutput", "EvaluatorRevisionQuery", "EvaluatorRevisionQueryFlags", "EvaluatorRevisionResolveResponse", "EvaluatorRevisionResponse", "EvaluatorRevisionsLog", "EvaluatorRevisionsResponse", "EvaluatorTemplate", "EvaluatorTemplatesResponse", "EvaluatorVariant", "EvaluatorVariantCreate", "EvaluatorVariantEdit", "EvaluatorVariantFlags", "EvaluatorVariantFork", "EvaluatorVariantResponse", "EvaluatorVariantsResponse", "EvaluatorsResponse", "Event", "EventQuery", "EventType", "EventsQueryResponse", "ExistenceOperator", "FetchLegacyAnalyticsRequestNewest", "FetchLegacyAnalyticsRequestOldest", "FetchSimpleTestsetToFileRequestFileType", "FetchTestsetRevisionToFileRequestFileType", "FilteringInput", "FilteringInputConditionsItem", "FilteringOutput", "FilteringOutputConditionsItem", "Focus", "Folder", "FolderCreate", "FolderEdit", "FolderIdResponse", "FolderKind", "FolderQuery", "FolderQueryKinds", "FolderResponse", "FoldersResponse", "Format", "Formatting", typing.Any, typing.Any, "GatewayToolConfig", "GatewayToolConfigPermission", "HarnessKind", "Header", "HttpValidationError", "InviteRequest", "Invocation", "InvocationCreate", "InvocationCreateLinks", "InvocationEdit", "InvocationEditLinks", "InvocationLinkResponse", "InvocationLinks", "InvocationQuery", "InvocationQueryLinks", "InvocationResponse", "InvocationsResponse", "JsonSchemasInput", "JsonSchemasOutput", typing.Any, typing.Any, "LegacyLifecycleDto", "ListApiKeysResponse", "ListOperator", "ListOptions", "LogicalOperator", "MetricSpec", "MetricType", "MetricsBucket", "Mount", "MountCreate", "MountCredentials", "MountCredentialsResponse", "MountData", "MountEdit", "MountFileDeletedResponse", "MountFileWrittenResponse", "MountFlags", "MountFolderCreatedResponse", "MountQuery", "MountResponse", "MountsResponse", "NumericOperator", "OTelEventInput", "OTelEventInputTimestamp", "OTelEventOutput", "OTelEventOutputTimestamp", "OTelHashInput", "OTelHashOutput", "OTelLinkInput", "OTelLinkOutput", "OTelLinksResponse", "OTelReferenceInput", "OTelReferenceOutput", "OTelSpanKind", "OTelStatusCode", "OTelTracingRequest", "OTelTracingResponse", "OldAnalyticsResponse", "Organization", "OrganizationDetails", "OrganizationDomainResponse", "OrganizationProviderResponse", "OrganizationUpdate", "Permission", "PlaygroundBuildKitContext", "ProjectsResponse", "QueriesResponse", "Query", "QueryApplicationVariantsRequestOrder", "QueryCreate", "QueryEdit", "QueryEnvironmentRevisionsRequestOrder", "QueryEnvironmentVariantsRequestOrder", "QueryEnvironmentsRequestOrder", "QueryEvaluatorVariantsRequestOrder", "QueryFlags", "QueryQueriesRequestOrder", "QueryQueryFlags", "QueryResponse", "QueryRevision", "QueryRevisionCommit", "QueryRevisionCreate", "QueryRevisionDataInput", "QueryRevisionDataOutput", "QueryRevisionEdit", "QueryRevisionQuery", "QueryRevisionResponse", "QueryRevisionsLog", "QueryRevisionsResponse", "QuerySpansAnalyticsRequestNewest", "QuerySpansAnalyticsRequestOldest", "QueryVariant", "QueryVariantCreate", "QueryVariantEdit", "QueryVariantFork", "QueryVariantQuery", "QueryVariantResponse", "QueryVariantsResponse", "QueryWorkflowRevisionsRequestOrder", "QueryWorkflowVariantsRequestOrder", "QueryWorkflowsRequestOrder", "Reference", "ReferenceRequestModelInput", "ReferenceRequestModelOutput", "RequestType", "ResolutionInfo", "ResolvedTool", "RetrievalInfo", "SecretDto", "SecretDtoData", "SecretKind", "SecretResponseDto", "SecretResponseDtoData", "Selector", "SessionHeartbeatResult", "SessionIdsResponse", "SessionInteraction", "SessionInteractionData", "SessionInteractionFlags", "SessionInteractionKind", "SessionInteractionQuery", "SessionInteractionQueryFlags", "SessionInteractionResponse", "SessionInteractionStatus", "SessionInteractionsResponse", "SessionMount", "SessionMountQuery", "SessionMountsResponse", "SessionRecord", "SessionRecordResponse", "SessionRecordsQueryResponse", "SessionResponse", "SessionStream", "SessionStreamCommandResponse", "SessionStreamFlags", "SessionStreamHeaderEdit", "SessionStreamResponse", "SessionStreamsResponse", "SessionTurn", "SessionTurnQuery", "SessionTurnResponse", "SessionTurnsResponse", "SessionsResponse", "SimpleApplication", "SimpleApplicationAdditionalContext", "SimpleApplicationCreate", "SimpleApplicationDataInput", "SimpleApplicationDataInputHeadersValue", "SimpleApplicationDataInputRuntime", "SimpleApplicationDataOutput", "SimpleApplicationDataOutputHeadersValue", "SimpleApplicationDataOutputRuntime", "SimpleApplicationEdit", "SimpleApplicationFlags", "SimpleApplicationQuery", "SimpleApplicationQueryFlags", "SimpleApplicationResponse", "SimpleApplicationsResponse", "SimpleEnvironment", "SimpleEnvironmentCreate", "SimpleEnvironmentEdit", "SimpleEnvironmentQuery", "SimpleEnvironmentResponse", "SimpleEnvironmentsResponse", "SimpleEvaluation", "SimpleEvaluationCreate", "SimpleEvaluationData", "SimpleEvaluationDataApplicationSteps", "SimpleEvaluationDataApplicationStepsOneValue", "SimpleEvaluationDataEvaluatorSteps", "SimpleEvaluationDataEvaluatorStepsOneValue", "SimpleEvaluationDataQuerySteps", "SimpleEvaluationDataQueryStepsOneValue", "SimpleEvaluationDataTestsetSteps", "SimpleEvaluationDataTestsetStepsOneValue", "SimpleEvaluationEdit", "SimpleEvaluationIdResponse", "SimpleEvaluationQuery", "SimpleEvaluationResponse", "SimpleEvaluationsResponse", "SimpleEvaluator", "SimpleEvaluatorCreate", "SimpleEvaluatorDataInput", "SimpleEvaluatorDataInputHeadersValue", "SimpleEvaluatorDataInputRuntime", "SimpleEvaluatorDataOutput", "SimpleEvaluatorDataOutputHeadersValue", "SimpleEvaluatorDataOutputRuntime", "SimpleEvaluatorEdit", "SimpleEvaluatorFlags", "SimpleEvaluatorQuery", "SimpleEvaluatorQueryFlags", "SimpleEvaluatorResponse", "SimpleEvaluatorsResponse", "SimpleQueriesResponse", "SimpleQuery", "SimpleQueryCreate", "SimpleQueryEdit", "SimpleQueryQuery", "SimpleQueryResponse", "SimpleQueue", "SimpleQueueCreate", "SimpleQueueData", "SimpleQueueDataEvaluators", "SimpleQueueDataEvaluatorsOneValue", "SimpleQueueIdResponse", "SimpleQueueIdsResponse", "SimpleQueueKind", "SimpleQueueQuery", "SimpleQueueResponse", "SimpleQueueScenariosQuery", "SimpleQueueScenariosResponse", "SimpleQueueSettings", "SimpleQueuesResponse", "SimpleTestset", "SimpleTestsetCreate", "SimpleTestsetEdit", "SimpleTestsetQuery", "SimpleTestsetResponse", "SimpleTestsetsResponse", "SimpleTrace", "SimpleTraceChannel", "SimpleTraceCreate", "SimpleTraceCreateLinks", "SimpleTraceEdit", "SimpleTraceEditLinks", "SimpleTraceKind", "SimpleTraceLinkResponse", "SimpleTraceLinks", "SimpleTraceOrigin", "SimpleTraceQuery", "SimpleTraceQueryLinks", "SimpleTraceReferences", "SimpleTraceResponse", "SimpleTracesResponse", "SimpleWorkflow", "SimpleWorkflowCreate", "SimpleWorkflowDataInput", "SimpleWorkflowDataInputHeadersValue", "SimpleWorkflowDataInputRuntime", "SimpleWorkflowDataOutput", "SimpleWorkflowDataOutputHeadersValue", "SimpleWorkflowDataOutputRuntime", "SimpleWorkflowEdit", "SimpleWorkflowFlags", "SimpleWorkflowQuery", "SimpleWorkflowQueryFlags", "SimpleWorkflowResponse", "SimpleWorkflowsResponse", "SpanInput", "SpanInputEndTime", "SpanInputStartTime", "SpanOutput", "SpanOutputEndTime", "SpanOutputStartTime", "SpanResponse", "SpanType", "SpansNodeInput", "SpansNodeInputEndTime", "SpansNodeInputSpansValue", "SpansNodeInputStartTime", "SpansNodeOutput", "SpansNodeOutputEndTime", "SpansNodeOutputSpansValue", "SpansNodeOutputStartTime", "SpansResponse", "SpansTreeInput", "SpansTreeInputSpansValue", "SpansTreeOutput", "SpansTreeOutputSpansValue", "SsoProviderDto", "SsoProviderInfo", "SsoProviderSettingsDto", "SsoProviders", "StandardProviderDto", "StandardProviderKind", "StandardProviderSettingsDto", "Status", "StringOperator", "TestcaseInput", "TestcaseOutput", "TestcaseResponse", "TestcasesResponse", "Testset", "TestsetCreate", "TestsetEdit", "TestsetFlags", "TestsetQuery", "TestsetResponse", "TestsetRevision", "TestsetRevisionCommit", "TestsetRevisionCreate", "TestsetRevisionDataInput", "TestsetRevisionDataOutput", "TestsetRevisionDelta", "TestsetRevisionDeltaColumns", "TestsetRevisionDeltaRows", "TestsetRevisionEdit", "TestsetRevisionQuery", "TestsetRevisionResponse", "TestsetRevisionsLog", "TestsetRevisionsResponse", "TestsetVariant", "TestsetVariantCreate", "TestsetVariantEdit", "TestsetVariantFork", "TestsetVariantQuery", "TestsetVariantResponse", "TestsetVariantsResponse", "TestsetsResponse", "TextOptions", "ToolAuthScheme", "ToolCallData", "ToolCallFunction", "ToolCallResponse", "ToolCatalogAction", "ToolCatalogActionDetails", "ToolCatalogActionResponse", "ToolCatalogActionResponseAction", "ToolCatalogActionsResponse", "ToolCatalogActionsResponseActionsItem", "ToolCatalogCategoriesResponse", "ToolCatalogCategory", "ToolCatalogIntegration", "ToolCatalogIntegrationDetails", "ToolCatalogIntegrationResponse", "ToolCatalogIntegrationResponseIntegration", "ToolCatalogIntegrationsResponse", "ToolCatalogIntegrationsResponseIntegrationsItem", "ToolCatalogProvider", "ToolCatalogProviderDetails", "ToolCatalogProviderResponse", "ToolCatalogProviderResponseProvider", "ToolCatalogProvidersResponse", "ToolCatalogProvidersResponseProvidersItem", "ToolConnection", "ToolConnectionCreate", "ToolConnectionCreateData", "ToolConnectionResponse", "ToolConnectionState", "ToolConnectionStatus", "ToolConnectionsResponse", "ToolProviderKind", "ToolResolveRequestToolsItem", "ToolResolveRequestToolsItem_Builtin", "ToolResolveRequestToolsItem_Gateway", "ToolResolveResponse", "ToolResult", "ToolResultData", "TraceIdResponse", "TraceIdsResponse", "TraceInput", "TraceInputSpansValue", "TraceOutput", "TraceOutputSpansValue", "TraceRequest", "TraceResponse", "TraceType", "TracesRequest", "TracesResponse", "TracingQuery", "TriggerAuthScheme", "TriggerCapabilitiesResult", "TriggerCapability", "TriggerCapabilityConnection", "TriggerCatalogEvent", "TriggerCatalogEventDetails", "TriggerCatalogEventResponse", "TriggerCatalogEventsResponse", "TriggerCatalogIntegration", "TriggerCatalogIntegrationResponse", "TriggerCatalogIntegrationsResponse", "TriggerCatalogProvider", "TriggerCatalogProviderResponse", "TriggerCatalogProvidersResponse", "TriggerConnectAffordance", "TriggerConnection", "TriggerConnectionCreate", "TriggerConnectionCreateData", "TriggerConnectionRequirement", "TriggerConnectionResponse", "TriggerConnectionStatus", "TriggerConnectionsResponse", "TriggerDeliveriesResponse", "TriggerDelivery", "TriggerDeliveryData", "TriggerDeliveryQuery", "TriggerDeliveryResponse", "TriggerDiscoveryConnectionState", "TriggerDiscoveryGuidance", "TriggerEventAck", "TriggerProviderKind", "TriggerSchedule", "TriggerScheduleCreate", "TriggerScheduleData", "TriggerScheduleDataInputsFields", "TriggerScheduleEdit", "TriggerScheduleFlags", "TriggerScheduleQuery", "TriggerScheduleResponse", "TriggerSchedulesResponse", "TriggerSubscription", "TriggerSubscriptionCreate", "TriggerSubscriptionCreateRequest", "TriggerSubscriptionData", "TriggerSubscriptionDataInputsFields", "TriggerSubscriptionEdit", "TriggerSubscriptionFlags", "TriggerSubscriptionQuery", "TriggerSubscriptionResponse", "TriggerSubscriptionsResponse", "UnprocessableEntityError", "UserIdsResponse", "ValidationError", "ValidationErrorLocItem", "WebhookDeliveriesResponse", "WebhookDelivery", "WebhookDeliveryCreate", "WebhookDeliveryData", "WebhookDeliveryQuery", "WebhookDeliveryResponse", "WebhookDeliveryResponseInfo", "WebhookEventType", "WebhookProviderDto", "WebhookProviderSettingsDto", "WebhookSubscription", "WebhookSubscriptionCreate", "WebhookSubscriptionData", "WebhookSubscriptionDataAuthMode", "WebhookSubscriptionEdit", "WebhookSubscriptionFlags", "WebhookSubscriptionQuery", "WebhookSubscriptionResponse", "WebhookSubscriptionTestRequestSubscription", "WebhookSubscriptionsResponse", "Windowing", "WindowingOrder", "Workflow", "WorkflowArtifactFlags", "WorkflowCatalogFlags", "WorkflowCatalogHarness", "WorkflowCatalogHarnessResponse", "WorkflowCatalogHarnessesResponse", "WorkflowCatalogPreset", "WorkflowCatalogPresetResponse", "WorkflowCatalogPresetsResponse", "WorkflowCatalogTemplate", "WorkflowCatalogTemplateResponse", "WorkflowCatalogTemplatesResponse", "WorkflowCatalogType", "WorkflowCatalogTypeResponse", "WorkflowCatalogTypesResponse", "WorkflowCreate", "WorkflowEdit", "WorkflowFlags", "WorkflowRequestData", "WorkflowResponse", "WorkflowRevisionCommit", "WorkflowRevisionCreate", "WorkflowRevisionDataInput", "WorkflowRevisionDataInputHeadersValue", "WorkflowRevisionDataInputRuntime", "WorkflowRevisionDataOutput", "WorkflowRevisionDataOutputHeadersValue", "WorkflowRevisionDataOutputRuntime", "WorkflowRevisionDelta", "WorkflowRevisionEdit", "WorkflowRevisionFlags", "WorkflowRevisionInput", "WorkflowRevisionOutput", "WorkflowRevisionResolveResponse", "WorkflowRevisionResponse", "WorkflowRevisionsLog", "WorkflowRevisionsResponse", "WorkflowVariant", "WorkflowVariantCreate", "WorkflowVariantEdit", "WorkflowVariantFlags", "WorkflowVariantFork", "WorkflowVariantResponse", "WorkflowVariantsResponse", "WorkflowsResponse", "Workspace", "WorkspaceMemberResponse", "WorkspacePermission", "WorkspaceResponse", "access", "annotations", "applications", "billing", "environments", "evaluations", "evaluators", "events", "folders", "invocations", "keys", "legacy", "mounts", "organizations", "projects", "queries", "secrets", "sessions", "status", "testcases", "testsets", "tools", "traces", "triggers", "users", "webhooks", "workflows", "workspaces"] diff --git a/clients/python/agenta_client/mounts/client.py b/clients/python/agenta_client/mounts/client.py index ca866f0a4a..7e1498177a 100644 --- a/clients/python/agenta_client/mounts/client.py +++ b/clients/python/agenta_client/mounts/client.py @@ -62,12 +62,14 @@ def create_mount(self, *, mount: MountCreate, request_options: typing.Optional[R _response = self._raw_client.create_mount(mount=mount, request_options=request_options) return _response.data - def query_mounts(self, *, session_id: typing.Optional[str] = None, include_archived: typing.Optional[bool] = None, mount: typing.Optional[MountQuery] = OMIT, windowing: typing.Optional[Windowing] = OMIT, request_options: typing.Optional[RequestOptions] = None) -> MountsResponse: + def query_mounts(self, *, session_id: typing.Optional[str] = None, agent_id: typing.Optional[str] = None, include_archived: typing.Optional[bool] = None, mount: typing.Optional[MountQuery] = OMIT, windowing: typing.Optional[Windowing] = OMIT, request_options: typing.Optional[RequestOptions] = None) -> MountsResponse: """ Parameters ---------- session_id : typing.Optional[str] + agent_id : typing.Optional[str] + include_archived : typing.Optional[bool] mount : typing.Optional[MountQuery] @@ -91,7 +93,7 @@ def query_mounts(self, *, session_id: typing.Optional[str] = None, include_archi ) client.mounts.query_mounts() """ - _response = self._raw_client.query_mounts(session_id=session_id, include_archived=include_archived, mount=mount, windowing=windowing, request_options=request_options) + _response = self._raw_client.query_mounts(session_id=session_id, agent_id=agent_id, include_archived=include_archived, mount=mount, windowing=windowing, request_options=request_options) return _response.data def sign_agent_mount_credentials(self, *, artifact_id: str, name: typing.Optional[str] = None, request_options: typing.Optional[RequestOptions] = None) -> MountCredentialsResponse: @@ -536,12 +538,14 @@ async def main() -> None: _response = await self._raw_client.create_mount(mount=mount, request_options=request_options) return _response.data - async def query_mounts(self, *, session_id: typing.Optional[str] = None, include_archived: typing.Optional[bool] = None, mount: typing.Optional[MountQuery] = OMIT, windowing: typing.Optional[Windowing] = OMIT, request_options: typing.Optional[RequestOptions] = None) -> MountsResponse: + async def query_mounts(self, *, session_id: typing.Optional[str] = None, agent_id: typing.Optional[str] = None, include_archived: typing.Optional[bool] = None, mount: typing.Optional[MountQuery] = OMIT, windowing: typing.Optional[Windowing] = OMIT, request_options: typing.Optional[RequestOptions] = None) -> MountsResponse: """ Parameters ---------- session_id : typing.Optional[str] + agent_id : typing.Optional[str] + include_archived : typing.Optional[bool] mount : typing.Optional[MountQuery] @@ -573,7 +577,7 @@ async def main() -> None: asyncio.run(main()) """ - _response = await self._raw_client.query_mounts(session_id=session_id, include_archived=include_archived, mount=mount, windowing=windowing, request_options=request_options) + _response = await self._raw_client.query_mounts(session_id=session_id, agent_id=agent_id, include_archived=include_archived, mount=mount, windowing=windowing, request_options=request_options) return _response.data async def sign_agent_mount_credentials(self, *, artifact_id: str, name: typing.Optional[str] = None, request_options: typing.Optional[RequestOptions] = None) -> MountCredentialsResponse: diff --git a/clients/python/agenta_client/mounts/raw_client.py b/clients/python/agenta_client/mounts/raw_client.py index 541ba1cf86..ee1d7801b6 100644 --- a/clients/python/agenta_client/mounts/raw_client.py +++ b/clients/python/agenta_client/mounts/raw_client.py @@ -77,12 +77,14 @@ def create_mount(self, *, mount: MountCreate, request_options: typing.Optional[R raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) - def query_mounts(self, *, session_id: typing.Optional[str] = None, include_archived: typing.Optional[bool] = None, mount: typing.Optional[MountQuery] = OMIT, windowing: typing.Optional[Windowing] = OMIT, request_options: typing.Optional[RequestOptions] = None) -> HttpResponse[MountsResponse]: + def query_mounts(self, *, session_id: typing.Optional[str] = None, agent_id: typing.Optional[str] = None, include_archived: typing.Optional[bool] = None, mount: typing.Optional[MountQuery] = OMIT, windowing: typing.Optional[Windowing] = OMIT, request_options: typing.Optional[RequestOptions] = None) -> HttpResponse[MountsResponse]: """ Parameters ---------- session_id : typing.Optional[str] + agent_id : typing.Optional[str] + include_archived : typing.Optional[bool] mount : typing.Optional[MountQuery] @@ -99,7 +101,7 @@ def query_mounts(self, *, session_id: typing.Optional[str] = None, include_archi """ _response = self._client_wrapper.httpx_client.request( "mounts/query",method="POST", - params={"session_id": session_id, "include_archived": include_archived, } + params={"session_id": session_id, "agent_id": agent_id, "include_archived": include_archived, } , json={ "mount": convert_and_respect_annotation_metadata(object_=mount, annotation=typing.Optional[MountQuery], direction="write"), @@ -768,12 +770,14 @@ async def create_mount(self, *, mount: MountCreate, request_options: typing.Opti raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) - async def query_mounts(self, *, session_id: typing.Optional[str] = None, include_archived: typing.Optional[bool] = None, mount: typing.Optional[MountQuery] = OMIT, windowing: typing.Optional[Windowing] = OMIT, request_options: typing.Optional[RequestOptions] = None) -> AsyncHttpResponse[MountsResponse]: + async def query_mounts(self, *, session_id: typing.Optional[str] = None, agent_id: typing.Optional[str] = None, include_archived: typing.Optional[bool] = None, mount: typing.Optional[MountQuery] = OMIT, windowing: typing.Optional[Windowing] = OMIT, request_options: typing.Optional[RequestOptions] = None) -> AsyncHttpResponse[MountsResponse]: """ Parameters ---------- session_id : typing.Optional[str] + agent_id : typing.Optional[str] + include_archived : typing.Optional[bool] mount : typing.Optional[MountQuery] @@ -790,7 +794,7 @@ async def query_mounts(self, *, session_id: typing.Optional[str] = None, include """ _response = await self._client_wrapper.httpx_client.request( "mounts/query",method="POST", - params={"session_id": session_id, "include_archived": include_archived, } + params={"session_id": session_id, "agent_id": agent_id, "include_archived": include_archived, } , json={ "mount": convert_and_respect_annotation_metadata(object_=mount, annotation=typing.Optional[MountQuery], direction="write"), diff --git a/clients/python/agenta_client/sessions/client.py b/clients/python/agenta_client/sessions/client.py index 188370cd13..9efd32bd98 100644 --- a/clients/python/agenta_client/sessions/client.py +++ b/clients/python/agenta_client/sessions/client.py @@ -6,9 +6,11 @@ from .. import core from ..core.client_wrapper import AsyncClientWrapper, SyncClientWrapper from ..core.request_options import RequestOptions +from ..types.harness_kind import HarnessKind from ..types.mount_credentials_response import MountCredentialsResponse from ..types.mount_file_written_response import MountFileWrittenResponse -from ..types.session_heartbeat_response_model import SessionHeartbeatResponseModel +from ..types.reference import Reference +from ..types.session_heartbeat_result import SessionHeartbeatResult from ..types.session_interaction_data import SessionInteractionData from ..types.session_interaction_flags import SessionInteractionFlags from ..types.session_interaction_kind import SessionInteractionKind @@ -20,12 +22,16 @@ from ..types.session_mounts_response import SessionMountsResponse from ..types.session_record_response import SessionRecordResponse from ..types.session_records_query_response import SessionRecordsQueryResponse -from ..types.session_state_data import SessionStateData -from ..types.session_state_response import SessionStateResponse -from ..types.session_stream_command_response_model import SessionStreamCommandResponseModel -from ..types.session_stream_response_model import SessionStreamResponseModel -from ..types.session_streams_response_model import SessionStreamsResponseModel +from ..types.session_response import SessionResponse +from ..types.session_stream_command_response import SessionStreamCommandResponse +from ..types.session_stream_response import SessionStreamResponse +from ..types.session_streams_response import SessionStreamsResponse +from ..types.session_turn_query import SessionTurnQuery +from ..types.session_turn_response import SessionTurnResponse +from ..types.session_turns_response import SessionTurnsResponse +from ..types.sessions_response import SessionsResponse from ..types.windowing import Windowing +from ..types.workflow_request_data import WorkflowRequestData from .raw_client import AsyncRawSessionsClient, RawSessionsClient # this is used as the default value for optional parameters @@ -45,7 +51,7 @@ def with_raw_response(self) -> RawSessionsClient: """ return self._raw_client - def fetch_session_stream(self, *, session_id: str, request_options: typing.Optional[RequestOptions] = None) -> SessionStreamResponseModel: + def fetch_session_stream(self, *, session_id: str, request_options: typing.Optional[RequestOptions] = None) -> SessionStreamResponse: """ Parameters ---------- @@ -56,7 +62,7 @@ def fetch_session_stream(self, *, session_id: str, request_options: typing.Optio Returns ------- - SessionStreamResponseModel + SessionStreamResponse Successful Response Examples @@ -73,13 +79,13 @@ def fetch_session_stream(self, *, session_id: str, request_options: typing.Optio _response = self._raw_client.fetch_session_stream(session_id=session_id, request_options=request_options) return _response.data - def set_session_stream(self, *, session_id: str, prompt: typing.Optional[str] = OMIT, force: typing.Optional[bool] = OMIT, detached: typing.Optional[bool] = OMIT, request_options: typing.Optional[RequestOptions] = None) -> SessionStreamCommandResponseModel: + def set_session_stream(self, *, session_id: str, data: typing.Optional[WorkflowRequestData] = OMIT, force: typing.Optional[bool] = OMIT, detached: typing.Optional[bool] = OMIT, request_options: typing.Optional[RequestOptions] = None) -> SessionStreamCommandResponse: """ Parameters ---------- session_id : str - prompt : typing.Optional[str] + data : typing.Optional[WorkflowRequestData] force : typing.Optional[bool] @@ -90,7 +96,7 @@ def set_session_stream(self, *, session_id: str, prompt: typing.Optional[str] = Returns ------- - SessionStreamCommandResponseModel + SessionStreamCommandResponse Successful Response Examples @@ -104,7 +110,7 @@ def set_session_stream(self, *, session_id: str, prompt: typing.Optional[str] = session_id="session_id", ) """ - _response = self._raw_client.set_session_stream(session_id=session_id, prompt=prompt, force=force, detached=detached, request_options=request_options) + _response = self._raw_client.set_session_stream(session_id=session_id, data=data, force=force, detached=detached, request_options=request_options) return _response.data def delete_session_stream(self, *, session_id: str, request_options: typing.Optional[RequestOptions] = None) -> typing.Dict[str, typing.Any]: @@ -135,7 +141,7 @@ def delete_session_stream(self, *, session_id: str, request_options: typing.Opti _response = self._raw_client.delete_session_stream(session_id=session_id, request_options=request_options) return _response.data - def query_session_streams(self, *, session_id: typing.Optional[str] = OMIT, is_alive: typing.Optional[bool] = OMIT, is_running: typing.Optional[bool] = OMIT, request_options: typing.Optional[RequestOptions] = None) -> SessionStreamsResponseModel: + def query_session_streams(self, *, session_id: typing.Optional[str] = OMIT, is_alive: typing.Optional[bool] = OMIT, is_running: typing.Optional[bool] = OMIT, request_options: typing.Optional[RequestOptions] = None) -> SessionStreamsResponse: """ Parameters ---------- @@ -150,7 +156,7 @@ def query_session_streams(self, *, session_id: typing.Optional[str] = OMIT, is_a Returns ------- - SessionStreamsResponseModel + SessionStreamsResponse Successful Response Examples @@ -196,7 +202,7 @@ def detach_session_stream(self, *, session_id: str, watcher_id: str, request_opt _response = self._raw_client.detach_session_stream(session_id=session_id, watcher_id=watcher_id, request_options=request_options) return _response.data - def heartbeat_session_stream(self, *, session_id: str, replica_id: str, turn_id: typing.Optional[str] = OMIT, is_running: typing.Optional[bool] = OMIT, request_options: typing.Optional[RequestOptions] = None) -> SessionHeartbeatResponseModel: + def heartbeat_session_stream(self, *, session_id: str, replica_id: str, turn_id: typing.Optional[str] = OMIT, is_running: typing.Optional[bool] = OMIT, request_options: typing.Optional[RequestOptions] = None) -> SessionHeartbeatResult: """ Parameters ---------- @@ -213,7 +219,7 @@ def heartbeat_session_stream(self, *, session_id: str, replica_id: str, turn_id: Returns ------- - SessionHeartbeatResponseModel + SessionHeartbeatResult Successful Response Examples @@ -231,6 +237,38 @@ def heartbeat_session_stream(self, *, session_id: str, replica_id: str, turn_id: _response = self._raw_client.heartbeat_session_stream(session_id=session_id, replica_id=replica_id, turn_id=turn_id, is_running=is_running, request_options=request_options) return _response.data + def set_session_stream_header(self, *, session_id: str, name: typing.Optional[str] = OMIT, description: typing.Optional[str] = OMIT, request_options: typing.Optional[RequestOptions] = None) -> SessionStreamResponse: + """ + Parameters + ---------- + session_id : str + + name : typing.Optional[str] + + description : typing.Optional[str] + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + SessionStreamResponse + Successful Response + + Examples + -------- + from agenta import AgentaApi + + client = AgentaApi( + api_key="YOUR_API_KEY", + ) + client.sessions.set_session_stream_header( + session_id="session_id", + ) + """ + _response = self._raw_client.set_session_stream_header(session_id=session_id, name=name, description=description, request_options=request_options) + return _response.data + def create_interaction(self, *, session_id: str, token: str, kind: SessionInteractionKind, turn_id: typing.Optional[str] = OMIT, data: typing.Optional[SessionInteractionData] = OMIT, flags: typing.Optional[SessionInteractionFlags] = OMIT, tags: typing.Optional[typing.Dict[str, typing.Any]] = OMIT, meta: typing.Optional[typing.Dict[str, typing.Any]] = OMIT, request_options: typing.Optional[RequestOptions] = None) -> SessionInteractionResponse: """ Parameters @@ -641,7 +679,7 @@ def get_record_event(self, record_id: str, *, request_options: typing.Optional[R _response = self._raw_client.get_record_event(record_id, request_options=request_options) return _response.data - def ingest_record(self, *, session_id: str, record_id: typing.Optional[str] = OMIT, record_index: typing.Optional[int] = OMIT, timestamp: typing.Optional[dt.datetime] = OMIT, record_type: typing.Optional[str] = OMIT, record_source: typing.Optional[str] = OMIT, attributes: typing.Optional[typing.Dict[str, typing.Any]] = OMIT, request_options: typing.Optional[RequestOptions] = None) -> typing.Dict[str, typing.Any]: + def ingest_record(self, *, session_id: str, record_id: typing.Optional[str] = OMIT, record_index: typing.Optional[int] = OMIT, timestamp: typing.Optional[dt.datetime] = OMIT, record_type: typing.Optional[str] = OMIT, record_source: typing.Optional[str] = OMIT, attributes: typing.Optional[typing.Dict[str, typing.Any]] = OMIT, turn_id: typing.Optional[str] = OMIT, span_id: typing.Optional[str] = OMIT, request_options: typing.Optional[RequestOptions] = None) -> typing.Dict[str, typing.Any]: """ Parameters ---------- @@ -659,6 +697,10 @@ def ingest_record(self, *, session_id: str, record_id: typing.Optional[str] = OM attributes : typing.Optional[typing.Dict[str, typing.Any]] + turn_id : typing.Optional[str] + + span_id : typing.Optional[str] + request_options : typing.Optional[RequestOptions] Request-specific configuration. @@ -678,10 +720,145 @@ def ingest_record(self, *, session_id: str, record_id: typing.Optional[str] = OM session_id="session_id", ) """ - _response = self._raw_client.ingest_record(session_id=session_id, record_id=record_id, record_index=record_index, timestamp=timestamp, record_type=record_type, record_source=record_source, attributes=attributes, request_options=request_options) + _response = self._raw_client.ingest_record(session_id=session_id, record_id=record_id, record_index=record_index, timestamp=timestamp, record_type=record_type, record_source=record_source, attributes=attributes, turn_id=turn_id, span_id=span_id, request_options=request_options) + return _response.data + + def append_turn(self, *, session_id: str, stream_id: str, turn_index: int, harness_kind: HarnessKind, agent_session_id: typing.Optional[str] = OMIT, sandbox_id: typing.Optional[str] = OMIT, references: typing.Optional[typing.Sequence[Reference]] = OMIT, trace_id: typing.Optional[str] = OMIT, span_id: typing.Optional[str] = OMIT, start_time: typing.Optional[dt.datetime] = OMIT, end_time: typing.Optional[dt.datetime] = OMIT, request_options: typing.Optional[RequestOptions] = None) -> SessionTurnResponse: + """ + Parameters + ---------- + session_id : str + + stream_id : str + + turn_index : int + + harness_kind : HarnessKind + + agent_session_id : typing.Optional[str] + + sandbox_id : typing.Optional[str] + + references : typing.Optional[typing.Sequence[Reference]] + + trace_id : typing.Optional[str] + + span_id : typing.Optional[str] + + start_time : typing.Optional[dt.datetime] + + end_time : typing.Optional[dt.datetime] + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + SessionTurnResponse + Successful Response + + Examples + -------- + from agenta import AgentaApi + + client = AgentaApi( + api_key="YOUR_API_KEY", + ) + client.sessions.append_turn( + session_id="session_id", + stream_id="stream_id", + turn_index=1, + harness_kind="pi_core", + ) + """ + _response = self._raw_client.append_turn(session_id=session_id, stream_id=stream_id, turn_index=turn_index, harness_kind=harness_kind, agent_session_id=agent_session_id, sandbox_id=sandbox_id, references=references, trace_id=trace_id, span_id=span_id, start_time=start_time, end_time=end_time, request_options=request_options) + return _response.data + + def query_turns(self, *, query: typing.Optional[SessionTurnQuery] = OMIT, windowing: typing.Optional[Windowing] = OMIT, request_options: typing.Optional[RequestOptions] = None) -> SessionTurnsResponse: + """ + Parameters + ---------- + query : typing.Optional[SessionTurnQuery] + + windowing : typing.Optional[Windowing] + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + SessionTurnsResponse + Successful Response + + Examples + -------- + from agenta import AgentaApi + + client = AgentaApi( + api_key="YOUR_API_KEY", + ) + client.sessions.query_turns() + """ + _response = self._raw_client.query_turns(query=query, windowing=windowing, request_options=request_options) + return _response.data + + def fetch_turn(self, turn_id: str, *, request_options: typing.Optional[RequestOptions] = None) -> SessionTurnResponse: + """ + Parameters + ---------- + turn_id : str + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + SessionTurnResponse + Successful Response + + Examples + -------- + from agenta import AgentaApi + + client = AgentaApi( + api_key="YOUR_API_KEY", + ) + client.sessions.fetch_turn( + turn_id="turn_id", + ) + """ + _response = self._raw_client.fetch_turn(turn_id, request_options=request_options) + return _response.data + + def query_sessions(self, *, references: typing.Optional[typing.Sequence[Reference]] = OMIT, windowing: typing.Optional[Windowing] = OMIT, request_options: typing.Optional[RequestOptions] = None) -> SessionsResponse: + """ + Parameters + ---------- + references : typing.Optional[typing.Sequence[Reference]] + + windowing : typing.Optional[Windowing] + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + SessionsResponse + Successful Response + + Examples + -------- + from agenta import AgentaApi + + client = AgentaApi( + api_key="YOUR_API_KEY", + ) + client.sessions.query_sessions() + """ + _response = self._raw_client.query_sessions(references=references, windowing=windowing, request_options=request_options) return _response.data - def get_state(self, *, session_id: str, request_options: typing.Optional[RequestOptions] = None) -> SessionStateResponse: + def delete_session(self, *, session_id: str, request_options: typing.Optional[RequestOptions] = None) -> typing.Dict[str, typing.Any]: """ Parameters ---------- @@ -692,7 +869,7 @@ def get_state(self, *, session_id: str, request_options: typing.Optional[Request Returns ------- - SessionStateResponse + typing.Dict[str, typing.Any] Successful Response Examples @@ -702,34 +879,53 @@ def get_state(self, *, session_id: str, request_options: typing.Optional[Request client = AgentaApi( api_key="YOUR_API_KEY", ) - client.sessions.get_state( + client.sessions.delete_session( session_id="session_id", ) """ - _response = self._raw_client.get_state(session_id=session_id, request_options=request_options) + _response = self._raw_client.delete_session(session_id=session_id, request_options=request_options) return _response.data - def set_state(self, *, session_id: str, data: typing.Optional[SessionStateData] = OMIT, sandbox_id: typing.Optional[str] = OMIT, sandbox_turn_index: typing.Optional[int] = OMIT, request_options: typing.Optional[RequestOptions] = None) -> SessionStateResponse: + def archive_session(self, *, session_id: str, request_options: typing.Optional[RequestOptions] = None) -> SessionResponse: """ Parameters ---------- session_id : str - data : typing.Optional[SessionStateData] - Full replacement of the continuity state (resume ids + staleness guard). + request_options : typing.Optional[RequestOptions] + Request-specific configuration. - sandbox_id : typing.Optional[str] - Remote sandbox id to record alongside the continuity state. + Returns + ------- + SessionResponse + Successful Response - sandbox_turn_index : typing.Optional[int] - the writer's conversation turn index; the pointer write is applied only when it is >= the row's data.latest_turn_index. + Examples + -------- + from agenta import AgentaApi + + client = AgentaApi( + api_key="YOUR_API_KEY", + ) + client.sessions.archive_session( + session_id="session_id", + ) + """ + _response = self._raw_client.archive_session(session_id=session_id, request_options=request_options) + return _response.data + + def unarchive_session(self, *, session_id: str, request_options: typing.Optional[RequestOptions] = None) -> SessionResponse: + """ + Parameters + ---------- + session_id : str request_options : typing.Optional[RequestOptions] Request-specific configuration. Returns ------- - SessionStateResponse + SessionResponse Successful Response Examples @@ -739,11 +935,11 @@ def set_state(self, *, session_id: str, data: typing.Optional[SessionStateData] client = AgentaApi( api_key="YOUR_API_KEY", ) - client.sessions.set_state( + client.sessions.unarchive_session( session_id="session_id", ) """ - _response = self._raw_client.set_state(session_id=session_id, data=data, sandbox_id=sandbox_id, sandbox_turn_index=sandbox_turn_index, request_options=request_options) + _response = self._raw_client.unarchive_session(session_id=session_id, request_options=request_options) return _response.data class AsyncSessionsClient: def __init__(self, *, client_wrapper: AsyncClientWrapper): @@ -760,7 +956,7 @@ def with_raw_response(self) -> AsyncRawSessionsClient: """ return self._raw_client - async def fetch_session_stream(self, *, session_id: str, request_options: typing.Optional[RequestOptions] = None) -> SessionStreamResponseModel: + async def fetch_session_stream(self, *, session_id: str, request_options: typing.Optional[RequestOptions] = None) -> SessionStreamResponse: """ Parameters ---------- @@ -771,7 +967,7 @@ async def fetch_session_stream(self, *, session_id: str, request_options: typing Returns ------- - SessionStreamResponseModel + SessionStreamResponse Successful Response Examples @@ -796,13 +992,13 @@ async def main() -> None: _response = await self._raw_client.fetch_session_stream(session_id=session_id, request_options=request_options) return _response.data - async def set_session_stream(self, *, session_id: str, prompt: typing.Optional[str] = OMIT, force: typing.Optional[bool] = OMIT, detached: typing.Optional[bool] = OMIT, request_options: typing.Optional[RequestOptions] = None) -> SessionStreamCommandResponseModel: + async def set_session_stream(self, *, session_id: str, data: typing.Optional[WorkflowRequestData] = OMIT, force: typing.Optional[bool] = OMIT, detached: typing.Optional[bool] = OMIT, request_options: typing.Optional[RequestOptions] = None) -> SessionStreamCommandResponse: """ Parameters ---------- session_id : str - prompt : typing.Optional[str] + data : typing.Optional[WorkflowRequestData] force : typing.Optional[bool] @@ -813,7 +1009,7 @@ async def set_session_stream(self, *, session_id: str, prompt: typing.Optional[s Returns ------- - SessionStreamCommandResponseModel + SessionStreamCommandResponse Successful Response Examples @@ -835,7 +1031,7 @@ async def main() -> None: asyncio.run(main()) """ - _response = await self._raw_client.set_session_stream(session_id=session_id, prompt=prompt, force=force, detached=detached, request_options=request_options) + _response = await self._raw_client.set_session_stream(session_id=session_id, data=data, force=force, detached=detached, request_options=request_options) return _response.data async def delete_session_stream(self, *, session_id: str, request_options: typing.Optional[RequestOptions] = None) -> typing.Dict[str, typing.Any]: @@ -874,7 +1070,7 @@ async def main() -> None: _response = await self._raw_client.delete_session_stream(session_id=session_id, request_options=request_options) return _response.data - async def query_session_streams(self, *, session_id: typing.Optional[str] = OMIT, is_alive: typing.Optional[bool] = OMIT, is_running: typing.Optional[bool] = OMIT, request_options: typing.Optional[RequestOptions] = None) -> SessionStreamsResponseModel: + async def query_session_streams(self, *, session_id: typing.Optional[str] = OMIT, is_alive: typing.Optional[bool] = OMIT, is_running: typing.Optional[bool] = OMIT, request_options: typing.Optional[RequestOptions] = None) -> SessionStreamsResponse: """ Parameters ---------- @@ -889,7 +1085,7 @@ async def query_session_streams(self, *, session_id: typing.Optional[str] = OMIT Returns ------- - SessionStreamsResponseModel + SessionStreamsResponse Successful Response Examples @@ -951,7 +1147,7 @@ async def main() -> None: _response = await self._raw_client.detach_session_stream(session_id=session_id, watcher_id=watcher_id, request_options=request_options) return _response.data - async def heartbeat_session_stream(self, *, session_id: str, replica_id: str, turn_id: typing.Optional[str] = OMIT, is_running: typing.Optional[bool] = OMIT, request_options: typing.Optional[RequestOptions] = None) -> SessionHeartbeatResponseModel: + async def heartbeat_session_stream(self, *, session_id: str, replica_id: str, turn_id: typing.Optional[str] = OMIT, is_running: typing.Optional[bool] = OMIT, request_options: typing.Optional[RequestOptions] = None) -> SessionHeartbeatResult: """ Parameters ---------- @@ -968,7 +1164,7 @@ async def heartbeat_session_stream(self, *, session_id: str, replica_id: str, tu Returns ------- - SessionHeartbeatResponseModel + SessionHeartbeatResult Successful Response Examples @@ -994,6 +1190,46 @@ async def main() -> None: _response = await self._raw_client.heartbeat_session_stream(session_id=session_id, replica_id=replica_id, turn_id=turn_id, is_running=is_running, request_options=request_options) return _response.data + async def set_session_stream_header(self, *, session_id: str, name: typing.Optional[str] = OMIT, description: typing.Optional[str] = OMIT, request_options: typing.Optional[RequestOptions] = None) -> SessionStreamResponse: + """ + Parameters + ---------- + session_id : str + + name : typing.Optional[str] + + description : typing.Optional[str] + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + SessionStreamResponse + Successful Response + + Examples + -------- + import asyncio + + from agenta import AsyncAgentaApi + + client = AsyncAgentaApi( + api_key="YOUR_API_KEY", + ) + + + async def main() -> None: + await client.sessions.set_session_stream_header( + session_id="session_id", + ) + + + asyncio.run(main()) + """ + _response = await self._raw_client.set_session_stream_header(session_id=session_id, name=name, description=description, request_options=request_options) + return _response.data + async def create_interaction(self, *, session_id: str, token: str, kind: SessionInteractionKind, turn_id: typing.Optional[str] = OMIT, data: typing.Optional[SessionInteractionData] = OMIT, flags: typing.Optional[SessionInteractionFlags] = OMIT, tags: typing.Optional[typing.Dict[str, typing.Any]] = OMIT, meta: typing.Optional[typing.Dict[str, typing.Any]] = OMIT, request_options: typing.Optional[RequestOptions] = None) -> SessionInteractionResponse: """ Parameters @@ -1508,7 +1744,7 @@ async def main() -> None: _response = await self._raw_client.get_record_event(record_id, request_options=request_options) return _response.data - async def ingest_record(self, *, session_id: str, record_id: typing.Optional[str] = OMIT, record_index: typing.Optional[int] = OMIT, timestamp: typing.Optional[dt.datetime] = OMIT, record_type: typing.Optional[str] = OMIT, record_source: typing.Optional[str] = OMIT, attributes: typing.Optional[typing.Dict[str, typing.Any]] = OMIT, request_options: typing.Optional[RequestOptions] = None) -> typing.Dict[str, typing.Any]: + async def ingest_record(self, *, session_id: str, record_id: typing.Optional[str] = OMIT, record_index: typing.Optional[int] = OMIT, timestamp: typing.Optional[dt.datetime] = OMIT, record_type: typing.Optional[str] = OMIT, record_source: typing.Optional[str] = OMIT, attributes: typing.Optional[typing.Dict[str, typing.Any]] = OMIT, turn_id: typing.Optional[str] = OMIT, span_id: typing.Optional[str] = OMIT, request_options: typing.Optional[RequestOptions] = None) -> typing.Dict[str, typing.Any]: """ Parameters ---------- @@ -1526,6 +1762,10 @@ async def ingest_record(self, *, session_id: str, record_id: typing.Optional[str attributes : typing.Optional[typing.Dict[str, typing.Any]] + turn_id : typing.Optional[str] + + span_id : typing.Optional[str] + request_options : typing.Optional[RequestOptions] Request-specific configuration. @@ -1553,10 +1793,177 @@ async def main() -> None: asyncio.run(main()) """ - _response = await self._raw_client.ingest_record(session_id=session_id, record_id=record_id, record_index=record_index, timestamp=timestamp, record_type=record_type, record_source=record_source, attributes=attributes, request_options=request_options) + _response = await self._raw_client.ingest_record(session_id=session_id, record_id=record_id, record_index=record_index, timestamp=timestamp, record_type=record_type, record_source=record_source, attributes=attributes, turn_id=turn_id, span_id=span_id, request_options=request_options) + return _response.data + + async def append_turn(self, *, session_id: str, stream_id: str, turn_index: int, harness_kind: HarnessKind, agent_session_id: typing.Optional[str] = OMIT, sandbox_id: typing.Optional[str] = OMIT, references: typing.Optional[typing.Sequence[Reference]] = OMIT, trace_id: typing.Optional[str] = OMIT, span_id: typing.Optional[str] = OMIT, start_time: typing.Optional[dt.datetime] = OMIT, end_time: typing.Optional[dt.datetime] = OMIT, request_options: typing.Optional[RequestOptions] = None) -> SessionTurnResponse: + """ + Parameters + ---------- + session_id : str + + stream_id : str + + turn_index : int + + harness_kind : HarnessKind + + agent_session_id : typing.Optional[str] + + sandbox_id : typing.Optional[str] + + references : typing.Optional[typing.Sequence[Reference]] + + trace_id : typing.Optional[str] + + span_id : typing.Optional[str] + + start_time : typing.Optional[dt.datetime] + + end_time : typing.Optional[dt.datetime] + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + SessionTurnResponse + Successful Response + + Examples + -------- + import asyncio + + from agenta import AsyncAgentaApi + + client = AsyncAgentaApi( + api_key="YOUR_API_KEY", + ) + + + async def main() -> None: + await client.sessions.append_turn( + session_id="session_id", + stream_id="stream_id", + turn_index=1, + harness_kind="pi_core", + ) + + + asyncio.run(main()) + """ + _response = await self._raw_client.append_turn(session_id=session_id, stream_id=stream_id, turn_index=turn_index, harness_kind=harness_kind, agent_session_id=agent_session_id, sandbox_id=sandbox_id, references=references, trace_id=trace_id, span_id=span_id, start_time=start_time, end_time=end_time, request_options=request_options) + return _response.data + + async def query_turns(self, *, query: typing.Optional[SessionTurnQuery] = OMIT, windowing: typing.Optional[Windowing] = OMIT, request_options: typing.Optional[RequestOptions] = None) -> SessionTurnsResponse: + """ + Parameters + ---------- + query : typing.Optional[SessionTurnQuery] + + windowing : typing.Optional[Windowing] + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + SessionTurnsResponse + Successful Response + + Examples + -------- + import asyncio + + from agenta import AsyncAgentaApi + + client = AsyncAgentaApi( + api_key="YOUR_API_KEY", + ) + + + async def main() -> None: + await client.sessions.query_turns() + + + asyncio.run(main()) + """ + _response = await self._raw_client.query_turns(query=query, windowing=windowing, request_options=request_options) + return _response.data + + async def fetch_turn(self, turn_id: str, *, request_options: typing.Optional[RequestOptions] = None) -> SessionTurnResponse: + """ + Parameters + ---------- + turn_id : str + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + SessionTurnResponse + Successful Response + + Examples + -------- + import asyncio + + from agenta import AsyncAgentaApi + + client = AsyncAgentaApi( + api_key="YOUR_API_KEY", + ) + + + async def main() -> None: + await client.sessions.fetch_turn( + turn_id="turn_id", + ) + + + asyncio.run(main()) + """ + _response = await self._raw_client.fetch_turn(turn_id, request_options=request_options) return _response.data - async def get_state(self, *, session_id: str, request_options: typing.Optional[RequestOptions] = None) -> SessionStateResponse: + async def query_sessions(self, *, references: typing.Optional[typing.Sequence[Reference]] = OMIT, windowing: typing.Optional[Windowing] = OMIT, request_options: typing.Optional[RequestOptions] = None) -> SessionsResponse: + """ + Parameters + ---------- + references : typing.Optional[typing.Sequence[Reference]] + + windowing : typing.Optional[Windowing] + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + SessionsResponse + Successful Response + + Examples + -------- + import asyncio + + from agenta import AsyncAgentaApi + + client = AsyncAgentaApi( + api_key="YOUR_API_KEY", + ) + + + async def main() -> None: + await client.sessions.query_sessions() + + + asyncio.run(main()) + """ + _response = await self._raw_client.query_sessions(references=references, windowing=windowing, request_options=request_options) + return _response.data + + async def delete_session(self, *, session_id: str, request_options: typing.Optional[RequestOptions] = None) -> typing.Dict[str, typing.Any]: """ Parameters ---------- @@ -1567,7 +1974,7 @@ async def get_state(self, *, session_id: str, request_options: typing.Optional[R Returns ------- - SessionStateResponse + typing.Dict[str, typing.Any] Successful Response Examples @@ -1582,37 +1989,64 @@ async def get_state(self, *, session_id: str, request_options: typing.Optional[R async def main() -> None: - await client.sessions.get_state( + await client.sessions.delete_session( session_id="session_id", ) asyncio.run(main()) """ - _response = await self._raw_client.get_state(session_id=session_id, request_options=request_options) + _response = await self._raw_client.delete_session(session_id=session_id, request_options=request_options) return _response.data - async def set_state(self, *, session_id: str, data: typing.Optional[SessionStateData] = OMIT, sandbox_id: typing.Optional[str] = OMIT, sandbox_turn_index: typing.Optional[int] = OMIT, request_options: typing.Optional[RequestOptions] = None) -> SessionStateResponse: + async def archive_session(self, *, session_id: str, request_options: typing.Optional[RequestOptions] = None) -> SessionResponse: """ Parameters ---------- session_id : str - data : typing.Optional[SessionStateData] - Full replacement of the continuity state (resume ids + staleness guard). + request_options : typing.Optional[RequestOptions] + Request-specific configuration. - sandbox_id : typing.Optional[str] - Remote sandbox id to record alongside the continuity state. + Returns + ------- + SessionResponse + Successful Response - sandbox_turn_index : typing.Optional[int] - the writer's conversation turn index; the pointer write is applied only when it is >= the row's data.latest_turn_index. + Examples + -------- + import asyncio + + from agenta import AsyncAgentaApi + + client = AsyncAgentaApi( + api_key="YOUR_API_KEY", + ) + + + async def main() -> None: + await client.sessions.archive_session( + session_id="session_id", + ) + + + asyncio.run(main()) + """ + _response = await self._raw_client.archive_session(session_id=session_id, request_options=request_options) + return _response.data + + async def unarchive_session(self, *, session_id: str, request_options: typing.Optional[RequestOptions] = None) -> SessionResponse: + """ + Parameters + ---------- + session_id : str request_options : typing.Optional[RequestOptions] Request-specific configuration. Returns ------- - SessionStateResponse + SessionResponse Successful Response Examples @@ -1627,12 +2061,12 @@ async def set_state(self, *, session_id: str, data: typing.Optional[SessionState async def main() -> None: - await client.sessions.set_state( + await client.sessions.unarchive_session( session_id="session_id", ) asyncio.run(main()) """ - _response = await self._raw_client.set_state(session_id=session_id, data=data, sandbox_id=sandbox_id, sandbox_turn_index=sandbox_turn_index, request_options=request_options) + _response = await self._raw_client.unarchive_session(session_id=session_id, request_options=request_options) return _response.data diff --git a/clients/python/agenta_client/sessions/raw_client.py b/clients/python/agenta_client/sessions/raw_client.py index 50d1aa93f1..d70566b474 100644 --- a/clients/python/agenta_client/sessions/raw_client.py +++ b/clients/python/agenta_client/sessions/raw_client.py @@ -13,10 +13,12 @@ from ..core.request_options import RequestOptions from ..core.serialization import convert_and_respect_annotation_metadata from ..errors.unprocessable_entity_error import UnprocessableEntityError +from ..types.harness_kind import HarnessKind from ..types.http_validation_error import HttpValidationError from ..types.mount_credentials_response import MountCredentialsResponse from ..types.mount_file_written_response import MountFileWrittenResponse -from ..types.session_heartbeat_response_model import SessionHeartbeatResponseModel +from ..types.reference import Reference +from ..types.session_heartbeat_result import SessionHeartbeatResult from ..types.session_interaction_data import SessionInteractionData from ..types.session_interaction_flags import SessionInteractionFlags from ..types.session_interaction_kind import SessionInteractionKind @@ -28,12 +30,16 @@ from ..types.session_mounts_response import SessionMountsResponse from ..types.session_record_response import SessionRecordResponse from ..types.session_records_query_response import SessionRecordsQueryResponse -from ..types.session_state_data import SessionStateData -from ..types.session_state_response import SessionStateResponse -from ..types.session_stream_command_response_model import SessionStreamCommandResponseModel -from ..types.session_stream_response_model import SessionStreamResponseModel -from ..types.session_streams_response_model import SessionStreamsResponseModel +from ..types.session_response import SessionResponse +from ..types.session_stream_command_response import SessionStreamCommandResponse +from ..types.session_stream_response import SessionStreamResponse +from ..types.session_streams_response import SessionStreamsResponse +from ..types.session_turn_query import SessionTurnQuery +from ..types.session_turn_response import SessionTurnResponse +from ..types.session_turns_response import SessionTurnsResponse +from ..types.sessions_response import SessionsResponse from ..types.windowing import Windowing +from ..types.workflow_request_data import WorkflowRequestData # this is used as the default value for optional parameters OMIT = typing.cast(typing.Any, ...) @@ -41,7 +47,7 @@ class RawSessionsClient: def __init__(self, *, client_wrapper: SyncClientWrapper): self._client_wrapper = client_wrapper - def fetch_session_stream(self, *, session_id: str, request_options: typing.Optional[RequestOptions] = None) -> HttpResponse[SessionStreamResponseModel]: + def fetch_session_stream(self, *, session_id: str, request_options: typing.Optional[RequestOptions] = None) -> HttpResponse[SessionStreamResponse]: """ Parameters ---------- @@ -52,7 +58,7 @@ def fetch_session_stream(self, *, session_id: str, request_options: typing.Optio Returns ------- - HttpResponse[SessionStreamResponseModel] + HttpResponse[SessionStreamResponse] Successful Response """ _response = self._client_wrapper.httpx_client.request( @@ -63,9 +69,9 @@ def fetch_session_stream(self, *, session_id: str, request_options: typing.Optio try: if 200 <= _response.status_code < 300: _data = typing.cast( - SessionStreamResponseModel, + SessionStreamResponse, parse_obj_as( - type_ =SessionStreamResponseModel, # type: ignore + type_ =SessionStreamResponse, # type: ignore object_ =_response.json() ) ) @@ -83,13 +89,13 @@ def fetch_session_stream(self, *, session_id: str, request_options: typing.Optio raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) - def set_session_stream(self, *, session_id: str, prompt: typing.Optional[str] = OMIT, force: typing.Optional[bool] = OMIT, detached: typing.Optional[bool] = OMIT, request_options: typing.Optional[RequestOptions] = None) -> HttpResponse[SessionStreamCommandResponseModel]: + def set_session_stream(self, *, session_id: str, data: typing.Optional[WorkflowRequestData] = OMIT, force: typing.Optional[bool] = OMIT, detached: typing.Optional[bool] = OMIT, request_options: typing.Optional[RequestOptions] = None) -> HttpResponse[SessionStreamCommandResponse]: """ Parameters ---------- session_id : str - prompt : typing.Optional[str] + data : typing.Optional[WorkflowRequestData] force : typing.Optional[bool] @@ -100,14 +106,14 @@ def set_session_stream(self, *, session_id: str, prompt: typing.Optional[str] = Returns ------- - HttpResponse[SessionStreamCommandResponseModel] + HttpResponse[SessionStreamCommandResponse] Successful Response """ _response = self._client_wrapper.httpx_client.request( "sessions/streams/",method="POST", json={ "session_id": session_id, - "prompt": prompt, + "data": convert_and_respect_annotation_metadata(object_=data, annotation=typing.Optional[WorkflowRequestData], direction="write"), "force": force, "detached": detached, } @@ -119,9 +125,9 @@ def set_session_stream(self, *, session_id: str, prompt: typing.Optional[str] = try: if 200 <= _response.status_code < 300: _data = typing.cast( - SessionStreamCommandResponseModel, + SessionStreamCommandResponse, parse_obj_as( - type_ =SessionStreamCommandResponseModel, # type: ignore + type_ =SessionStreamCommandResponse, # type: ignore object_ =_response.json() ) ) @@ -181,7 +187,7 @@ def delete_session_stream(self, *, session_id: str, request_options: typing.Opti raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) - def query_session_streams(self, *, session_id: typing.Optional[str] = OMIT, is_alive: typing.Optional[bool] = OMIT, is_running: typing.Optional[bool] = OMIT, request_options: typing.Optional[RequestOptions] = None) -> HttpResponse[SessionStreamsResponseModel]: + def query_session_streams(self, *, session_id: typing.Optional[str] = OMIT, is_alive: typing.Optional[bool] = OMIT, is_running: typing.Optional[bool] = OMIT, request_options: typing.Optional[RequestOptions] = None) -> HttpResponse[SessionStreamsResponse]: """ Parameters ---------- @@ -196,7 +202,7 @@ def query_session_streams(self, *, session_id: typing.Optional[str] = OMIT, is_a Returns ------- - HttpResponse[SessionStreamsResponseModel] + HttpResponse[SessionStreamsResponse] Successful Response """ _response = self._client_wrapper.httpx_client.request( @@ -214,9 +220,9 @@ def query_session_streams(self, *, session_id: typing.Optional[str] = OMIT, is_a try: if 200 <= _response.status_code < 300: _data = typing.cast( - SessionStreamsResponseModel, + SessionStreamsResponse, parse_obj_as( - type_ =SessionStreamsResponseModel, # type: ignore + type_ =SessionStreamsResponse, # type: ignore object_ =_response.json() ) ) @@ -284,7 +290,7 @@ def detach_session_stream(self, *, session_id: str, watcher_id: str, request_opt raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) - def heartbeat_session_stream(self, *, session_id: str, replica_id: str, turn_id: typing.Optional[str] = OMIT, is_running: typing.Optional[bool] = OMIT, request_options: typing.Optional[RequestOptions] = None) -> HttpResponse[SessionHeartbeatResponseModel]: + def heartbeat_session_stream(self, *, session_id: str, replica_id: str, turn_id: typing.Optional[str] = OMIT, is_running: typing.Optional[bool] = OMIT, request_options: typing.Optional[RequestOptions] = None) -> HttpResponse[SessionHeartbeatResult]: """ Parameters ---------- @@ -301,7 +307,7 @@ def heartbeat_session_stream(self, *, session_id: str, replica_id: str, turn_id: Returns ------- - HttpResponse[SessionHeartbeatResponseModel] + HttpResponse[SessionHeartbeatResult] Successful Response """ _response = self._client_wrapper.httpx_client.request( @@ -320,9 +326,63 @@ def heartbeat_session_stream(self, *, session_id: str, replica_id: str, turn_id: try: if 200 <= _response.status_code < 300: _data = typing.cast( - SessionHeartbeatResponseModel, + SessionHeartbeatResult, parse_obj_as( - type_ =SessionHeartbeatResponseModel, # type: ignore + type_ =SessionHeartbeatResult, # type: ignore + object_ =_response.json() + ) + ) + return HttpResponse(response=_response, data=_data) + if _response.status_code == 422: + raise UnprocessableEntityError(headers=dict(_response.headers), body=typing.cast( + HttpValidationError, + parse_obj_as( + type_ =HttpValidationError, # type: ignore + object_ =_response.json() + ) + )) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) + + def set_session_stream_header(self, *, session_id: str, name: typing.Optional[str] = OMIT, description: typing.Optional[str] = OMIT, request_options: typing.Optional[RequestOptions] = None) -> HttpResponse[SessionStreamResponse]: + """ + Parameters + ---------- + session_id : str + + name : typing.Optional[str] + + description : typing.Optional[str] + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + HttpResponse[SessionStreamResponse] + Successful Response + """ + _response = self._client_wrapper.httpx_client.request( + "sessions/streams/header",method="PUT", + params={"session_id": session_id, } + , + json={ + "name": name, + "description": description, + } + , + headers={"content-type": "application/json", } + , + request_options=request_options,omit=OMIT, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + SessionStreamResponse, + parse_obj_as( + type_ =SessionStreamResponse, # type: ignore object_ =_response.json() ) ) @@ -987,7 +1047,7 @@ def get_record_event(self, record_id: str, *, request_options: typing.Optional[R raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) - def ingest_record(self, *, session_id: str, record_id: typing.Optional[str] = OMIT, record_index: typing.Optional[int] = OMIT, timestamp: typing.Optional[dt.datetime] = OMIT, record_type: typing.Optional[str] = OMIT, record_source: typing.Optional[str] = OMIT, attributes: typing.Optional[typing.Dict[str, typing.Any]] = OMIT, request_options: typing.Optional[RequestOptions] = None) -> HttpResponse[typing.Dict[str, typing.Any]]: + def ingest_record(self, *, session_id: str, record_id: typing.Optional[str] = OMIT, record_index: typing.Optional[int] = OMIT, timestamp: typing.Optional[dt.datetime] = OMIT, record_type: typing.Optional[str] = OMIT, record_source: typing.Optional[str] = OMIT, attributes: typing.Optional[typing.Dict[str, typing.Any]] = OMIT, turn_id: typing.Optional[str] = OMIT, span_id: typing.Optional[str] = OMIT, request_options: typing.Optional[RequestOptions] = None) -> HttpResponse[typing.Dict[str, typing.Any]]: """ Parameters ---------- @@ -1005,6 +1065,10 @@ def ingest_record(self, *, session_id: str, record_id: typing.Optional[str] = OM attributes : typing.Optional[typing.Dict[str, typing.Any]] + turn_id : typing.Optional[str] + + span_id : typing.Optional[str] + request_options : typing.Optional[RequestOptions] Request-specific configuration. @@ -1023,6 +1087,8 @@ def ingest_record(self, *, session_id: str, record_id: typing.Optional[str] = OM "record_type": record_type, "record_source": record_source, "attributes": attributes, + "turn_id": turn_id, + "span_id": span_id, } , headers={"content-type": "application/json", } @@ -1052,31 +1118,66 @@ def ingest_record(self, *, session_id: str, record_id: typing.Optional[str] = OM raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) - def get_state(self, *, session_id: str, request_options: typing.Optional[RequestOptions] = None) -> HttpResponse[SessionStateResponse]: + def append_turn(self, *, session_id: str, stream_id: str, turn_index: int, harness_kind: HarnessKind, agent_session_id: typing.Optional[str] = OMIT, sandbox_id: typing.Optional[str] = OMIT, references: typing.Optional[typing.Sequence[Reference]] = OMIT, trace_id: typing.Optional[str] = OMIT, span_id: typing.Optional[str] = OMIT, start_time: typing.Optional[dt.datetime] = OMIT, end_time: typing.Optional[dt.datetime] = OMIT, request_options: typing.Optional[RequestOptions] = None) -> HttpResponse[SessionTurnResponse]: """ Parameters ---------- session_id : str + stream_id : str + + turn_index : int + + harness_kind : HarnessKind + + agent_session_id : typing.Optional[str] + + sandbox_id : typing.Optional[str] + + references : typing.Optional[typing.Sequence[Reference]] + + trace_id : typing.Optional[str] + + span_id : typing.Optional[str] + + start_time : typing.Optional[dt.datetime] + + end_time : typing.Optional[dt.datetime] + request_options : typing.Optional[RequestOptions] Request-specific configuration. Returns ------- - HttpResponse[SessionStateResponse] + HttpResponse[SessionTurnResponse] Successful Response """ _response = self._client_wrapper.httpx_client.request( - "sessions/states/",method="GET", - params={"session_id": session_id, } + "sessions/turns/",method="POST", + json={ + "session_id": session_id, + "stream_id": stream_id, + "turn_index": turn_index, + "harness_kind": harness_kind, + "agent_session_id": agent_session_id, + "sandbox_id": sandbox_id, + "references": convert_and_respect_annotation_metadata(object_=references, annotation=typing.Optional[typing.Sequence[Reference]], direction="write"), + "trace_id": trace_id, + "span_id": span_id, + "start_time": start_time, + "end_time": end_time, + } , - request_options=request_options,) + headers={"content-type": "application/json", } + , + request_options=request_options,omit=OMIT, + ) try: if 200 <= _response.status_code < 300: _data = typing.cast( - SessionStateResponse, + SessionTurnResponse, parse_obj_as( - type_ =SessionStateResponse, # type: ignore + type_ =SessionTurnResponse, # type: ignore object_ =_response.json() ) ) @@ -1094,37 +1195,27 @@ def get_state(self, *, session_id: str, request_options: typing.Optional[Request raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) - def set_state(self, *, session_id: str, data: typing.Optional[SessionStateData] = OMIT, sandbox_id: typing.Optional[str] = OMIT, sandbox_turn_index: typing.Optional[int] = OMIT, request_options: typing.Optional[RequestOptions] = None) -> HttpResponse[SessionStateResponse]: + def query_turns(self, *, query: typing.Optional[SessionTurnQuery] = OMIT, windowing: typing.Optional[Windowing] = OMIT, request_options: typing.Optional[RequestOptions] = None) -> HttpResponse[SessionTurnsResponse]: """ Parameters ---------- - session_id : str + query : typing.Optional[SessionTurnQuery] - data : typing.Optional[SessionStateData] - Full replacement of the continuity state (resume ids + staleness guard). - - sandbox_id : typing.Optional[str] - Remote sandbox id to record alongside the continuity state. - - sandbox_turn_index : typing.Optional[int] - the writer's conversation turn index; the pointer write is applied only when it is >= the row's data.latest_turn_index. + windowing : typing.Optional[Windowing] request_options : typing.Optional[RequestOptions] Request-specific configuration. Returns ------- - HttpResponse[SessionStateResponse] + HttpResponse[SessionTurnsResponse] Successful Response """ _response = self._client_wrapper.httpx_client.request( - "sessions/states/",method="PUT", - params={"session_id": session_id, } - , + "sessions/turns/query",method="POST", json={ - "data": convert_and_respect_annotation_metadata(object_=data, annotation=typing.Optional[SessionStateData], direction="write"), - "sandbox_id": sandbox_id, - "sandbox_turn_index": sandbox_turn_index, + "query": convert_and_respect_annotation_metadata(object_=query, annotation=typing.Optional[SessionTurnQuery], direction="write"), + "windowing": convert_and_respect_annotation_metadata(object_=windowing, annotation=typing.Optional[Windowing], direction="write"), } , headers={"content-type": "application/json", } @@ -1134,9 +1225,9 @@ def set_state(self, *, session_id: str, data: typing.Optional[SessionStateData] try: if 200 <= _response.status_code < 300: _data = typing.cast( - SessionStateResponse, + SessionTurnsResponse, parse_obj_as( - type_ =SessionStateResponse, # type: ignore + type_ =SessionTurnsResponse, # type: ignore object_ =_response.json() ) ) @@ -1153,39 +1244,34 @@ def set_state(self, *, session_id: str, data: typing.Optional[SessionStateData] except JSONDecodeError: raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) -class AsyncRawSessionsClient: - def __init__(self, *, client_wrapper: AsyncClientWrapper): - self._client_wrapper = client_wrapper - async def fetch_session_stream(self, *, session_id: str, request_options: typing.Optional[RequestOptions] = None) -> AsyncHttpResponse[SessionStreamResponseModel]: + def fetch_turn(self, turn_id: str, *, request_options: typing.Optional[RequestOptions] = None) -> HttpResponse[SessionTurnResponse]: """ Parameters ---------- - session_id : str + turn_id : str request_options : typing.Optional[RequestOptions] Request-specific configuration. Returns ------- - AsyncHttpResponse[SessionStreamResponseModel] + HttpResponse[SessionTurnResponse] Successful Response """ - _response = await self._client_wrapper.httpx_client.request( - "sessions/streams/",method="GET", - params={"session_id": session_id, } - , + _response = self._client_wrapper.httpx_client.request( + f"sessions/turns/{jsonable_encoder(turn_id)}",method="GET", request_options=request_options,) try: if 200 <= _response.status_code < 300: _data = typing.cast( - SessionStreamResponseModel, + SessionTurnResponse, parse_obj_as( - type_ =SessionStreamResponseModel, # type: ignore + type_ =SessionTurnResponse, # type: ignore object_ =_response.json() ) ) - return AsyncHttpResponse(response=_response, data=_data) + return HttpResponse(response=_response, data=_data) if _response.status_code == 422: raise UnprocessableEntityError(headers=dict(_response.headers), body=typing.cast( HttpValidationError, @@ -1199,33 +1285,27 @@ async def fetch_session_stream(self, *, session_id: str, request_options: typing raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) - async def set_session_stream(self, *, session_id: str, prompt: typing.Optional[str] = OMIT, force: typing.Optional[bool] = OMIT, detached: typing.Optional[bool] = OMIT, request_options: typing.Optional[RequestOptions] = None) -> AsyncHttpResponse[SessionStreamCommandResponseModel]: + def query_sessions(self, *, references: typing.Optional[typing.Sequence[Reference]] = OMIT, windowing: typing.Optional[Windowing] = OMIT, request_options: typing.Optional[RequestOptions] = None) -> HttpResponse[SessionsResponse]: """ Parameters ---------- - session_id : str - - prompt : typing.Optional[str] - - force : typing.Optional[bool] + references : typing.Optional[typing.Sequence[Reference]] - detached : typing.Optional[bool] + windowing : typing.Optional[Windowing] request_options : typing.Optional[RequestOptions] Request-specific configuration. Returns ------- - AsyncHttpResponse[SessionStreamCommandResponseModel] + HttpResponse[SessionsResponse] Successful Response """ - _response = await self._client_wrapper.httpx_client.request( - "sessions/streams/",method="POST", + _response = self._client_wrapper.httpx_client.request( + "sessions/query",method="POST", json={ - "session_id": session_id, - "prompt": prompt, - "force": force, - "detached": detached, + "references": convert_and_respect_annotation_metadata(object_=references, annotation=typing.Optional[typing.Sequence[Reference]], direction="write"), + "windowing": convert_and_respect_annotation_metadata(object_=windowing, annotation=typing.Optional[Windowing], direction="write"), } , headers={"content-type": "application/json", } @@ -1235,13 +1315,13 @@ async def set_session_stream(self, *, session_id: str, prompt: typing.Optional[s try: if 200 <= _response.status_code < 300: _data = typing.cast( - SessionStreamCommandResponseModel, + SessionsResponse, parse_obj_as( - type_ =SessionStreamCommandResponseModel, # type: ignore + type_ =SessionsResponse, # type: ignore object_ =_response.json() ) ) - return AsyncHttpResponse(response=_response, data=_data) + return HttpResponse(response=_response, data=_data) if _response.status_code == 422: raise UnprocessableEntityError(headers=dict(_response.headers), body=typing.cast( HttpValidationError, @@ -1255,7 +1335,7 @@ async def set_session_stream(self, *, session_id: str, prompt: typing.Optional[s raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) - async def delete_session_stream(self, *, session_id: str, request_options: typing.Optional[RequestOptions] = None) -> AsyncHttpResponse[typing.Dict[str, typing.Any]]: + def delete_session(self, *, session_id: str, request_options: typing.Optional[RequestOptions] = None) -> HttpResponse[typing.Dict[str, typing.Any]]: """ Parameters ---------- @@ -1266,11 +1346,11 @@ async def delete_session_stream(self, *, session_id: str, request_options: typin Returns ------- - AsyncHttpResponse[typing.Dict[str, typing.Any]] + HttpResponse[typing.Dict[str, typing.Any]] Successful Response """ - _response = await self._client_wrapper.httpx_client.request( - "sessions/streams/",method="DELETE", + _response = self._client_wrapper.httpx_client.request( + "sessions/",method="DELETE", params={"session_id": session_id, } , request_options=request_options,) @@ -1283,7 +1363,7 @@ async def delete_session_stream(self, *, session_id: str, request_options: typin object_ =_response.json() ) ) - return AsyncHttpResponse(response=_response, data=_data) + return HttpResponse(response=_response, data=_data) if _response.status_code == 422: raise UnprocessableEntityError(headers=dict(_response.headers), body=typing.cast( HttpValidationError, @@ -1297,46 +1377,35 @@ async def delete_session_stream(self, *, session_id: str, request_options: typin raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) - async def query_session_streams(self, *, session_id: typing.Optional[str] = OMIT, is_alive: typing.Optional[bool] = OMIT, is_running: typing.Optional[bool] = OMIT, request_options: typing.Optional[RequestOptions] = None) -> AsyncHttpResponse[SessionStreamsResponseModel]: + def archive_session(self, *, session_id: str, request_options: typing.Optional[RequestOptions] = None) -> HttpResponse[SessionResponse]: """ Parameters ---------- - session_id : typing.Optional[str] - - is_alive : typing.Optional[bool] - - is_running : typing.Optional[bool] + session_id : str request_options : typing.Optional[RequestOptions] Request-specific configuration. Returns ------- - AsyncHttpResponse[SessionStreamsResponseModel] + HttpResponse[SessionResponse] Successful Response """ - _response = await self._client_wrapper.httpx_client.request( - "sessions/streams/query",method="POST", - json={ - "session_id": session_id, - "is_alive": is_alive, - "is_running": is_running, - } - , - headers={"content-type": "application/json", } + _response = self._client_wrapper.httpx_client.request( + "sessions/archive",method="POST", + params={"session_id": session_id, } , - request_options=request_options,omit=OMIT, - ) + request_options=request_options,) try: if 200 <= _response.status_code < 300: _data = typing.cast( - SessionStreamsResponseModel, + SessionResponse, parse_obj_as( - type_ =SessionStreamsResponseModel, # type: ignore + type_ =SessionResponse, # type: ignore object_ =_response.json() ) ) - return AsyncHttpResponse(response=_response, data=_data) + return HttpResponse(response=_response, data=_data) if _response.status_code == 422: raise UnprocessableEntityError(headers=dict(_response.headers), body=typing.cast( HttpValidationError, @@ -1350,43 +1419,35 @@ async def query_session_streams(self, *, session_id: typing.Optional[str] = OMIT raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) - async def detach_session_stream(self, *, session_id: str, watcher_id: str, request_options: typing.Optional[RequestOptions] = None) -> AsyncHttpResponse[typing.Dict[str, typing.Any]]: + def unarchive_session(self, *, session_id: str, request_options: typing.Optional[RequestOptions] = None) -> HttpResponse[SessionResponse]: """ Parameters ---------- session_id : str - watcher_id : str - request_options : typing.Optional[RequestOptions] Request-specific configuration. Returns ------- - AsyncHttpResponse[typing.Dict[str, typing.Any]] + HttpResponse[SessionResponse] Successful Response """ - _response = await self._client_wrapper.httpx_client.request( - "sessions/streams/detach",method="POST", - json={ - "session_id": session_id, - "watcher_id": watcher_id, - } - , - headers={"content-type": "application/json", } + _response = self._client_wrapper.httpx_client.request( + "sessions/unarchive",method="POST", + params={"session_id": session_id, } , - request_options=request_options,omit=OMIT, - ) + request_options=request_options,) try: if 200 <= _response.status_code < 300: _data = typing.cast( - typing.Dict[str, typing.Any], + SessionResponse, parse_obj_as( - type_ =typing.Dict[str, typing.Any], # type: ignore + type_ =SessionResponse, # type: ignore object_ =_response.json() ) ) - return AsyncHttpResponse(response=_response, data=_data) + return HttpResponse(response=_response, data=_data) if _response.status_code == 422: raise UnprocessableEntityError(headers=dict(_response.headers), body=typing.cast( HttpValidationError, @@ -1399,46 +1460,35 @@ async def detach_session_stream(self, *, session_id: str, watcher_id: str, reque except JSONDecodeError: raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) +class AsyncRawSessionsClient: + def __init__(self, *, client_wrapper: AsyncClientWrapper): + self._client_wrapper = client_wrapper - async def heartbeat_session_stream(self, *, session_id: str, replica_id: str, turn_id: typing.Optional[str] = OMIT, is_running: typing.Optional[bool] = OMIT, request_options: typing.Optional[RequestOptions] = None) -> AsyncHttpResponse[SessionHeartbeatResponseModel]: + async def fetch_session_stream(self, *, session_id: str, request_options: typing.Optional[RequestOptions] = None) -> AsyncHttpResponse[SessionStreamResponse]: """ Parameters ---------- session_id : str - replica_id : str - - turn_id : typing.Optional[str] - - is_running : typing.Optional[bool] - request_options : typing.Optional[RequestOptions] Request-specific configuration. Returns ------- - AsyncHttpResponse[SessionHeartbeatResponseModel] + AsyncHttpResponse[SessionStreamResponse] Successful Response """ _response = await self._client_wrapper.httpx_client.request( - "sessions/streams/heartbeat",method="POST", - json={ - "session_id": session_id, - "replica_id": replica_id, - "turn_id": turn_id, - "is_running": is_running, - } - , - headers={"content-type": "application/json", } + "sessions/streams/",method="GET", + params={"session_id": session_id, } , - request_options=request_options,omit=OMIT, - ) + request_options=request_options,) try: if 200 <= _response.status_code < 300: _data = typing.cast( - SessionHeartbeatResponseModel, + SessionStreamResponse, parse_obj_as( - type_ =SessionHeartbeatResponseModel, # type: ignore + type_ =SessionStreamResponse, # type: ignore object_ =_response.json() ) ) @@ -1456,45 +1506,33 @@ async def heartbeat_session_stream(self, *, session_id: str, replica_id: str, tu raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) - async def create_interaction(self, *, session_id: str, token: str, kind: SessionInteractionKind, turn_id: typing.Optional[str] = OMIT, data: typing.Optional[SessionInteractionData] = OMIT, flags: typing.Optional[SessionInteractionFlags] = OMIT, tags: typing.Optional[typing.Dict[str, typing.Any]] = OMIT, meta: typing.Optional[typing.Dict[str, typing.Any]] = OMIT, request_options: typing.Optional[RequestOptions] = None) -> AsyncHttpResponse[SessionInteractionResponse]: + async def set_session_stream(self, *, session_id: str, data: typing.Optional[WorkflowRequestData] = OMIT, force: typing.Optional[bool] = OMIT, detached: typing.Optional[bool] = OMIT, request_options: typing.Optional[RequestOptions] = None) -> AsyncHttpResponse[SessionStreamCommandResponse]: """ Parameters ---------- session_id : str - token : str - - kind : SessionInteractionKind - - turn_id : typing.Optional[str] - - data : typing.Optional[SessionInteractionData] - - flags : typing.Optional[SessionInteractionFlags] + data : typing.Optional[WorkflowRequestData] - tags : typing.Optional[typing.Dict[str, typing.Any]] + force : typing.Optional[bool] - meta : typing.Optional[typing.Dict[str, typing.Any]] + detached : typing.Optional[bool] request_options : typing.Optional[RequestOptions] Request-specific configuration. Returns ------- - AsyncHttpResponse[SessionInteractionResponse] + AsyncHttpResponse[SessionStreamCommandResponse] Successful Response """ _response = await self._client_wrapper.httpx_client.request( - "sessions/interactions/",method="POST", + "sessions/streams/",method="POST", json={ "session_id": session_id, - "turn_id": turn_id, - "token": token, - "kind": kind, - "data": convert_and_respect_annotation_metadata(object_=data, annotation=typing.Optional[SessionInteractionData], direction="write"), - "flags": convert_and_respect_annotation_metadata(object_=flags, annotation=SessionInteractionFlags, direction="write"), - "tags": tags, - "meta": meta, + "data": convert_and_respect_annotation_metadata(object_=data, annotation=typing.Optional[WorkflowRequestData], direction="write"), + "force": force, + "detached": detached, } , headers={"content-type": "application/json", } @@ -1504,9 +1542,9 @@ async def create_interaction(self, *, session_id: str, token: str, kind: Session try: if 200 <= _response.status_code < 300: _data = typing.cast( - SessionInteractionResponse, + SessionStreamCommandResponse, parse_obj_as( - type_ =SessionInteractionResponse, # type: ignore + type_ =SessionStreamCommandResponse, # type: ignore object_ =_response.json() ) ) @@ -1524,39 +1562,31 @@ async def create_interaction(self, *, session_id: str, token: str, kind: Session raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) - async def query_interactions(self, *, query: typing.Optional[SessionInteractionQuery] = OMIT, windowing: typing.Optional[Windowing] = OMIT, request_options: typing.Optional[RequestOptions] = None) -> AsyncHttpResponse[SessionInteractionsResponse]: + async def delete_session_stream(self, *, session_id: str, request_options: typing.Optional[RequestOptions] = None) -> AsyncHttpResponse[typing.Dict[str, typing.Any]]: """ Parameters ---------- - query : typing.Optional[SessionInteractionQuery] - - windowing : typing.Optional[Windowing] + session_id : str request_options : typing.Optional[RequestOptions] Request-specific configuration. Returns ------- - AsyncHttpResponse[SessionInteractionsResponse] + AsyncHttpResponse[typing.Dict[str, typing.Any]] Successful Response """ _response = await self._client_wrapper.httpx_client.request( - "sessions/interactions/query",method="POST", - json={ - "query": convert_and_respect_annotation_metadata(object_=query, annotation=typing.Optional[SessionInteractionQuery], direction="write"), - "windowing": convert_and_respect_annotation_metadata(object_=windowing, annotation=typing.Optional[Windowing], direction="write"), - } - , - headers={"content-type": "application/json", } + "sessions/streams/",method="DELETE", + params={"session_id": session_id, } , - request_options=request_options,omit=OMIT, - ) + request_options=request_options,) try: if 200 <= _response.status_code < 300: _data = typing.cast( - SessionInteractionsResponse, + typing.Dict[str, typing.Any], parse_obj_as( - type_ =SessionInteractionsResponse, # type: ignore + type_ =typing.Dict[str, typing.Any], # type: ignore object_ =_response.json() ) ) @@ -1574,30 +1604,30 @@ async def query_interactions(self, *, query: typing.Optional[SessionInteractionQ raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) - async def transition_interaction(self, *, session_id: str, token: str, status: SessionInteractionStatus, request_options: typing.Optional[RequestOptions] = None) -> AsyncHttpResponse[SessionInteractionResponse]: + async def query_session_streams(self, *, session_id: typing.Optional[str] = OMIT, is_alive: typing.Optional[bool] = OMIT, is_running: typing.Optional[bool] = OMIT, request_options: typing.Optional[RequestOptions] = None) -> AsyncHttpResponse[SessionStreamsResponse]: """ Parameters ---------- - session_id : str + session_id : typing.Optional[str] - token : str + is_alive : typing.Optional[bool] - status : SessionInteractionStatus + is_running : typing.Optional[bool] request_options : typing.Optional[RequestOptions] Request-specific configuration. Returns ------- - AsyncHttpResponse[SessionInteractionResponse] + AsyncHttpResponse[SessionStreamsResponse] Successful Response """ _response = await self._client_wrapper.httpx_client.request( - "sessions/interactions/transition",method="POST", + "sessions/streams/query",method="POST", json={ "session_id": session_id, - "token": token, - "status": status, + "is_alive": is_alive, + "is_running": is_running, } , headers={"content-type": "application/json", } @@ -1607,9 +1637,9 @@ async def transition_interaction(self, *, session_id: str, token: str, status: S try: if 200 <= _response.status_code < 300: _data = typing.cast( - SessionInteractionResponse, + SessionStreamsResponse, parse_obj_as( - type_ =SessionInteractionResponse, # type: ignore + type_ =SessionStreamsResponse, # type: ignore object_ =_response.json() ) ) @@ -1627,15 +1657,13 @@ async def transition_interaction(self, *, session_id: str, token: str, status: S raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) - async def cancel_stale_interactions(self, *, session_id: str, turn_id: str, tokens: typing.Optional[typing.Sequence[str]] = OMIT, request_options: typing.Optional[RequestOptions] = None) -> AsyncHttpResponse[typing.Dict[str, typing.Any]]: + async def detach_session_stream(self, *, session_id: str, watcher_id: str, request_options: typing.Optional[RequestOptions] = None) -> AsyncHttpResponse[typing.Dict[str, typing.Any]]: """ Parameters ---------- session_id : str - turn_id : str - - tokens : typing.Optional[typing.Sequence[str]] + watcher_id : str request_options : typing.Optional[RequestOptions] Request-specific configuration. @@ -1646,7 +1674,340 @@ async def cancel_stale_interactions(self, *, session_id: str, turn_id: str, toke Successful Response """ _response = await self._client_wrapper.httpx_client.request( - "sessions/interactions/cancel-stale",method="POST", + "sessions/streams/detach",method="POST", + json={ + "session_id": session_id, + "watcher_id": watcher_id, + } + , + headers={"content-type": "application/json", } + , + request_options=request_options,omit=OMIT, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + typing.Dict[str, typing.Any], + parse_obj_as( + type_ =typing.Dict[str, typing.Any], # type: ignore + object_ =_response.json() + ) + ) + return AsyncHttpResponse(response=_response, data=_data) + if _response.status_code == 422: + raise UnprocessableEntityError(headers=dict(_response.headers), body=typing.cast( + HttpValidationError, + parse_obj_as( + type_ =HttpValidationError, # type: ignore + object_ =_response.json() + ) + )) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) + + async def heartbeat_session_stream(self, *, session_id: str, replica_id: str, turn_id: typing.Optional[str] = OMIT, is_running: typing.Optional[bool] = OMIT, request_options: typing.Optional[RequestOptions] = None) -> AsyncHttpResponse[SessionHeartbeatResult]: + """ + Parameters + ---------- + session_id : str + + replica_id : str + + turn_id : typing.Optional[str] + + is_running : typing.Optional[bool] + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + AsyncHttpResponse[SessionHeartbeatResult] + Successful Response + """ + _response = await self._client_wrapper.httpx_client.request( + "sessions/streams/heartbeat",method="POST", + json={ + "session_id": session_id, + "replica_id": replica_id, + "turn_id": turn_id, + "is_running": is_running, + } + , + headers={"content-type": "application/json", } + , + request_options=request_options,omit=OMIT, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + SessionHeartbeatResult, + parse_obj_as( + type_ =SessionHeartbeatResult, # type: ignore + object_ =_response.json() + ) + ) + return AsyncHttpResponse(response=_response, data=_data) + if _response.status_code == 422: + raise UnprocessableEntityError(headers=dict(_response.headers), body=typing.cast( + HttpValidationError, + parse_obj_as( + type_ =HttpValidationError, # type: ignore + object_ =_response.json() + ) + )) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) + + async def set_session_stream_header(self, *, session_id: str, name: typing.Optional[str] = OMIT, description: typing.Optional[str] = OMIT, request_options: typing.Optional[RequestOptions] = None) -> AsyncHttpResponse[SessionStreamResponse]: + """ + Parameters + ---------- + session_id : str + + name : typing.Optional[str] + + description : typing.Optional[str] + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + AsyncHttpResponse[SessionStreamResponse] + Successful Response + """ + _response = await self._client_wrapper.httpx_client.request( + "sessions/streams/header",method="PUT", + params={"session_id": session_id, } + , + json={ + "name": name, + "description": description, + } + , + headers={"content-type": "application/json", } + , + request_options=request_options,omit=OMIT, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + SessionStreamResponse, + parse_obj_as( + type_ =SessionStreamResponse, # type: ignore + object_ =_response.json() + ) + ) + return AsyncHttpResponse(response=_response, data=_data) + if _response.status_code == 422: + raise UnprocessableEntityError(headers=dict(_response.headers), body=typing.cast( + HttpValidationError, + parse_obj_as( + type_ =HttpValidationError, # type: ignore + object_ =_response.json() + ) + )) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) + + async def create_interaction(self, *, session_id: str, token: str, kind: SessionInteractionKind, turn_id: typing.Optional[str] = OMIT, data: typing.Optional[SessionInteractionData] = OMIT, flags: typing.Optional[SessionInteractionFlags] = OMIT, tags: typing.Optional[typing.Dict[str, typing.Any]] = OMIT, meta: typing.Optional[typing.Dict[str, typing.Any]] = OMIT, request_options: typing.Optional[RequestOptions] = None) -> AsyncHttpResponse[SessionInteractionResponse]: + """ + Parameters + ---------- + session_id : str + + token : str + + kind : SessionInteractionKind + + turn_id : typing.Optional[str] + + data : typing.Optional[SessionInteractionData] + + flags : typing.Optional[SessionInteractionFlags] + + tags : typing.Optional[typing.Dict[str, typing.Any]] + + meta : typing.Optional[typing.Dict[str, typing.Any]] + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + AsyncHttpResponse[SessionInteractionResponse] + Successful Response + """ + _response = await self._client_wrapper.httpx_client.request( + "sessions/interactions/",method="POST", + json={ + "session_id": session_id, + "turn_id": turn_id, + "token": token, + "kind": kind, + "data": convert_and_respect_annotation_metadata(object_=data, annotation=typing.Optional[SessionInteractionData], direction="write"), + "flags": convert_and_respect_annotation_metadata(object_=flags, annotation=SessionInteractionFlags, direction="write"), + "tags": tags, + "meta": meta, + } + , + headers={"content-type": "application/json", } + , + request_options=request_options,omit=OMIT, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + SessionInteractionResponse, + parse_obj_as( + type_ =SessionInteractionResponse, # type: ignore + object_ =_response.json() + ) + ) + return AsyncHttpResponse(response=_response, data=_data) + if _response.status_code == 422: + raise UnprocessableEntityError(headers=dict(_response.headers), body=typing.cast( + HttpValidationError, + parse_obj_as( + type_ =HttpValidationError, # type: ignore + object_ =_response.json() + ) + )) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) + + async def query_interactions(self, *, query: typing.Optional[SessionInteractionQuery] = OMIT, windowing: typing.Optional[Windowing] = OMIT, request_options: typing.Optional[RequestOptions] = None) -> AsyncHttpResponse[SessionInteractionsResponse]: + """ + Parameters + ---------- + query : typing.Optional[SessionInteractionQuery] + + windowing : typing.Optional[Windowing] + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + AsyncHttpResponse[SessionInteractionsResponse] + Successful Response + """ + _response = await self._client_wrapper.httpx_client.request( + "sessions/interactions/query",method="POST", + json={ + "query": convert_and_respect_annotation_metadata(object_=query, annotation=typing.Optional[SessionInteractionQuery], direction="write"), + "windowing": convert_and_respect_annotation_metadata(object_=windowing, annotation=typing.Optional[Windowing], direction="write"), + } + , + headers={"content-type": "application/json", } + , + request_options=request_options,omit=OMIT, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + SessionInteractionsResponse, + parse_obj_as( + type_ =SessionInteractionsResponse, # type: ignore + object_ =_response.json() + ) + ) + return AsyncHttpResponse(response=_response, data=_data) + if _response.status_code == 422: + raise UnprocessableEntityError(headers=dict(_response.headers), body=typing.cast( + HttpValidationError, + parse_obj_as( + type_ =HttpValidationError, # type: ignore + object_ =_response.json() + ) + )) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) + + async def transition_interaction(self, *, session_id: str, token: str, status: SessionInteractionStatus, request_options: typing.Optional[RequestOptions] = None) -> AsyncHttpResponse[SessionInteractionResponse]: + """ + Parameters + ---------- + session_id : str + + token : str + + status : SessionInteractionStatus + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + AsyncHttpResponse[SessionInteractionResponse] + Successful Response + """ + _response = await self._client_wrapper.httpx_client.request( + "sessions/interactions/transition",method="POST", + json={ + "session_id": session_id, + "token": token, + "status": status, + } + , + headers={"content-type": "application/json", } + , + request_options=request_options,omit=OMIT, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + SessionInteractionResponse, + parse_obj_as( + type_ =SessionInteractionResponse, # type: ignore + object_ =_response.json() + ) + ) + return AsyncHttpResponse(response=_response, data=_data) + if _response.status_code == 422: + raise UnprocessableEntityError(headers=dict(_response.headers), body=typing.cast( + HttpValidationError, + parse_obj_as( + type_ =HttpValidationError, # type: ignore + object_ =_response.json() + ) + )) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) + + async def cancel_stale_interactions(self, *, session_id: str, turn_id: str, tokens: typing.Optional[typing.Sequence[str]] = OMIT, request_options: typing.Optional[RequestOptions] = None) -> AsyncHttpResponse[typing.Dict[str, typing.Any]]: + """ + Parameters + ---------- + session_id : str + + turn_id : str + + tokens : typing.Optional[typing.Sequence[str]] + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + AsyncHttpResponse[typing.Dict[str, typing.Any]] + Successful Response + """ + _response = await self._client_wrapper.httpx_client.request( + "sessions/interactions/cancel-stale",method="POST", json={ "session_id": session_id, "turn_id": turn_id, @@ -2103,7 +2464,7 @@ async def get_record_event(self, record_id: str, *, request_options: typing.Opti raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) - async def ingest_record(self, *, session_id: str, record_id: typing.Optional[str] = OMIT, record_index: typing.Optional[int] = OMIT, timestamp: typing.Optional[dt.datetime] = OMIT, record_type: typing.Optional[str] = OMIT, record_source: typing.Optional[str] = OMIT, attributes: typing.Optional[typing.Dict[str, typing.Any]] = OMIT, request_options: typing.Optional[RequestOptions] = None) -> AsyncHttpResponse[typing.Dict[str, typing.Any]]: + async def ingest_record(self, *, session_id: str, record_id: typing.Optional[str] = OMIT, record_index: typing.Optional[int] = OMIT, timestamp: typing.Optional[dt.datetime] = OMIT, record_type: typing.Optional[str] = OMIT, record_source: typing.Optional[str] = OMIT, attributes: typing.Optional[typing.Dict[str, typing.Any]] = OMIT, turn_id: typing.Optional[str] = OMIT, span_id: typing.Optional[str] = OMIT, request_options: typing.Optional[RequestOptions] = None) -> AsyncHttpResponse[typing.Dict[str, typing.Any]]: """ Parameters ---------- @@ -2121,6 +2482,10 @@ async def ingest_record(self, *, session_id: str, record_id: typing.Optional[str attributes : typing.Optional[typing.Dict[str, typing.Any]] + turn_id : typing.Optional[str] + + span_id : typing.Optional[str] + request_options : typing.Optional[RequestOptions] Request-specific configuration. @@ -2139,6 +2504,8 @@ async def ingest_record(self, *, session_id: str, record_id: typing.Optional[str "record_type": record_type, "record_source": record_source, "attributes": attributes, + "turn_id": turn_id, + "span_id": span_id, } , headers={"content-type": "application/json", } @@ -2168,31 +2535,66 @@ async def ingest_record(self, *, session_id: str, record_id: typing.Optional[str raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) - async def get_state(self, *, session_id: str, request_options: typing.Optional[RequestOptions] = None) -> AsyncHttpResponse[SessionStateResponse]: + async def append_turn(self, *, session_id: str, stream_id: str, turn_index: int, harness_kind: HarnessKind, agent_session_id: typing.Optional[str] = OMIT, sandbox_id: typing.Optional[str] = OMIT, references: typing.Optional[typing.Sequence[Reference]] = OMIT, trace_id: typing.Optional[str] = OMIT, span_id: typing.Optional[str] = OMIT, start_time: typing.Optional[dt.datetime] = OMIT, end_time: typing.Optional[dt.datetime] = OMIT, request_options: typing.Optional[RequestOptions] = None) -> AsyncHttpResponse[SessionTurnResponse]: """ Parameters ---------- session_id : str + stream_id : str + + turn_index : int + + harness_kind : HarnessKind + + agent_session_id : typing.Optional[str] + + sandbox_id : typing.Optional[str] + + references : typing.Optional[typing.Sequence[Reference]] + + trace_id : typing.Optional[str] + + span_id : typing.Optional[str] + + start_time : typing.Optional[dt.datetime] + + end_time : typing.Optional[dt.datetime] + request_options : typing.Optional[RequestOptions] Request-specific configuration. Returns ------- - AsyncHttpResponse[SessionStateResponse] + AsyncHttpResponse[SessionTurnResponse] Successful Response """ _response = await self._client_wrapper.httpx_client.request( - "sessions/states/",method="GET", - params={"session_id": session_id, } + "sessions/turns/",method="POST", + json={ + "session_id": session_id, + "stream_id": stream_id, + "turn_index": turn_index, + "harness_kind": harness_kind, + "agent_session_id": agent_session_id, + "sandbox_id": sandbox_id, + "references": convert_and_respect_annotation_metadata(object_=references, annotation=typing.Optional[typing.Sequence[Reference]], direction="write"), + "trace_id": trace_id, + "span_id": span_id, + "start_time": start_time, + "end_time": end_time, + } , - request_options=request_options,) + headers={"content-type": "application/json", } + , + request_options=request_options,omit=OMIT, + ) try: if 200 <= _response.status_code < 300: _data = typing.cast( - SessionStateResponse, + SessionTurnResponse, parse_obj_as( - type_ =SessionStateResponse, # type: ignore + type_ =SessionTurnResponse, # type: ignore object_ =_response.json() ) ) @@ -2210,37 +2612,117 @@ async def get_state(self, *, session_id: str, request_options: typing.Optional[R raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) - async def set_state(self, *, session_id: str, data: typing.Optional[SessionStateData] = OMIT, sandbox_id: typing.Optional[str] = OMIT, sandbox_turn_index: typing.Optional[int] = OMIT, request_options: typing.Optional[RequestOptions] = None) -> AsyncHttpResponse[SessionStateResponse]: + async def query_turns(self, *, query: typing.Optional[SessionTurnQuery] = OMIT, windowing: typing.Optional[Windowing] = OMIT, request_options: typing.Optional[RequestOptions] = None) -> AsyncHttpResponse[SessionTurnsResponse]: """ Parameters ---------- - session_id : str + query : typing.Optional[SessionTurnQuery] - data : typing.Optional[SessionStateData] - Full replacement of the continuity state (resume ids + staleness guard). + windowing : typing.Optional[Windowing] - sandbox_id : typing.Optional[str] - Remote sandbox id to record alongside the continuity state. + request_options : typing.Optional[RequestOptions] + Request-specific configuration. - sandbox_turn_index : typing.Optional[int] - the writer's conversation turn index; the pointer write is applied only when it is >= the row's data.latest_turn_index. + Returns + ------- + AsyncHttpResponse[SessionTurnsResponse] + Successful Response + """ + _response = await self._client_wrapper.httpx_client.request( + "sessions/turns/query",method="POST", + json={ + "query": convert_and_respect_annotation_metadata(object_=query, annotation=typing.Optional[SessionTurnQuery], direction="write"), + "windowing": convert_and_respect_annotation_metadata(object_=windowing, annotation=typing.Optional[Windowing], direction="write"), + } + , + headers={"content-type": "application/json", } + , + request_options=request_options,omit=OMIT, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + SessionTurnsResponse, + parse_obj_as( + type_ =SessionTurnsResponse, # type: ignore + object_ =_response.json() + ) + ) + return AsyncHttpResponse(response=_response, data=_data) + if _response.status_code == 422: + raise UnprocessableEntityError(headers=dict(_response.headers), body=typing.cast( + HttpValidationError, + parse_obj_as( + type_ =HttpValidationError, # type: ignore + object_ =_response.json() + ) + )) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) + + async def fetch_turn(self, turn_id: str, *, request_options: typing.Optional[RequestOptions] = None) -> AsyncHttpResponse[SessionTurnResponse]: + """ + Parameters + ---------- + turn_id : str request_options : typing.Optional[RequestOptions] Request-specific configuration. Returns ------- - AsyncHttpResponse[SessionStateResponse] + AsyncHttpResponse[SessionTurnResponse] Successful Response """ _response = await self._client_wrapper.httpx_client.request( - "sessions/states/",method="PUT", - params={"session_id": session_id, } - , + f"sessions/turns/{jsonable_encoder(turn_id)}",method="GET", + request_options=request_options,) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + SessionTurnResponse, + parse_obj_as( + type_ =SessionTurnResponse, # type: ignore + object_ =_response.json() + ) + ) + return AsyncHttpResponse(response=_response, data=_data) + if _response.status_code == 422: + raise UnprocessableEntityError(headers=dict(_response.headers), body=typing.cast( + HttpValidationError, + parse_obj_as( + type_ =HttpValidationError, # type: ignore + object_ =_response.json() + ) + )) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) + + async def query_sessions(self, *, references: typing.Optional[typing.Sequence[Reference]] = OMIT, windowing: typing.Optional[Windowing] = OMIT, request_options: typing.Optional[RequestOptions] = None) -> AsyncHttpResponse[SessionsResponse]: + """ + Parameters + ---------- + references : typing.Optional[typing.Sequence[Reference]] + + windowing : typing.Optional[Windowing] + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + AsyncHttpResponse[SessionsResponse] + Successful Response + """ + _response = await self._client_wrapper.httpx_client.request( + "sessions/query",method="POST", json={ - "data": convert_and_respect_annotation_metadata(object_=data, annotation=typing.Optional[SessionStateData], direction="write"), - "sandbox_id": sandbox_id, - "sandbox_turn_index": sandbox_turn_index, + "references": convert_and_respect_annotation_metadata(object_=references, annotation=typing.Optional[typing.Sequence[Reference]], direction="write"), + "windowing": convert_and_respect_annotation_metadata(object_=windowing, annotation=typing.Optional[Windowing], direction="write"), } , headers={"content-type": "application/json", } @@ -2250,9 +2732,135 @@ async def set_state(self, *, session_id: str, data: typing.Optional[SessionState try: if 200 <= _response.status_code < 300: _data = typing.cast( - SessionStateResponse, + SessionsResponse, + parse_obj_as( + type_ =SessionsResponse, # type: ignore + object_ =_response.json() + ) + ) + return AsyncHttpResponse(response=_response, data=_data) + if _response.status_code == 422: + raise UnprocessableEntityError(headers=dict(_response.headers), body=typing.cast( + HttpValidationError, + parse_obj_as( + type_ =HttpValidationError, # type: ignore + object_ =_response.json() + ) + )) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) + + async def delete_session(self, *, session_id: str, request_options: typing.Optional[RequestOptions] = None) -> AsyncHttpResponse[typing.Dict[str, typing.Any]]: + """ + Parameters + ---------- + session_id : str + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + AsyncHttpResponse[typing.Dict[str, typing.Any]] + Successful Response + """ + _response = await self._client_wrapper.httpx_client.request( + "sessions/",method="DELETE", + params={"session_id": session_id, } + , + request_options=request_options,) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + typing.Dict[str, typing.Any], + parse_obj_as( + type_ =typing.Dict[str, typing.Any], # type: ignore + object_ =_response.json() + ) + ) + return AsyncHttpResponse(response=_response, data=_data) + if _response.status_code == 422: + raise UnprocessableEntityError(headers=dict(_response.headers), body=typing.cast( + HttpValidationError, + parse_obj_as( + type_ =HttpValidationError, # type: ignore + object_ =_response.json() + ) + )) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) + + async def archive_session(self, *, session_id: str, request_options: typing.Optional[RequestOptions] = None) -> AsyncHttpResponse[SessionResponse]: + """ + Parameters + ---------- + session_id : str + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + AsyncHttpResponse[SessionResponse] + Successful Response + """ + _response = await self._client_wrapper.httpx_client.request( + "sessions/archive",method="POST", + params={"session_id": session_id, } + , + request_options=request_options,) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + SessionResponse, + parse_obj_as( + type_ =SessionResponse, # type: ignore + object_ =_response.json() + ) + ) + return AsyncHttpResponse(response=_response, data=_data) + if _response.status_code == 422: + raise UnprocessableEntityError(headers=dict(_response.headers), body=typing.cast( + HttpValidationError, + parse_obj_as( + type_ =HttpValidationError, # type: ignore + object_ =_response.json() + ) + )) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) + + async def unarchive_session(self, *, session_id: str, request_options: typing.Optional[RequestOptions] = None) -> AsyncHttpResponse[SessionResponse]: + """ + Parameters + ---------- + session_id : str + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + AsyncHttpResponse[SessionResponse] + Successful Response + """ + _response = await self._client_wrapper.httpx_client.request( + "sessions/unarchive",method="POST", + params={"session_id": session_id, } + , + request_options=request_options,) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + SessionResponse, parse_obj_as( - type_ =SessionStateResponse, # type: ignore + type_ =SessionResponse, # type: ignore object_ =_response.json() ) ) diff --git a/clients/python/agenta_client/types/__init__.py b/clients/python/agenta_client/types/__init__.py index 957f68737f..298de43177 100644 --- a/clients/python/agenta_client/types/__init__.py +++ b/clients/python/agenta_client/types/__init__.py @@ -307,7 +307,7 @@ from .full_json_output import FullJsonOutput from .gateway_tool_config import GatewayToolConfig from .gateway_tool_config_permission import GatewayToolConfigPermission - from .harness_session_record import HarnessSessionRecord + from .harness_kind import HarnessKind from .header import Header from .http_validation_error import HttpValidationError from .invite_request import InviteRequest @@ -409,7 +409,7 @@ from .secret_response_dto import SecretResponseDto from .secret_response_dto_data import SecretResponseDtoData from .selector import Selector - from .session_heartbeat_response_model import SessionHeartbeatResponseModel + from .session_heartbeat_result import SessionHeartbeatResult from .session_ids_response import SessionIdsResponse from .session_interaction import SessionInteraction from .session_interaction_data import SessionInteractionData @@ -426,16 +426,18 @@ from .session_record import SessionRecord from .session_record_response import SessionRecordResponse from .session_records_query_response import SessionRecordsQueryResponse - from .session_state import SessionState - from .session_state_data import SessionStateData - from .session_state_flags import SessionStateFlags - from .session_state_response import SessionStateResponse - from .session_state_upsert_request import SessionStateUpsertRequest + from .session_response import SessionResponse from .session_stream import SessionStream - from .session_stream_command_response_model import SessionStreamCommandResponseModel + from .session_stream_command_response import SessionStreamCommandResponse from .session_stream_flags import SessionStreamFlags - from .session_stream_response_model import SessionStreamResponseModel - from .session_streams_response_model import SessionStreamsResponseModel + from .session_stream_header_edit import SessionStreamHeaderEdit + from .session_stream_response import SessionStreamResponse + from .session_streams_response import SessionStreamsResponse + from .session_turn import SessionTurn + from .session_turn_query import SessionTurnQuery + from .session_turn_response import SessionTurnResponse + from .session_turns_response import SessionTurnsResponse + from .sessions_response import SessionsResponse from .simple_application import SimpleApplication from .simple_application_additional_context import SimpleApplicationAdditionalContext from .simple_application_create import SimpleApplicationCreate @@ -743,6 +745,7 @@ from .workflow_create import WorkflowCreate from .workflow_edit import WorkflowEdit from .workflow_flags import WorkflowFlags + from .workflow_request_data import WorkflowRequestData from .workflow_response import WorkflowResponse from .workflow_revision_commit import WorkflowRevisionCommit from .workflow_revision_create import WorkflowRevisionCreate @@ -773,7 +776,7 @@ from .workspace_member_response import WorkspaceMemberResponse from .workspace_permission import WorkspacePermission from .workspace_response import WorkspaceResponse -_dynamic_imports: typing.Dict[str, str] = {"AdminAccountCreateOptions": ".admin_account_create_options", "AdminAccountRead": ".admin_account_read", "AdminAccountsCreate": ".admin_accounts_create", "AdminAccountsDelete": ".admin_accounts_delete", "AdminAccountsDeleteTarget": ".admin_accounts_delete_target", "AdminAccountsResponse": ".admin_accounts_response", "AdminApiKeyCreate": ".admin_api_key_create", "AdminApiKeyResponse": ".admin_api_key_response", "AdminDeleteResponse": ".admin_delete_response", "AdminDeletedEntities": ".admin_deleted_entities", "AdminDeletedEntity": ".admin_deleted_entity", "AdminOrganizationCreate": ".admin_organization_create", "AdminOrganizationMembershipCreate": ".admin_organization_membership_create", "AdminOrganizationMembershipRead": ".admin_organization_membership_read", "AdminOrganizationRead": ".admin_organization_read", "AdminProjectCreate": ".admin_project_create", "AdminProjectMembershipCreate": ".admin_project_membership_create", "AdminProjectMembershipRead": ".admin_project_membership_read", "AdminProjectRead": ".admin_project_read", "AdminSimpleAccountCreate": ".admin_simple_account_create", "AdminSimpleAccountDeleteEntry": ".admin_simple_account_delete_entry", "AdminSimpleAccountRead": ".admin_simple_account_read", "AdminSimpleAccountsApiKeysCreate": ".admin_simple_accounts_api_keys_create", "AdminSimpleAccountsCreate": ".admin_simple_accounts_create", "AdminSimpleAccountsDelete": ".admin_simple_accounts_delete", "AdminSimpleAccountsOrganizationsCreate": ".admin_simple_accounts_organizations_create", "AdminSimpleAccountsOrganizationsMembershipsCreate": ".admin_simple_accounts_organizations_memberships_create", "AdminSimpleAccountsOrganizationsTransferOwnership": ".admin_simple_accounts_organizations_transfer_ownership", "AdminSimpleAccountsOrganizationsTransferOwnershipIncludeProjects": ".admin_simple_accounts_organizations_transfer_ownership_include_projects", "AdminSimpleAccountsOrganizationsTransferOwnershipIncludeProjectsZero": ".admin_simple_accounts_organizations_transfer_ownership_include_projects_zero", "AdminSimpleAccountsOrganizationsTransferOwnershipIncludeWorkspaces": ".admin_simple_accounts_organizations_transfer_ownership_include_workspaces", "AdminSimpleAccountsOrganizationsTransferOwnershipIncludeWorkspacesZero": ".admin_simple_accounts_organizations_transfer_ownership_include_workspaces_zero", "AdminSimpleAccountsOrganizationsTransferOwnershipResponse": ".admin_simple_accounts_organizations_transfer_ownership_response", "AdminSimpleAccountsProjectsCreate": ".admin_simple_accounts_projects_create", "AdminSimpleAccountsProjectsMembershipsCreate": ".admin_simple_accounts_projects_memberships_create", "AdminSimpleAccountsResponse": ".admin_simple_accounts_response", "AdminSimpleAccountsUsersCreate": ".admin_simple_accounts_users_create", "AdminSimpleAccountsUsersIdentitiesCreate": ".admin_simple_accounts_users_identities_create", "AdminSimpleAccountsUsersResetPassword": ".admin_simple_accounts_users_reset_password", "AdminSimpleAccountsWorkspacesCreate": ".admin_simple_accounts_workspaces_create", "AdminSimpleAccountsWorkspacesMembershipsCreate": ".admin_simple_accounts_workspaces_memberships_create", "AdminStructuredError": ".admin_structured_error", "AdminSubscriptionCreate": ".admin_subscription_create", "AdminSubscriptionRead": ".admin_subscription_read", "AdminUserCreate": ".admin_user_create", "AdminUserIdentityCreate": ".admin_user_identity_create", "AdminUserIdentityRead": ".admin_user_identity_read", "AdminUserIdentityReadStatus": ".admin_user_identity_read_status", "AdminUserRead": ".admin_user_read", "AdminWorkspaceCreate": ".admin_workspace_create", "AdminWorkspaceMembershipCreate": ".admin_workspace_membership_create", "AdminWorkspaceMembershipRead": ".admin_workspace_membership_read", "AdminWorkspaceRead": ".admin_workspace_read", "AgentTemplateOverlay": ".agent_template_overlay", "Analytics": ".analytics", "AnalyticsResponse": ".analytics_response", "Annotation": ".annotation", "AnnotationCreate": ".annotation_create", "AnnotationCreateLinks": ".annotation_create_links", "AnnotationEdit": ".annotation_edit", "AnnotationEditLinks": ".annotation_edit_links", "AnnotationLinkResponse": ".annotation_link_response", "AnnotationLinks": ".annotation_links", "AnnotationQuery": ".annotation_query", "AnnotationQueryLinks": ".annotation_query_links", "AnnotationResponse": ".annotation_response", "AnnotationsResponse": ".annotations_response", "Application": ".application", "ApplicationArtifactFlags": ".application_artifact_flags", "ApplicationArtifactQueryFlags": ".application_artifact_query_flags", "ApplicationCatalogPreset": ".application_catalog_preset", "ApplicationCatalogPresetResponse": ".application_catalog_preset_response", "ApplicationCatalogPresetsResponse": ".application_catalog_presets_response", "ApplicationCatalogTemplate": ".application_catalog_template", "ApplicationCatalogTemplateResponse": ".application_catalog_template_response", "ApplicationCatalogTemplatesResponse": ".application_catalog_templates_response", "ApplicationCatalogType": ".application_catalog_type", "ApplicationCatalogTypesResponse": ".application_catalog_types_response", "ApplicationCreate": ".application_create", "ApplicationEdit": ".application_edit", "ApplicationFlags": ".application_flags", "ApplicationQuery": ".application_query", "ApplicationResponse": ".application_response", "ApplicationRevisionCommit": ".application_revision_commit", "ApplicationRevisionCreate": ".application_revision_create", "ApplicationRevisionDataInput": ".application_revision_data_input", "ApplicationRevisionDataInputHeadersValue": ".application_revision_data_input_headers_value", "ApplicationRevisionDataInputRuntime": ".application_revision_data_input_runtime", "ApplicationRevisionDataOutput": ".application_revision_data_output", "ApplicationRevisionDataOutputHeadersValue": ".application_revision_data_output_headers_value", "ApplicationRevisionDataOutputRuntime": ".application_revision_data_output_runtime", "ApplicationRevisionEdit": ".application_revision_edit", "ApplicationRevisionFlags": ".application_revision_flags", "ApplicationRevisionInput": ".application_revision_input", "ApplicationRevisionOutput": ".application_revision_output", "ApplicationRevisionQuery": ".application_revision_query", "ApplicationRevisionQueryFlags": ".application_revision_query_flags", "ApplicationRevisionResolveResponse": ".application_revision_resolve_response", "ApplicationRevisionResponse": ".application_revision_response", "ApplicationRevisionsLog": ".application_revisions_log", "ApplicationRevisionsResponse": ".application_revisions_response", "ApplicationVariant": ".application_variant", "ApplicationVariantCreate": ".application_variant_create", "ApplicationVariantEdit": ".application_variant_edit", "ApplicationVariantFlags": ".application_variant_flags", "ApplicationVariantFork": ".application_variant_fork", "ApplicationVariantResponse": ".application_variant_response", "ApplicationVariantsResponse": ".application_variants_response", "ApplicationsResponse": ".applications_response", "BodyConfigsFetchVariantsConfigsFetchPost": ".body_configs_fetch_variants_configs_fetch_post", "Bucket": ".bucket", "BuiltinToolConfig": ".builtin_tool_config", "BuiltinToolConfigPermission": ".builtin_tool_config_permission", "CapabilitiesResult": ".capabilities_result", "Capability": ".capability", "CapabilityConnection": ".capability_connection", "CapabilityGuidance": ".capability_guidance", "CollectStatusResponse": ".collect_status_response", "CommandMode": ".command_mode", "ComparisonOperator": ".comparison_operator", "Condition": ".condition", "ConditionOperator": ".condition_operator", "ConditionOptions": ".condition_options", "ConditionValue": ".condition_value", "ConfigResponseModel": ".config_response_model", "ConnectAffordance": ".connect_affordance", "ConnectionRequirement": ".connection_requirement", "CustomModelSettingsDto": ".custom_model_settings_dto", "CustomProviderDto": ".custom_provider_dto", "CustomProviderKind": ".custom_provider_kind", "CustomProviderSettingsDto": ".custom_provider_settings_dto", "CustomSecretDto": ".custom_secret_dto", "CustomSecretFormat": ".custom_secret_format", "CustomSecretSettingsDto": ".custom_secret_settings_dto", "CustomSecretSettingsDtoContent": ".custom_secret_settings_dto_content", "CustomSecretSettingsDtoContentOneValue": ".custom_secret_settings_dto_content_one_value", "DictOperator": ".dict_operator", "DiscoverResponse": ".discover_response", "DiscoverResponseMethodsValue": ".discover_response_methods_value", "DiscoveredAlternative": ".discovered_alternative", "DiscoveredTool": ".discovered_tool", "DiscoveredToolType": ".discovered_tool_type", "DiscoveredTriggerAlternative": ".discovered_trigger_alternative", "DiscoveredTriggerEvent": ".discovered_trigger_event", "DiscoveredTriggerEventType": ".discovered_trigger_event_type", "EntityRef": ".entity_ref", "Environment": ".environment", "EnvironmentCreate": ".environment_create", "EnvironmentEdit": ".environment_edit", "EnvironmentFlags": ".environment_flags", "EnvironmentQueryFlags": ".environment_query_flags", "EnvironmentResponse": ".environment_response", "EnvironmentRevisionCommit": ".environment_revision_commit", "EnvironmentRevisionCreate": ".environment_revision_create", "EnvironmentRevisionData": ".environment_revision_data", "EnvironmentRevisionDelta": ".environment_revision_delta", "EnvironmentRevisionEdit": ".environment_revision_edit", "EnvironmentRevisionInput": ".environment_revision_input", "EnvironmentRevisionOutput": ".environment_revision_output", "EnvironmentRevisionResolveResponse": ".environment_revision_resolve_response", "EnvironmentRevisionResponse": ".environment_revision_response", "EnvironmentRevisionsLog": ".environment_revisions_log", "EnvironmentRevisionsResponse": ".environment_revisions_response", "EnvironmentVariant": ".environment_variant", "EnvironmentVariantCreate": ".environment_variant_create", "EnvironmentVariantEdit": ".environment_variant_edit", "EnvironmentVariantFork": ".environment_variant_fork", "EnvironmentVariantResponse": ".environment_variant_response", "EnvironmentVariantsResponse": ".environment_variants_response", "EnvironmentsResponse": ".environments_response", "ErrorPolicy": ".error_policy", "EvaluationMetrics": ".evaluation_metrics", "EvaluationMetricsCreate": ".evaluation_metrics_create", "EvaluationMetricsIdsResponse": ".evaluation_metrics_ids_response", "EvaluationMetricsQuery": ".evaluation_metrics_query", "EvaluationMetricsQueryScenarioIds": ".evaluation_metrics_query_scenario_ids", "EvaluationMetricsQueryTimestamps": ".evaluation_metrics_query_timestamps", "EvaluationMetricsRefresh": ".evaluation_metrics_refresh", "EvaluationMetricsResponse": ".evaluation_metrics_response", "EvaluationMetricsSetRequest": ".evaluation_metrics_set_request", "EvaluationQueue": ".evaluation_queue", "EvaluationQueueCreate": ".evaluation_queue_create", "EvaluationQueueData": ".evaluation_queue_data", "EvaluationQueueEdit": ".evaluation_queue_edit", "EvaluationQueueFlags": ".evaluation_queue_flags", "EvaluationQueueIdResponse": ".evaluation_queue_id_response", "EvaluationQueueIdsResponse": ".evaluation_queue_ids_response", "EvaluationQueueQuery": ".evaluation_queue_query", "EvaluationQueueQueryFlags": ".evaluation_queue_query_flags", "EvaluationQueueResponse": ".evaluation_queue_response", "EvaluationQueueScenariosQuery": ".evaluation_queue_scenarios_query", "EvaluationQueuesResponse": ".evaluation_queues_response", "EvaluationResult": ".evaluation_result", "EvaluationResultCreate": ".evaluation_result_create", "EvaluationResultIdResponse": ".evaluation_result_id_response", "EvaluationResultIdsResponse": ".evaluation_result_ids_response", "EvaluationResultQuery": ".evaluation_result_query", "EvaluationResultResponse": ".evaluation_result_response", "EvaluationResultsResponse": ".evaluation_results_response", "EvaluationResultsSetRequest": ".evaluation_results_set_request", "EvaluationRun": ".evaluation_run", "EvaluationRunCreate": ".evaluation_run_create", "EvaluationRunDataConcurrency": ".evaluation_run_data_concurrency", "EvaluationRunDataInput": ".evaluation_run_data_input", "EvaluationRunDataMapping": ".evaluation_run_data_mapping", "EvaluationRunDataMappingColumn": ".evaluation_run_data_mapping_column", "EvaluationRunDataMappingStep": ".evaluation_run_data_mapping_step", "EvaluationRunDataOutput": ".evaluation_run_data_output", "EvaluationRunDataStepInput": ".evaluation_run_data_step_input", "EvaluationRunDataStepInputKey": ".evaluation_run_data_step_input_key", "EvaluationRunDataStepInputOrigin": ".evaluation_run_data_step_input_origin", "EvaluationRunDataStepInputType": ".evaluation_run_data_step_input_type", "EvaluationRunDataStepOutput": ".evaluation_run_data_step_output", "EvaluationRunDataStepOutputOrigin": ".evaluation_run_data_step_output_origin", "EvaluationRunDataStepOutputType": ".evaluation_run_data_step_output_type", "EvaluationRunEdit": ".evaluation_run_edit", "EvaluationRunFlags": ".evaluation_run_flags", "EvaluationRunIdResponse": ".evaluation_run_id_response", "EvaluationRunIdsRequest": ".evaluation_run_ids_request", "EvaluationRunIdsResponse": ".evaluation_run_ids_response", "EvaluationRunQuery": ".evaluation_run_query", "EvaluationRunQueryFlags": ".evaluation_run_query_flags", "EvaluationRunResponse": ".evaluation_run_response", "EvaluationRunsResponse": ".evaluation_runs_response", "EvaluationScenario": ".evaluation_scenario", "EvaluationScenarioCreate": ".evaluation_scenario_create", "EvaluationScenarioEdit": ".evaluation_scenario_edit", "EvaluationScenarioIdResponse": ".evaluation_scenario_id_response", "EvaluationScenarioIdsResponse": ".evaluation_scenario_ids_response", "EvaluationScenarioQuery": ".evaluation_scenario_query", "EvaluationScenarioResponse": ".evaluation_scenario_response", "EvaluationScenariosResponse": ".evaluation_scenarios_response", "EvaluationStatus": ".evaluation_status", "Evaluator": ".evaluator", "EvaluatorArtifactFlags": ".evaluator_artifact_flags", "EvaluatorArtifactQueryFlags": ".evaluator_artifact_query_flags", "EvaluatorCatalogPreset": ".evaluator_catalog_preset", "EvaluatorCatalogPresetResponse": ".evaluator_catalog_preset_response", "EvaluatorCatalogPresetsResponse": ".evaluator_catalog_presets_response", "EvaluatorCatalogTemplate": ".evaluator_catalog_template", "EvaluatorCatalogTemplateResponse": ".evaluator_catalog_template_response", "EvaluatorCatalogTemplatesResponse": ".evaluator_catalog_templates_response", "EvaluatorCatalogType": ".evaluator_catalog_type", "EvaluatorCatalogTypesResponse": ".evaluator_catalog_types_response", "EvaluatorCreate": ".evaluator_create", "EvaluatorEdit": ".evaluator_edit", "EvaluatorFlags": ".evaluator_flags", "EvaluatorQuery": ".evaluator_query", "EvaluatorResponse": ".evaluator_response", "EvaluatorRevisionCommit": ".evaluator_revision_commit", "EvaluatorRevisionCreate": ".evaluator_revision_create", "EvaluatorRevisionDataInput": ".evaluator_revision_data_input", "EvaluatorRevisionDataInputHeadersValue": ".evaluator_revision_data_input_headers_value", "EvaluatorRevisionDataInputRuntime": ".evaluator_revision_data_input_runtime", "EvaluatorRevisionDataOutput": ".evaluator_revision_data_output", "EvaluatorRevisionDataOutputHeadersValue": ".evaluator_revision_data_output_headers_value", "EvaluatorRevisionDataOutputRuntime": ".evaluator_revision_data_output_runtime", "EvaluatorRevisionEdit": ".evaluator_revision_edit", "EvaluatorRevisionFlags": ".evaluator_revision_flags", "EvaluatorRevisionInput": ".evaluator_revision_input", "EvaluatorRevisionOutput": ".evaluator_revision_output", "EvaluatorRevisionQuery": ".evaluator_revision_query", "EvaluatorRevisionQueryFlags": ".evaluator_revision_query_flags", "EvaluatorRevisionResolveResponse": ".evaluator_revision_resolve_response", "EvaluatorRevisionResponse": ".evaluator_revision_response", "EvaluatorRevisionsLog": ".evaluator_revisions_log", "EvaluatorRevisionsResponse": ".evaluator_revisions_response", "EvaluatorTemplate": ".evaluator_template", "EvaluatorTemplatesResponse": ".evaluator_templates_response", "EvaluatorVariant": ".evaluator_variant", "EvaluatorVariantCreate": ".evaluator_variant_create", "EvaluatorVariantEdit": ".evaluator_variant_edit", "EvaluatorVariantFlags": ".evaluator_variant_flags", "EvaluatorVariantFork": ".evaluator_variant_fork", "EvaluatorVariantResponse": ".evaluator_variant_response", "EvaluatorVariantsResponse": ".evaluator_variants_response", "EvaluatorsResponse": ".evaluators_response", "Event": ".event", "EventQuery": ".event_query", "EventType": ".event_type", "EventsQueryResponse": ".events_query_response", "ExistenceOperator": ".existence_operator", "FilteringInput": ".filtering_input", "FilteringInputConditionsItem": ".filtering_input_conditions_item", "FilteringOutput": ".filtering_output", "FilteringOutputConditionsItem": ".filtering_output_conditions_item", "Focus": ".focus", "Folder": ".folder", "FolderCreate": ".folder_create", "FolderEdit": ".folder_edit", "FolderIdResponse": ".folder_id_response", "FolderKind": ".folder_kind", "FolderQuery": ".folder_query", "FolderQueryKinds": ".folder_query_kinds", "FolderResponse": ".folder_response", "FoldersResponse": ".folders_response", "Format": ".format", "Formatting": ".formatting", typing.Any: ".full_json_input", typing.Any: ".full_json_output", "GatewayToolConfig": ".gateway_tool_config", "GatewayToolConfigPermission": ".gateway_tool_config_permission", "HarnessSessionRecord": ".harness_session_record", "Header": ".header", "HttpValidationError": ".http_validation_error", "InviteRequest": ".invite_request", "Invocation": ".invocation", "InvocationCreate": ".invocation_create", "InvocationCreateLinks": ".invocation_create_links", "InvocationEdit": ".invocation_edit", "InvocationEditLinks": ".invocation_edit_links", "InvocationLinkResponse": ".invocation_link_response", "InvocationLinks": ".invocation_links", "InvocationQuery": ".invocation_query", "InvocationQueryLinks": ".invocation_query_links", "InvocationResponse": ".invocation_response", "InvocationsResponse": ".invocations_response", "JsonSchemasInput": ".json_schemas_input", "JsonSchemasOutput": ".json_schemas_output", typing.Any: ".label_json_input", typing.Any: ".label_json_output", "LegacyLifecycleDto": ".legacy_lifecycle_dto", "ListApiKeysResponse": ".list_api_keys_response", "ListOperator": ".list_operator", "ListOptions": ".list_options", "LogicalOperator": ".logical_operator", "MetricSpec": ".metric_spec", "MetricType": ".metric_type", "MetricsBucket": ".metrics_bucket", "Mount": ".mount", "MountCreate": ".mount_create", "MountCredentials": ".mount_credentials", "MountCredentialsResponse": ".mount_credentials_response", "MountData": ".mount_data", "MountEdit": ".mount_edit", "MountFileDeletedResponse": ".mount_file_deleted_response", "MountFileWrittenResponse": ".mount_file_written_response", "MountFlags": ".mount_flags", "MountFolderCreatedResponse": ".mount_folder_created_response", "MountQuery": ".mount_query", "MountResponse": ".mount_response", "MountsResponse": ".mounts_response", "NumericOperator": ".numeric_operator", "OTelEventInput": ".o_tel_event_input", "OTelEventInputTimestamp": ".o_tel_event_input_timestamp", "OTelEventOutput": ".o_tel_event_output", "OTelEventOutputTimestamp": ".o_tel_event_output_timestamp", "OTelHashInput": ".o_tel_hash_input", "OTelHashOutput": ".o_tel_hash_output", "OTelLinkInput": ".o_tel_link_input", "OTelLinkOutput": ".o_tel_link_output", "OTelLinksResponse": ".o_tel_links_response", "OTelReferenceInput": ".o_tel_reference_input", "OTelReferenceOutput": ".o_tel_reference_output", "OTelSpanKind": ".o_tel_span_kind", "OTelStatusCode": ".o_tel_status_code", "OTelTracingRequest": ".o_tel_tracing_request", "OTelTracingResponse": ".o_tel_tracing_response", "OldAnalyticsResponse": ".old_analytics_response", "Organization": ".organization", "OrganizationDetails": ".organization_details", "OrganizationDomainResponse": ".organization_domain_response", "OrganizationProviderResponse": ".organization_provider_response", "OrganizationUpdate": ".organization_update", "Permission": ".permission", "PlaygroundBuildKitContext": ".playground_build_kit_context", "ProjectsResponse": ".projects_response", "QueriesResponse": ".queries_response", "Query": ".query", "QueryCreate": ".query_create", "QueryEdit": ".query_edit", "QueryFlags": ".query_flags", "QueryQueryFlags": ".query_query_flags", "QueryResponse": ".query_response", "QueryRevision": ".query_revision", "QueryRevisionCommit": ".query_revision_commit", "QueryRevisionCreate": ".query_revision_create", "QueryRevisionDataInput": ".query_revision_data_input", "QueryRevisionDataOutput": ".query_revision_data_output", "QueryRevisionEdit": ".query_revision_edit", "QueryRevisionQuery": ".query_revision_query", "QueryRevisionResponse": ".query_revision_response", "QueryRevisionsLog": ".query_revisions_log", "QueryRevisionsResponse": ".query_revisions_response", "QueryVariant": ".query_variant", "QueryVariantCreate": ".query_variant_create", "QueryVariantEdit": ".query_variant_edit", "QueryVariantFork": ".query_variant_fork", "QueryVariantQuery": ".query_variant_query", "QueryVariantResponse": ".query_variant_response", "QueryVariantsResponse": ".query_variants_response", "Reference": ".reference", "ReferenceRequestModelInput": ".reference_request_model_input", "ReferenceRequestModelOutput": ".reference_request_model_output", "RequestType": ".request_type", "ResolutionInfo": ".resolution_info", "ResolvedTool": ".resolved_tool", "RetrievalInfo": ".retrieval_info", "SecretDto": ".secret_dto", "SecretDtoData": ".secret_dto_data", "SecretKind": ".secret_kind", "SecretResponseDto": ".secret_response_dto", "SecretResponseDtoData": ".secret_response_dto_data", "Selector": ".selector", "SessionHeartbeatResponseModel": ".session_heartbeat_response_model", "SessionIdsResponse": ".session_ids_response", "SessionInteraction": ".session_interaction", "SessionInteractionData": ".session_interaction_data", "SessionInteractionFlags": ".session_interaction_flags", "SessionInteractionKind": ".session_interaction_kind", "SessionInteractionQuery": ".session_interaction_query", "SessionInteractionQueryFlags": ".session_interaction_query_flags", "SessionInteractionResponse": ".session_interaction_response", "SessionInteractionStatus": ".session_interaction_status", "SessionInteractionsResponse": ".session_interactions_response", "SessionMount": ".session_mount", "SessionMountQuery": ".session_mount_query", "SessionMountsResponse": ".session_mounts_response", "SessionRecord": ".session_record", "SessionRecordResponse": ".session_record_response", "SessionRecordsQueryResponse": ".session_records_query_response", "SessionState": ".session_state", "SessionStateData": ".session_state_data", "SessionStateFlags": ".session_state_flags", "SessionStateResponse": ".session_state_response", "SessionStateUpsertRequest": ".session_state_upsert_request", "SessionStream": ".session_stream", "SessionStreamCommandResponseModel": ".session_stream_command_response_model", "SessionStreamFlags": ".session_stream_flags", "SessionStreamResponseModel": ".session_stream_response_model", "SessionStreamsResponseModel": ".session_streams_response_model", "SimpleApplication": ".simple_application", "SimpleApplicationAdditionalContext": ".simple_application_additional_context", "SimpleApplicationCreate": ".simple_application_create", "SimpleApplicationDataInput": ".simple_application_data_input", "SimpleApplicationDataInputHeadersValue": ".simple_application_data_input_headers_value", "SimpleApplicationDataInputRuntime": ".simple_application_data_input_runtime", "SimpleApplicationDataOutput": ".simple_application_data_output", "SimpleApplicationDataOutputHeadersValue": ".simple_application_data_output_headers_value", "SimpleApplicationDataOutputRuntime": ".simple_application_data_output_runtime", "SimpleApplicationEdit": ".simple_application_edit", "SimpleApplicationFlags": ".simple_application_flags", "SimpleApplicationQuery": ".simple_application_query", "SimpleApplicationQueryFlags": ".simple_application_query_flags", "SimpleApplicationResponse": ".simple_application_response", "SimpleApplicationsResponse": ".simple_applications_response", "SimpleEnvironment": ".simple_environment", "SimpleEnvironmentCreate": ".simple_environment_create", "SimpleEnvironmentEdit": ".simple_environment_edit", "SimpleEnvironmentQuery": ".simple_environment_query", "SimpleEnvironmentResponse": ".simple_environment_response", "SimpleEnvironmentsResponse": ".simple_environments_response", "SimpleEvaluation": ".simple_evaluation", "SimpleEvaluationCreate": ".simple_evaluation_create", "SimpleEvaluationData": ".simple_evaluation_data", "SimpleEvaluationDataApplicationSteps": ".simple_evaluation_data_application_steps", "SimpleEvaluationDataApplicationStepsOneValue": ".simple_evaluation_data_application_steps_one_value", "SimpleEvaluationDataEvaluatorSteps": ".simple_evaluation_data_evaluator_steps", "SimpleEvaluationDataEvaluatorStepsOneValue": ".simple_evaluation_data_evaluator_steps_one_value", "SimpleEvaluationDataQuerySteps": ".simple_evaluation_data_query_steps", "SimpleEvaluationDataQueryStepsOneValue": ".simple_evaluation_data_query_steps_one_value", "SimpleEvaluationDataTestsetSteps": ".simple_evaluation_data_testset_steps", "SimpleEvaluationDataTestsetStepsOneValue": ".simple_evaluation_data_testset_steps_one_value", "SimpleEvaluationEdit": ".simple_evaluation_edit", "SimpleEvaluationIdResponse": ".simple_evaluation_id_response", "SimpleEvaluationQuery": ".simple_evaluation_query", "SimpleEvaluationResponse": ".simple_evaluation_response", "SimpleEvaluationsResponse": ".simple_evaluations_response", "SimpleEvaluator": ".simple_evaluator", "SimpleEvaluatorCreate": ".simple_evaluator_create", "SimpleEvaluatorDataInput": ".simple_evaluator_data_input", "SimpleEvaluatorDataInputHeadersValue": ".simple_evaluator_data_input_headers_value", "SimpleEvaluatorDataInputRuntime": ".simple_evaluator_data_input_runtime", "SimpleEvaluatorDataOutput": ".simple_evaluator_data_output", "SimpleEvaluatorDataOutputHeadersValue": ".simple_evaluator_data_output_headers_value", "SimpleEvaluatorDataOutputRuntime": ".simple_evaluator_data_output_runtime", "SimpleEvaluatorEdit": ".simple_evaluator_edit", "SimpleEvaluatorFlags": ".simple_evaluator_flags", "SimpleEvaluatorQuery": ".simple_evaluator_query", "SimpleEvaluatorQueryFlags": ".simple_evaluator_query_flags", "SimpleEvaluatorResponse": ".simple_evaluator_response", "SimpleEvaluatorsResponse": ".simple_evaluators_response", "SimpleQueriesResponse": ".simple_queries_response", "SimpleQuery": ".simple_query", "SimpleQueryCreate": ".simple_query_create", "SimpleQueryEdit": ".simple_query_edit", "SimpleQueryQuery": ".simple_query_query", "SimpleQueryResponse": ".simple_query_response", "SimpleQueue": ".simple_queue", "SimpleQueueCreate": ".simple_queue_create", "SimpleQueueData": ".simple_queue_data", "SimpleQueueDataEvaluators": ".simple_queue_data_evaluators", "SimpleQueueDataEvaluatorsOneValue": ".simple_queue_data_evaluators_one_value", "SimpleQueueIdResponse": ".simple_queue_id_response", "SimpleQueueIdsResponse": ".simple_queue_ids_response", "SimpleQueueKind": ".simple_queue_kind", "SimpleQueueQuery": ".simple_queue_query", "SimpleQueueResponse": ".simple_queue_response", "SimpleQueueScenariosQuery": ".simple_queue_scenarios_query", "SimpleQueueScenariosResponse": ".simple_queue_scenarios_response", "SimpleQueueSettings": ".simple_queue_settings", "SimpleQueuesResponse": ".simple_queues_response", "SimpleTestset": ".simple_testset", "SimpleTestsetCreate": ".simple_testset_create", "SimpleTestsetEdit": ".simple_testset_edit", "SimpleTestsetQuery": ".simple_testset_query", "SimpleTestsetResponse": ".simple_testset_response", "SimpleTestsetsResponse": ".simple_testsets_response", "SimpleTrace": ".simple_trace", "SimpleTraceChannel": ".simple_trace_channel", "SimpleTraceCreate": ".simple_trace_create", "SimpleTraceCreateLinks": ".simple_trace_create_links", "SimpleTraceEdit": ".simple_trace_edit", "SimpleTraceEditLinks": ".simple_trace_edit_links", "SimpleTraceKind": ".simple_trace_kind", "SimpleTraceLinkResponse": ".simple_trace_link_response", "SimpleTraceLinks": ".simple_trace_links", "SimpleTraceOrigin": ".simple_trace_origin", "SimpleTraceQuery": ".simple_trace_query", "SimpleTraceQueryLinks": ".simple_trace_query_links", "SimpleTraceReferences": ".simple_trace_references", "SimpleTraceResponse": ".simple_trace_response", "SimpleTracesResponse": ".simple_traces_response", "SimpleWorkflow": ".simple_workflow", "SimpleWorkflowCreate": ".simple_workflow_create", "SimpleWorkflowDataInput": ".simple_workflow_data_input", "SimpleWorkflowDataInputHeadersValue": ".simple_workflow_data_input_headers_value", "SimpleWorkflowDataInputRuntime": ".simple_workflow_data_input_runtime", "SimpleWorkflowDataOutput": ".simple_workflow_data_output", "SimpleWorkflowDataOutputHeadersValue": ".simple_workflow_data_output_headers_value", "SimpleWorkflowDataOutputRuntime": ".simple_workflow_data_output_runtime", "SimpleWorkflowEdit": ".simple_workflow_edit", "SimpleWorkflowFlags": ".simple_workflow_flags", "SimpleWorkflowQuery": ".simple_workflow_query", "SimpleWorkflowQueryFlags": ".simple_workflow_query_flags", "SimpleWorkflowResponse": ".simple_workflow_response", "SimpleWorkflowsResponse": ".simple_workflows_response", "SpanInput": ".span_input", "SpanInputEndTime": ".span_input_end_time", "SpanInputStartTime": ".span_input_start_time", "SpanOutput": ".span_output", "SpanOutputEndTime": ".span_output_end_time", "SpanOutputStartTime": ".span_output_start_time", "SpanResponse": ".span_response", "SpanType": ".span_type", "SpansNodeInput": ".spans_node_input", "SpansNodeInputEndTime": ".spans_node_input_end_time", "SpansNodeInputSpansValue": ".spans_node_input_spans_value", "SpansNodeInputStartTime": ".spans_node_input_start_time", "SpansNodeOutput": ".spans_node_output", "SpansNodeOutputEndTime": ".spans_node_output_end_time", "SpansNodeOutputSpansValue": ".spans_node_output_spans_value", "SpansNodeOutputStartTime": ".spans_node_output_start_time", "SpansResponse": ".spans_response", "SpansTreeInput": ".spans_tree_input", "SpansTreeInputSpansValue": ".spans_tree_input_spans_value", "SpansTreeOutput": ".spans_tree_output", "SpansTreeOutputSpansValue": ".spans_tree_output_spans_value", "SsoProviderDto": ".sso_provider_dto", "SsoProviderInfo": ".sso_provider_info", "SsoProviderSettingsDto": ".sso_provider_settings_dto", "SsoProviders": ".sso_providers", "StandardProviderDto": ".standard_provider_dto", "StandardProviderKind": ".standard_provider_kind", "StandardProviderSettingsDto": ".standard_provider_settings_dto", "Status": ".status", "StringOperator": ".string_operator", "TestcaseInput": ".testcase_input", "TestcaseOutput": ".testcase_output", "TestcaseResponse": ".testcase_response", "TestcasesResponse": ".testcases_response", "Testset": ".testset", "TestsetCreate": ".testset_create", "TestsetEdit": ".testset_edit", "TestsetFlags": ".testset_flags", "TestsetQuery": ".testset_query", "TestsetResponse": ".testset_response", "TestsetRevision": ".testset_revision", "TestsetRevisionCommit": ".testset_revision_commit", "TestsetRevisionCreate": ".testset_revision_create", "TestsetRevisionDataInput": ".testset_revision_data_input", "TestsetRevisionDataOutput": ".testset_revision_data_output", "TestsetRevisionDelta": ".testset_revision_delta", "TestsetRevisionDeltaColumns": ".testset_revision_delta_columns", "TestsetRevisionDeltaRows": ".testset_revision_delta_rows", "TestsetRevisionEdit": ".testset_revision_edit", "TestsetRevisionQuery": ".testset_revision_query", "TestsetRevisionResponse": ".testset_revision_response", "TestsetRevisionsLog": ".testset_revisions_log", "TestsetRevisionsResponse": ".testset_revisions_response", "TestsetVariant": ".testset_variant", "TestsetVariantCreate": ".testset_variant_create", "TestsetVariantEdit": ".testset_variant_edit", "TestsetVariantFork": ".testset_variant_fork", "TestsetVariantQuery": ".testset_variant_query", "TestsetVariantResponse": ".testset_variant_response", "TestsetVariantsResponse": ".testset_variants_response", "TestsetsResponse": ".testsets_response", "TextOptions": ".text_options", "ToolAuthScheme": ".tool_auth_scheme", "ToolCallData": ".tool_call_data", "ToolCallFunction": ".tool_call_function", "ToolCallResponse": ".tool_call_response", "ToolCatalogAction": ".tool_catalog_action", "ToolCatalogActionDetails": ".tool_catalog_action_details", "ToolCatalogActionResponse": ".tool_catalog_action_response", "ToolCatalogActionResponseAction": ".tool_catalog_action_response_action", "ToolCatalogActionsResponse": ".tool_catalog_actions_response", "ToolCatalogActionsResponseActionsItem": ".tool_catalog_actions_response_actions_item", "ToolCatalogCategoriesResponse": ".tool_catalog_categories_response", "ToolCatalogCategory": ".tool_catalog_category", "ToolCatalogIntegration": ".tool_catalog_integration", "ToolCatalogIntegrationDetails": ".tool_catalog_integration_details", "ToolCatalogIntegrationResponse": ".tool_catalog_integration_response", "ToolCatalogIntegrationResponseIntegration": ".tool_catalog_integration_response_integration", "ToolCatalogIntegrationsResponse": ".tool_catalog_integrations_response", "ToolCatalogIntegrationsResponseIntegrationsItem": ".tool_catalog_integrations_response_integrations_item", "ToolCatalogProvider": ".tool_catalog_provider", "ToolCatalogProviderDetails": ".tool_catalog_provider_details", "ToolCatalogProviderResponse": ".tool_catalog_provider_response", "ToolCatalogProviderResponseProvider": ".tool_catalog_provider_response_provider", "ToolCatalogProvidersResponse": ".tool_catalog_providers_response", "ToolCatalogProvidersResponseProvidersItem": ".tool_catalog_providers_response_providers_item", "ToolConnection": ".tool_connection", "ToolConnectionCreate": ".tool_connection_create", "ToolConnectionCreateData": ".tool_connection_create_data", "ToolConnectionResponse": ".tool_connection_response", "ToolConnectionState": ".tool_connection_state", "ToolConnectionStatus": ".tool_connection_status", "ToolConnectionsResponse": ".tool_connections_response", "ToolProviderKind": ".tool_provider_kind", "ToolResolveResponse": ".tool_resolve_response", "ToolResult": ".tool_result", "ToolResultData": ".tool_result_data", "TraceIdResponse": ".trace_id_response", "TraceIdsResponse": ".trace_ids_response", "TraceInput": ".trace_input", "TraceInputSpansValue": ".trace_input_spans_value", "TraceOutput": ".trace_output", "TraceOutputSpansValue": ".trace_output_spans_value", "TraceRequest": ".trace_request", "TraceResponse": ".trace_response", "TraceType": ".trace_type", "TracesRequest": ".traces_request", "TracesResponse": ".traces_response", "TracingQuery": ".tracing_query", "TriggerAuthScheme": ".trigger_auth_scheme", "TriggerCapabilitiesResult": ".trigger_capabilities_result", "TriggerCapability": ".trigger_capability", "TriggerCapabilityConnection": ".trigger_capability_connection", "TriggerCatalogEvent": ".trigger_catalog_event", "TriggerCatalogEventDetails": ".trigger_catalog_event_details", "TriggerCatalogEventResponse": ".trigger_catalog_event_response", "TriggerCatalogEventsResponse": ".trigger_catalog_events_response", "TriggerCatalogIntegration": ".trigger_catalog_integration", "TriggerCatalogIntegrationResponse": ".trigger_catalog_integration_response", "TriggerCatalogIntegrationsResponse": ".trigger_catalog_integrations_response", "TriggerCatalogProvider": ".trigger_catalog_provider", "TriggerCatalogProviderResponse": ".trigger_catalog_provider_response", "TriggerCatalogProvidersResponse": ".trigger_catalog_providers_response", "TriggerConnectAffordance": ".trigger_connect_affordance", "TriggerConnection": ".trigger_connection", "TriggerConnectionCreate": ".trigger_connection_create", "TriggerConnectionCreateData": ".trigger_connection_create_data", "TriggerConnectionRequirement": ".trigger_connection_requirement", "TriggerConnectionResponse": ".trigger_connection_response", "TriggerConnectionStatus": ".trigger_connection_status", "TriggerConnectionsResponse": ".trigger_connections_response", "TriggerDeliveriesResponse": ".trigger_deliveries_response", "TriggerDelivery": ".trigger_delivery", "TriggerDeliveryData": ".trigger_delivery_data", "TriggerDeliveryQuery": ".trigger_delivery_query", "TriggerDeliveryResponse": ".trigger_delivery_response", "TriggerDiscoveryConnectionState": ".trigger_discovery_connection_state", "TriggerDiscoveryGuidance": ".trigger_discovery_guidance", "TriggerEventAck": ".trigger_event_ack", "TriggerProviderKind": ".trigger_provider_kind", "TriggerSchedule": ".trigger_schedule", "TriggerScheduleCreate": ".trigger_schedule_create", "TriggerScheduleData": ".trigger_schedule_data", "TriggerScheduleDataInputsFields": ".trigger_schedule_data_inputs_fields", "TriggerScheduleEdit": ".trigger_schedule_edit", "TriggerScheduleFlags": ".trigger_schedule_flags", "TriggerScheduleQuery": ".trigger_schedule_query", "TriggerScheduleResponse": ".trigger_schedule_response", "TriggerSchedulesResponse": ".trigger_schedules_response", "TriggerSubscription": ".trigger_subscription", "TriggerSubscriptionCreate": ".trigger_subscription_create", "TriggerSubscriptionCreateRequest": ".trigger_subscription_create_request", "TriggerSubscriptionData": ".trigger_subscription_data", "TriggerSubscriptionDataInputsFields": ".trigger_subscription_data_inputs_fields", "TriggerSubscriptionEdit": ".trigger_subscription_edit", "TriggerSubscriptionFlags": ".trigger_subscription_flags", "TriggerSubscriptionQuery": ".trigger_subscription_query", "TriggerSubscriptionResponse": ".trigger_subscription_response", "TriggerSubscriptionsResponse": ".trigger_subscriptions_response", "UserIdsResponse": ".user_ids_response", "ValidationError": ".validation_error", "ValidationErrorLocItem": ".validation_error_loc_item", "WebhookDeliveriesResponse": ".webhook_deliveries_response", "WebhookDelivery": ".webhook_delivery", "WebhookDeliveryCreate": ".webhook_delivery_create", "WebhookDeliveryData": ".webhook_delivery_data", "WebhookDeliveryQuery": ".webhook_delivery_query", "WebhookDeliveryResponse": ".webhook_delivery_response", "WebhookDeliveryResponseInfo": ".webhook_delivery_response_info", "WebhookEventType": ".webhook_event_type", "WebhookProviderDto": ".webhook_provider_dto", "WebhookProviderSettingsDto": ".webhook_provider_settings_dto", "WebhookSubscription": ".webhook_subscription", "WebhookSubscriptionCreate": ".webhook_subscription_create", "WebhookSubscriptionData": ".webhook_subscription_data", "WebhookSubscriptionDataAuthMode": ".webhook_subscription_data_auth_mode", "WebhookSubscriptionEdit": ".webhook_subscription_edit", "WebhookSubscriptionFlags": ".webhook_subscription_flags", "WebhookSubscriptionQuery": ".webhook_subscription_query", "WebhookSubscriptionResponse": ".webhook_subscription_response", "WebhookSubscriptionsResponse": ".webhook_subscriptions_response", "Windowing": ".windowing", "WindowingOrder": ".windowing_order", "Workflow": ".workflow", "WorkflowArtifactFlags": ".workflow_artifact_flags", "WorkflowCatalogFlags": ".workflow_catalog_flags", "WorkflowCatalogHarness": ".workflow_catalog_harness", "WorkflowCatalogHarnessResponse": ".workflow_catalog_harness_response", "WorkflowCatalogHarnessesResponse": ".workflow_catalog_harnesses_response", "WorkflowCatalogPreset": ".workflow_catalog_preset", "WorkflowCatalogPresetResponse": ".workflow_catalog_preset_response", "WorkflowCatalogPresetsResponse": ".workflow_catalog_presets_response", "WorkflowCatalogTemplate": ".workflow_catalog_template", "WorkflowCatalogTemplateResponse": ".workflow_catalog_template_response", "WorkflowCatalogTemplatesResponse": ".workflow_catalog_templates_response", "WorkflowCatalogType": ".workflow_catalog_type", "WorkflowCatalogTypeResponse": ".workflow_catalog_type_response", "WorkflowCatalogTypesResponse": ".workflow_catalog_types_response", "WorkflowCreate": ".workflow_create", "WorkflowEdit": ".workflow_edit", "WorkflowFlags": ".workflow_flags", "WorkflowResponse": ".workflow_response", "WorkflowRevisionCommit": ".workflow_revision_commit", "WorkflowRevisionCreate": ".workflow_revision_create", "WorkflowRevisionDataInput": ".workflow_revision_data_input", "WorkflowRevisionDataInputHeadersValue": ".workflow_revision_data_input_headers_value", "WorkflowRevisionDataInputRuntime": ".workflow_revision_data_input_runtime", "WorkflowRevisionDataOutput": ".workflow_revision_data_output", "WorkflowRevisionDataOutputHeadersValue": ".workflow_revision_data_output_headers_value", "WorkflowRevisionDataOutputRuntime": ".workflow_revision_data_output_runtime", "WorkflowRevisionDelta": ".workflow_revision_delta", "WorkflowRevisionEdit": ".workflow_revision_edit", "WorkflowRevisionFlags": ".workflow_revision_flags", "WorkflowRevisionInput": ".workflow_revision_input", "WorkflowRevisionOutput": ".workflow_revision_output", "WorkflowRevisionResolveResponse": ".workflow_revision_resolve_response", "WorkflowRevisionResponse": ".workflow_revision_response", "WorkflowRevisionsLog": ".workflow_revisions_log", "WorkflowRevisionsResponse": ".workflow_revisions_response", "WorkflowVariant": ".workflow_variant", "WorkflowVariantCreate": ".workflow_variant_create", "WorkflowVariantEdit": ".workflow_variant_edit", "WorkflowVariantFlags": ".workflow_variant_flags", "WorkflowVariantFork": ".workflow_variant_fork", "WorkflowVariantResponse": ".workflow_variant_response", "WorkflowVariantsResponse": ".workflow_variants_response", "WorkflowsResponse": ".workflows_response", "Workspace": ".workspace", "WorkspaceMemberResponse": ".workspace_member_response", "WorkspacePermission": ".workspace_permission", "WorkspaceResponse": ".workspace_response"} +_dynamic_imports: typing.Dict[str, str] = {"AdminAccountCreateOptions": ".admin_account_create_options", "AdminAccountRead": ".admin_account_read", "AdminAccountsCreate": ".admin_accounts_create", "AdminAccountsDelete": ".admin_accounts_delete", "AdminAccountsDeleteTarget": ".admin_accounts_delete_target", "AdminAccountsResponse": ".admin_accounts_response", "AdminApiKeyCreate": ".admin_api_key_create", "AdminApiKeyResponse": ".admin_api_key_response", "AdminDeleteResponse": ".admin_delete_response", "AdminDeletedEntities": ".admin_deleted_entities", "AdminDeletedEntity": ".admin_deleted_entity", "AdminOrganizationCreate": ".admin_organization_create", "AdminOrganizationMembershipCreate": ".admin_organization_membership_create", "AdminOrganizationMembershipRead": ".admin_organization_membership_read", "AdminOrganizationRead": ".admin_organization_read", "AdminProjectCreate": ".admin_project_create", "AdminProjectMembershipCreate": ".admin_project_membership_create", "AdminProjectMembershipRead": ".admin_project_membership_read", "AdminProjectRead": ".admin_project_read", "AdminSimpleAccountCreate": ".admin_simple_account_create", "AdminSimpleAccountDeleteEntry": ".admin_simple_account_delete_entry", "AdminSimpleAccountRead": ".admin_simple_account_read", "AdminSimpleAccountsApiKeysCreate": ".admin_simple_accounts_api_keys_create", "AdminSimpleAccountsCreate": ".admin_simple_accounts_create", "AdminSimpleAccountsDelete": ".admin_simple_accounts_delete", "AdminSimpleAccountsOrganizationsCreate": ".admin_simple_accounts_organizations_create", "AdminSimpleAccountsOrganizationsMembershipsCreate": ".admin_simple_accounts_organizations_memberships_create", "AdminSimpleAccountsOrganizationsTransferOwnership": ".admin_simple_accounts_organizations_transfer_ownership", "AdminSimpleAccountsOrganizationsTransferOwnershipIncludeProjects": ".admin_simple_accounts_organizations_transfer_ownership_include_projects", "AdminSimpleAccountsOrganizationsTransferOwnershipIncludeProjectsZero": ".admin_simple_accounts_organizations_transfer_ownership_include_projects_zero", "AdminSimpleAccountsOrganizationsTransferOwnershipIncludeWorkspaces": ".admin_simple_accounts_organizations_transfer_ownership_include_workspaces", "AdminSimpleAccountsOrganizationsTransferOwnershipIncludeWorkspacesZero": ".admin_simple_accounts_organizations_transfer_ownership_include_workspaces_zero", "AdminSimpleAccountsOrganizationsTransferOwnershipResponse": ".admin_simple_accounts_organizations_transfer_ownership_response", "AdminSimpleAccountsProjectsCreate": ".admin_simple_accounts_projects_create", "AdminSimpleAccountsProjectsMembershipsCreate": ".admin_simple_accounts_projects_memberships_create", "AdminSimpleAccountsResponse": ".admin_simple_accounts_response", "AdminSimpleAccountsUsersCreate": ".admin_simple_accounts_users_create", "AdminSimpleAccountsUsersIdentitiesCreate": ".admin_simple_accounts_users_identities_create", "AdminSimpleAccountsUsersResetPassword": ".admin_simple_accounts_users_reset_password", "AdminSimpleAccountsWorkspacesCreate": ".admin_simple_accounts_workspaces_create", "AdminSimpleAccountsWorkspacesMembershipsCreate": ".admin_simple_accounts_workspaces_memberships_create", "AdminStructuredError": ".admin_structured_error", "AdminSubscriptionCreate": ".admin_subscription_create", "AdminSubscriptionRead": ".admin_subscription_read", "AdminUserCreate": ".admin_user_create", "AdminUserIdentityCreate": ".admin_user_identity_create", "AdminUserIdentityRead": ".admin_user_identity_read", "AdminUserIdentityReadStatus": ".admin_user_identity_read_status", "AdminUserRead": ".admin_user_read", "AdminWorkspaceCreate": ".admin_workspace_create", "AdminWorkspaceMembershipCreate": ".admin_workspace_membership_create", "AdminWorkspaceMembershipRead": ".admin_workspace_membership_read", "AdminWorkspaceRead": ".admin_workspace_read", "AgentTemplateOverlay": ".agent_template_overlay", "Analytics": ".analytics", "AnalyticsResponse": ".analytics_response", "Annotation": ".annotation", "AnnotationCreate": ".annotation_create", "AnnotationCreateLinks": ".annotation_create_links", "AnnotationEdit": ".annotation_edit", "AnnotationEditLinks": ".annotation_edit_links", "AnnotationLinkResponse": ".annotation_link_response", "AnnotationLinks": ".annotation_links", "AnnotationQuery": ".annotation_query", "AnnotationQueryLinks": ".annotation_query_links", "AnnotationResponse": ".annotation_response", "AnnotationsResponse": ".annotations_response", "Application": ".application", "ApplicationArtifactFlags": ".application_artifact_flags", "ApplicationArtifactQueryFlags": ".application_artifact_query_flags", "ApplicationCatalogPreset": ".application_catalog_preset", "ApplicationCatalogPresetResponse": ".application_catalog_preset_response", "ApplicationCatalogPresetsResponse": ".application_catalog_presets_response", "ApplicationCatalogTemplate": ".application_catalog_template", "ApplicationCatalogTemplateResponse": ".application_catalog_template_response", "ApplicationCatalogTemplatesResponse": ".application_catalog_templates_response", "ApplicationCatalogType": ".application_catalog_type", "ApplicationCatalogTypesResponse": ".application_catalog_types_response", "ApplicationCreate": ".application_create", "ApplicationEdit": ".application_edit", "ApplicationFlags": ".application_flags", "ApplicationQuery": ".application_query", "ApplicationResponse": ".application_response", "ApplicationRevisionCommit": ".application_revision_commit", "ApplicationRevisionCreate": ".application_revision_create", "ApplicationRevisionDataInput": ".application_revision_data_input", "ApplicationRevisionDataInputHeadersValue": ".application_revision_data_input_headers_value", "ApplicationRevisionDataInputRuntime": ".application_revision_data_input_runtime", "ApplicationRevisionDataOutput": ".application_revision_data_output", "ApplicationRevisionDataOutputHeadersValue": ".application_revision_data_output_headers_value", "ApplicationRevisionDataOutputRuntime": ".application_revision_data_output_runtime", "ApplicationRevisionEdit": ".application_revision_edit", "ApplicationRevisionFlags": ".application_revision_flags", "ApplicationRevisionInput": ".application_revision_input", "ApplicationRevisionOutput": ".application_revision_output", "ApplicationRevisionQuery": ".application_revision_query", "ApplicationRevisionQueryFlags": ".application_revision_query_flags", "ApplicationRevisionResolveResponse": ".application_revision_resolve_response", "ApplicationRevisionResponse": ".application_revision_response", "ApplicationRevisionsLog": ".application_revisions_log", "ApplicationRevisionsResponse": ".application_revisions_response", "ApplicationVariant": ".application_variant", "ApplicationVariantCreate": ".application_variant_create", "ApplicationVariantEdit": ".application_variant_edit", "ApplicationVariantFlags": ".application_variant_flags", "ApplicationVariantFork": ".application_variant_fork", "ApplicationVariantResponse": ".application_variant_response", "ApplicationVariantsResponse": ".application_variants_response", "ApplicationsResponse": ".applications_response", "BodyConfigsFetchVariantsConfigsFetchPost": ".body_configs_fetch_variants_configs_fetch_post", "Bucket": ".bucket", "BuiltinToolConfig": ".builtin_tool_config", "BuiltinToolConfigPermission": ".builtin_tool_config_permission", "CapabilitiesResult": ".capabilities_result", "Capability": ".capability", "CapabilityConnection": ".capability_connection", "CapabilityGuidance": ".capability_guidance", "CollectStatusResponse": ".collect_status_response", "CommandMode": ".command_mode", "ComparisonOperator": ".comparison_operator", "Condition": ".condition", "ConditionOperator": ".condition_operator", "ConditionOptions": ".condition_options", "ConditionValue": ".condition_value", "ConfigResponseModel": ".config_response_model", "ConnectAffordance": ".connect_affordance", "ConnectionRequirement": ".connection_requirement", "CustomModelSettingsDto": ".custom_model_settings_dto", "CustomProviderDto": ".custom_provider_dto", "CustomProviderKind": ".custom_provider_kind", "CustomProviderSettingsDto": ".custom_provider_settings_dto", "CustomSecretDto": ".custom_secret_dto", "CustomSecretFormat": ".custom_secret_format", "CustomSecretSettingsDto": ".custom_secret_settings_dto", "CustomSecretSettingsDtoContent": ".custom_secret_settings_dto_content", "CustomSecretSettingsDtoContentOneValue": ".custom_secret_settings_dto_content_one_value", "DictOperator": ".dict_operator", "DiscoverResponse": ".discover_response", "DiscoverResponseMethodsValue": ".discover_response_methods_value", "DiscoveredAlternative": ".discovered_alternative", "DiscoveredTool": ".discovered_tool", "DiscoveredToolType": ".discovered_tool_type", "DiscoveredTriggerAlternative": ".discovered_trigger_alternative", "DiscoveredTriggerEvent": ".discovered_trigger_event", "DiscoveredTriggerEventType": ".discovered_trigger_event_type", "EntityRef": ".entity_ref", "Environment": ".environment", "EnvironmentCreate": ".environment_create", "EnvironmentEdit": ".environment_edit", "EnvironmentFlags": ".environment_flags", "EnvironmentQueryFlags": ".environment_query_flags", "EnvironmentResponse": ".environment_response", "EnvironmentRevisionCommit": ".environment_revision_commit", "EnvironmentRevisionCreate": ".environment_revision_create", "EnvironmentRevisionData": ".environment_revision_data", "EnvironmentRevisionDelta": ".environment_revision_delta", "EnvironmentRevisionEdit": ".environment_revision_edit", "EnvironmentRevisionInput": ".environment_revision_input", "EnvironmentRevisionOutput": ".environment_revision_output", "EnvironmentRevisionResolveResponse": ".environment_revision_resolve_response", "EnvironmentRevisionResponse": ".environment_revision_response", "EnvironmentRevisionsLog": ".environment_revisions_log", "EnvironmentRevisionsResponse": ".environment_revisions_response", "EnvironmentVariant": ".environment_variant", "EnvironmentVariantCreate": ".environment_variant_create", "EnvironmentVariantEdit": ".environment_variant_edit", "EnvironmentVariantFork": ".environment_variant_fork", "EnvironmentVariantResponse": ".environment_variant_response", "EnvironmentVariantsResponse": ".environment_variants_response", "EnvironmentsResponse": ".environments_response", "ErrorPolicy": ".error_policy", "EvaluationMetrics": ".evaluation_metrics", "EvaluationMetricsCreate": ".evaluation_metrics_create", "EvaluationMetricsIdsResponse": ".evaluation_metrics_ids_response", "EvaluationMetricsQuery": ".evaluation_metrics_query", "EvaluationMetricsQueryScenarioIds": ".evaluation_metrics_query_scenario_ids", "EvaluationMetricsQueryTimestamps": ".evaluation_metrics_query_timestamps", "EvaluationMetricsRefresh": ".evaluation_metrics_refresh", "EvaluationMetricsResponse": ".evaluation_metrics_response", "EvaluationMetricsSetRequest": ".evaluation_metrics_set_request", "EvaluationQueue": ".evaluation_queue", "EvaluationQueueCreate": ".evaluation_queue_create", "EvaluationQueueData": ".evaluation_queue_data", "EvaluationQueueEdit": ".evaluation_queue_edit", "EvaluationQueueFlags": ".evaluation_queue_flags", "EvaluationQueueIdResponse": ".evaluation_queue_id_response", "EvaluationQueueIdsResponse": ".evaluation_queue_ids_response", "EvaluationQueueQuery": ".evaluation_queue_query", "EvaluationQueueQueryFlags": ".evaluation_queue_query_flags", "EvaluationQueueResponse": ".evaluation_queue_response", "EvaluationQueueScenariosQuery": ".evaluation_queue_scenarios_query", "EvaluationQueuesResponse": ".evaluation_queues_response", "EvaluationResult": ".evaluation_result", "EvaluationResultCreate": ".evaluation_result_create", "EvaluationResultIdResponse": ".evaluation_result_id_response", "EvaluationResultIdsResponse": ".evaluation_result_ids_response", "EvaluationResultQuery": ".evaluation_result_query", "EvaluationResultResponse": ".evaluation_result_response", "EvaluationResultsResponse": ".evaluation_results_response", "EvaluationResultsSetRequest": ".evaluation_results_set_request", "EvaluationRun": ".evaluation_run", "EvaluationRunCreate": ".evaluation_run_create", "EvaluationRunDataConcurrency": ".evaluation_run_data_concurrency", "EvaluationRunDataInput": ".evaluation_run_data_input", "EvaluationRunDataMapping": ".evaluation_run_data_mapping", "EvaluationRunDataMappingColumn": ".evaluation_run_data_mapping_column", "EvaluationRunDataMappingStep": ".evaluation_run_data_mapping_step", "EvaluationRunDataOutput": ".evaluation_run_data_output", "EvaluationRunDataStepInput": ".evaluation_run_data_step_input", "EvaluationRunDataStepInputKey": ".evaluation_run_data_step_input_key", "EvaluationRunDataStepInputOrigin": ".evaluation_run_data_step_input_origin", "EvaluationRunDataStepInputType": ".evaluation_run_data_step_input_type", "EvaluationRunDataStepOutput": ".evaluation_run_data_step_output", "EvaluationRunDataStepOutputOrigin": ".evaluation_run_data_step_output_origin", "EvaluationRunDataStepOutputType": ".evaluation_run_data_step_output_type", "EvaluationRunEdit": ".evaluation_run_edit", "EvaluationRunFlags": ".evaluation_run_flags", "EvaluationRunIdResponse": ".evaluation_run_id_response", "EvaluationRunIdsRequest": ".evaluation_run_ids_request", "EvaluationRunIdsResponse": ".evaluation_run_ids_response", "EvaluationRunQuery": ".evaluation_run_query", "EvaluationRunQueryFlags": ".evaluation_run_query_flags", "EvaluationRunResponse": ".evaluation_run_response", "EvaluationRunsResponse": ".evaluation_runs_response", "EvaluationScenario": ".evaluation_scenario", "EvaluationScenarioCreate": ".evaluation_scenario_create", "EvaluationScenarioEdit": ".evaluation_scenario_edit", "EvaluationScenarioIdResponse": ".evaluation_scenario_id_response", "EvaluationScenarioIdsResponse": ".evaluation_scenario_ids_response", "EvaluationScenarioQuery": ".evaluation_scenario_query", "EvaluationScenarioResponse": ".evaluation_scenario_response", "EvaluationScenariosResponse": ".evaluation_scenarios_response", "EvaluationStatus": ".evaluation_status", "Evaluator": ".evaluator", "EvaluatorArtifactFlags": ".evaluator_artifact_flags", "EvaluatorArtifactQueryFlags": ".evaluator_artifact_query_flags", "EvaluatorCatalogPreset": ".evaluator_catalog_preset", "EvaluatorCatalogPresetResponse": ".evaluator_catalog_preset_response", "EvaluatorCatalogPresetsResponse": ".evaluator_catalog_presets_response", "EvaluatorCatalogTemplate": ".evaluator_catalog_template", "EvaluatorCatalogTemplateResponse": ".evaluator_catalog_template_response", "EvaluatorCatalogTemplatesResponse": ".evaluator_catalog_templates_response", "EvaluatorCatalogType": ".evaluator_catalog_type", "EvaluatorCatalogTypesResponse": ".evaluator_catalog_types_response", "EvaluatorCreate": ".evaluator_create", "EvaluatorEdit": ".evaluator_edit", "EvaluatorFlags": ".evaluator_flags", "EvaluatorQuery": ".evaluator_query", "EvaluatorResponse": ".evaluator_response", "EvaluatorRevisionCommit": ".evaluator_revision_commit", "EvaluatorRevisionCreate": ".evaluator_revision_create", "EvaluatorRevisionDataInput": ".evaluator_revision_data_input", "EvaluatorRevisionDataInputHeadersValue": ".evaluator_revision_data_input_headers_value", "EvaluatorRevisionDataInputRuntime": ".evaluator_revision_data_input_runtime", "EvaluatorRevisionDataOutput": ".evaluator_revision_data_output", "EvaluatorRevisionDataOutputHeadersValue": ".evaluator_revision_data_output_headers_value", "EvaluatorRevisionDataOutputRuntime": ".evaluator_revision_data_output_runtime", "EvaluatorRevisionEdit": ".evaluator_revision_edit", "EvaluatorRevisionFlags": ".evaluator_revision_flags", "EvaluatorRevisionInput": ".evaluator_revision_input", "EvaluatorRevisionOutput": ".evaluator_revision_output", "EvaluatorRevisionQuery": ".evaluator_revision_query", "EvaluatorRevisionQueryFlags": ".evaluator_revision_query_flags", "EvaluatorRevisionResolveResponse": ".evaluator_revision_resolve_response", "EvaluatorRevisionResponse": ".evaluator_revision_response", "EvaluatorRevisionsLog": ".evaluator_revisions_log", "EvaluatorRevisionsResponse": ".evaluator_revisions_response", "EvaluatorTemplate": ".evaluator_template", "EvaluatorTemplatesResponse": ".evaluator_templates_response", "EvaluatorVariant": ".evaluator_variant", "EvaluatorVariantCreate": ".evaluator_variant_create", "EvaluatorVariantEdit": ".evaluator_variant_edit", "EvaluatorVariantFlags": ".evaluator_variant_flags", "EvaluatorVariantFork": ".evaluator_variant_fork", "EvaluatorVariantResponse": ".evaluator_variant_response", "EvaluatorVariantsResponse": ".evaluator_variants_response", "EvaluatorsResponse": ".evaluators_response", "Event": ".event", "EventQuery": ".event_query", "EventType": ".event_type", "EventsQueryResponse": ".events_query_response", "ExistenceOperator": ".existence_operator", "FilteringInput": ".filtering_input", "FilteringInputConditionsItem": ".filtering_input_conditions_item", "FilteringOutput": ".filtering_output", "FilteringOutputConditionsItem": ".filtering_output_conditions_item", "Focus": ".focus", "Folder": ".folder", "FolderCreate": ".folder_create", "FolderEdit": ".folder_edit", "FolderIdResponse": ".folder_id_response", "FolderKind": ".folder_kind", "FolderQuery": ".folder_query", "FolderQueryKinds": ".folder_query_kinds", "FolderResponse": ".folder_response", "FoldersResponse": ".folders_response", "Format": ".format", "Formatting": ".formatting", typing.Any: ".full_json_input", typing.Any: ".full_json_output", "GatewayToolConfig": ".gateway_tool_config", "GatewayToolConfigPermission": ".gateway_tool_config_permission", "HarnessKind": ".harness_kind", "Header": ".header", "HttpValidationError": ".http_validation_error", "InviteRequest": ".invite_request", "Invocation": ".invocation", "InvocationCreate": ".invocation_create", "InvocationCreateLinks": ".invocation_create_links", "InvocationEdit": ".invocation_edit", "InvocationEditLinks": ".invocation_edit_links", "InvocationLinkResponse": ".invocation_link_response", "InvocationLinks": ".invocation_links", "InvocationQuery": ".invocation_query", "InvocationQueryLinks": ".invocation_query_links", "InvocationResponse": ".invocation_response", "InvocationsResponse": ".invocations_response", "JsonSchemasInput": ".json_schemas_input", "JsonSchemasOutput": ".json_schemas_output", typing.Any: ".label_json_input", typing.Any: ".label_json_output", "LegacyLifecycleDto": ".legacy_lifecycle_dto", "ListApiKeysResponse": ".list_api_keys_response", "ListOperator": ".list_operator", "ListOptions": ".list_options", "LogicalOperator": ".logical_operator", "MetricSpec": ".metric_spec", "MetricType": ".metric_type", "MetricsBucket": ".metrics_bucket", "Mount": ".mount", "MountCreate": ".mount_create", "MountCredentials": ".mount_credentials", "MountCredentialsResponse": ".mount_credentials_response", "MountData": ".mount_data", "MountEdit": ".mount_edit", "MountFileDeletedResponse": ".mount_file_deleted_response", "MountFileWrittenResponse": ".mount_file_written_response", "MountFlags": ".mount_flags", "MountFolderCreatedResponse": ".mount_folder_created_response", "MountQuery": ".mount_query", "MountResponse": ".mount_response", "MountsResponse": ".mounts_response", "NumericOperator": ".numeric_operator", "OTelEventInput": ".o_tel_event_input", "OTelEventInputTimestamp": ".o_tel_event_input_timestamp", "OTelEventOutput": ".o_tel_event_output", "OTelEventOutputTimestamp": ".o_tel_event_output_timestamp", "OTelHashInput": ".o_tel_hash_input", "OTelHashOutput": ".o_tel_hash_output", "OTelLinkInput": ".o_tel_link_input", "OTelLinkOutput": ".o_tel_link_output", "OTelLinksResponse": ".o_tel_links_response", "OTelReferenceInput": ".o_tel_reference_input", "OTelReferenceOutput": ".o_tel_reference_output", "OTelSpanKind": ".o_tel_span_kind", "OTelStatusCode": ".o_tel_status_code", "OTelTracingRequest": ".o_tel_tracing_request", "OTelTracingResponse": ".o_tel_tracing_response", "OldAnalyticsResponse": ".old_analytics_response", "Organization": ".organization", "OrganizationDetails": ".organization_details", "OrganizationDomainResponse": ".organization_domain_response", "OrganizationProviderResponse": ".organization_provider_response", "OrganizationUpdate": ".organization_update", "Permission": ".permission", "PlaygroundBuildKitContext": ".playground_build_kit_context", "ProjectsResponse": ".projects_response", "QueriesResponse": ".queries_response", "Query": ".query", "QueryCreate": ".query_create", "QueryEdit": ".query_edit", "QueryFlags": ".query_flags", "QueryQueryFlags": ".query_query_flags", "QueryResponse": ".query_response", "QueryRevision": ".query_revision", "QueryRevisionCommit": ".query_revision_commit", "QueryRevisionCreate": ".query_revision_create", "QueryRevisionDataInput": ".query_revision_data_input", "QueryRevisionDataOutput": ".query_revision_data_output", "QueryRevisionEdit": ".query_revision_edit", "QueryRevisionQuery": ".query_revision_query", "QueryRevisionResponse": ".query_revision_response", "QueryRevisionsLog": ".query_revisions_log", "QueryRevisionsResponse": ".query_revisions_response", "QueryVariant": ".query_variant", "QueryVariantCreate": ".query_variant_create", "QueryVariantEdit": ".query_variant_edit", "QueryVariantFork": ".query_variant_fork", "QueryVariantQuery": ".query_variant_query", "QueryVariantResponse": ".query_variant_response", "QueryVariantsResponse": ".query_variants_response", "Reference": ".reference", "ReferenceRequestModelInput": ".reference_request_model_input", "ReferenceRequestModelOutput": ".reference_request_model_output", "RequestType": ".request_type", "ResolutionInfo": ".resolution_info", "ResolvedTool": ".resolved_tool", "RetrievalInfo": ".retrieval_info", "SecretDto": ".secret_dto", "SecretDtoData": ".secret_dto_data", "SecretKind": ".secret_kind", "SecretResponseDto": ".secret_response_dto", "SecretResponseDtoData": ".secret_response_dto_data", "Selector": ".selector", "SessionHeartbeatResult": ".session_heartbeat_result", "SessionIdsResponse": ".session_ids_response", "SessionInteraction": ".session_interaction", "SessionInteractionData": ".session_interaction_data", "SessionInteractionFlags": ".session_interaction_flags", "SessionInteractionKind": ".session_interaction_kind", "SessionInteractionQuery": ".session_interaction_query", "SessionInteractionQueryFlags": ".session_interaction_query_flags", "SessionInteractionResponse": ".session_interaction_response", "SessionInteractionStatus": ".session_interaction_status", "SessionInteractionsResponse": ".session_interactions_response", "SessionMount": ".session_mount", "SessionMountQuery": ".session_mount_query", "SessionMountsResponse": ".session_mounts_response", "SessionRecord": ".session_record", "SessionRecordResponse": ".session_record_response", "SessionRecordsQueryResponse": ".session_records_query_response", "SessionResponse": ".session_response", "SessionStream": ".session_stream", "SessionStreamCommandResponse": ".session_stream_command_response", "SessionStreamFlags": ".session_stream_flags", "SessionStreamHeaderEdit": ".session_stream_header_edit", "SessionStreamResponse": ".session_stream_response", "SessionStreamsResponse": ".session_streams_response", "SessionTurn": ".session_turn", "SessionTurnQuery": ".session_turn_query", "SessionTurnResponse": ".session_turn_response", "SessionTurnsResponse": ".session_turns_response", "SessionsResponse": ".sessions_response", "SimpleApplication": ".simple_application", "SimpleApplicationAdditionalContext": ".simple_application_additional_context", "SimpleApplicationCreate": ".simple_application_create", "SimpleApplicationDataInput": ".simple_application_data_input", "SimpleApplicationDataInputHeadersValue": ".simple_application_data_input_headers_value", "SimpleApplicationDataInputRuntime": ".simple_application_data_input_runtime", "SimpleApplicationDataOutput": ".simple_application_data_output", "SimpleApplicationDataOutputHeadersValue": ".simple_application_data_output_headers_value", "SimpleApplicationDataOutputRuntime": ".simple_application_data_output_runtime", "SimpleApplicationEdit": ".simple_application_edit", "SimpleApplicationFlags": ".simple_application_flags", "SimpleApplicationQuery": ".simple_application_query", "SimpleApplicationQueryFlags": ".simple_application_query_flags", "SimpleApplicationResponse": ".simple_application_response", "SimpleApplicationsResponse": ".simple_applications_response", "SimpleEnvironment": ".simple_environment", "SimpleEnvironmentCreate": ".simple_environment_create", "SimpleEnvironmentEdit": ".simple_environment_edit", "SimpleEnvironmentQuery": ".simple_environment_query", "SimpleEnvironmentResponse": ".simple_environment_response", "SimpleEnvironmentsResponse": ".simple_environments_response", "SimpleEvaluation": ".simple_evaluation", "SimpleEvaluationCreate": ".simple_evaluation_create", "SimpleEvaluationData": ".simple_evaluation_data", "SimpleEvaluationDataApplicationSteps": ".simple_evaluation_data_application_steps", "SimpleEvaluationDataApplicationStepsOneValue": ".simple_evaluation_data_application_steps_one_value", "SimpleEvaluationDataEvaluatorSteps": ".simple_evaluation_data_evaluator_steps", "SimpleEvaluationDataEvaluatorStepsOneValue": ".simple_evaluation_data_evaluator_steps_one_value", "SimpleEvaluationDataQuerySteps": ".simple_evaluation_data_query_steps", "SimpleEvaluationDataQueryStepsOneValue": ".simple_evaluation_data_query_steps_one_value", "SimpleEvaluationDataTestsetSteps": ".simple_evaluation_data_testset_steps", "SimpleEvaluationDataTestsetStepsOneValue": ".simple_evaluation_data_testset_steps_one_value", "SimpleEvaluationEdit": ".simple_evaluation_edit", "SimpleEvaluationIdResponse": ".simple_evaluation_id_response", "SimpleEvaluationQuery": ".simple_evaluation_query", "SimpleEvaluationResponse": ".simple_evaluation_response", "SimpleEvaluationsResponse": ".simple_evaluations_response", "SimpleEvaluator": ".simple_evaluator", "SimpleEvaluatorCreate": ".simple_evaluator_create", "SimpleEvaluatorDataInput": ".simple_evaluator_data_input", "SimpleEvaluatorDataInputHeadersValue": ".simple_evaluator_data_input_headers_value", "SimpleEvaluatorDataInputRuntime": ".simple_evaluator_data_input_runtime", "SimpleEvaluatorDataOutput": ".simple_evaluator_data_output", "SimpleEvaluatorDataOutputHeadersValue": ".simple_evaluator_data_output_headers_value", "SimpleEvaluatorDataOutputRuntime": ".simple_evaluator_data_output_runtime", "SimpleEvaluatorEdit": ".simple_evaluator_edit", "SimpleEvaluatorFlags": ".simple_evaluator_flags", "SimpleEvaluatorQuery": ".simple_evaluator_query", "SimpleEvaluatorQueryFlags": ".simple_evaluator_query_flags", "SimpleEvaluatorResponse": ".simple_evaluator_response", "SimpleEvaluatorsResponse": ".simple_evaluators_response", "SimpleQueriesResponse": ".simple_queries_response", "SimpleQuery": ".simple_query", "SimpleQueryCreate": ".simple_query_create", "SimpleQueryEdit": ".simple_query_edit", "SimpleQueryQuery": ".simple_query_query", "SimpleQueryResponse": ".simple_query_response", "SimpleQueue": ".simple_queue", "SimpleQueueCreate": ".simple_queue_create", "SimpleQueueData": ".simple_queue_data", "SimpleQueueDataEvaluators": ".simple_queue_data_evaluators", "SimpleQueueDataEvaluatorsOneValue": ".simple_queue_data_evaluators_one_value", "SimpleQueueIdResponse": ".simple_queue_id_response", "SimpleQueueIdsResponse": ".simple_queue_ids_response", "SimpleQueueKind": ".simple_queue_kind", "SimpleQueueQuery": ".simple_queue_query", "SimpleQueueResponse": ".simple_queue_response", "SimpleQueueScenariosQuery": ".simple_queue_scenarios_query", "SimpleQueueScenariosResponse": ".simple_queue_scenarios_response", "SimpleQueueSettings": ".simple_queue_settings", "SimpleQueuesResponse": ".simple_queues_response", "SimpleTestset": ".simple_testset", "SimpleTestsetCreate": ".simple_testset_create", "SimpleTestsetEdit": ".simple_testset_edit", "SimpleTestsetQuery": ".simple_testset_query", "SimpleTestsetResponse": ".simple_testset_response", "SimpleTestsetsResponse": ".simple_testsets_response", "SimpleTrace": ".simple_trace", "SimpleTraceChannel": ".simple_trace_channel", "SimpleTraceCreate": ".simple_trace_create", "SimpleTraceCreateLinks": ".simple_trace_create_links", "SimpleTraceEdit": ".simple_trace_edit", "SimpleTraceEditLinks": ".simple_trace_edit_links", "SimpleTraceKind": ".simple_trace_kind", "SimpleTraceLinkResponse": ".simple_trace_link_response", "SimpleTraceLinks": ".simple_trace_links", "SimpleTraceOrigin": ".simple_trace_origin", "SimpleTraceQuery": ".simple_trace_query", "SimpleTraceQueryLinks": ".simple_trace_query_links", "SimpleTraceReferences": ".simple_trace_references", "SimpleTraceResponse": ".simple_trace_response", "SimpleTracesResponse": ".simple_traces_response", "SimpleWorkflow": ".simple_workflow", "SimpleWorkflowCreate": ".simple_workflow_create", "SimpleWorkflowDataInput": ".simple_workflow_data_input", "SimpleWorkflowDataInputHeadersValue": ".simple_workflow_data_input_headers_value", "SimpleWorkflowDataInputRuntime": ".simple_workflow_data_input_runtime", "SimpleWorkflowDataOutput": ".simple_workflow_data_output", "SimpleWorkflowDataOutputHeadersValue": ".simple_workflow_data_output_headers_value", "SimpleWorkflowDataOutputRuntime": ".simple_workflow_data_output_runtime", "SimpleWorkflowEdit": ".simple_workflow_edit", "SimpleWorkflowFlags": ".simple_workflow_flags", "SimpleWorkflowQuery": ".simple_workflow_query", "SimpleWorkflowQueryFlags": ".simple_workflow_query_flags", "SimpleWorkflowResponse": ".simple_workflow_response", "SimpleWorkflowsResponse": ".simple_workflows_response", "SpanInput": ".span_input", "SpanInputEndTime": ".span_input_end_time", "SpanInputStartTime": ".span_input_start_time", "SpanOutput": ".span_output", "SpanOutputEndTime": ".span_output_end_time", "SpanOutputStartTime": ".span_output_start_time", "SpanResponse": ".span_response", "SpanType": ".span_type", "SpansNodeInput": ".spans_node_input", "SpansNodeInputEndTime": ".spans_node_input_end_time", "SpansNodeInputSpansValue": ".spans_node_input_spans_value", "SpansNodeInputStartTime": ".spans_node_input_start_time", "SpansNodeOutput": ".spans_node_output", "SpansNodeOutputEndTime": ".spans_node_output_end_time", "SpansNodeOutputSpansValue": ".spans_node_output_spans_value", "SpansNodeOutputStartTime": ".spans_node_output_start_time", "SpansResponse": ".spans_response", "SpansTreeInput": ".spans_tree_input", "SpansTreeInputSpansValue": ".spans_tree_input_spans_value", "SpansTreeOutput": ".spans_tree_output", "SpansTreeOutputSpansValue": ".spans_tree_output_spans_value", "SsoProviderDto": ".sso_provider_dto", "SsoProviderInfo": ".sso_provider_info", "SsoProviderSettingsDto": ".sso_provider_settings_dto", "SsoProviders": ".sso_providers", "StandardProviderDto": ".standard_provider_dto", "StandardProviderKind": ".standard_provider_kind", "StandardProviderSettingsDto": ".standard_provider_settings_dto", "Status": ".status", "StringOperator": ".string_operator", "TestcaseInput": ".testcase_input", "TestcaseOutput": ".testcase_output", "TestcaseResponse": ".testcase_response", "TestcasesResponse": ".testcases_response", "Testset": ".testset", "TestsetCreate": ".testset_create", "TestsetEdit": ".testset_edit", "TestsetFlags": ".testset_flags", "TestsetQuery": ".testset_query", "TestsetResponse": ".testset_response", "TestsetRevision": ".testset_revision", "TestsetRevisionCommit": ".testset_revision_commit", "TestsetRevisionCreate": ".testset_revision_create", "TestsetRevisionDataInput": ".testset_revision_data_input", "TestsetRevisionDataOutput": ".testset_revision_data_output", "TestsetRevisionDelta": ".testset_revision_delta", "TestsetRevisionDeltaColumns": ".testset_revision_delta_columns", "TestsetRevisionDeltaRows": ".testset_revision_delta_rows", "TestsetRevisionEdit": ".testset_revision_edit", "TestsetRevisionQuery": ".testset_revision_query", "TestsetRevisionResponse": ".testset_revision_response", "TestsetRevisionsLog": ".testset_revisions_log", "TestsetRevisionsResponse": ".testset_revisions_response", "TestsetVariant": ".testset_variant", "TestsetVariantCreate": ".testset_variant_create", "TestsetVariantEdit": ".testset_variant_edit", "TestsetVariantFork": ".testset_variant_fork", "TestsetVariantQuery": ".testset_variant_query", "TestsetVariantResponse": ".testset_variant_response", "TestsetVariantsResponse": ".testset_variants_response", "TestsetsResponse": ".testsets_response", "TextOptions": ".text_options", "ToolAuthScheme": ".tool_auth_scheme", "ToolCallData": ".tool_call_data", "ToolCallFunction": ".tool_call_function", "ToolCallResponse": ".tool_call_response", "ToolCatalogAction": ".tool_catalog_action", "ToolCatalogActionDetails": ".tool_catalog_action_details", "ToolCatalogActionResponse": ".tool_catalog_action_response", "ToolCatalogActionResponseAction": ".tool_catalog_action_response_action", "ToolCatalogActionsResponse": ".tool_catalog_actions_response", "ToolCatalogActionsResponseActionsItem": ".tool_catalog_actions_response_actions_item", "ToolCatalogCategoriesResponse": ".tool_catalog_categories_response", "ToolCatalogCategory": ".tool_catalog_category", "ToolCatalogIntegration": ".tool_catalog_integration", "ToolCatalogIntegrationDetails": ".tool_catalog_integration_details", "ToolCatalogIntegrationResponse": ".tool_catalog_integration_response", "ToolCatalogIntegrationResponseIntegration": ".tool_catalog_integration_response_integration", "ToolCatalogIntegrationsResponse": ".tool_catalog_integrations_response", "ToolCatalogIntegrationsResponseIntegrationsItem": ".tool_catalog_integrations_response_integrations_item", "ToolCatalogProvider": ".tool_catalog_provider", "ToolCatalogProviderDetails": ".tool_catalog_provider_details", "ToolCatalogProviderResponse": ".tool_catalog_provider_response", "ToolCatalogProviderResponseProvider": ".tool_catalog_provider_response_provider", "ToolCatalogProvidersResponse": ".tool_catalog_providers_response", "ToolCatalogProvidersResponseProvidersItem": ".tool_catalog_providers_response_providers_item", "ToolConnection": ".tool_connection", "ToolConnectionCreate": ".tool_connection_create", "ToolConnectionCreateData": ".tool_connection_create_data", "ToolConnectionResponse": ".tool_connection_response", "ToolConnectionState": ".tool_connection_state", "ToolConnectionStatus": ".tool_connection_status", "ToolConnectionsResponse": ".tool_connections_response", "ToolProviderKind": ".tool_provider_kind", "ToolResolveResponse": ".tool_resolve_response", "ToolResult": ".tool_result", "ToolResultData": ".tool_result_data", "TraceIdResponse": ".trace_id_response", "TraceIdsResponse": ".trace_ids_response", "TraceInput": ".trace_input", "TraceInputSpansValue": ".trace_input_spans_value", "TraceOutput": ".trace_output", "TraceOutputSpansValue": ".trace_output_spans_value", "TraceRequest": ".trace_request", "TraceResponse": ".trace_response", "TraceType": ".trace_type", "TracesRequest": ".traces_request", "TracesResponse": ".traces_response", "TracingQuery": ".tracing_query", "TriggerAuthScheme": ".trigger_auth_scheme", "TriggerCapabilitiesResult": ".trigger_capabilities_result", "TriggerCapability": ".trigger_capability", "TriggerCapabilityConnection": ".trigger_capability_connection", "TriggerCatalogEvent": ".trigger_catalog_event", "TriggerCatalogEventDetails": ".trigger_catalog_event_details", "TriggerCatalogEventResponse": ".trigger_catalog_event_response", "TriggerCatalogEventsResponse": ".trigger_catalog_events_response", "TriggerCatalogIntegration": ".trigger_catalog_integration", "TriggerCatalogIntegrationResponse": ".trigger_catalog_integration_response", "TriggerCatalogIntegrationsResponse": ".trigger_catalog_integrations_response", "TriggerCatalogProvider": ".trigger_catalog_provider", "TriggerCatalogProviderResponse": ".trigger_catalog_provider_response", "TriggerCatalogProvidersResponse": ".trigger_catalog_providers_response", "TriggerConnectAffordance": ".trigger_connect_affordance", "TriggerConnection": ".trigger_connection", "TriggerConnectionCreate": ".trigger_connection_create", "TriggerConnectionCreateData": ".trigger_connection_create_data", "TriggerConnectionRequirement": ".trigger_connection_requirement", "TriggerConnectionResponse": ".trigger_connection_response", "TriggerConnectionStatus": ".trigger_connection_status", "TriggerConnectionsResponse": ".trigger_connections_response", "TriggerDeliveriesResponse": ".trigger_deliveries_response", "TriggerDelivery": ".trigger_delivery", "TriggerDeliveryData": ".trigger_delivery_data", "TriggerDeliveryQuery": ".trigger_delivery_query", "TriggerDeliveryResponse": ".trigger_delivery_response", "TriggerDiscoveryConnectionState": ".trigger_discovery_connection_state", "TriggerDiscoveryGuidance": ".trigger_discovery_guidance", "TriggerEventAck": ".trigger_event_ack", "TriggerProviderKind": ".trigger_provider_kind", "TriggerSchedule": ".trigger_schedule", "TriggerScheduleCreate": ".trigger_schedule_create", "TriggerScheduleData": ".trigger_schedule_data", "TriggerScheduleDataInputsFields": ".trigger_schedule_data_inputs_fields", "TriggerScheduleEdit": ".trigger_schedule_edit", "TriggerScheduleFlags": ".trigger_schedule_flags", "TriggerScheduleQuery": ".trigger_schedule_query", "TriggerScheduleResponse": ".trigger_schedule_response", "TriggerSchedulesResponse": ".trigger_schedules_response", "TriggerSubscription": ".trigger_subscription", "TriggerSubscriptionCreate": ".trigger_subscription_create", "TriggerSubscriptionCreateRequest": ".trigger_subscription_create_request", "TriggerSubscriptionData": ".trigger_subscription_data", "TriggerSubscriptionDataInputsFields": ".trigger_subscription_data_inputs_fields", "TriggerSubscriptionEdit": ".trigger_subscription_edit", "TriggerSubscriptionFlags": ".trigger_subscription_flags", "TriggerSubscriptionQuery": ".trigger_subscription_query", "TriggerSubscriptionResponse": ".trigger_subscription_response", "TriggerSubscriptionsResponse": ".trigger_subscriptions_response", "UserIdsResponse": ".user_ids_response", "ValidationError": ".validation_error", "ValidationErrorLocItem": ".validation_error_loc_item", "WebhookDeliveriesResponse": ".webhook_deliveries_response", "WebhookDelivery": ".webhook_delivery", "WebhookDeliveryCreate": ".webhook_delivery_create", "WebhookDeliveryData": ".webhook_delivery_data", "WebhookDeliveryQuery": ".webhook_delivery_query", "WebhookDeliveryResponse": ".webhook_delivery_response", "WebhookDeliveryResponseInfo": ".webhook_delivery_response_info", "WebhookEventType": ".webhook_event_type", "WebhookProviderDto": ".webhook_provider_dto", "WebhookProviderSettingsDto": ".webhook_provider_settings_dto", "WebhookSubscription": ".webhook_subscription", "WebhookSubscriptionCreate": ".webhook_subscription_create", "WebhookSubscriptionData": ".webhook_subscription_data", "WebhookSubscriptionDataAuthMode": ".webhook_subscription_data_auth_mode", "WebhookSubscriptionEdit": ".webhook_subscription_edit", "WebhookSubscriptionFlags": ".webhook_subscription_flags", "WebhookSubscriptionQuery": ".webhook_subscription_query", "WebhookSubscriptionResponse": ".webhook_subscription_response", "WebhookSubscriptionsResponse": ".webhook_subscriptions_response", "Windowing": ".windowing", "WindowingOrder": ".windowing_order", "Workflow": ".workflow", "WorkflowArtifactFlags": ".workflow_artifact_flags", "WorkflowCatalogFlags": ".workflow_catalog_flags", "WorkflowCatalogHarness": ".workflow_catalog_harness", "WorkflowCatalogHarnessResponse": ".workflow_catalog_harness_response", "WorkflowCatalogHarnessesResponse": ".workflow_catalog_harnesses_response", "WorkflowCatalogPreset": ".workflow_catalog_preset", "WorkflowCatalogPresetResponse": ".workflow_catalog_preset_response", "WorkflowCatalogPresetsResponse": ".workflow_catalog_presets_response", "WorkflowCatalogTemplate": ".workflow_catalog_template", "WorkflowCatalogTemplateResponse": ".workflow_catalog_template_response", "WorkflowCatalogTemplatesResponse": ".workflow_catalog_templates_response", "WorkflowCatalogType": ".workflow_catalog_type", "WorkflowCatalogTypeResponse": ".workflow_catalog_type_response", "WorkflowCatalogTypesResponse": ".workflow_catalog_types_response", "WorkflowCreate": ".workflow_create", "WorkflowEdit": ".workflow_edit", "WorkflowFlags": ".workflow_flags", "WorkflowRequestData": ".workflow_request_data", "WorkflowResponse": ".workflow_response", "WorkflowRevisionCommit": ".workflow_revision_commit", "WorkflowRevisionCreate": ".workflow_revision_create", "WorkflowRevisionDataInput": ".workflow_revision_data_input", "WorkflowRevisionDataInputHeadersValue": ".workflow_revision_data_input_headers_value", "WorkflowRevisionDataInputRuntime": ".workflow_revision_data_input_runtime", "WorkflowRevisionDataOutput": ".workflow_revision_data_output", "WorkflowRevisionDataOutputHeadersValue": ".workflow_revision_data_output_headers_value", "WorkflowRevisionDataOutputRuntime": ".workflow_revision_data_output_runtime", "WorkflowRevisionDelta": ".workflow_revision_delta", "WorkflowRevisionEdit": ".workflow_revision_edit", "WorkflowRevisionFlags": ".workflow_revision_flags", "WorkflowRevisionInput": ".workflow_revision_input", "WorkflowRevisionOutput": ".workflow_revision_output", "WorkflowRevisionResolveResponse": ".workflow_revision_resolve_response", "WorkflowRevisionResponse": ".workflow_revision_response", "WorkflowRevisionsLog": ".workflow_revisions_log", "WorkflowRevisionsResponse": ".workflow_revisions_response", "WorkflowVariant": ".workflow_variant", "WorkflowVariantCreate": ".workflow_variant_create", "WorkflowVariantEdit": ".workflow_variant_edit", "WorkflowVariantFlags": ".workflow_variant_flags", "WorkflowVariantFork": ".workflow_variant_fork", "WorkflowVariantResponse": ".workflow_variant_response", "WorkflowVariantsResponse": ".workflow_variants_response", "WorkflowsResponse": ".workflows_response", "Workspace": ".workspace", "WorkspaceMemberResponse": ".workspace_member_response", "WorkspacePermission": ".workspace_permission", "WorkspaceResponse": ".workspace_response"} def __getattr__(attr_name: str) -> typing.Any: module_name = _dynamic_imports.get(attr_name) if module_name is None: @@ -791,4 +794,4 @@ def __getattr__(attr_name: str) -> typing.Any: def __dir__(): lazy_attrs = list(_dynamic_imports.keys()) return sorted(lazy_attrs) -__all__ = ["AdminAccountCreateOptions", "AdminAccountRead", "AdminAccountsCreate", "AdminAccountsDelete", "AdminAccountsDeleteTarget", "AdminAccountsResponse", "AdminApiKeyCreate", "AdminApiKeyResponse", "AdminDeleteResponse", "AdminDeletedEntities", "AdminDeletedEntity", "AdminOrganizationCreate", "AdminOrganizationMembershipCreate", "AdminOrganizationMembershipRead", "AdminOrganizationRead", "AdminProjectCreate", "AdminProjectMembershipCreate", "AdminProjectMembershipRead", "AdminProjectRead", "AdminSimpleAccountCreate", "AdminSimpleAccountDeleteEntry", "AdminSimpleAccountRead", "AdminSimpleAccountsApiKeysCreate", "AdminSimpleAccountsCreate", "AdminSimpleAccountsDelete", "AdminSimpleAccountsOrganizationsCreate", "AdminSimpleAccountsOrganizationsMembershipsCreate", "AdminSimpleAccountsOrganizationsTransferOwnership", "AdminSimpleAccountsOrganizationsTransferOwnershipIncludeProjects", "AdminSimpleAccountsOrganizationsTransferOwnershipIncludeProjectsZero", "AdminSimpleAccountsOrganizationsTransferOwnershipIncludeWorkspaces", "AdminSimpleAccountsOrganizationsTransferOwnershipIncludeWorkspacesZero", "AdminSimpleAccountsOrganizationsTransferOwnershipResponse", "AdminSimpleAccountsProjectsCreate", "AdminSimpleAccountsProjectsMembershipsCreate", "AdminSimpleAccountsResponse", "AdminSimpleAccountsUsersCreate", "AdminSimpleAccountsUsersIdentitiesCreate", "AdminSimpleAccountsUsersResetPassword", "AdminSimpleAccountsWorkspacesCreate", "AdminSimpleAccountsWorkspacesMembershipsCreate", "AdminStructuredError", "AdminSubscriptionCreate", "AdminSubscriptionRead", "AdminUserCreate", "AdminUserIdentityCreate", "AdminUserIdentityRead", "AdminUserIdentityReadStatus", "AdminUserRead", "AdminWorkspaceCreate", "AdminWorkspaceMembershipCreate", "AdminWorkspaceMembershipRead", "AdminWorkspaceRead", "AgentTemplateOverlay", "Analytics", "AnalyticsResponse", "Annotation", "AnnotationCreate", "AnnotationCreateLinks", "AnnotationEdit", "AnnotationEditLinks", "AnnotationLinkResponse", "AnnotationLinks", "AnnotationQuery", "AnnotationQueryLinks", "AnnotationResponse", "AnnotationsResponse", "Application", "ApplicationArtifactFlags", "ApplicationArtifactQueryFlags", "ApplicationCatalogPreset", "ApplicationCatalogPresetResponse", "ApplicationCatalogPresetsResponse", "ApplicationCatalogTemplate", "ApplicationCatalogTemplateResponse", "ApplicationCatalogTemplatesResponse", "ApplicationCatalogType", "ApplicationCatalogTypesResponse", "ApplicationCreate", "ApplicationEdit", "ApplicationFlags", "ApplicationQuery", "ApplicationResponse", "ApplicationRevisionCommit", "ApplicationRevisionCreate", "ApplicationRevisionDataInput", "ApplicationRevisionDataInputHeadersValue", "ApplicationRevisionDataInputRuntime", "ApplicationRevisionDataOutput", "ApplicationRevisionDataOutputHeadersValue", "ApplicationRevisionDataOutputRuntime", "ApplicationRevisionEdit", "ApplicationRevisionFlags", "ApplicationRevisionInput", "ApplicationRevisionOutput", "ApplicationRevisionQuery", "ApplicationRevisionQueryFlags", "ApplicationRevisionResolveResponse", "ApplicationRevisionResponse", "ApplicationRevisionsLog", "ApplicationRevisionsResponse", "ApplicationVariant", "ApplicationVariantCreate", "ApplicationVariantEdit", "ApplicationVariantFlags", "ApplicationVariantFork", "ApplicationVariantResponse", "ApplicationVariantsResponse", "ApplicationsResponse", "BodyConfigsFetchVariantsConfigsFetchPost", "Bucket", "BuiltinToolConfig", "BuiltinToolConfigPermission", "CapabilitiesResult", "Capability", "CapabilityConnection", "CapabilityGuidance", "CollectStatusResponse", "CommandMode", "ComparisonOperator", "Condition", "ConditionOperator", "ConditionOptions", "ConditionValue", "ConfigResponseModel", "ConnectAffordance", "ConnectionRequirement", "CustomModelSettingsDto", "CustomProviderDto", "CustomProviderKind", "CustomProviderSettingsDto", "CustomSecretDto", "CustomSecretFormat", "CustomSecretSettingsDto", "CustomSecretSettingsDtoContent", "CustomSecretSettingsDtoContentOneValue", "DictOperator", "DiscoverResponse", "DiscoverResponseMethodsValue", "DiscoveredAlternative", "DiscoveredTool", "DiscoveredToolType", "DiscoveredTriggerAlternative", "DiscoveredTriggerEvent", "DiscoveredTriggerEventType", "EntityRef", "Environment", "EnvironmentCreate", "EnvironmentEdit", "EnvironmentFlags", "EnvironmentQueryFlags", "EnvironmentResponse", "EnvironmentRevisionCommit", "EnvironmentRevisionCreate", "EnvironmentRevisionData", "EnvironmentRevisionDelta", "EnvironmentRevisionEdit", "EnvironmentRevisionInput", "EnvironmentRevisionOutput", "EnvironmentRevisionResolveResponse", "EnvironmentRevisionResponse", "EnvironmentRevisionsLog", "EnvironmentRevisionsResponse", "EnvironmentVariant", "EnvironmentVariantCreate", "EnvironmentVariantEdit", "EnvironmentVariantFork", "EnvironmentVariantResponse", "EnvironmentVariantsResponse", "EnvironmentsResponse", "ErrorPolicy", "EvaluationMetrics", "EvaluationMetricsCreate", "EvaluationMetricsIdsResponse", "EvaluationMetricsQuery", "EvaluationMetricsQueryScenarioIds", "EvaluationMetricsQueryTimestamps", "EvaluationMetricsRefresh", "EvaluationMetricsResponse", "EvaluationMetricsSetRequest", "EvaluationQueue", "EvaluationQueueCreate", "EvaluationQueueData", "EvaluationQueueEdit", "EvaluationQueueFlags", "EvaluationQueueIdResponse", "EvaluationQueueIdsResponse", "EvaluationQueueQuery", "EvaluationQueueQueryFlags", "EvaluationQueueResponse", "EvaluationQueueScenariosQuery", "EvaluationQueuesResponse", "EvaluationResult", "EvaluationResultCreate", "EvaluationResultIdResponse", "EvaluationResultIdsResponse", "EvaluationResultQuery", "EvaluationResultResponse", "EvaluationResultsResponse", "EvaluationResultsSetRequest", "EvaluationRun", "EvaluationRunCreate", "EvaluationRunDataConcurrency", "EvaluationRunDataInput", "EvaluationRunDataMapping", "EvaluationRunDataMappingColumn", "EvaluationRunDataMappingStep", "EvaluationRunDataOutput", "EvaluationRunDataStepInput", "EvaluationRunDataStepInputKey", "EvaluationRunDataStepInputOrigin", "EvaluationRunDataStepInputType", "EvaluationRunDataStepOutput", "EvaluationRunDataStepOutputOrigin", "EvaluationRunDataStepOutputType", "EvaluationRunEdit", "EvaluationRunFlags", "EvaluationRunIdResponse", "EvaluationRunIdsRequest", "EvaluationRunIdsResponse", "EvaluationRunQuery", "EvaluationRunQueryFlags", "EvaluationRunResponse", "EvaluationRunsResponse", "EvaluationScenario", "EvaluationScenarioCreate", "EvaluationScenarioEdit", "EvaluationScenarioIdResponse", "EvaluationScenarioIdsResponse", "EvaluationScenarioQuery", "EvaluationScenarioResponse", "EvaluationScenariosResponse", "EvaluationStatus", "Evaluator", "EvaluatorArtifactFlags", "EvaluatorArtifactQueryFlags", "EvaluatorCatalogPreset", "EvaluatorCatalogPresetResponse", "EvaluatorCatalogPresetsResponse", "EvaluatorCatalogTemplate", "EvaluatorCatalogTemplateResponse", "EvaluatorCatalogTemplatesResponse", "EvaluatorCatalogType", "EvaluatorCatalogTypesResponse", "EvaluatorCreate", "EvaluatorEdit", "EvaluatorFlags", "EvaluatorQuery", "EvaluatorResponse", "EvaluatorRevisionCommit", "EvaluatorRevisionCreate", "EvaluatorRevisionDataInput", "EvaluatorRevisionDataInputHeadersValue", "EvaluatorRevisionDataInputRuntime", "EvaluatorRevisionDataOutput", "EvaluatorRevisionDataOutputHeadersValue", "EvaluatorRevisionDataOutputRuntime", "EvaluatorRevisionEdit", "EvaluatorRevisionFlags", "EvaluatorRevisionInput", "EvaluatorRevisionOutput", "EvaluatorRevisionQuery", "EvaluatorRevisionQueryFlags", "EvaluatorRevisionResolveResponse", "EvaluatorRevisionResponse", "EvaluatorRevisionsLog", "EvaluatorRevisionsResponse", "EvaluatorTemplate", "EvaluatorTemplatesResponse", "EvaluatorVariant", "EvaluatorVariantCreate", "EvaluatorVariantEdit", "EvaluatorVariantFlags", "EvaluatorVariantFork", "EvaluatorVariantResponse", "EvaluatorVariantsResponse", "EvaluatorsResponse", "Event", "EventQuery", "EventType", "EventsQueryResponse", "ExistenceOperator", "FilteringInput", "FilteringInputConditionsItem", "FilteringOutput", "FilteringOutputConditionsItem", "Focus", "Folder", "FolderCreate", "FolderEdit", "FolderIdResponse", "FolderKind", "FolderQuery", "FolderQueryKinds", "FolderResponse", "FoldersResponse", "Format", "Formatting", typing.Any, typing.Any, "GatewayToolConfig", "GatewayToolConfigPermission", "HarnessSessionRecord", "Header", "HttpValidationError", "InviteRequest", "Invocation", "InvocationCreate", "InvocationCreateLinks", "InvocationEdit", "InvocationEditLinks", "InvocationLinkResponse", "InvocationLinks", "InvocationQuery", "InvocationQueryLinks", "InvocationResponse", "InvocationsResponse", "JsonSchemasInput", "JsonSchemasOutput", typing.Any, typing.Any, "LegacyLifecycleDto", "ListApiKeysResponse", "ListOperator", "ListOptions", "LogicalOperator", "MetricSpec", "MetricType", "MetricsBucket", "Mount", "MountCreate", "MountCredentials", "MountCredentialsResponse", "MountData", "MountEdit", "MountFileDeletedResponse", "MountFileWrittenResponse", "MountFlags", "MountFolderCreatedResponse", "MountQuery", "MountResponse", "MountsResponse", "NumericOperator", "OTelEventInput", "OTelEventInputTimestamp", "OTelEventOutput", "OTelEventOutputTimestamp", "OTelHashInput", "OTelHashOutput", "OTelLinkInput", "OTelLinkOutput", "OTelLinksResponse", "OTelReferenceInput", "OTelReferenceOutput", "OTelSpanKind", "OTelStatusCode", "OTelTracingRequest", "OTelTracingResponse", "OldAnalyticsResponse", "Organization", "OrganizationDetails", "OrganizationDomainResponse", "OrganizationProviderResponse", "OrganizationUpdate", "Permission", "PlaygroundBuildKitContext", "ProjectsResponse", "QueriesResponse", "Query", "QueryCreate", "QueryEdit", "QueryFlags", "QueryQueryFlags", "QueryResponse", "QueryRevision", "QueryRevisionCommit", "QueryRevisionCreate", "QueryRevisionDataInput", "QueryRevisionDataOutput", "QueryRevisionEdit", "QueryRevisionQuery", "QueryRevisionResponse", "QueryRevisionsLog", "QueryRevisionsResponse", "QueryVariant", "QueryVariantCreate", "QueryVariantEdit", "QueryVariantFork", "QueryVariantQuery", "QueryVariantResponse", "QueryVariantsResponse", "Reference", "ReferenceRequestModelInput", "ReferenceRequestModelOutput", "RequestType", "ResolutionInfo", "ResolvedTool", "RetrievalInfo", "SecretDto", "SecretDtoData", "SecretKind", "SecretResponseDto", "SecretResponseDtoData", "Selector", "SessionHeartbeatResponseModel", "SessionIdsResponse", "SessionInteraction", "SessionInteractionData", "SessionInteractionFlags", "SessionInteractionKind", "SessionInteractionQuery", "SessionInteractionQueryFlags", "SessionInteractionResponse", "SessionInteractionStatus", "SessionInteractionsResponse", "SessionMount", "SessionMountQuery", "SessionMountsResponse", "SessionRecord", "SessionRecordResponse", "SessionRecordsQueryResponse", "SessionState", "SessionStateData", "SessionStateFlags", "SessionStateResponse", "SessionStateUpsertRequest", "SessionStream", "SessionStreamCommandResponseModel", "SessionStreamFlags", "SessionStreamResponseModel", "SessionStreamsResponseModel", "SimpleApplication", "SimpleApplicationAdditionalContext", "SimpleApplicationCreate", "SimpleApplicationDataInput", "SimpleApplicationDataInputHeadersValue", "SimpleApplicationDataInputRuntime", "SimpleApplicationDataOutput", "SimpleApplicationDataOutputHeadersValue", "SimpleApplicationDataOutputRuntime", "SimpleApplicationEdit", "SimpleApplicationFlags", "SimpleApplicationQuery", "SimpleApplicationQueryFlags", "SimpleApplicationResponse", "SimpleApplicationsResponse", "SimpleEnvironment", "SimpleEnvironmentCreate", "SimpleEnvironmentEdit", "SimpleEnvironmentQuery", "SimpleEnvironmentResponse", "SimpleEnvironmentsResponse", "SimpleEvaluation", "SimpleEvaluationCreate", "SimpleEvaluationData", "SimpleEvaluationDataApplicationSteps", "SimpleEvaluationDataApplicationStepsOneValue", "SimpleEvaluationDataEvaluatorSteps", "SimpleEvaluationDataEvaluatorStepsOneValue", "SimpleEvaluationDataQuerySteps", "SimpleEvaluationDataQueryStepsOneValue", "SimpleEvaluationDataTestsetSteps", "SimpleEvaluationDataTestsetStepsOneValue", "SimpleEvaluationEdit", "SimpleEvaluationIdResponse", "SimpleEvaluationQuery", "SimpleEvaluationResponse", "SimpleEvaluationsResponse", "SimpleEvaluator", "SimpleEvaluatorCreate", "SimpleEvaluatorDataInput", "SimpleEvaluatorDataInputHeadersValue", "SimpleEvaluatorDataInputRuntime", "SimpleEvaluatorDataOutput", "SimpleEvaluatorDataOutputHeadersValue", "SimpleEvaluatorDataOutputRuntime", "SimpleEvaluatorEdit", "SimpleEvaluatorFlags", "SimpleEvaluatorQuery", "SimpleEvaluatorQueryFlags", "SimpleEvaluatorResponse", "SimpleEvaluatorsResponse", "SimpleQueriesResponse", "SimpleQuery", "SimpleQueryCreate", "SimpleQueryEdit", "SimpleQueryQuery", "SimpleQueryResponse", "SimpleQueue", "SimpleQueueCreate", "SimpleQueueData", "SimpleQueueDataEvaluators", "SimpleQueueDataEvaluatorsOneValue", "SimpleQueueIdResponse", "SimpleQueueIdsResponse", "SimpleQueueKind", "SimpleQueueQuery", "SimpleQueueResponse", "SimpleQueueScenariosQuery", "SimpleQueueScenariosResponse", "SimpleQueueSettings", "SimpleQueuesResponse", "SimpleTestset", "SimpleTestsetCreate", "SimpleTestsetEdit", "SimpleTestsetQuery", "SimpleTestsetResponse", "SimpleTestsetsResponse", "SimpleTrace", "SimpleTraceChannel", "SimpleTraceCreate", "SimpleTraceCreateLinks", "SimpleTraceEdit", "SimpleTraceEditLinks", "SimpleTraceKind", "SimpleTraceLinkResponse", "SimpleTraceLinks", "SimpleTraceOrigin", "SimpleTraceQuery", "SimpleTraceQueryLinks", "SimpleTraceReferences", "SimpleTraceResponse", "SimpleTracesResponse", "SimpleWorkflow", "SimpleWorkflowCreate", "SimpleWorkflowDataInput", "SimpleWorkflowDataInputHeadersValue", "SimpleWorkflowDataInputRuntime", "SimpleWorkflowDataOutput", "SimpleWorkflowDataOutputHeadersValue", "SimpleWorkflowDataOutputRuntime", "SimpleWorkflowEdit", "SimpleWorkflowFlags", "SimpleWorkflowQuery", "SimpleWorkflowQueryFlags", "SimpleWorkflowResponse", "SimpleWorkflowsResponse", "SpanInput", "SpanInputEndTime", "SpanInputStartTime", "SpanOutput", "SpanOutputEndTime", "SpanOutputStartTime", "SpanResponse", "SpanType", "SpansNodeInput", "SpansNodeInputEndTime", "SpansNodeInputSpansValue", "SpansNodeInputStartTime", "SpansNodeOutput", "SpansNodeOutputEndTime", "SpansNodeOutputSpansValue", "SpansNodeOutputStartTime", "SpansResponse", "SpansTreeInput", "SpansTreeInputSpansValue", "SpansTreeOutput", "SpansTreeOutputSpansValue", "SsoProviderDto", "SsoProviderInfo", "SsoProviderSettingsDto", "SsoProviders", "StandardProviderDto", "StandardProviderKind", "StandardProviderSettingsDto", "Status", "StringOperator", "TestcaseInput", "TestcaseOutput", "TestcaseResponse", "TestcasesResponse", "Testset", "TestsetCreate", "TestsetEdit", "TestsetFlags", "TestsetQuery", "TestsetResponse", "TestsetRevision", "TestsetRevisionCommit", "TestsetRevisionCreate", "TestsetRevisionDataInput", "TestsetRevisionDataOutput", "TestsetRevisionDelta", "TestsetRevisionDeltaColumns", "TestsetRevisionDeltaRows", "TestsetRevisionEdit", "TestsetRevisionQuery", "TestsetRevisionResponse", "TestsetRevisionsLog", "TestsetRevisionsResponse", "TestsetVariant", "TestsetVariantCreate", "TestsetVariantEdit", "TestsetVariantFork", "TestsetVariantQuery", "TestsetVariantResponse", "TestsetVariantsResponse", "TestsetsResponse", "TextOptions", "ToolAuthScheme", "ToolCallData", "ToolCallFunction", "ToolCallResponse", "ToolCatalogAction", "ToolCatalogActionDetails", "ToolCatalogActionResponse", "ToolCatalogActionResponseAction", "ToolCatalogActionsResponse", "ToolCatalogActionsResponseActionsItem", "ToolCatalogCategoriesResponse", "ToolCatalogCategory", "ToolCatalogIntegration", "ToolCatalogIntegrationDetails", "ToolCatalogIntegrationResponse", "ToolCatalogIntegrationResponseIntegration", "ToolCatalogIntegrationsResponse", "ToolCatalogIntegrationsResponseIntegrationsItem", "ToolCatalogProvider", "ToolCatalogProviderDetails", "ToolCatalogProviderResponse", "ToolCatalogProviderResponseProvider", "ToolCatalogProvidersResponse", "ToolCatalogProvidersResponseProvidersItem", "ToolConnection", "ToolConnectionCreate", "ToolConnectionCreateData", "ToolConnectionResponse", "ToolConnectionState", "ToolConnectionStatus", "ToolConnectionsResponse", "ToolProviderKind", "ToolResolveResponse", "ToolResult", "ToolResultData", "TraceIdResponse", "TraceIdsResponse", "TraceInput", "TraceInputSpansValue", "TraceOutput", "TraceOutputSpansValue", "TraceRequest", "TraceResponse", "TraceType", "TracesRequest", "TracesResponse", "TracingQuery", "TriggerAuthScheme", "TriggerCapabilitiesResult", "TriggerCapability", "TriggerCapabilityConnection", "TriggerCatalogEvent", "TriggerCatalogEventDetails", "TriggerCatalogEventResponse", "TriggerCatalogEventsResponse", "TriggerCatalogIntegration", "TriggerCatalogIntegrationResponse", "TriggerCatalogIntegrationsResponse", "TriggerCatalogProvider", "TriggerCatalogProviderResponse", "TriggerCatalogProvidersResponse", "TriggerConnectAffordance", "TriggerConnection", "TriggerConnectionCreate", "TriggerConnectionCreateData", "TriggerConnectionRequirement", "TriggerConnectionResponse", "TriggerConnectionStatus", "TriggerConnectionsResponse", "TriggerDeliveriesResponse", "TriggerDelivery", "TriggerDeliveryData", "TriggerDeliveryQuery", "TriggerDeliveryResponse", "TriggerDiscoveryConnectionState", "TriggerDiscoveryGuidance", "TriggerEventAck", "TriggerProviderKind", "TriggerSchedule", "TriggerScheduleCreate", "TriggerScheduleData", "TriggerScheduleDataInputsFields", "TriggerScheduleEdit", "TriggerScheduleFlags", "TriggerScheduleQuery", "TriggerScheduleResponse", "TriggerSchedulesResponse", "TriggerSubscription", "TriggerSubscriptionCreate", "TriggerSubscriptionCreateRequest", "TriggerSubscriptionData", "TriggerSubscriptionDataInputsFields", "TriggerSubscriptionEdit", "TriggerSubscriptionFlags", "TriggerSubscriptionQuery", "TriggerSubscriptionResponse", "TriggerSubscriptionsResponse", "UserIdsResponse", "ValidationError", "ValidationErrorLocItem", "WebhookDeliveriesResponse", "WebhookDelivery", "WebhookDeliveryCreate", "WebhookDeliveryData", "WebhookDeliveryQuery", "WebhookDeliveryResponse", "WebhookDeliveryResponseInfo", "WebhookEventType", "WebhookProviderDto", "WebhookProviderSettingsDto", "WebhookSubscription", "WebhookSubscriptionCreate", "WebhookSubscriptionData", "WebhookSubscriptionDataAuthMode", "WebhookSubscriptionEdit", "WebhookSubscriptionFlags", "WebhookSubscriptionQuery", "WebhookSubscriptionResponse", "WebhookSubscriptionsResponse", "Windowing", "WindowingOrder", "Workflow", "WorkflowArtifactFlags", "WorkflowCatalogFlags", "WorkflowCatalogHarness", "WorkflowCatalogHarnessResponse", "WorkflowCatalogHarnessesResponse", "WorkflowCatalogPreset", "WorkflowCatalogPresetResponse", "WorkflowCatalogPresetsResponse", "WorkflowCatalogTemplate", "WorkflowCatalogTemplateResponse", "WorkflowCatalogTemplatesResponse", "WorkflowCatalogType", "WorkflowCatalogTypeResponse", "WorkflowCatalogTypesResponse", "WorkflowCreate", "WorkflowEdit", "WorkflowFlags", "WorkflowResponse", "WorkflowRevisionCommit", "WorkflowRevisionCreate", "WorkflowRevisionDataInput", "WorkflowRevisionDataInputHeadersValue", "WorkflowRevisionDataInputRuntime", "WorkflowRevisionDataOutput", "WorkflowRevisionDataOutputHeadersValue", "WorkflowRevisionDataOutputRuntime", "WorkflowRevisionDelta", "WorkflowRevisionEdit", "WorkflowRevisionFlags", "WorkflowRevisionInput", "WorkflowRevisionOutput", "WorkflowRevisionResolveResponse", "WorkflowRevisionResponse", "WorkflowRevisionsLog", "WorkflowRevisionsResponse", "WorkflowVariant", "WorkflowVariantCreate", "WorkflowVariantEdit", "WorkflowVariantFlags", "WorkflowVariantFork", "WorkflowVariantResponse", "WorkflowVariantsResponse", "WorkflowsResponse", "Workspace", "WorkspaceMemberResponse", "WorkspacePermission", "WorkspaceResponse"] +__all__ = ["AdminAccountCreateOptions", "AdminAccountRead", "AdminAccountsCreate", "AdminAccountsDelete", "AdminAccountsDeleteTarget", "AdminAccountsResponse", "AdminApiKeyCreate", "AdminApiKeyResponse", "AdminDeleteResponse", "AdminDeletedEntities", "AdminDeletedEntity", "AdminOrganizationCreate", "AdminOrganizationMembershipCreate", "AdminOrganizationMembershipRead", "AdminOrganizationRead", "AdminProjectCreate", "AdminProjectMembershipCreate", "AdminProjectMembershipRead", "AdminProjectRead", "AdminSimpleAccountCreate", "AdminSimpleAccountDeleteEntry", "AdminSimpleAccountRead", "AdminSimpleAccountsApiKeysCreate", "AdminSimpleAccountsCreate", "AdminSimpleAccountsDelete", "AdminSimpleAccountsOrganizationsCreate", "AdminSimpleAccountsOrganizationsMembershipsCreate", "AdminSimpleAccountsOrganizationsTransferOwnership", "AdminSimpleAccountsOrganizationsTransferOwnershipIncludeProjects", "AdminSimpleAccountsOrganizationsTransferOwnershipIncludeProjectsZero", "AdminSimpleAccountsOrganizationsTransferOwnershipIncludeWorkspaces", "AdminSimpleAccountsOrganizationsTransferOwnershipIncludeWorkspacesZero", "AdminSimpleAccountsOrganizationsTransferOwnershipResponse", "AdminSimpleAccountsProjectsCreate", "AdminSimpleAccountsProjectsMembershipsCreate", "AdminSimpleAccountsResponse", "AdminSimpleAccountsUsersCreate", "AdminSimpleAccountsUsersIdentitiesCreate", "AdminSimpleAccountsUsersResetPassword", "AdminSimpleAccountsWorkspacesCreate", "AdminSimpleAccountsWorkspacesMembershipsCreate", "AdminStructuredError", "AdminSubscriptionCreate", "AdminSubscriptionRead", "AdminUserCreate", "AdminUserIdentityCreate", "AdminUserIdentityRead", "AdminUserIdentityReadStatus", "AdminUserRead", "AdminWorkspaceCreate", "AdminWorkspaceMembershipCreate", "AdminWorkspaceMembershipRead", "AdminWorkspaceRead", "AgentTemplateOverlay", "Analytics", "AnalyticsResponse", "Annotation", "AnnotationCreate", "AnnotationCreateLinks", "AnnotationEdit", "AnnotationEditLinks", "AnnotationLinkResponse", "AnnotationLinks", "AnnotationQuery", "AnnotationQueryLinks", "AnnotationResponse", "AnnotationsResponse", "Application", "ApplicationArtifactFlags", "ApplicationArtifactQueryFlags", "ApplicationCatalogPreset", "ApplicationCatalogPresetResponse", "ApplicationCatalogPresetsResponse", "ApplicationCatalogTemplate", "ApplicationCatalogTemplateResponse", "ApplicationCatalogTemplatesResponse", "ApplicationCatalogType", "ApplicationCatalogTypesResponse", "ApplicationCreate", "ApplicationEdit", "ApplicationFlags", "ApplicationQuery", "ApplicationResponse", "ApplicationRevisionCommit", "ApplicationRevisionCreate", "ApplicationRevisionDataInput", "ApplicationRevisionDataInputHeadersValue", "ApplicationRevisionDataInputRuntime", "ApplicationRevisionDataOutput", "ApplicationRevisionDataOutputHeadersValue", "ApplicationRevisionDataOutputRuntime", "ApplicationRevisionEdit", "ApplicationRevisionFlags", "ApplicationRevisionInput", "ApplicationRevisionOutput", "ApplicationRevisionQuery", "ApplicationRevisionQueryFlags", "ApplicationRevisionResolveResponse", "ApplicationRevisionResponse", "ApplicationRevisionsLog", "ApplicationRevisionsResponse", "ApplicationVariant", "ApplicationVariantCreate", "ApplicationVariantEdit", "ApplicationVariantFlags", "ApplicationVariantFork", "ApplicationVariantResponse", "ApplicationVariantsResponse", "ApplicationsResponse", "BodyConfigsFetchVariantsConfigsFetchPost", "Bucket", "BuiltinToolConfig", "BuiltinToolConfigPermission", "CapabilitiesResult", "Capability", "CapabilityConnection", "CapabilityGuidance", "CollectStatusResponse", "CommandMode", "ComparisonOperator", "Condition", "ConditionOperator", "ConditionOptions", "ConditionValue", "ConfigResponseModel", "ConnectAffordance", "ConnectionRequirement", "CustomModelSettingsDto", "CustomProviderDto", "CustomProviderKind", "CustomProviderSettingsDto", "CustomSecretDto", "CustomSecretFormat", "CustomSecretSettingsDto", "CustomSecretSettingsDtoContent", "CustomSecretSettingsDtoContentOneValue", "DictOperator", "DiscoverResponse", "DiscoverResponseMethodsValue", "DiscoveredAlternative", "DiscoveredTool", "DiscoveredToolType", "DiscoveredTriggerAlternative", "DiscoveredTriggerEvent", "DiscoveredTriggerEventType", "EntityRef", "Environment", "EnvironmentCreate", "EnvironmentEdit", "EnvironmentFlags", "EnvironmentQueryFlags", "EnvironmentResponse", "EnvironmentRevisionCommit", "EnvironmentRevisionCreate", "EnvironmentRevisionData", "EnvironmentRevisionDelta", "EnvironmentRevisionEdit", "EnvironmentRevisionInput", "EnvironmentRevisionOutput", "EnvironmentRevisionResolveResponse", "EnvironmentRevisionResponse", "EnvironmentRevisionsLog", "EnvironmentRevisionsResponse", "EnvironmentVariant", "EnvironmentVariantCreate", "EnvironmentVariantEdit", "EnvironmentVariantFork", "EnvironmentVariantResponse", "EnvironmentVariantsResponse", "EnvironmentsResponse", "ErrorPolicy", "EvaluationMetrics", "EvaluationMetricsCreate", "EvaluationMetricsIdsResponse", "EvaluationMetricsQuery", "EvaluationMetricsQueryScenarioIds", "EvaluationMetricsQueryTimestamps", "EvaluationMetricsRefresh", "EvaluationMetricsResponse", "EvaluationMetricsSetRequest", "EvaluationQueue", "EvaluationQueueCreate", "EvaluationQueueData", "EvaluationQueueEdit", "EvaluationQueueFlags", "EvaluationQueueIdResponse", "EvaluationQueueIdsResponse", "EvaluationQueueQuery", "EvaluationQueueQueryFlags", "EvaluationQueueResponse", "EvaluationQueueScenariosQuery", "EvaluationQueuesResponse", "EvaluationResult", "EvaluationResultCreate", "EvaluationResultIdResponse", "EvaluationResultIdsResponse", "EvaluationResultQuery", "EvaluationResultResponse", "EvaluationResultsResponse", "EvaluationResultsSetRequest", "EvaluationRun", "EvaluationRunCreate", "EvaluationRunDataConcurrency", "EvaluationRunDataInput", "EvaluationRunDataMapping", "EvaluationRunDataMappingColumn", "EvaluationRunDataMappingStep", "EvaluationRunDataOutput", "EvaluationRunDataStepInput", "EvaluationRunDataStepInputKey", "EvaluationRunDataStepInputOrigin", "EvaluationRunDataStepInputType", "EvaluationRunDataStepOutput", "EvaluationRunDataStepOutputOrigin", "EvaluationRunDataStepOutputType", "EvaluationRunEdit", "EvaluationRunFlags", "EvaluationRunIdResponse", "EvaluationRunIdsRequest", "EvaluationRunIdsResponse", "EvaluationRunQuery", "EvaluationRunQueryFlags", "EvaluationRunResponse", "EvaluationRunsResponse", "EvaluationScenario", "EvaluationScenarioCreate", "EvaluationScenarioEdit", "EvaluationScenarioIdResponse", "EvaluationScenarioIdsResponse", "EvaluationScenarioQuery", "EvaluationScenarioResponse", "EvaluationScenariosResponse", "EvaluationStatus", "Evaluator", "EvaluatorArtifactFlags", "EvaluatorArtifactQueryFlags", "EvaluatorCatalogPreset", "EvaluatorCatalogPresetResponse", "EvaluatorCatalogPresetsResponse", "EvaluatorCatalogTemplate", "EvaluatorCatalogTemplateResponse", "EvaluatorCatalogTemplatesResponse", "EvaluatorCatalogType", "EvaluatorCatalogTypesResponse", "EvaluatorCreate", "EvaluatorEdit", "EvaluatorFlags", "EvaluatorQuery", "EvaluatorResponse", "EvaluatorRevisionCommit", "EvaluatorRevisionCreate", "EvaluatorRevisionDataInput", "EvaluatorRevisionDataInputHeadersValue", "EvaluatorRevisionDataInputRuntime", "EvaluatorRevisionDataOutput", "EvaluatorRevisionDataOutputHeadersValue", "EvaluatorRevisionDataOutputRuntime", "EvaluatorRevisionEdit", "EvaluatorRevisionFlags", "EvaluatorRevisionInput", "EvaluatorRevisionOutput", "EvaluatorRevisionQuery", "EvaluatorRevisionQueryFlags", "EvaluatorRevisionResolveResponse", "EvaluatorRevisionResponse", "EvaluatorRevisionsLog", "EvaluatorRevisionsResponse", "EvaluatorTemplate", "EvaluatorTemplatesResponse", "EvaluatorVariant", "EvaluatorVariantCreate", "EvaluatorVariantEdit", "EvaluatorVariantFlags", "EvaluatorVariantFork", "EvaluatorVariantResponse", "EvaluatorVariantsResponse", "EvaluatorsResponse", "Event", "EventQuery", "EventType", "EventsQueryResponse", "ExistenceOperator", "FilteringInput", "FilteringInputConditionsItem", "FilteringOutput", "FilteringOutputConditionsItem", "Focus", "Folder", "FolderCreate", "FolderEdit", "FolderIdResponse", "FolderKind", "FolderQuery", "FolderQueryKinds", "FolderResponse", "FoldersResponse", "Format", "Formatting", typing.Any, typing.Any, "GatewayToolConfig", "GatewayToolConfigPermission", "HarnessKind", "Header", "HttpValidationError", "InviteRequest", "Invocation", "InvocationCreate", "InvocationCreateLinks", "InvocationEdit", "InvocationEditLinks", "InvocationLinkResponse", "InvocationLinks", "InvocationQuery", "InvocationQueryLinks", "InvocationResponse", "InvocationsResponse", "JsonSchemasInput", "JsonSchemasOutput", typing.Any, typing.Any, "LegacyLifecycleDto", "ListApiKeysResponse", "ListOperator", "ListOptions", "LogicalOperator", "MetricSpec", "MetricType", "MetricsBucket", "Mount", "MountCreate", "MountCredentials", "MountCredentialsResponse", "MountData", "MountEdit", "MountFileDeletedResponse", "MountFileWrittenResponse", "MountFlags", "MountFolderCreatedResponse", "MountQuery", "MountResponse", "MountsResponse", "NumericOperator", "OTelEventInput", "OTelEventInputTimestamp", "OTelEventOutput", "OTelEventOutputTimestamp", "OTelHashInput", "OTelHashOutput", "OTelLinkInput", "OTelLinkOutput", "OTelLinksResponse", "OTelReferenceInput", "OTelReferenceOutput", "OTelSpanKind", "OTelStatusCode", "OTelTracingRequest", "OTelTracingResponse", "OldAnalyticsResponse", "Organization", "OrganizationDetails", "OrganizationDomainResponse", "OrganizationProviderResponse", "OrganizationUpdate", "Permission", "PlaygroundBuildKitContext", "ProjectsResponse", "QueriesResponse", "Query", "QueryCreate", "QueryEdit", "QueryFlags", "QueryQueryFlags", "QueryResponse", "QueryRevision", "QueryRevisionCommit", "QueryRevisionCreate", "QueryRevisionDataInput", "QueryRevisionDataOutput", "QueryRevisionEdit", "QueryRevisionQuery", "QueryRevisionResponse", "QueryRevisionsLog", "QueryRevisionsResponse", "QueryVariant", "QueryVariantCreate", "QueryVariantEdit", "QueryVariantFork", "QueryVariantQuery", "QueryVariantResponse", "QueryVariantsResponse", "Reference", "ReferenceRequestModelInput", "ReferenceRequestModelOutput", "RequestType", "ResolutionInfo", "ResolvedTool", "RetrievalInfo", "SecretDto", "SecretDtoData", "SecretKind", "SecretResponseDto", "SecretResponseDtoData", "Selector", "SessionHeartbeatResult", "SessionIdsResponse", "SessionInteraction", "SessionInteractionData", "SessionInteractionFlags", "SessionInteractionKind", "SessionInteractionQuery", "SessionInteractionQueryFlags", "SessionInteractionResponse", "SessionInteractionStatus", "SessionInteractionsResponse", "SessionMount", "SessionMountQuery", "SessionMountsResponse", "SessionRecord", "SessionRecordResponse", "SessionRecordsQueryResponse", "SessionResponse", "SessionStream", "SessionStreamCommandResponse", "SessionStreamFlags", "SessionStreamHeaderEdit", "SessionStreamResponse", "SessionStreamsResponse", "SessionTurn", "SessionTurnQuery", "SessionTurnResponse", "SessionTurnsResponse", "SessionsResponse", "SimpleApplication", "SimpleApplicationAdditionalContext", "SimpleApplicationCreate", "SimpleApplicationDataInput", "SimpleApplicationDataInputHeadersValue", "SimpleApplicationDataInputRuntime", "SimpleApplicationDataOutput", "SimpleApplicationDataOutputHeadersValue", "SimpleApplicationDataOutputRuntime", "SimpleApplicationEdit", "SimpleApplicationFlags", "SimpleApplicationQuery", "SimpleApplicationQueryFlags", "SimpleApplicationResponse", "SimpleApplicationsResponse", "SimpleEnvironment", "SimpleEnvironmentCreate", "SimpleEnvironmentEdit", "SimpleEnvironmentQuery", "SimpleEnvironmentResponse", "SimpleEnvironmentsResponse", "SimpleEvaluation", "SimpleEvaluationCreate", "SimpleEvaluationData", "SimpleEvaluationDataApplicationSteps", "SimpleEvaluationDataApplicationStepsOneValue", "SimpleEvaluationDataEvaluatorSteps", "SimpleEvaluationDataEvaluatorStepsOneValue", "SimpleEvaluationDataQuerySteps", "SimpleEvaluationDataQueryStepsOneValue", "SimpleEvaluationDataTestsetSteps", "SimpleEvaluationDataTestsetStepsOneValue", "SimpleEvaluationEdit", "SimpleEvaluationIdResponse", "SimpleEvaluationQuery", "SimpleEvaluationResponse", "SimpleEvaluationsResponse", "SimpleEvaluator", "SimpleEvaluatorCreate", "SimpleEvaluatorDataInput", "SimpleEvaluatorDataInputHeadersValue", "SimpleEvaluatorDataInputRuntime", "SimpleEvaluatorDataOutput", "SimpleEvaluatorDataOutputHeadersValue", "SimpleEvaluatorDataOutputRuntime", "SimpleEvaluatorEdit", "SimpleEvaluatorFlags", "SimpleEvaluatorQuery", "SimpleEvaluatorQueryFlags", "SimpleEvaluatorResponse", "SimpleEvaluatorsResponse", "SimpleQueriesResponse", "SimpleQuery", "SimpleQueryCreate", "SimpleQueryEdit", "SimpleQueryQuery", "SimpleQueryResponse", "SimpleQueue", "SimpleQueueCreate", "SimpleQueueData", "SimpleQueueDataEvaluators", "SimpleQueueDataEvaluatorsOneValue", "SimpleQueueIdResponse", "SimpleQueueIdsResponse", "SimpleQueueKind", "SimpleQueueQuery", "SimpleQueueResponse", "SimpleQueueScenariosQuery", "SimpleQueueScenariosResponse", "SimpleQueueSettings", "SimpleQueuesResponse", "SimpleTestset", "SimpleTestsetCreate", "SimpleTestsetEdit", "SimpleTestsetQuery", "SimpleTestsetResponse", "SimpleTestsetsResponse", "SimpleTrace", "SimpleTraceChannel", "SimpleTraceCreate", "SimpleTraceCreateLinks", "SimpleTraceEdit", "SimpleTraceEditLinks", "SimpleTraceKind", "SimpleTraceLinkResponse", "SimpleTraceLinks", "SimpleTraceOrigin", "SimpleTraceQuery", "SimpleTraceQueryLinks", "SimpleTraceReferences", "SimpleTraceResponse", "SimpleTracesResponse", "SimpleWorkflow", "SimpleWorkflowCreate", "SimpleWorkflowDataInput", "SimpleWorkflowDataInputHeadersValue", "SimpleWorkflowDataInputRuntime", "SimpleWorkflowDataOutput", "SimpleWorkflowDataOutputHeadersValue", "SimpleWorkflowDataOutputRuntime", "SimpleWorkflowEdit", "SimpleWorkflowFlags", "SimpleWorkflowQuery", "SimpleWorkflowQueryFlags", "SimpleWorkflowResponse", "SimpleWorkflowsResponse", "SpanInput", "SpanInputEndTime", "SpanInputStartTime", "SpanOutput", "SpanOutputEndTime", "SpanOutputStartTime", "SpanResponse", "SpanType", "SpansNodeInput", "SpansNodeInputEndTime", "SpansNodeInputSpansValue", "SpansNodeInputStartTime", "SpansNodeOutput", "SpansNodeOutputEndTime", "SpansNodeOutputSpansValue", "SpansNodeOutputStartTime", "SpansResponse", "SpansTreeInput", "SpansTreeInputSpansValue", "SpansTreeOutput", "SpansTreeOutputSpansValue", "SsoProviderDto", "SsoProviderInfo", "SsoProviderSettingsDto", "SsoProviders", "StandardProviderDto", "StandardProviderKind", "StandardProviderSettingsDto", "Status", "StringOperator", "TestcaseInput", "TestcaseOutput", "TestcaseResponse", "TestcasesResponse", "Testset", "TestsetCreate", "TestsetEdit", "TestsetFlags", "TestsetQuery", "TestsetResponse", "TestsetRevision", "TestsetRevisionCommit", "TestsetRevisionCreate", "TestsetRevisionDataInput", "TestsetRevisionDataOutput", "TestsetRevisionDelta", "TestsetRevisionDeltaColumns", "TestsetRevisionDeltaRows", "TestsetRevisionEdit", "TestsetRevisionQuery", "TestsetRevisionResponse", "TestsetRevisionsLog", "TestsetRevisionsResponse", "TestsetVariant", "TestsetVariantCreate", "TestsetVariantEdit", "TestsetVariantFork", "TestsetVariantQuery", "TestsetVariantResponse", "TestsetVariantsResponse", "TestsetsResponse", "TextOptions", "ToolAuthScheme", "ToolCallData", "ToolCallFunction", "ToolCallResponse", "ToolCatalogAction", "ToolCatalogActionDetails", "ToolCatalogActionResponse", "ToolCatalogActionResponseAction", "ToolCatalogActionsResponse", "ToolCatalogActionsResponseActionsItem", "ToolCatalogCategoriesResponse", "ToolCatalogCategory", "ToolCatalogIntegration", "ToolCatalogIntegrationDetails", "ToolCatalogIntegrationResponse", "ToolCatalogIntegrationResponseIntegration", "ToolCatalogIntegrationsResponse", "ToolCatalogIntegrationsResponseIntegrationsItem", "ToolCatalogProvider", "ToolCatalogProviderDetails", "ToolCatalogProviderResponse", "ToolCatalogProviderResponseProvider", "ToolCatalogProvidersResponse", "ToolCatalogProvidersResponseProvidersItem", "ToolConnection", "ToolConnectionCreate", "ToolConnectionCreateData", "ToolConnectionResponse", "ToolConnectionState", "ToolConnectionStatus", "ToolConnectionsResponse", "ToolProviderKind", "ToolResolveResponse", "ToolResult", "ToolResultData", "TraceIdResponse", "TraceIdsResponse", "TraceInput", "TraceInputSpansValue", "TraceOutput", "TraceOutputSpansValue", "TraceRequest", "TraceResponse", "TraceType", "TracesRequest", "TracesResponse", "TracingQuery", "TriggerAuthScheme", "TriggerCapabilitiesResult", "TriggerCapability", "TriggerCapabilityConnection", "TriggerCatalogEvent", "TriggerCatalogEventDetails", "TriggerCatalogEventResponse", "TriggerCatalogEventsResponse", "TriggerCatalogIntegration", "TriggerCatalogIntegrationResponse", "TriggerCatalogIntegrationsResponse", "TriggerCatalogProvider", "TriggerCatalogProviderResponse", "TriggerCatalogProvidersResponse", "TriggerConnectAffordance", "TriggerConnection", "TriggerConnectionCreate", "TriggerConnectionCreateData", "TriggerConnectionRequirement", "TriggerConnectionResponse", "TriggerConnectionStatus", "TriggerConnectionsResponse", "TriggerDeliveriesResponse", "TriggerDelivery", "TriggerDeliveryData", "TriggerDeliveryQuery", "TriggerDeliveryResponse", "TriggerDiscoveryConnectionState", "TriggerDiscoveryGuidance", "TriggerEventAck", "TriggerProviderKind", "TriggerSchedule", "TriggerScheduleCreate", "TriggerScheduleData", "TriggerScheduleDataInputsFields", "TriggerScheduleEdit", "TriggerScheduleFlags", "TriggerScheduleQuery", "TriggerScheduleResponse", "TriggerSchedulesResponse", "TriggerSubscription", "TriggerSubscriptionCreate", "TriggerSubscriptionCreateRequest", "TriggerSubscriptionData", "TriggerSubscriptionDataInputsFields", "TriggerSubscriptionEdit", "TriggerSubscriptionFlags", "TriggerSubscriptionQuery", "TriggerSubscriptionResponse", "TriggerSubscriptionsResponse", "UserIdsResponse", "ValidationError", "ValidationErrorLocItem", "WebhookDeliveriesResponse", "WebhookDelivery", "WebhookDeliveryCreate", "WebhookDeliveryData", "WebhookDeliveryQuery", "WebhookDeliveryResponse", "WebhookDeliveryResponseInfo", "WebhookEventType", "WebhookProviderDto", "WebhookProviderSettingsDto", "WebhookSubscription", "WebhookSubscriptionCreate", "WebhookSubscriptionData", "WebhookSubscriptionDataAuthMode", "WebhookSubscriptionEdit", "WebhookSubscriptionFlags", "WebhookSubscriptionQuery", "WebhookSubscriptionResponse", "WebhookSubscriptionsResponse", "Windowing", "WindowingOrder", "Workflow", "WorkflowArtifactFlags", "WorkflowCatalogFlags", "WorkflowCatalogHarness", "WorkflowCatalogHarnessResponse", "WorkflowCatalogHarnessesResponse", "WorkflowCatalogPreset", "WorkflowCatalogPresetResponse", "WorkflowCatalogPresetsResponse", "WorkflowCatalogTemplate", "WorkflowCatalogTemplateResponse", "WorkflowCatalogTemplatesResponse", "WorkflowCatalogType", "WorkflowCatalogTypeResponse", "WorkflowCatalogTypesResponse", "WorkflowCreate", "WorkflowEdit", "WorkflowFlags", "WorkflowRequestData", "WorkflowResponse", "WorkflowRevisionCommit", "WorkflowRevisionCreate", "WorkflowRevisionDataInput", "WorkflowRevisionDataInputHeadersValue", "WorkflowRevisionDataInputRuntime", "WorkflowRevisionDataOutput", "WorkflowRevisionDataOutputHeadersValue", "WorkflowRevisionDataOutputRuntime", "WorkflowRevisionDelta", "WorkflowRevisionEdit", "WorkflowRevisionFlags", "WorkflowRevisionInput", "WorkflowRevisionOutput", "WorkflowRevisionResolveResponse", "WorkflowRevisionResponse", "WorkflowRevisionsLog", "WorkflowRevisionsResponse", "WorkflowVariant", "WorkflowVariantCreate", "WorkflowVariantEdit", "WorkflowVariantFlags", "WorkflowVariantFork", "WorkflowVariantResponse", "WorkflowVariantsResponse", "WorkflowsResponse", "Workspace", "WorkspaceMemberResponse", "WorkspacePermission", "WorkspaceResponse"] diff --git a/clients/python/agenta_client/types/harness_kind.py b/clients/python/agenta_client/types/harness_kind.py new file mode 100644 index 0000000000..5bd68558ef --- /dev/null +++ b/clients/python/agenta_client/types/harness_kind.py @@ -0,0 +1,5 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +HarnessKind = typing.Union[typing.Literal["pi_core", "claude", "pi_agenta"], typing.Any] diff --git a/clients/python/agenta_client/types/harness_session_record.py b/clients/python/agenta_client/types/harness_session_record.py deleted file mode 100644 index 2b1ecddc61..0000000000 --- a/clients/python/agenta_client/types/harness_session_record.py +++ /dev/null @@ -1,30 +0,0 @@ -# This file was auto-generated by Fern from our API Definition. - -import typing - -import pydantic -from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel - - -class HarnessSessionRecord(UniversalBaseModel): - """ - Per-harness resume state. Value shape of `data.harness_sessions[]`. - """ - agent_session_id: typing.Optional[str] = pydantic.Field(default=None) - """ - This harness's own agentSessionId, fed to session/load on resume. - """ - - turn_index: typing.Optional[int] = pydantic.Field(default=None) - """ - Conversation turn number this harness last ran at. Load-eligible only when equal to the conversation's latest_turn_index; otherwise this harness's session file is stale (another harness ran since). - """ - - - if IS_PYDANTIC_V2: - model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2 - else: - class Config: - frozen = True - smart_union = True - extra = pydantic.Extra.allow diff --git a/clients/python/agenta_client/types/mount.py b/clients/python/agenta_client/types/mount.py index e4d1a79799..448afb7240 100644 --- a/clients/python/agenta_client/types/mount.py +++ b/clients/python/agenta_client/types/mount.py @@ -22,6 +22,7 @@ class Mount(UniversalBaseModel): id: typing.Optional[str] = None project_id: str session_id: typing.Optional[str] = None + agent_id: typing.Optional[str] = None data: typing.Optional[MountData] = None flags: typing.Optional[MountFlags] = None tags: typing.Optional[typing.Dict[str, typing.Any]] = None diff --git a/clients/python/agenta_client/types/mount_create.py b/clients/python/agenta_client/types/mount_create.py index 0684119f8d..4636a3a02d 100644 --- a/clients/python/agenta_client/types/mount_create.py +++ b/clients/python/agenta_client/types/mount_create.py @@ -12,6 +12,7 @@ class MountCreate(UniversalBaseModel): description: typing.Optional[str] = None slug: typing.Optional[str] = None session_id: typing.Optional[str] = None + agent_id: typing.Optional[str] = None flags: typing.Optional[MountFlags] = None tags: typing.Optional[typing.Dict[str, typing.Any]] = None meta: typing.Optional[typing.Dict[str, typing.Any]] = None diff --git a/clients/python/agenta_client/types/mount_query.py b/clients/python/agenta_client/types/mount_query.py index fa9ec1d0a4..808909fe51 100644 --- a/clients/python/agenta_client/types/mount_query.py +++ b/clients/python/agenta_client/types/mount_query.py @@ -8,6 +8,7 @@ class MountQuery(UniversalBaseModel): session_id: typing.Optional[str] = None + agent_id: typing.Optional[str] = None include_archived: typing.Optional[bool] = None if IS_PYDANTIC_V2: diff --git a/clients/python/agenta_client/types/session_heartbeat_result.py b/clients/python/agenta_client/types/session_heartbeat_result.py new file mode 100644 index 0000000000..eec26d3180 --- /dev/null +++ b/clients/python/agenta_client/types/session_heartbeat_result.py @@ -0,0 +1,37 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel +from .session_stream import SessionStream + + +class SessionHeartbeatResult(UniversalBaseModel): + """ + A heartbeat's outcome: the reconciled stream plus the session's actual owner replica. + + `replica_id` is the replica that currently holds the affinity key after the claim + (this caller if it won or already held it, another replica otherwise). The runner reads + it to refuse serving a local sandbox session it does not own. + + `stream` is None when a losing replica heartbeats a session that has no row yet: it may + not create or stamp one, since that row belongs to the owner. + + `is_current_turn` (W7.4) is False when this turn_id's alive/running lock was gone or + reassigned at the moment of this beat — i.e. a cancel/steer/kill interrupted this turn + since the last heartbeat. The runner's watchdog reads this to abort the in-flight run; + without it a cancel that raced a heartbeat's nx=True re-acquire would silently re-arm the + SAME lock under the SAME turn_id and the interruption would never surface. + """ + stream: typing.Optional[SessionStream] = None + replica_id: str + is_current_turn: typing.Optional[bool] = None + + if IS_PYDANTIC_V2: + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2 + else: + class Config: + frozen = True + smart_union = True + extra = pydantic.Extra.allow diff --git a/clients/python/agenta_client/types/session_interaction.py b/clients/python/agenta_client/types/session_interaction.py index 3340a4179b..9e1b25b43a 100644 --- a/clients/python/agenta_client/types/session_interaction.py +++ b/clients/python/agenta_client/types/session_interaction.py @@ -1,5 +1,6 @@ # This file was auto-generated by Fern from our API Definition. +import datetime as dt import typing import pydantic @@ -11,13 +12,13 @@ class SessionInteraction(UniversalBaseModel): - id: typing.Optional[str] = None - created_at: typing.Optional[typing.Any] = None - updated_at: typing.Optional[typing.Any] = None - deleted_at: typing.Optional[typing.Any] = None + created_at: typing.Optional[dt.datetime] = None + updated_at: typing.Optional[dt.datetime] = None + deleted_at: typing.Optional[dt.datetime] = None created_by_id: typing.Optional[str] = None updated_by_id: typing.Optional[str] = None deleted_by_id: typing.Optional[str] = None + id: typing.Optional[str] = None project_id: typing.Optional[str] = None session_id: str turn_id: typing.Optional[str] = None diff --git a/clients/python/agenta_client/types/session_mount.py b/clients/python/agenta_client/types/session_mount.py index f4e4d100ec..e14cb79f06 100644 --- a/clients/python/agenta_client/types/session_mount.py +++ b/clients/python/agenta_client/types/session_mount.py @@ -22,6 +22,7 @@ class SessionMount(UniversalBaseModel): id: typing.Optional[str] = None project_id: str session_id: str + agent_id: typing.Optional[str] = None data: typing.Optional[MountData] = None flags: typing.Optional[MountFlags] = None tags: typing.Optional[typing.Dict[str, typing.Any]] = None diff --git a/clients/python/agenta_client/types/session_mount_query.py b/clients/python/agenta_client/types/session_mount_query.py index 911c6e6555..7b69bbf6d1 100644 --- a/clients/python/agenta_client/types/session_mount_query.py +++ b/clients/python/agenta_client/types/session_mount_query.py @@ -8,6 +8,7 @@ class SessionMountQuery(UniversalBaseModel): session_id: str + agent_id: typing.Optional[str] = None include_archived: typing.Optional[bool] = None if IS_PYDANTIC_V2: diff --git a/clients/python/agenta_client/types/session_record.py b/clients/python/agenta_client/types/session_record.py index 13809d2f47..a6f7ed9a2c 100644 --- a/clients/python/agenta_client/types/session_record.py +++ b/clients/python/agenta_client/types/session_record.py @@ -8,6 +8,12 @@ class SessionRecord(UniversalBaseModel): + created_at: typing.Optional[dt.datetime] = None + updated_at: typing.Optional[dt.datetime] = None + deleted_at: typing.Optional[dt.datetime] = None + created_by_id: typing.Optional[str] = None + updated_by_id: typing.Optional[str] = None + deleted_by_id: typing.Optional[str] = None record_id: str session_id: str project_id: str @@ -16,7 +22,8 @@ class SessionRecord(UniversalBaseModel): record_type: typing.Optional[str] = None record_source: typing.Optional[str] = None attributes: typing.Optional[typing.Dict[str, typing.Any]] = None - created_at: typing.Optional[dt.datetime] = None + turn_id: typing.Optional[str] = None + span_id: typing.Optional[str] = None if IS_PYDANTIC_V2: model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2 diff --git a/clients/python/agenta_client/types/session_heartbeat_response_model.py b/clients/python/agenta_client/types/session_response.py similarity index 79% rename from clients/python/agenta_client/types/session_heartbeat_response_model.py rename to clients/python/agenta_client/types/session_response.py index c12798d404..7cd6737bca 100644 --- a/clients/python/agenta_client/types/session_heartbeat_response_model.py +++ b/clients/python/agenta_client/types/session_response.py @@ -7,9 +7,9 @@ from .session_stream import SessionStream -class SessionHeartbeatResponseModel(UniversalBaseModel): - stream: typing.Optional[SessionStream] = None - replica_id: str +class SessionResponse(UniversalBaseModel): + count: typing.Optional[int] = None + session: typing.Optional[SessionStream] = None if IS_PYDANTIC_V2: model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2 diff --git a/clients/python/agenta_client/types/session_state.py b/clients/python/agenta_client/types/session_state.py deleted file mode 100644 index 9c31f20b15..0000000000 --- a/clients/python/agenta_client/types/session_state.py +++ /dev/null @@ -1,50 +0,0 @@ -# This file was auto-generated by Fern from our API Definition. - -import datetime as dt -import typing - -import pydantic -from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel -from .session_state_data import SessionStateData -from .session_state_flags import SessionStateFlags - - -class SessionState(UniversalBaseModel): - created_at: typing.Optional[dt.datetime] = None - updated_at: typing.Optional[dt.datetime] = None - deleted_at: typing.Optional[dt.datetime] = None - created_by_id: typing.Optional[str] = None - updated_by_id: typing.Optional[str] = None - deleted_by_id: typing.Optional[str] = None - id: typing.Optional[str] = pydantic.Field(default=None) - """ - Own uuid7 pk (state_id). - """ - - project_id: typing.Optional[str] = None - session_id: str = pydantic.Field() - """ - Bare session correlator (not an FK). - """ - - data: typing.Optional[SessionStateData] = pydantic.Field(default=None) - """ - Durable continuity state (resume ids + staleness guard). - """ - - sandbox_id: typing.Optional[str] = pydantic.Field(default=None) - """ - Remote sandbox id — the single source of truth resume pointer. - """ - - flags: typing.Optional[SessionStateFlags] = None - tags: typing.Optional[typing.Dict[str, typing.Any]] = None - meta: typing.Optional[typing.Dict[str, typing.Any]] = None - - if IS_PYDANTIC_V2: - model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2 - else: - class Config: - frozen = True - smart_union = True - extra = pydantic.Extra.allow diff --git a/clients/python/agenta_client/types/session_state_data.py b/clients/python/agenta_client/types/session_state_data.py deleted file mode 100644 index 09d4b57ce4..0000000000 --- a/clients/python/agenta_client/types/session_state_data.py +++ /dev/null @@ -1,40 +0,0 @@ -# This file was auto-generated by Fern from our API Definition. - -import typing - -import pydantic -from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel -from .harness_session_record import HarnessSessionRecord - - -class SessionStateData(UniversalBaseModel): - """ - Typed shape of the `data` column: the session's durable continuity state. - - Stored in the existing `data` JSON column (no dedicated columns) — every field is - read and compared in the runner, never queried server-side, so a typed DTO gives the - contract without a schema change. - """ - latest_agent_session_id: typing.Optional[str] = pydantic.Field(default=None) - """ - The latest-run harness's agentSessionId; a fast-path mirror of harness_sessions[].agent_session_id. - """ - - latest_turn_index: typing.Optional[int] = pydantic.Field(default=None) - """ - Conversation-level turn counter compared against each harness's turn_index. - """ - - harness_sessions: typing.Optional[typing.Dict[str, typing.Optional[HarnessSessionRecord]]] = pydantic.Field(default=None) - """ - Per-harness resume state, keyed by harness id (e.g. 'claude', 'pi'). Durable mirror of the staleness guard. - """ - - - if IS_PYDANTIC_V2: - model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2 - else: - class Config: - frozen = True - smart_union = True - extra = pydantic.Extra.allow diff --git a/clients/python/agenta_client/types/session_state_upsert_request.py b/clients/python/agenta_client/types/session_state_upsert_request.py deleted file mode 100644 index 1876845536..0000000000 --- a/clients/python/agenta_client/types/session_state_upsert_request.py +++ /dev/null @@ -1,33 +0,0 @@ -# This file was auto-generated by Fern from our API Definition. - -import typing - -import pydantic -from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel -from .session_state_data import SessionStateData - - -class SessionStateUpsertRequest(UniversalBaseModel): - data: typing.Optional[SessionStateData] = pydantic.Field(default=None) - """ - Full replacement of the continuity state (resume ids + staleness guard). - """ - - sandbox_id: typing.Optional[str] = pydantic.Field(default=None) - """ - Remote sandbox id to record alongside the continuity state. - """ - - sandbox_turn_index: typing.Optional[int] = pydantic.Field(default=None) - """ - the writer's conversation turn index; the pointer write is applied only when it is >= the row's data.latest_turn_index. - """ - - - if IS_PYDANTIC_V2: - model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2 - else: - class Config: - frozen = True - smart_union = True - extra = pydantic.Extra.allow diff --git a/clients/python/agenta_client/types/session_stream.py b/clients/python/agenta_client/types/session_stream.py index 8809e57195..f489faa8ba 100644 --- a/clients/python/agenta_client/types/session_stream.py +++ b/clients/python/agenta_client/types/session_stream.py @@ -9,13 +9,15 @@ class SessionStream(UniversalBaseModel): - id: str created_at: typing.Optional[dt.datetime] = None updated_at: typing.Optional[dt.datetime] = None deleted_at: typing.Optional[dt.datetime] = None created_by_id: typing.Optional[str] = None updated_by_id: typing.Optional[str] = None deleted_by_id: typing.Optional[str] = None + name: typing.Optional[str] = None + description: typing.Optional[str] = None + id: typing.Optional[str] = None project_id: str session_id: str flags: typing.Optional[SessionStreamFlags] = None diff --git a/clients/python/agenta_client/types/session_stream_command_response_model.py b/clients/python/agenta_client/types/session_stream_command_response.py similarity index 91% rename from clients/python/agenta_client/types/session_stream_command_response_model.py rename to clients/python/agenta_client/types/session_stream_command_response.py index dc5bd2abdd..8e1c27959c 100644 --- a/clients/python/agenta_client/types/session_stream_command_response_model.py +++ b/clients/python/agenta_client/types/session_stream_command_response.py @@ -7,7 +7,7 @@ from .command_mode import CommandMode -class SessionStreamCommandResponseModel(UniversalBaseModel): +class SessionStreamCommandResponse(UniversalBaseModel): mode: CommandMode session_id: str turn_id: typing.Optional[str] = None diff --git a/clients/python/agenta_client/types/session_stream_header_edit.py b/clients/python/agenta_client/types/session_stream_header_edit.py new file mode 100644 index 0000000000..fd49999542 --- /dev/null +++ b/clients/python/agenta_client/types/session_stream_header_edit.py @@ -0,0 +1,25 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel + + +class SessionStreamHeaderEdit(UniversalBaseModel): + """ + The rename edit: a full-PUT of the header fields only. + + Distinct from SessionStreamEdit (used by the flag-mirror/heartbeat paths) so the + liveness-only writes can never carry name/description, and vice versa. + """ + name: typing.Optional[str] = None + description: typing.Optional[str] = None + + if IS_PYDANTIC_V2: + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2 + else: + class Config: + frozen = True + smart_union = True + extra = pydantic.Extra.allow diff --git a/clients/python/agenta_client/types/session_stream_response_model.py b/clients/python/agenta_client/types/session_stream_response.py similarity index 91% rename from clients/python/agenta_client/types/session_stream_response_model.py rename to clients/python/agenta_client/types/session_stream_response.py index e064d078ba..960153ca32 100644 --- a/clients/python/agenta_client/types/session_stream_response_model.py +++ b/clients/python/agenta_client/types/session_stream_response.py @@ -7,7 +7,7 @@ from .session_stream import SessionStream -class SessionStreamResponseModel(UniversalBaseModel): +class SessionStreamResponse(UniversalBaseModel): stream: typing.Optional[SessionStream] = None if IS_PYDANTIC_V2: diff --git a/clients/python/agenta_client/types/session_streams_response_model.py b/clients/python/agenta_client/types/session_streams_response.py similarity index 91% rename from clients/python/agenta_client/types/session_streams_response_model.py rename to clients/python/agenta_client/types/session_streams_response.py index 53a1cab65e..a938580be2 100644 --- a/clients/python/agenta_client/types/session_streams_response_model.py +++ b/clients/python/agenta_client/types/session_streams_response.py @@ -7,7 +7,7 @@ from .session_stream import SessionStream -class SessionStreamsResponseModel(UniversalBaseModel): +class SessionStreamsResponse(UniversalBaseModel): count: int streams: typing.List[SessionStream] diff --git a/clients/python/agenta_client/types/session_turn.py b/clients/python/agenta_client/types/session_turn.py new file mode 100644 index 0000000000..6ae6d39525 --- /dev/null +++ b/clients/python/agenta_client/types/session_turn.py @@ -0,0 +1,39 @@ +# This file was auto-generated by Fern from our API Definition. + +import datetime as dt +import typing + +import pydantic +from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel +from .harness_kind import HarnessKind +from .reference import Reference + + +class SessionTurn(UniversalBaseModel): + created_at: typing.Optional[dt.datetime] = None + updated_at: typing.Optional[dt.datetime] = None + deleted_at: typing.Optional[dt.datetime] = None + created_by_id: typing.Optional[str] = None + updated_by_id: typing.Optional[str] = None + deleted_by_id: typing.Optional[str] = None + id: typing.Optional[str] = None + project_id: str + session_id: str + stream_id: str + turn_index: int + harness_kind: HarnessKind + agent_session_id: typing.Optional[str] = None + sandbox_id: typing.Optional[str] = None + references: typing.Optional[typing.List[Reference]] = None + trace_id: typing.Optional[str] = None + span_id: typing.Optional[str] = None + start_time: typing.Optional[dt.datetime] = None + end_time: typing.Optional[dt.datetime] = None + + if IS_PYDANTIC_V2: + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2 + else: + class Config: + frozen = True + smart_union = True + extra = pydantic.Extra.allow diff --git a/clients/python/agenta_client/types/session_turn_query.py b/clients/python/agenta_client/types/session_turn_query.py new file mode 100644 index 0000000000..420348e266 --- /dev/null +++ b/clients/python/agenta_client/types/session_turn_query.py @@ -0,0 +1,23 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel +from .harness_kind import HarnessKind +from .reference import Reference + + +class SessionTurnQuery(UniversalBaseModel): + session_id: typing.Optional[str] = None + stream_id: typing.Optional[str] = None + harness_kind: typing.Optional[HarnessKind] = None + references: typing.Optional[typing.List[Reference]] = None + + if IS_PYDANTIC_V2: + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2 + else: + class Config: + frozen = True + smart_union = True + extra = pydantic.Extra.allow diff --git a/clients/python/agenta_client/types/session_state_response.py b/clients/python/agenta_client/types/session_turn_response.py similarity index 77% rename from clients/python/agenta_client/types/session_state_response.py rename to clients/python/agenta_client/types/session_turn_response.py index a932d1413b..4872017d58 100644 --- a/clients/python/agenta_client/types/session_state_response.py +++ b/clients/python/agenta_client/types/session_turn_response.py @@ -4,12 +4,12 @@ import pydantic from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel -from .session_state import SessionState +from .session_turn import SessionTurn -class SessionStateResponse(UniversalBaseModel): +class SessionTurnResponse(UniversalBaseModel): count: typing.Optional[int] = None - session_state: typing.Optional[SessionState] = None + turn: typing.Optional[SessionTurn] = None if IS_PYDANTIC_V2: model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2 diff --git a/clients/python/agenta_client/types/session_state_flags.py b/clients/python/agenta_client/types/session_turns_response.py similarity index 71% rename from clients/python/agenta_client/types/session_state_flags.py rename to clients/python/agenta_client/types/session_turns_response.py index 09cd4194b8..e75756ceb2 100644 --- a/clients/python/agenta_client/types/session_state_flags.py +++ b/clients/python/agenta_client/types/session_turns_response.py @@ -4,9 +4,12 @@ import pydantic from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel +from .session_turn import SessionTurn -class SessionStateFlags(UniversalBaseModel): +class SessionTurnsResponse(UniversalBaseModel): + count: typing.Optional[int] = None + turns: typing.Optional[typing.List[SessionTurn]] = None if IS_PYDANTIC_V2: model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2 diff --git a/clients/python/agenta_client/types/sessions_response.py b/clients/python/agenta_client/types/sessions_response.py new file mode 100644 index 0000000000..3351dcd5ca --- /dev/null +++ b/clients/python/agenta_client/types/sessions_response.py @@ -0,0 +1,20 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel +from .session_stream import SessionStream + + +class SessionsResponse(UniversalBaseModel): + count: typing.Optional[int] = None + sessions: typing.Optional[typing.List[SessionStream]] = None + + if IS_PYDANTIC_V2: + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2 + else: + class Config: + frozen = True + smart_union = True + extra = pydantic.Extra.allow diff --git a/clients/python/agenta_client/types/span_input.py b/clients/python/agenta_client/types/span_input.py index 33bbc09401..8216691c6c 100644 --- a/clients/python/agenta_client/types/span_input.py +++ b/clients/python/agenta_client/types/span_input.py @@ -37,6 +37,9 @@ class SpanInput(UniversalBaseModel): end_time: typing.Optional[SpanInputEndTime] = None status_code: typing.Optional[OTelStatusCode] = None status_message: typing.Optional[str] = None + session_id: typing.Optional[str] = None + user_id: typing.Optional[str] = None + agent_id: typing.Optional[str] = None attributes: typing.Optional[typing.Dict[str, typing.Any]] = None references: typing.Optional[typing.List[OTelReferenceInput]] = None links: typing.Optional[typing.List[OTelLinkInput]] = None diff --git a/clients/python/agenta_client/types/span_output.py b/clients/python/agenta_client/types/span_output.py index 0f06f0a74a..a68d1db012 100644 --- a/clients/python/agenta_client/types/span_output.py +++ b/clients/python/agenta_client/types/span_output.py @@ -37,6 +37,9 @@ class SpanOutput(UniversalBaseModel): end_time: typing.Optional[SpanOutputEndTime] = None status_code: typing.Optional[OTelStatusCode] = None status_message: typing.Optional[str] = None + session_id: typing.Optional[str] = None + user_id: typing.Optional[str] = None + agent_id: typing.Optional[str] = None attributes: typing.Optional[typing.Dict[str, typing.Any]] = None references: typing.Optional[typing.List[OTelReferenceOutput]] = None links: typing.Optional[typing.List[OTelLinkOutput]] = None diff --git a/clients/python/agenta_client/types/spans_node_input.py b/clients/python/agenta_client/types/spans_node_input.py index 2c357d0780..8b84e6c2e5 100644 --- a/clients/python/agenta_client/types/spans_node_input.py +++ b/clients/python/agenta_client/types/spans_node_input.py @@ -38,6 +38,9 @@ class SpansNodeInput(UniversalBaseModel): end_time: typing.Optional[SpansNodeInputEndTime] = None status_code: typing.Optional[OTelStatusCode] = None status_message: typing.Optional[str] = None + session_id: typing.Optional[str] = None + user_id: typing.Optional[str] = None + agent_id: typing.Optional[str] = None attributes: typing.Optional[typing.Dict[str, typing.Any]] = None references: typing.Optional[typing.List[OTelReferenceInput]] = None links: typing.Optional[typing.List[OTelLinkInput]] = None diff --git a/clients/python/agenta_client/types/spans_node_output.py b/clients/python/agenta_client/types/spans_node_output.py index 857dbd3fbc..2fedad9a4c 100644 --- a/clients/python/agenta_client/types/spans_node_output.py +++ b/clients/python/agenta_client/types/spans_node_output.py @@ -38,6 +38,9 @@ class SpansNodeOutput(UniversalBaseModel): end_time: typing.Optional[SpansNodeOutputEndTime] = None status_code: typing.Optional[OTelStatusCode] = None status_message: typing.Optional[str] = None + session_id: typing.Optional[str] = None + user_id: typing.Optional[str] = None + agent_id: typing.Optional[str] = None attributes: typing.Optional[typing.Dict[str, typing.Any]] = None references: typing.Optional[typing.List[OTelReferenceOutput]] = None links: typing.Optional[typing.List[OTelLinkOutput]] = None diff --git a/clients/python/agenta_client/types/workflow_request_data.py b/clients/python/agenta_client/types/workflow_request_data.py new file mode 100644 index 0000000000..af9a5ea82d --- /dev/null +++ b/clients/python/agenta_client/types/workflow_request_data.py @@ -0,0 +1,23 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel + + +class WorkflowRequestData(UniversalBaseModel): + revision: typing.Optional[typing.Dict[str, typing.Any]] = None + parameters: typing.Optional[typing.Dict[str, typing.Any]] = None + testcase: typing.Optional[typing.Dict[str, typing.Any]] = None + inputs: typing.Optional[typing.Dict[str, typing.Any]] = None + trace: typing.Optional[typing.Dict[str, typing.Any]] = None + outputs: typing.Optional[typing.Any] = None + + if IS_PYDANTIC_V2: + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2 + else: + class Config: + frozen = True + smart_union = True + extra = pydantic.Extra.allow diff --git a/docs/docs/self-host/reference/01-configuration.mdx b/docs/docs/self-host/reference/01-configuration.mdx index 5b07ba593c..97bf16d996 100644 --- a/docs/docs/self-host/reference/01-configuration.mdx +++ b/docs/docs/self-host/reference/01-configuration.mdx @@ -210,12 +210,14 @@ so the Services container can reach the runner across the Compose network. `AGEN ### Routing and authentication These locate the runner and authenticate the call. Two of the three are caller-side: the Services -API and its SDK read them, not the runner. +API and its SDK read them, not the runner. The platform API also reads the locator and token +directly, for the session `kill` verb's sandbox teardown (`POST /kill` on the runner) — a second, +independent caller of the same runner HTTP surface, not routed through Services. | Variable | Role | Read by | Default | Secret | Helm | |---|---|---|---|---|---| -| `AGENTA_RUNNER_INTERNAL_URL` | Runner locator | Services API (caller-side) | Compose: `http://runner:8765` | no | `agentRunner.externalUrl`, else derived from `agentRunner.enabled` | -| `AGENTA_RUNNER_TOKEN` | Shared request credential | Services sends it; runner verifies it | **Required — no default** | yes | `agenta.runnerToken`, or `agentRunner.auth.tokenSecretRef` to source it from your own Secret | +| `AGENTA_RUNNER_INTERNAL_URL` | Runner locator | Services API and the platform API (both caller-side) | Compose: `http://runner:8765` | no | `agentRunner.externalUrl`, else derived from `agentRunner.enabled` | +| `AGENTA_RUNNER_TOKEN` | Shared request credential | Services and the platform API send it; runner verifies it | **Required — no default** | yes | `agenta.runnerToken`, or `agentRunner.auth.tokenSecretRef` to source it from your own Secret | | `AGENTA_RUNNER_TIMEOUT_SECONDS` | Idle timeout the caller allows on the run request | Services API and SDK (caller-side) | `360` | no | n/a | `AGENTA_RUNNER_TOKEN` is **required**, like `AGENTA_AUTH_KEY`. The runner refuses to start without @@ -234,11 +236,11 @@ Kubernetes, set `agenta.runnerToken` (the chart fails to render without it), or The token is defense in depth on top of network isolation, not a replacement for it: keep the runner on a private network and never publish its port. -`AGENTA_RUNNER_TOKEN` is the same credential at both ends. It does not belong to a sandbox or a -harness. The runner refuses to start without it and rejects any un-tokened request with `401`; there -is no unauthenticated mode. Set the same value on the runner and on the Services API; in Helm, point -`agentRunner.auth.tokenSecretRef` at a Secret holding it — the chart injects the same value into both -the runner and the Services deployment. +`AGENTA_RUNNER_TOKEN` is the same credential across all parties. It does not belong to a sandbox or +a harness. The runner refuses to start without it and rejects any un-tokened request with `401`; +there is no unauthenticated mode. Set the same value on the runner, the Services API, and the +platform API; in Helm, point `agentRunner.auth.tokenSecretRef` at a Secret holding it — the chart +injects the same value into the runner, Services, and API deployments. `AGENTA_RUNNER_TIMEOUT_SECONDS` bounds idle time on the caller's streaming transport, not the total run duration, and the runner never reads it. The runner enforces its own deadlines through diff --git a/hosting/docker-compose/ee/docker-compose.dev.yml b/hosting/docker-compose/ee/docker-compose.dev.yml index 19956cb1c8..b26c27e0cf 100644 --- a/hosting/docker-compose/ee/docker-compose.dev.yml +++ b/hosting/docker-compose/ee/docker-compose.dev.yml @@ -125,6 +125,10 @@ services: - ${ENV_FILE:-./.env.ee.dev} environment: DOCKER_NETWORK_MODE: ${DOCKER_NETWORK_MODE:-bridge} + # kill's direct runner hop (W7.3, sandbox teardown) — same compose-network + # address + shared token as the `services` container's runner_url(). + AGENTA_RUNNER_INTERNAL_URL: ${AGENTA_RUNNER_INTERNAL_URL:-http://runner:8765} + AGENTA_RUNNER_TOKEN: ${AGENTA_RUNNER_TOKEN:?AGENTA_RUNNER_TOKEN is required} # === NETWORK ============================================== # networks: - agenta-network diff --git a/hosting/docker-compose/ee/docker-compose.gh.yml b/hosting/docker-compose/ee/docker-compose.gh.yml index 0877f2a80f..3ad83934fd 100644 --- a/hosting/docker-compose/ee/docker-compose.gh.yml +++ b/hosting/docker-compose/ee/docker-compose.gh.yml @@ -54,6 +54,10 @@ services: environment: - SCRIPT_NAME=/api - DOCKER_NETWORK_MODE=${DOCKER_NETWORK_MODE:-bridge} + # kill's direct runner hop (W7.3, sandbox teardown) — same address + shared + # token as the `services` container's runner_url(). + - AGENTA_RUNNER_INTERNAL_URL=${AGENTA_RUNNER_INTERNAL_URL:-http://runner:8765} + - AGENTA_RUNNER_TOKEN=${AGENTA_RUNNER_TOKEN:?AGENTA_RUNNER_TOKEN is required} - AGENTA_STORE_ENDPOINT_URL=${AGENTA_STORE_ENDPOINT_URL:-} - AGENTA_STORE_ACCESS_KEY=${AGENTA_STORE_ACCESS_KEY:-} - AGENTA_STORE_SECRET_KEY=${AGENTA_STORE_SECRET_KEY:-} diff --git a/hosting/docker-compose/oss/docker-compose.dev.yml b/hosting/docker-compose/oss/docker-compose.dev.yml index a2fb80bb18..604d62bfa1 100644 --- a/hosting/docker-compose/oss/docker-compose.dev.yml +++ b/hosting/docker-compose/oss/docker-compose.dev.yml @@ -120,6 +120,10 @@ services: - ${ENV_FILE:-./.env.oss.dev} environment: DOCKER_NETWORK_MODE: ${DOCKER_NETWORK_MODE:-bridge} + # kill's direct runner hop (W7.3, sandbox teardown) — same compose-network + # address + shared token as the `services` container's runner_url(). + AGENTA_RUNNER_INTERNAL_URL: ${AGENTA_RUNNER_INTERNAL_URL:-http://runner:8765} + AGENTA_RUNNER_TOKEN: ${AGENTA_RUNNER_TOKEN:?AGENTA_RUNNER_TOKEN is required} # === NETWORK ============================================== # networks: - agenta-network diff --git a/hosting/docker-compose/oss/docker-compose.gh.yml b/hosting/docker-compose/oss/docker-compose.gh.yml index c08d24d555..a52b43edd8 100644 --- a/hosting/docker-compose/oss/docker-compose.gh.yml +++ b/hosting/docker-compose/oss/docker-compose.gh.yml @@ -54,6 +54,10 @@ services: environment: - SCRIPT_NAME=/api - DOCKER_NETWORK_MODE=${DOCKER_NETWORK_MODE:-bridge} + # kill's direct runner hop (W7.3, sandbox teardown) — same address + shared + # token as the `services` container's runner_url(). + - AGENTA_RUNNER_INTERNAL_URL=${AGENTA_RUNNER_INTERNAL_URL:-http://runner:8765} + - AGENTA_RUNNER_TOKEN=${AGENTA_RUNNER_TOKEN:?AGENTA_RUNNER_TOKEN is required} - AGENTA_STORE_ENDPOINT_URL=${AGENTA_STORE_ENDPOINT_URL:-} - AGENTA_STORE_ACCESS_KEY=${AGENTA_STORE_ACCESS_KEY:-} - AGENTA_STORE_SECRET_KEY=${AGENTA_STORE_SECRET_KEY:-} diff --git a/hosting/kubernetes/helm/templates/api-deployment.yaml b/hosting/kubernetes/helm/templates/api-deployment.yaml index 8b9e1c6acf..8143adc034 100644 --- a/hosting/kubernetes/helm/templates/api-deployment.yaml +++ b/hosting/kubernetes/helm/templates/api-deployment.yaml @@ -75,6 +75,7 @@ spec: {{- include "agenta.commonEnv" . | nindent 12 }} - name: SCRIPT_NAME value: "/api" + {{- include "agenta.agentRunner.servicesEnv" . | nindent 12 }} {{- range $key, $val := $api.env }} - name: {{ $key }} value: {{ $val | quote }} diff --git a/sdks/python/agenta/sdk/agents/__init__.py b/sdks/python/agenta/sdk/agents/__init__.py index 9ab8ef6e2f..f25778e075 100644 --- a/sdks/python/agenta/sdk/agents/__init__.py +++ b/sdks/python/agenta/sdk/agents/__init__.py @@ -65,7 +65,7 @@ HarnessAgentTemplate, HarnessCapabilities, HarnessIdentity, - HarnessType, + HarnessKind, InvalidPermissionDefaultError, Message, NetworkEgress, @@ -160,7 +160,7 @@ "PiAgentTemplate", "ClaudeAgentTemplate", "AgentaAgentTemplate", - "HarnessType", + "HarnessKind", "HarnessIdentity", "HARNESS_IDENTITIES", "HarnessCapabilities", diff --git a/sdks/python/agenta/sdk/agents/adapters/harnesses.py b/sdks/python/agenta/sdk/agents/adapters/harnesses.py index e04c60be75..8fcf837086 100644 --- a/sdks/python/agenta/sdk/agents/adapters/harnesses.py +++ b/sdks/python/agenta/sdk/agents/adapters/harnesses.py @@ -26,7 +26,7 @@ from ..dtos import ( AgentaAgentTemplate, ClaudeAgentTemplate, - HarnessType, + HarnessKind, PiAgentTemplate, SessionConfig, ) @@ -56,7 +56,7 @@ def _normalize_tool_specs(specs: List[Dict[str, Any]]) -> List[ToolSpec]: class PiHarness(Harness): - harness_type = HarnessType.PI + harness_type = HarnessKind.PI def _to_harness_config(self, config: SessionConfig) -> PiAgentTemplate: # Pi delivers tools natively: built-in names plus resolved specs registered through @@ -86,7 +86,7 @@ def _to_harness_config(self, config: SessionConfig) -> PiAgentTemplate: class ClaudeHarness(Harness): - harness_type = HarnessType.CLAUDE + harness_type = HarnessKind.CLAUDE def _to_harness_config(self, config: SessionConfig) -> ClaudeAgentTemplate: # Claude has no Pi built-in tools; drop them rather than ship a name Claude cannot @@ -125,7 +125,7 @@ class AgentaHarness(Harness): forced platform skill(s) are unioned in (de-duped by name) so a custom config that drops the default template's ``_agenta`` embed still carries the platform skill.""" - harness_type = HarnessType.AGENTA + harness_type = HarnessKind.AGENTA def _to_harness_config(self, config: SessionConfig) -> AgentaAgentTemplate: # The author's Pi options still apply; the pi_agenta harness reads the same harness @@ -153,15 +153,15 @@ def _to_harness_config(self, config: SessionConfig) -> AgentaAgentTemplate: ) -_HARNESSES: Dict[HarnessType, Type[Harness]] = { - HarnessType.PI: PiHarness, - HarnessType.CLAUDE: ClaudeHarness, - HarnessType.AGENTA: AgentaHarness, +_HARNESSES: Dict[HarnessKind, Type[Harness]] = { + HarnessKind.PI: PiHarness, + HarnessKind.CLAUDE: ClaudeHarness, + HarnessKind.AGENTA: AgentaHarness, } def make_harness( - harness_type: "HarnessType | str", environment: Environment + harness_type: "HarnessKind | str", environment: Environment ) -> Harness: """Construct the Harness for a harness type over an environment. @@ -169,7 +169,7 @@ def make_harness( :class:`~agenta.sdk.agents.errors.UnsupportedHarnessError` if the environment's backend cannot drive it. """ - resolved = HarnessType.coerce(harness_type) + resolved = HarnessKind.coerce(harness_type) try: cls = _HARNESSES[resolved] except KeyError as exc: diff --git a/sdks/python/agenta/sdk/agents/adapters/local.py b/sdks/python/agenta/sdk/agents/adapters/local.py index 68d0332f34..4f2dfe761d 100644 --- a/sdks/python/agenta/sdk/agents/adapters/local.py +++ b/sdks/python/agenta/sdk/agents/adapters/local.py @@ -22,14 +22,14 @@ from typing import Mapping, Optional -from ..dtos import HarnessAgentTemplate, HarnessType, RunContext, TraceContext +from ..dtos import HarnessAgentTemplate, HarnessKind, RunContext, TraceContext from ..interfaces import Backend, Sandbox, Session class LocalBackend(Backend): """Run Pi (bundled JS) or Claude (``claude-agent-sdk``) on this machine.""" - supported_harnesses = frozenset({HarnessType.PI, HarnessType.CLAUDE}) + supported_harnesses = frozenset({HarnessKind.PI, HarnessKind.CLAUDE}) async def create_sandbox(self) -> Sandbox: raise NotImplementedError( @@ -42,7 +42,7 @@ async def create_session( sandbox: Sandbox, config: HarnessAgentTemplate, *, - harness: HarnessType, + harness: HarnessKind, secrets: Optional[Mapping[str, str]] = None, trace: Optional[TraceContext] = None, run_context: Optional[RunContext] = None, diff --git a/sdks/python/agenta/sdk/agents/adapters/sandbox_agent.py b/sdks/python/agenta/sdk/agents/adapters/sandbox_agent.py index f641a994e1..93c7844f95 100644 --- a/sdks/python/agenta/sdk/agents/adapters/sandbox_agent.py +++ b/sdks/python/agenta/sdk/agents/adapters/sandbox_agent.py @@ -21,7 +21,7 @@ AgentResult, EventSink, HarnessAgentTemplate, - HarnessType, + HarnessKind, Message, RunContext, TraceContext, @@ -63,7 +63,7 @@ def __init__( sandbox: SandboxAgentSandbox, config: HarnessAgentTemplate, *, - harness: HarnessType, + harness: HarnessKind, secrets: Optional[Mapping[str, str]], trace: Optional[TraceContext], run_context: Optional[RunContext], @@ -124,7 +124,7 @@ class SandboxAgentBackend(Backend): """The sandbox-agent engine: a harness over ACP through the TS runner. Pi, Claude, and Agenta.""" supported_harnesses = frozenset( - {HarnessType.PI, HarnessType.CLAUDE, HarnessType.AGENTA} + {HarnessKind.PI, HarnessKind.CLAUDE, HarnessKind.AGENTA} ) def __init__( @@ -155,7 +155,7 @@ async def create_session( sandbox: Sandbox, config: HarnessAgentTemplate, *, - harness: HarnessType, + harness: HarnessKind, secrets: Optional[Mapping[str, str]] = None, trace: Optional[TraceContext] = None, run_context: Optional[RunContext] = None, diff --git a/sdks/python/agenta/sdk/agents/dtos.py b/sdks/python/agenta/sdk/agents/dtos.py index 455e120f99..b7b733d2c3 100644 --- a/sdks/python/agenta/sdk/agents/dtos.py +++ b/sdks/python/agenta/sdk/agents/dtos.py @@ -42,7 +42,7 @@ # --------------------------------------------------------------------------- -class HarnessType(str, Enum): +class HarnessKind(str, Enum): """The coding agent program a run drives. A backend declares which it supports. ``pi_core`` is plain Pi; ``pi_agenta`` is Pi with Agenta's forced skills, prompt, and @@ -54,7 +54,7 @@ class HarnessType(str, Enum): AGENTA = "pi_agenta" @classmethod - def coerce(cls, value: "HarnessType | str") -> "HarnessType": + def coerce(cls, value: "HarnessKind | str") -> "HarnessKind": """Accept either an enum or a loose string (the playground sends a string).""" if isinstance(value, cls): return value @@ -76,7 +76,7 @@ def coerce(cls, value: "HarnessType | str") -> "HarnessType": class HarnessIdentity(BaseModel): """One harness's interface identity: its bare value, versioned slug, and display name. - ``value`` is the wire/runtime selector (the ``HarnessType`` value); ``slug`` is the + ``value`` is the wire/runtime selector (the ``HarnessKind`` value); ``slug`` is the versioned contract identity in the repo's slug grammar; ``name`` is the human-facing label the playground dropdown shows. This is the single source the agent_template schema builds the harness ``oneOf`` from, so the slug, name, and value never drift across the SDK, the service @@ -87,22 +87,22 @@ class HarnessIdentity(BaseModel): name: str -# One entry per ``HarnessType``. The slug version is ``v0`` for every harness today (the +# One entry per ``HarnessKind``. The slug version is ``v0`` for every harness today (the # contract has not broken). ``HARNESS_IDENTITIES`` is the single source of truth. HARNESS_IDENTITIES: List[HarnessIdentity] = [ HarnessIdentity( - value=HarnessType.PI.value, - slug=f"agenta:harness:{HarnessType.PI.value}:v0", + value=HarnessKind.PI.value, + slug=f"agenta:harness:{HarnessKind.PI.value}:v0", name="Pi", ), HarnessIdentity( - value=HarnessType.AGENTA.value, - slug=f"agenta:harness:{HarnessType.AGENTA.value}:v0", + value=HarnessKind.AGENTA.value, + slug=f"agenta:harness:{HarnessKind.AGENTA.value}:v0", name="Pi (Agenta)", ), HarnessIdentity( - value=HarnessType.CLAUDE.value, - slug=f"agenta:harness:{HarnessType.CLAUDE.value}:v0", + value=HarnessKind.CLAUDE.value, + slug=f"agenta:harness:{HarnessKind.CLAUDE.value}:v0", name="Claude Code", ), ] @@ -691,7 +691,7 @@ class HarnessAgentTemplate(BaseModel): model_config = ConfigDict(populate_by_name=True) - harness: ClassVar[HarnessType] + harness: ClassVar[HarnessKind] agents_md: Optional[str] = None # ``model`` stays the back-compat plain string the adapter hands to the harness. @@ -852,7 +852,7 @@ class PiAgentTemplate(HarnessAgentTemplate): either way, so AGENTS.md remains the right home for project conventions and these are only for changing or extending Pi's base persona.""" - harness: ClassVar[HarnessType] = HarnessType.PI + harness: ClassVar[HarnessKind] = HarnessKind.PI builtin_names: List[str] = Field( default_factory=list, @@ -900,7 +900,7 @@ def wire_prompt(self) -> Dict[str, Any]: class ClaudeAgentTemplate(HarnessAgentTemplate): """Claude's config. No Pi built-ins; tools are delivered over MCP.""" - harness: ClassVar[HarnessType] = HarnessType.CLAUDE + harness: ClassVar[HarnessKind] = HarnessKind.CLAUDE tool_specs: List[ToolSpec] = Field( default_factory=list, @@ -958,7 +958,7 @@ class AgentaAgentTemplate(PiAgentTemplate): system-prompt layers). ``skills`` ride the inherited :meth:`wire_skills` seam as resolved inline packages, not through ``wire_tools`` (skills are not tools).""" - harness: ClassVar[HarnessType] = HarnessType.AGENTA + harness: ClassVar[HarnessKind] = HarnessKind.AGENTA # --------------------------------------------------------------------------- diff --git a/sdks/python/agenta/sdk/agents/errors.py b/sdks/python/agenta/sdk/agents/errors.py index d37619b5dd..a1a64b1b1c 100644 --- a/sdks/python/agenta/sdk/agents/errors.py +++ b/sdks/python/agenta/sdk/agents/errors.py @@ -6,7 +6,7 @@ from agenta.sdk.engines.running.errors import ERRORS_BASE_URL, ErrorStatus -from .dtos import HarnessType +from .dtos import HarnessKind from .tools.errors import ToolResolutionError __all__ = [ @@ -23,7 +23,7 @@ class UnsupportedHarnessError(RuntimeError): """Raised when a harness is asked to run on a backend that cannot drive it.""" - def __init__(self, harness: HarnessType, backend: "Backend") -> None: + def __init__(self, harness: HarnessKind, backend: "Backend") -> None: supported = ", ".join(sorted(h.value for h in backend.supported_harnesses)) super().__init__( f"{type(backend).__name__} cannot drive harness '{harness.value}'; " diff --git a/sdks/python/agenta/sdk/agents/interfaces.py b/sdks/python/agenta/sdk/agents/interfaces.py index 28b7ddd36c..dce5efa88a 100644 --- a/sdks/python/agenta/sdk/agents/interfaces.py +++ b/sdks/python/agenta/sdk/agents/interfaces.py @@ -25,7 +25,7 @@ AgentResult, EventSink, HarnessAgentTemplate, - HarnessType, + HarnessKind, Message, RunContext, SessionConfig, @@ -101,9 +101,9 @@ class Backend(ABC): """ #: The single source of truth for what this engine can run. - supported_harnesses: ClassVar[FrozenSet[HarnessType]] = frozenset() + supported_harnesses: ClassVar[FrozenSet[HarnessKind]] = frozenset() - def supports(self, harness: HarnessType) -> bool: + def supports(self, harness: HarnessKind) -> bool: return harness in self.supported_harnesses async def setup(self) -> None: @@ -124,7 +124,7 @@ async def create_session( sandbox: Sandbox, config: HarnessAgentTemplate, *, - harness: HarnessType, + harness: HarnessKind, secrets: Optional[Mapping[str, str]] = None, trace: Optional[TraceContext] = None, run_context: Optional[RunContext] = None, @@ -178,7 +178,7 @@ async def create_session( self, config: HarnessAgentTemplate, *, - harness: HarnessType, + harness: HarnessKind, session_config: SessionConfig, provisioning: Optional[Mapping[str, bytes]] = None, ) -> Session: @@ -211,7 +211,7 @@ class Harness(ABC): the per-harness knowledge lives here. """ - harness_type: ClassVar[HarnessType] + harness_type: ClassVar[HarnessKind] def __init__(self, environment: Environment) -> None: if not environment.backend.supports(self.harness_type): @@ -245,7 +245,7 @@ def _provisioning(self, config: SessionConfig) -> Mapping[str, bytes]: instructions = config.agent.instructions if instructions and instructions.strip(): filename = ( - "CLAUDE.md" if self.harness_type is HarnessType.CLAUDE else "AGENTS.md" + "CLAUDE.md" if self.harness_type is HarnessKind.CLAUDE else "AGENTS.md" ) files[filename] = instructions.encode("utf-8") return files diff --git a/sdks/python/agenta/sdk/agents/utils/wire.py b/sdks/python/agenta/sdk/agents/utils/wire.py index da7c957d46..d8e00e2111 100644 --- a/sdks/python/agenta/sdk/agents/utils/wire.py +++ b/sdks/python/agenta/sdk/agents/utils/wire.py @@ -29,7 +29,7 @@ AgentResult, HarnessAgentTemplate, HarnessCapabilities, - HarnessType, + HarnessKind, Message, RunContext, TraceContext, @@ -81,7 +81,7 @@ def sanitize_runner_error(error: Any) -> str: def request_to_wire( *, - harness: HarnessType, + harness: HarnessKind, sandbox: str, config: HarnessAgentTemplate, messages: Sequence[Message], diff --git a/sdks/python/agenta/sdk/engines/tracing/tracing.py b/sdks/python/agenta/sdk/engines/tracing/tracing.py index f785528798..4e0a7bb047 100644 --- a/sdks/python/agenta/sdk/engines/tracing/tracing.py +++ b/sdks/python/agenta/sdk/engines/tracing/tracing.py @@ -258,6 +258,26 @@ def store_user( if user_id: span.set_attribute("id", user_id, namespace="user") + def store_agent( + self, + agent_id: Optional[str] = None, + span: Optional[Span] = None, + ): + """Set agent attributes on the current span. + + Args: + agent_id: Unique identifier for the running artifact (workflow / + application / evaluator id) + span: Optional span to set attributes on (defaults to current span) + """ + self._warn_if_not_initialized("store_agent") + with suppress(): + if span is None: + span = self.get_current_span() + + if agent_id: + span.set_attribute("id", agent_id, namespace="agent") + def extract( self, *args, diff --git a/sdks/python/agenta/sdk/middlewares/running/normalizer.py b/sdks/python/agenta/sdk/middlewares/running/normalizer.py index 1d8304ca69..d5f1ad9b64 100644 --- a/sdks/python/agenta/sdk/middlewares/running/normalizer.py +++ b/sdks/python/agenta/sdk/middlewares/running/normalizer.py @@ -117,6 +117,16 @@ async def _normalize_request( return normalized + @staticmethod + def _resolve_agent_id(ctx: RunningContext) -> Optional[str]: + """The running artifact's id (workflow / application / evaluator) — + they all resolve through the same WorkflowRevision.artifact_id.""" + revision = ctx.revision + if not isinstance(revision, dict): + return None + agent_id = revision.get("artifact_id") or revision.get("workflow_id") + return str(agent_id) if agent_id else None + @staticmethod def _correlation_ids(): """trace_id / span_id (from the span link) + session_id — all off the @@ -276,11 +286,16 @@ async def __call__( scoped.session_id = session_id token = TracingContext.set(scoped) try: - if session_id: + agent_id = self._resolve_agent_id(ctx) + + if session_id or agent_id: import agenta as ag if ag.tracing is not None: - ag.tracing.store_session(session_id=session_id) + if session_id: + ag.tracing.store_session(session_id=session_id) + if agent_id: + ag.tracing.store_agent(agent_id=agent_id) kwargs = await self._normalize_request(request, handler) diff --git a/sdks/python/agenta/sdk/models/tracing.py b/sdks/python/agenta/sdk/models/tracing.py index a7c60f3904..68aee3f5f1 100644 --- a/sdks/python/agenta/sdk/models/tracing.py +++ b/sdks/python/agenta/sdk/models/tracing.py @@ -203,6 +203,12 @@ class Span(Lifecycle): status_code: Optional[OTelStatusCode] = None status_message: Optional[str] = None + # Root-span-only (parent_id is None); lifted from ag.session/user/agent + # attributes at ingestion. Never set on child spans. + session_id: Optional[str] = None + user_id: Optional[str] = None + agent_id: Optional[str] = None + attributes: Optional[OTelAttributes] = None references: Optional[OTelReferences] = None links: Optional[OTelLinks] = None diff --git a/sdks/python/oss/tests/pytest/integration/agents/_fake_runner_backend.py b/sdks/python/oss/tests/pytest/integration/agents/_fake_runner_backend.py index 524c3ef93a..5ad3350ef9 100644 --- a/sdks/python/oss/tests/pytest/integration/agents/_fake_runner_backend.py +++ b/sdks/python/oss/tests/pytest/integration/agents/_fake_runner_backend.py @@ -17,7 +17,7 @@ AgentResult, EventSink, HarnessAgentTemplate, - HarnessType, + HarnessKind, Message, RunContext, TraceContext, @@ -53,7 +53,7 @@ def __init__( backend: "FakeRunnerBackend", config: HarnessAgentTemplate, *, - harness: HarnessType, + harness: HarnessKind, secrets: Optional[Mapping[str, str]], trace: Optional[TraceContext], run_context: Optional[RunContext], @@ -120,7 +120,7 @@ class FakeRunnerBackend(Backend): """ supported_harnesses = frozenset( - {HarnessType.PI, HarnessType.CLAUDE, HarnessType.AGENTA} + {HarnessKind.PI, HarnessKind.CLAUDE, HarnessKind.AGENTA} ) def __init__( @@ -149,7 +149,7 @@ async def create_session( sandbox: Sandbox, config: HarnessAgentTemplate, *, - harness: HarnessType, + harness: HarnessKind, secrets: Optional[Mapping[str, str]] = None, trace: Optional[TraceContext] = None, run_context: Optional[RunContext] = None, diff --git a/sdks/python/oss/tests/pytest/integration/agents/_qa_transcripts.py b/sdks/python/oss/tests/pytest/integration/agents/_qa_transcripts.py index 8887c0318d..8867bf5b76 100644 --- a/sdks/python/oss/tests/pytest/integration/agents/_qa_transcripts.py +++ b/sdks/python/oss/tests/pytest/integration/agents/_qa_transcripts.py @@ -35,8 +35,8 @@ ) # The QA captures predate the harness-value rename (``matrix.md``'s 2026-06-25 wire-contract -# note): the wire moved from ``pi``/``agenta``/``claude`` to today's ``HarnessType`` values -# ``pi_core``/``pi_agenta``/``claude``. A bare ``"pi"`` no longer parses (``HarnessType("pi")`` +# note): the wire moved from ``pi``/``agenta``/``claude`` to today's ``HarnessKind`` values +# ``pi_core``/``pi_agenta``/``claude``. A bare ``"pi"`` no longer parses (``HarnessKind("pi")`` # raises), so this table is required, not cosmetic. _HARNESS_RENAME = {"pi": "pi_core", "agenta": "pi_agenta", "claude": "claude"} diff --git a/sdks/python/oss/tests/pytest/unit/agents/conftest.py b/sdks/python/oss/tests/pytest/unit/agents/conftest.py index c319e04fff..e5375b6d05 100644 --- a/sdks/python/oss/tests/pytest/unit/agents/conftest.py +++ b/sdks/python/oss/tests/pytest/unit/agents/conftest.py @@ -21,7 +21,7 @@ from agenta.sdk.agents import ( AgentResult, Environment, - HarnessType, + HarnessKind, ) from agenta.sdk.agents.interfaces import Backend, Sandbox, Session from agenta.sdk.agents.streaming import AgentStream @@ -108,7 +108,7 @@ class FakeBackend(Backend): def __init__( self, *, - supported: Sequence[HarnessType] = (HarnessType.PI, HarnessType.CLAUDE), + supported: Sequence[HarnessKind] = (HarnessKind.PI, HarnessKind.CLAUDE), result: Optional[AgentResult] = None, result_session_id: Optional[str] = None, raise_on_prompt: bool = False, diff --git a/sdks/python/oss/tests/pytest/unit/agents/connections/test_dtos_model_ref.py b/sdks/python/oss/tests/pytest/unit/agents/connections/test_dtos_model_ref.py index 4d9ae6377f..0ff60ae8a6 100644 --- a/sdks/python/oss/tests/pytest/unit/agents/connections/test_dtos_model_ref.py +++ b/sdks/python/oss/tests/pytest/unit/agents/connections/test_dtos_model_ref.py @@ -11,7 +11,7 @@ from agenta.sdk.agents import ( AgentTemplate, Connection, - HarnessType, + HarnessKind, Message, ModelRef, PiAgentTemplate, @@ -105,7 +105,7 @@ def test_wire_model_ref_emits_self_managed_connection_without_slug(): def test_string_only_config_wire_has_no_new_keys(): # The whole point of Slice 1: a string-only config's payload gains no new keys. payload = request_to_wire( - harness=HarnessType.PI, + harness=HarnessKind.PI, sandbox="local", config=PiAgentTemplate(model="openai-codex/gpt-5.5"), messages=[Message(role="user", content="hi")], @@ -117,7 +117,7 @@ def test_string_only_config_wire_has_no_new_keys(): def test_structured_config_wire_carries_provider_and_connection(): payload = request_to_wire( - harness=HarnessType.PI, + harness=HarnessKind.PI, sandbox="local", config=PiAgentTemplate( model={ diff --git a/sdks/python/oss/tests/pytest/unit/agents/skills/test_skills_e2e.py b/sdks/python/oss/tests/pytest/unit/agents/skills/test_skills_e2e.py index f5e85c308a..fee54841c9 100644 --- a/sdks/python/oss/tests/pytest/unit/agents/skills/test_skills_e2e.py +++ b/sdks/python/oss/tests/pytest/unit/agents/skills/test_skills_e2e.py @@ -26,7 +26,7 @@ from agenta.sdk.agents import ( AgentTemplate, Environment, - HarnessType, + HarnessKind, Message, PiHarness, SessionConfig, @@ -55,7 +55,7 @@ def _pi_wire(env: Environment, agent: AgentTemplate) -> dict: harness = PiHarness(env) pi_config = harness._to_harness_config(SessionConfig(agent=agent)) return request_to_wire( - harness=HarnessType.PI, + harness=HarnessKind.PI, sandbox="local", config=pi_config, messages=[Message(role="user", content="ship it")], @@ -66,7 +66,7 @@ def _pi_wire(env: Environment, agent: AgentTemplate) -> dict: def test_inline_skill_materializes_on_the_wire(make_env): - env = make_env(supported=[HarnessType.PI]) + env = make_env(supported=[HarnessKind.PI]) agent = AgentTemplate(instructions="hi", model="gpt-5.5", skills=[_INLINE_SKILL]) wire = _pi_wire(env, agent) @@ -92,7 +92,7 @@ def test_inline_skill_materializes_on_the_wire(make_env): def test_minimal_inline_skill_omits_optional_flags_on_the_wire(make_env): - env = make_env(supported=[HarnessType.PI]) + env = make_env(supported=[HarnessKind.PI]) agent = AgentTemplate( instructions="hi", model="gpt-5.5", @@ -193,7 +193,7 @@ async def post(self, *args, **kwargs): # Now carry the resolved params the rest of the way, exactly as the handler does: build the # AgentTemplate from them, translate through the harness, and serialize the wire. - env = make_env(supported=[HarnessType.PI]) + env = make_env(supported=[HarnessKind.PI]) agent = AgentTemplate.from_params(request.data.parameters) wire = _pi_wire(env, agent) diff --git a/sdks/python/oss/tests/pytest/unit/agents/test_agent_composition_seam.py b/sdks/python/oss/tests/pytest/unit/agents/test_agent_composition_seam.py index 57181dfaf6..b30691e652 100644 --- a/sdks/python/oss/tests/pytest/unit/agents/test_agent_composition_seam.py +++ b/sdks/python/oss/tests/pytest/unit/agents/test_agent_composition_seam.py @@ -12,7 +12,7 @@ import pytest -from agenta.sdk.agents import AgentResult, HarnessType +from agenta.sdk.agents import AgentResult, HarnessKind from agenta.sdk.agents.connections import ( ConnectionResolutionError, ResolvedConnection, @@ -77,7 +77,7 @@ async def destroy(self) -> None: class _FakeBackend(Backend): supported_harnesses = frozenset( - {HarnessType.PI, HarnessType.CLAUDE, HarnessType.AGENTA} + {HarnessKind.PI, HarnessKind.CLAUDE, HarnessKind.AGENTA} ) def __init__(self, *, output: str = "hi") -> None: diff --git a/sdks/python/oss/tests/pytest/unit/agents/test_dtos_capabilities_events.py b/sdks/python/oss/tests/pytest/unit/agents/test_dtos_capabilities_events.py index 253c0a1c35..0bf431aebb 100644 --- a/sdks/python/oss/tests/pytest/unit/agents/test_dtos_capabilities_events.py +++ b/sdks/python/oss/tests/pytest/unit/agents/test_dtos_capabilities_events.py @@ -12,7 +12,7 @@ from agenta.sdk.agents import ( Event, HarnessCapabilities, - HarnessType, + HarnessKind, ToolCallback, TraceContext, ) @@ -78,10 +78,10 @@ def test_tool_callback_to_wire(): def test_harness_type_coerce(): - assert HarnessType.coerce(HarnessType.PI) is HarnessType.PI - assert HarnessType.coerce("pi_core") is HarnessType.PI - assert HarnessType.coerce("PI_CORE") is HarnessType.PI # case-insensitive - assert HarnessType.coerce("pi_agenta") is HarnessType.AGENTA - assert HarnessType.coerce("claude") is HarnessType.CLAUDE + assert HarnessKind.coerce(HarnessKind.PI) is HarnessKind.PI + assert HarnessKind.coerce("pi_core") is HarnessKind.PI + assert HarnessKind.coerce("PI_CORE") is HarnessKind.PI # case-insensitive + assert HarnessKind.coerce("pi_agenta") is HarnessKind.AGENTA + assert HarnessKind.coerce("claude") is HarnessKind.CLAUDE with pytest.raises(ValueError): - HarnessType.coerce("bogus") + HarnessKind.coerce("bogus") diff --git a/sdks/python/oss/tests/pytest/unit/agents/test_environment_lifecycle.py b/sdks/python/oss/tests/pytest/unit/agents/test_environment_lifecycle.py index 4cfd442e3c..1672234daa 100644 --- a/sdks/python/oss/tests/pytest/unit/agents/test_environment_lifecycle.py +++ b/sdks/python/oss/tests/pytest/unit/agents/test_environment_lifecycle.py @@ -13,7 +13,7 @@ AgentTemplate, AgentResult, ClaudeHarness, - HarnessType, + HarnessKind, Message, PiHarness, SessionConfig, @@ -33,12 +33,12 @@ async def test_fresh_sandbox_per_session(make_env): await env.create_session( PiHarness(env)._to_harness_config(config), - harness=HarnessType.PI, + harness=HarnessKind.PI, session_config=config, ) await env.create_session( PiHarness(env)._to_harness_config(config), - harness=HarnessType.PI, + harness=HarnessKind.PI, session_config=config, ) @@ -52,7 +52,7 @@ async def test_shared_sandbox_when_not_per_session(make_env): for _ in range(2): await env.create_session( PiHarness(env)._to_harness_config(config), - harness=HarnessType.PI, + harness=HarnessKind.PI, session_config=config, ) diff --git a/sdks/python/oss/tests/pytest/unit/agents/test_harness_adapters.py b/sdks/python/oss/tests/pytest/unit/agents/test_harness_adapters.py index 4a29d4ad2a..bbaab7729b 100644 --- a/sdks/python/oss/tests/pytest/unit/agents/test_harness_adapters.py +++ b/sdks/python/oss/tests/pytest/unit/agents/test_harness_adapters.py @@ -19,7 +19,7 @@ ClaudeAgentTemplate, ClaudeHarness, ClientToolSpec, - HarnessType, + HarnessKind, PiAgentTemplate, PiHarness, SessionConfig, @@ -50,7 +50,7 @@ def _session_config(**kwargs) -> SessionConfig: def test_pi_keeps_builtins_and_native_tools(make_env): - harness = PiHarness(make_env(supported=[HarnessType.PI])) + harness = PiHarness(make_env(supported=[HarnessKind.PI])) config = _session_config( builtin_tools=["read", "write"], custom_tools=[{"name": "t", "callRef": "ref"}], @@ -75,7 +75,7 @@ def test_pi_threads_model_ref_so_connection_reaches_wire(make_env): and the runner never sees the connection slug, so it cannot build its ``models.json`` plan for an OpenAI-compatible custom connection (the run then falls back to a default provider). """ - harness = PiHarness(make_env(supported=[HarnessType.PI])) + harness = PiHarness(make_env(supported=[HarnessKind.PI])) agent = AgentTemplate( instructions="hi", model={ @@ -97,7 +97,7 @@ def test_pi_threads_model_ref_so_connection_reaches_wire(make_env): def test_agenta_threads_model_ref_so_connection_reaches_wire(make_env): """Same guarantee as Pi for the ``pi_agenta`` harness (it also runs Pi).""" - harness = AgentaHarness(make_env(supported=[HarnessType.AGENTA])) + harness = AgentaHarness(make_env(supported=[HarnessKind.AGENTA])) agent = AgentTemplate( instructions="hi", model={ @@ -116,7 +116,7 @@ def test_agenta_threads_model_ref_so_connection_reaches_wire(make_env): def test_pi_reads_its_harness_extras_slice(make_env): - harness = PiHarness(make_env(supported=[HarnessType.PI])) + harness = PiHarness(make_env(supported=[HarnessKind.PI])) agent = AgentTemplate( instructions="hi", harness_extras={"system": "You are Pi.", "append_system": "Be terse."}, @@ -135,7 +135,7 @@ def test_pi_reads_its_harness_extras_slice(make_env): def test_pi_drops_blank_harness_extras(make_env): - harness = PiHarness(make_env(supported=[HarnessType.PI])) + harness = PiHarness(make_env(supported=[HarnessKind.PI])) agent = AgentTemplate( instructions="hi", harness_extras={"system": " ", "append_system": ""}, @@ -152,7 +152,7 @@ def test_pi_drops_blank_harness_extras(make_env): def test_agenta_forces_tools_preamble_and_persona_and_carries_skills(make_env): - harness = AgentaHarness(make_env(supported=[HarnessType.AGENTA])) + harness = AgentaHarness(make_env(supported=[HarnessKind.AGENTA])) skill = { "name": "release-notes", "description": "Draft release notes.", @@ -194,7 +194,7 @@ def test_agenta_forces_tools_preamble_and_persona_and_carries_skills(make_env): def test_agenta_forces_platform_skill_on_a_skill_less_config(make_env): # The actually-forced behavior: a custom pi_agenta config with NO skills (the default # template's `_agenta` embed dropped) still carries the platform skill on every run. - harness = AgentaHarness(make_env(supported=[HarnessType.AGENTA])) + harness = AgentaHarness(make_env(supported=[HarnessKind.AGENTA])) config = _session_config( agent=AgentTemplate(instructions="My project rules.", model="m", skills=[]) ) @@ -207,7 +207,7 @@ def test_agenta_forces_platform_skill_on_a_skill_less_config(make_env): def test_agenta_does_not_duplicate_an_already_present_platform_skill(make_env): # A config that already carries the resolved platform skill (e.g. via the default template's # embed) is not doubled: the author's copy wins on the name clash. - harness = AgentaHarness(make_env(supported=[HarnessType.AGENTA])) + harness = AgentaHarness(make_env(supported=[HarnessKind.AGENTA])) existing = GETTING_STARTED_WITH_AGENTA_SKILL.model_dump(mode="json") config = _session_config( agent=AgentTemplate(instructions="hi", model="m", skills=[existing]) @@ -235,7 +235,7 @@ def test_force_skills_unions_forced_after_author_skills(): def test_agenta_forces_tools_without_duplicates(make_env): - harness = AgentaHarness(make_env(supported=[HarnessType.AGENTA])) + harness = AgentaHarness(make_env(supported=[HarnessKind.AGENTA])) # `read` already configured: it must not be duplicated when forced. config = _session_config(builtin_tools=["read"]) @@ -245,7 +245,7 @@ def test_agenta_forces_tools_without_duplicates(make_env): def test_agenta_passes_through_user_pi_options(make_env): - harness = AgentaHarness(make_env(supported=[HarnessType.AGENTA])) + harness = AgentaHarness(make_env(supported=[HarnessKind.AGENTA])) agent = AgentTemplate( instructions="hi", harness_extras={"system": "You are Pi.", "append_system": "Be terse."}, @@ -265,7 +265,7 @@ def test_agenta_is_sandbox_agent_supported(): # `agenta` run on a non-local sandbox (e.g. daytona) instead of raising. from agenta.sdk.agents import SandboxAgentBackend - assert SandboxAgentBackend(url="http://runner").supports(HarnessType.AGENTA) + assert SandboxAgentBackend(url="http://runner").supports(HarnessKind.AGENTA) # ------------------------------------------------------------------------- Claude @@ -278,7 +278,7 @@ def test_claude_drops_builtins_and_warns(make_env, monkeypatch): "log", type("L", (), {"warning": lambda self, *a, **k: recorded.append(a)})(), ) - harness = ClaudeHarness(make_env(supported=[HarnessType.CLAUDE])) + harness = ClaudeHarness(make_env(supported=[HarnessKind.CLAUDE])) config = _session_config( builtin_tools=["read"], custom_tools=[{"name": "t", "callRef": "ref"}], @@ -295,7 +295,7 @@ def test_claude_drops_builtins_and_warns(make_env, monkeypatch): def test_claude_carries_skills_for_project_local_materialization(make_env): - harness = ClaudeHarness(make_env(supported=[HarnessType.CLAUDE])) + harness = ClaudeHarness(make_env(supported=[HarnessKind.CLAUDE])) skill = { "name": "release-notes", "description": "Draft release notes.", @@ -320,7 +320,7 @@ def test_claude_no_warning_without_builtins(make_env, monkeypatch): "log", type("L", (), {"warning": lambda self, *a, **k: recorded.append(a)})(), ) - harness = ClaudeHarness(make_env(supported=[HarnessType.CLAUDE])) + harness = ClaudeHarness(make_env(supported=[HarnessKind.CLAUDE])) harness._to_harness_config(_session_config(permission_default="allow_reads")) @@ -330,7 +330,7 @@ def test_claude_no_warning_without_builtins(make_env, monkeypatch): def test_claude_threads_permissions_and_renders_settings_file(make_env): import json - harness = ClaudeHarness(make_env(supported=[HarnessType.CLAUDE])) + harness = ClaudeHarness(make_env(supported=[HarnessKind.CLAUDE])) permissions = { "default_mode": "acceptEdits", "allow": ["Read"], @@ -355,7 +355,7 @@ def test_claude_threads_permissions_and_renders_settings_file(make_env): def test_claude_without_permissions_renders_no_files(make_env): - harness = ClaudeHarness(make_env(supported=[HarnessType.CLAUDE])) + harness = ClaudeHarness(make_env(supported=[HarnessKind.CLAUDE])) result = harness._to_harness_config(_session_config()) @@ -390,7 +390,7 @@ def test_compat_normalize_tool_specs_returns_typed_specs(): def test_harness_accepts_typed_tool_specs_without_normalizing_dicts(make_env): - harness = PiHarness(make_env(supported=[HarnessType.PI])) + harness = PiHarness(make_env(supported=[HarnessKind.PI])) spec = ClientToolSpec(name="pick", description="Pick") result = harness._to_harness_config(_session_config(tool_specs=[spec])) assert result.tool_specs == [spec] @@ -413,24 +413,24 @@ def test_opt_str_keeps_only_nonempty_strings(): def test_make_harness_maps_string_to_class(make_env): - env = make_env(supported=[HarnessType.PI, HarnessType.CLAUDE, HarnessType.AGENTA]) + env = make_env(supported=[HarnessKind.PI, HarnessKind.CLAUDE, HarnessKind.AGENTA]) assert isinstance(make_harness("pi_core", env), PiHarness) assert isinstance( make_harness("PI_CORE", env), PiHarness ) # coerced, case-insensitive assert isinstance(make_harness("claude", env), ClaudeHarness) - assert isinstance(make_harness(HarnessType.CLAUDE, env), ClaudeHarness) + assert isinstance(make_harness(HarnessKind.CLAUDE, env), ClaudeHarness) assert isinstance(make_harness("pi_agenta", env), AgentaHarness) - assert isinstance(make_harness(HarnessType.AGENTA, env), AgentaHarness) + assert isinstance(make_harness(HarnessKind.AGENTA, env), AgentaHarness) def test_make_harness_unsupported_backend_raises(make_env): - env = make_env(supported=[HarnessType.PI]) # backend cannot drive Claude + env = make_env(supported=[HarnessKind.PI]) # backend cannot drive Claude with pytest.raises(UnsupportedHarnessError): make_harness("claude", env) def test_make_harness_unknown_name_raises(make_env): - env = make_env(supported=[HarnessType.PI]) + env = make_env(supported=[HarnessKind.PI]) with pytest.raises(ValueError): make_harness("bogus", env) diff --git a/sdks/python/oss/tests/pytest/unit/agents/test_harness_identity.py b/sdks/python/oss/tests/pytest/unit/agents/test_harness_identity.py index 5b1726f577..90c70f0366 100644 --- a/sdks/python/oss/tests/pytest/unit/agents/test_harness_identity.py +++ b/sdks/python/oss/tests/pytest/unit/agents/test_harness_identity.py @@ -9,14 +9,14 @@ from __future__ import annotations -from agenta.sdk.agents import HARNESS_IDENTITIES, HarnessType +from agenta.sdk.agents import HARNESS_IDENTITIES, HarnessKind from agenta.sdk.utils.types import CATALOG_TYPES def test_one_identity_per_harness_type(): - # Every HarnessType value has exactly one identity, and nothing extra. + # Every HarnessKind value has exactly one identity, and nothing extra. by_value = {identity.value: identity for identity in HARNESS_IDENTITIES} - assert set(by_value) == {h.value for h in HarnessType} + assert set(by_value) == {h.value for h in HarnessKind} assert len(by_value) == len(HARNESS_IDENTITIES) @@ -28,7 +28,7 @@ def test_slug_follows_the_repo_versioned_slug_grammar(): def test_identity_value_is_the_bare_harness_string(): - # The identity's `value` is the bare HarnessType value (the runtime/wire selector), NOT the + # The identity's `value` is the bare HarnessKind value (the runtime/wire selector), NOT the # slug — so the wire/runner contract is unchanged. values = {identity.value for identity in HARNESS_IDENTITIES} assert values == {"pi_core", "pi_agenta", "claude"} diff --git a/sdks/python/oss/tests/pytest/unit/agents/test_wire_contract.py b/sdks/python/oss/tests/pytest/unit/agents/test_wire_contract.py index 0c73b519c4..f3fb58e528 100644 --- a/sdks/python/oss/tests/pytest/unit/agents/test_wire_contract.py +++ b/sdks/python/oss/tests/pytest/unit/agents/test_wire_contract.py @@ -23,7 +23,7 @@ AgentaAgentTemplate, ClaudeAgentTemplate, Endpoint, - HarnessType, + HarnessKind, Message, PiAgentTemplate, ResolvedConnection, @@ -131,7 +131,7 @@ def _pi_payload(): append_system="Be terse.", ) return request_to_wire( - harness=HarnessType.PI, + harness=HarnessKind.PI, sandbox="local", config=config, messages=[Message(role="user", content="hi")], @@ -179,7 +179,7 @@ def _claude_payload(): }, ) return request_to_wire( - harness=HarnessType.CLAUDE, + harness=HarnessKind.CLAUDE, sandbox="local", config=config, messages=[Message(role="user", content="hi")], @@ -201,7 +201,7 @@ def _agenta_payload(): skills=[dict(_SKILL)], ) return request_to_wire( - harness=HarnessType.AGENTA, + harness=HarnessKind.AGENTA, sandbox="local", config=config, messages=[Message(role="user", content="hi")], @@ -230,7 +230,7 @@ def test_request_to_wire_skills_ride_their_own_seam_not_tools(): def test_request_to_wire_omits_skills_when_none(): # No declared skills -> no `skills` key (keeps a skill-free payload byte-identical). payload = request_to_wire( - harness=HarnessType.PI, + harness=HarnessKind.PI, sandbox="local", config=PiAgentTemplate(), messages=[Message(role="user", content="hi")], @@ -313,7 +313,7 @@ def test_request_to_wire_omits_run_context_when_none(): # No run context passed -> no `runContext` key (a run that needs no `call.context` binding stays # byte-identical to before, the same discipline skills/mcpServers/sandboxPermission use). payload = request_to_wire( - harness=HarnessType.PI, + harness=HarnessKind.PI, sandbox="local", config=PiAgentTemplate(), messages=[Message(role="user", content="hi")], @@ -325,7 +325,7 @@ def test_request_to_wire_omits_run_context_when_empty(): # An entirely-empty run context (no identity to bind) serializes to {} and is dropped, so it # never rides the wire as a noise `"runContext": {}` key. payload = request_to_wire( - harness=HarnessType.PI, + harness=HarnessKind.PI, sandbox="local", config=PiAgentTemplate(), messages=[Message(role="user", content="hi")], @@ -336,7 +336,7 @@ def test_request_to_wire_omits_run_context_when_empty(): def test_request_to_wire_carries_turn_id_when_set(): payload = request_to_wire( - harness=HarnessType.PI, + harness=HarnessKind.PI, sandbox="local", config=PiAgentTemplate(), messages=[Message(role="user", content="hi")], @@ -348,7 +348,7 @@ def test_request_to_wire_carries_turn_id_when_set(): def test_request_to_wire_omits_turn_id_when_none(): payload = request_to_wire( - harness=HarnessType.PI, + harness=HarnessKind.PI, sandbox="local", config=PiAgentTemplate(), messages=[Message(role="user", content="hi")], @@ -358,7 +358,7 @@ def test_request_to_wire_omits_turn_id_when_none(): def test_request_to_wire_carries_project_id_when_set(): payload = request_to_wire( - harness=HarnessType.PI, + harness=HarnessKind.PI, sandbox="local", config=PiAgentTemplate(), messages=[Message(role="user", content="hi")], @@ -370,7 +370,7 @@ def test_request_to_wire_carries_project_id_when_set(): def test_request_to_wire_omits_project_id_when_none(): payload = request_to_wire( - harness=HarnessType.PI, + harness=HarnessKind.PI, sandbox="local", config=PiAgentTemplate(), messages=[Message(role="user", content="hi")], @@ -442,7 +442,7 @@ def test_author_permission_rules_exclude_mcp_from_wire_but_keep_settings(): } ) payload = request_to_wire( - harness=HarnessType.CLAUDE, + harness=HarnessKind.CLAUDE, sandbox="local", config=config, messages=[Message(role="user", content="hi")], @@ -472,7 +472,7 @@ def test_request_to_wire_has_no_prompt_key(): # The serializer emits `messages` only; the TS side derives the latest turn with # `resolvePromptText`. This asymmetry is intentional and easy to break, so lock it. payload = request_to_wire( - harness=HarnessType.PI, + harness=HarnessKind.PI, sandbox="local", config=PiAgentTemplate(), messages=[Message(role="user", content="hi")], @@ -507,7 +507,7 @@ def test_request_to_wire_carries_resolved_connection_non_secret_descriptor(): ), ) payload = request_to_wire( - harness=HarnessType.PI, + harness=HarnessKind.PI, sandbox="local", config=config, messages=[Message(role="user", content="hi")], @@ -533,7 +533,7 @@ def test_request_to_wire_omits_resolved_connection_when_none(): # byte-identical to before (the golden contract; the golden fixtures set none). config = PiAgentTemplate(model="gpt-5.5") payload = request_to_wire( - harness=HarnessType.PI, + harness=HarnessKind.PI, sandbox="local", config=config, messages=[Message(role="user", content="hi")], @@ -549,7 +549,7 @@ def test_request_to_wire_omits_resolved_connection_when_none(): def test_pi_permissions_default_to_allow_reads(): # Pi ships the same default permission plan as the other harnesses. payload = request_to_wire( - harness=HarnessType.PI, + harness=HarnessKind.PI, sandbox="local", config=PiAgentTemplate(), messages=[Message(role="user", content="hi")], @@ -674,7 +674,7 @@ def test_request_to_wire_carries_code_client_and_mcp_specs(): ], ) payload = request_to_wire( - harness=HarnessType.PI, + harness=HarnessKind.PI, sandbox="local", config=config, messages=[Message(role="user", content="hi")], @@ -706,7 +706,7 @@ def test_request_to_wire_carries_code_client_and_mcp_specs(): def test_request_to_wire_omits_mcp_servers_when_none(): # No declared servers -> no `mcpServers` key (keeps a tool-free payload byte-identical). payload = request_to_wire( - harness=HarnessType.PI, + harness=HarnessKind.PI, sandbox="local", config=PiAgentTemplate(), messages=[Message(role="user", content="hi")], @@ -718,7 +718,7 @@ def test_request_to_wire_omits_sandbox_permission_when_none(): # No declared boundary -> no `sandboxPermission` key (keeps a boundary-free payload # byte-identical, so existing configs/fixtures are unaffected). payload = request_to_wire( - harness=HarnessType.PI, + harness=HarnessKind.PI, sandbox="local", config=PiAgentTemplate(), messages=[Message(role="user", content="hi")], @@ -730,7 +730,7 @@ def test_request_to_wire_omits_harness_files_when_none(): # No authored options on a Claude config -> the claude adapter renders nothing, so no # `harnessFiles` key (a Claude run without harness options is byte-identical to before). payload = request_to_wire( - harness=HarnessType.CLAUDE, + harness=HarnessKind.CLAUDE, sandbox="local", config=ClaudeAgentTemplate(), messages=[Message(role="user", content="hi")], @@ -744,7 +744,7 @@ def test_request_to_wire_pi_renders_no_harness_files_from_its_options(): # `appendSystemPrompt`, not a file). config = PiAgentTemplate(system="You are Pi.") payload = request_to_wire( - harness=HarnessType.PI, + harness=HarnessKind.PI, sandbox="local", config=config, messages=[Message(role="user", content="hi")], @@ -771,7 +771,7 @@ def test_request_to_wire_claude_renders_settings_from_options_and_boundaries(): ], ) payload = request_to_wire( - harness=HarnessType.CLAUDE, + harness=HarnessKind.CLAUDE, sandbox="local", config=config, messages=[Message(role="user", content="hi")], @@ -796,7 +796,7 @@ def test_request_to_wire_carries_sandbox_permission_allowlist(): ) ) payload = request_to_wire( - harness=HarnessType.PI, + harness=HarnessKind.PI, sandbox="local", config=config, messages=[Message(role="user", content="hi")], diff --git a/sdks/python/oss/tests/pytest/unit/test_batch_fold_stream_contract_routing.py b/sdks/python/oss/tests/pytest/unit/test_batch_fold_stream_contract_routing.py index 0282e08125..157a440097 100644 --- a/sdks/python/oss/tests/pytest/unit/test_batch_fold_stream_contract_routing.py +++ b/sdks/python/oss/tests/pytest/unit/test_batch_fold_stream_contract_routing.py @@ -26,7 +26,7 @@ from fastapi import FastAPI from fastapi.testclient import TestClient -from agenta.sdk.agents import AgentResult, HarnessType +from agenta.sdk.agents import AgentResult, HarnessKind from agenta.sdk.agents.dtos import Event from agenta.sdk.agents.fold import fold from agenta.sdk.agents.handler import AgentComposition, make_agent_handler @@ -123,7 +123,7 @@ async def destroy(self) -> None: class _FakeBackend(Backend): supported_harnesses = frozenset( - {HarnessType.PI, HarnessType.CLAUDE, HarnessType.AGENTA} + {HarnessKind.PI, HarnessKind.CLAUDE, HarnessKind.AGENTA} ) def __init__(self, *, events: List[Event]) -> None: diff --git a/sdks/python/oss/tests/pytest/unit/test_invoke_real_handlers_negotiation_routing.py b/sdks/python/oss/tests/pytest/unit/test_invoke_real_handlers_negotiation_routing.py index 5ba8282e59..00775f56ab 100644 --- a/sdks/python/oss/tests/pytest/unit/test_invoke_real_handlers_negotiation_routing.py +++ b/sdks/python/oss/tests/pytest/unit/test_invoke_real_handlers_negotiation_routing.py @@ -35,7 +35,7 @@ from fastapi import FastAPI from fastapi.testclient import TestClient -from agenta.sdk.agents import AgentResult, HarnessType +from agenta.sdk.agents import AgentResult, HarnessKind from agenta.sdk.agents.dtos import Event from agenta.sdk.agents.handler import AgentComposition, make_agent_handler from agenta.sdk.agents.interfaces import Backend, Sandbox, Session @@ -126,7 +126,7 @@ async def destroy(self) -> None: class _FakeBackend(Backend): supported_harnesses = frozenset( - {HarnessType.PI, HarnessType.CLAUDE, HarnessType.AGENTA} + {HarnessKind.PI, HarnessKind.CLAUDE, HarnessKind.AGENTA} ) def __init__(self, *, events: List[Event], output: str = "") -> None: diff --git a/sdks/python/oss/tests/pytest/unit/test_normalizer_passthrough.py b/sdks/python/oss/tests/pytest/unit/test_normalizer_passthrough.py index 56b9ef82c9..f3dbb09f68 100644 --- a/sdks/python/oss/tests/pytest/unit/test_normalizer_passthrough.py +++ b/sdks/python/oss/tests/pytest/unit/test_normalizer_passthrough.py @@ -161,6 +161,102 @@ def handler(session_id): assert request.session_id == sid assert response.session_id == sid + @pytest.mark.asyncio + async def test_call_stores_agent_id_from_running_context_revision( + self, monkeypatch + ): + import agenta as ag + + class _TracingSpy: + def __init__(self): + self.store_session_calls = [] + self.store_agent_calls = [] + + def store_session(self, session_id): + self.store_session_calls.append(session_id) + + def store_agent(self, agent_id): + self.store_agent_calls.append(agent_id) + + spy = _TracingSpy() + monkeypatch.setattr(ag, "tracing", spy, raising=False) + + def handler(request): + return {"ok": True} + + request = WorkflowServiceRequest( + session_id="sess_agent_test", + data=WorkflowRequestData(), + ) + + token = RunningContext.set( + RunningContext( + handler=handler, + revision={"artifact_id": "workflow-abc"}, + ) + ) + try: + await NormalizerMiddleware()(request, lambda req: None) + finally: + RunningContext.reset(token) + + assert spy.store_agent_calls == ["workflow-abc"] + assert spy.store_session_calls == ["sess_agent_test"] + + @pytest.mark.asyncio + async def test_call_does_not_store_agent_id_when_revision_missing( + self, monkeypatch + ): + import agenta as ag + + class _TracingSpy: + def __init__(self): + self.store_agent_calls = [] + + def store_session(self, session_id): + pass + + def store_agent(self, agent_id): + self.store_agent_calls.append(agent_id) + + spy = _TracingSpy() + monkeypatch.setattr(ag, "tracing", spy, raising=False) + + def handler(request): + return {"ok": True} + + request = WorkflowServiceRequest(data=WorkflowRequestData()) + + token = RunningContext.set(RunningContext(handler=handler)) + try: + await NormalizerMiddleware()(request, lambda req: None) + finally: + RunningContext.reset(token) + + assert spy.store_agent_calls == [] + + +class TestResolveAgentId: + def test_resolves_artifact_id_from_revision(self): + ctx = RunningContext(revision={"artifact_id": "wf-1"}) + assert NormalizerMiddleware._resolve_agent_id(ctx) == "wf-1" + + def test_falls_back_to_workflow_id_alias(self): + ctx = RunningContext(revision={"workflow_id": "wf-2"}) + assert NormalizerMiddleware._resolve_agent_id(ctx) == "wf-2" + + def test_none_when_revision_missing(self): + ctx = RunningContext() + assert NormalizerMiddleware._resolve_agent_id(ctx) is None + + def test_none_when_revision_not_a_dict(self): + ctx = RunningContext.model_construct(revision="not-a-dict") + assert NormalizerMiddleware._resolve_agent_id(ctx) is None + + def test_none_when_artifact_id_absent(self): + ctx = RunningContext(revision={"id": "rev-1"}) + assert NormalizerMiddleware._resolve_agent_id(ctx) is None + class TestAsyncGenerator: @pytest.mark.asyncio diff --git a/sdks/python/oss/tests/pytest/unit/test_tracing_store_agent.py b/sdks/python/oss/tests/pytest/unit/test_tracing_store_agent.py new file mode 100644 index 0000000000..1b1133f160 --- /dev/null +++ b/sdks/python/oss/tests/pytest/unit/test_tracing_store_agent.py @@ -0,0 +1,33 @@ +"""Unit tests for Tracing.store_agent (mirrors store_session/store_user). + +Verifies the attribute is set on the span with the `agent` namespace and that a +falsy agent_id is a no-op. +""" + +from unittest.mock import MagicMock + +import pytest + +from agenta.sdk.engines.tracing.tracing import Tracing + + +@pytest.fixture +def tracing(): + return Tracing(url="http://localhost:4318/v1/traces") + + +def test_store_agent_sets_id_attribute_on_given_span(tracing): + span = MagicMock() + + tracing.store_agent(agent_id="agent-123", span=span) + + span.set_attribute.assert_called_once_with("id", "agent-123", namespace="agent") + + +def test_store_agent_is_noop_when_agent_id_falsy(tracing): + span = MagicMock() + + tracing.store_agent(agent_id=None, span=span) + tracing.store_agent(agent_id="", span=span) + + span.set_attribute.assert_not_called() diff --git a/services/oss/tests/pytest/unit/agent/conftest.py b/services/oss/tests/pytest/unit/agent/conftest.py index b5da23e063..7e0e6b9f1b 100644 --- a/services/oss/tests/pytest/unit/agent/conftest.py +++ b/services/oss/tests/pytest/unit/agent/conftest.py @@ -14,7 +14,7 @@ import pytest -from agenta.sdk.agents import AgentResult, HarnessType +from agenta.sdk.agents import AgentResult, HarnessKind from agenta.sdk.agents.interfaces import Backend, Sandbox, Session from agenta.sdk.agents.streaming import AgentStream @@ -87,10 +87,10 @@ def __init__( self, *, result: Optional[AgentResult] = None, - supported: Sequence[HarnessType] = ( - HarnessType.PI, - HarnessType.CLAUDE, - HarnessType.AGENTA, + supported: Sequence[HarnessKind] = ( + HarnessKind.PI, + HarnessKind.CLAUDE, + HarnessKind.AGENTA, ), ) -> None: self.supported_harnesses = frozenset(supported) diff --git a/web/oss/src/components/SessionInspector/api.ts b/web/oss/src/components/SessionInspector/api.ts index 2fcd4b1a43..e2fb53715e 100644 --- a/web/oss/src/components/SessionInspector/api.ts +++ b/web/oss/src/components/SessionInspector/api.ts @@ -38,8 +38,11 @@ export async function fetchRecords(sessionId: string, projectId?: string | null) } export async function fetchState(sessionId: string, projectId?: string | null) { - const res = await client().sessions.getState({session_id: sessionId}, scope(projectId)) - return res.session_state ?? null + const res = await client().sessions.fetchSessionStream( + {session_id: sessionId}, + scope(projectId), + ) + return res.stream ?? null } export async function fetchMounts(sessionId: string, projectId?: string | null) { diff --git a/web/packages/agenta-api-client/src/generated/api/resources/mounts/client/Client.ts b/web/packages/agenta-api-client/src/generated/api/resources/mounts/client/Client.ts index c6ae004942..a54a836ad9 100644 --- a/web/packages/agenta-api-client/src/generated/api/resources/mounts/client/Client.ts +++ b/web/packages/agenta-api-client/src/generated/api/resources/mounts/client/Client.ts @@ -116,9 +116,10 @@ export class MountsClient { request: AgentaApi.MountQueryRequest = {}, requestOptions?: MountsClient.RequestOptions, ): Promise> { - const { session_id: sessionId, include_archived: includeArchived, ..._body } = request; + const { session_id: sessionId, agent_id: agentId, include_archived: includeArchived, ..._body } = request; const _queryParams: Record = { session_id: sessionId, + agent_id: agentId, include_archived: includeArchived, }; const _authRequest: core.AuthRequest = await this._options.authProvider.getAuthRequest(); diff --git a/web/packages/agenta-api-client/src/generated/api/resources/mounts/client/requests/MountQueryRequest.ts b/web/packages/agenta-api-client/src/generated/api/resources/mounts/client/requests/MountQueryRequest.ts index 1aa9578739..2c1d841f91 100644 --- a/web/packages/agenta-api-client/src/generated/api/resources/mounts/client/requests/MountQueryRequest.ts +++ b/web/packages/agenta-api-client/src/generated/api/resources/mounts/client/requests/MountQueryRequest.ts @@ -8,6 +8,7 @@ import type * as AgentaApi from "../../../../index.js"; */ export interface MountQueryRequest { session_id?: string | null; + agent_id?: string | null; include_archived?: boolean; mount?: AgentaApi.MountQuery | null; windowing?: AgentaApi.Windowing | null; diff --git a/web/packages/agenta-api-client/src/generated/api/resources/sessions/client/Client.ts b/web/packages/agenta-api-client/src/generated/api/resources/sessions/client/Client.ts index 7cfe7b9ef7..bf269f42e5 100644 --- a/web/packages/agenta-api-client/src/generated/api/resources/sessions/client/Client.ts +++ b/web/packages/agenta-api-client/src/generated/api/resources/sessions/client/Client.ts @@ -39,14 +39,14 @@ export class SessionsClient { public fetchSessionStream( request: AgentaApi.FetchSessionStreamRequest, requestOptions?: SessionsClient.RequestOptions, - ): core.HttpResponsePromise { + ): core.HttpResponsePromise { return core.HttpResponsePromise.fromPromise(this.__fetchSessionStream(request, requestOptions)); } private async __fetchSessionStream( request: AgentaApi.FetchSessionStreamRequest, requestOptions?: SessionsClient.RequestOptions, - ): Promise> { + ): Promise> { const { session_id: sessionId } = request; const _queryParams: Record = { session_id: sessionId, @@ -75,7 +75,7 @@ export class SessionsClient { logging: this._options.logging, }); if (_response.ok) { - return { data: _response.body as AgentaApi.SessionStreamResponseModel, rawResponse: _response.rawResponse }; + return { data: _response.body as AgentaApi.SessionStreamResponse, rawResponse: _response.rawResponse }; } if (_response.error.reason === "status-code") { @@ -98,7 +98,7 @@ export class SessionsClient { } /** - * @param {AgentaApi.SessionStreamCommandRequestModel} request + * @param {AgentaApi.SessionStreamCommandRequest} request * @param {SessionsClient.RequestOptions} requestOptions - Request-specific configuration. * * @throws {@link AgentaApi.UnprocessableEntityError} @@ -109,16 +109,16 @@ export class SessionsClient { * }) */ public setSessionStream( - request: AgentaApi.SessionStreamCommandRequestModel, + request: AgentaApi.SessionStreamCommandRequest, requestOptions?: SessionsClient.RequestOptions, - ): core.HttpResponsePromise { + ): core.HttpResponsePromise { return core.HttpResponsePromise.fromPromise(this.__setSessionStream(request, requestOptions)); } private async __setSessionStream( - request: AgentaApi.SessionStreamCommandRequestModel, + request: AgentaApi.SessionStreamCommandRequest, requestOptions?: SessionsClient.RequestOptions, - ): Promise> { + ): Promise> { const _authRequest: core.AuthRequest = await this._options.authProvider.getAuthRequest(); const _headers: core.Fetcher.Args["headers"] = mergeHeaders( _authRequest.headers, @@ -147,7 +147,7 @@ export class SessionsClient { }); if (_response.ok) { return { - data: _response.body as AgentaApi.SessionStreamCommandResponseModel, + data: _response.body as AgentaApi.SessionStreamCommandResponse, rawResponse: _response.rawResponse, }; } @@ -244,7 +244,7 @@ export class SessionsClient { } /** - * @param {AgentaApi.SessionStreamQueryRequestModel} request + * @param {AgentaApi.SessionStreamQueryRequest} request * @param {SessionsClient.RequestOptions} requestOptions - Request-specific configuration. * * @throws {@link AgentaApi.UnprocessableEntityError} @@ -253,16 +253,16 @@ export class SessionsClient { * await client.sessions.querySessionStreams() */ public querySessionStreams( - request: AgentaApi.SessionStreamQueryRequestModel = {}, + request: AgentaApi.SessionStreamQueryRequest = {}, requestOptions?: SessionsClient.RequestOptions, - ): core.HttpResponsePromise { + ): core.HttpResponsePromise { return core.HttpResponsePromise.fromPromise(this.__querySessionStreams(request, requestOptions)); } private async __querySessionStreams( - request: AgentaApi.SessionStreamQueryRequestModel = {}, + request: AgentaApi.SessionStreamQueryRequest = {}, requestOptions?: SessionsClient.RequestOptions, - ): Promise> { + ): Promise> { const _authRequest: core.AuthRequest = await this._options.authProvider.getAuthRequest(); const _headers: core.Fetcher.Args["headers"] = mergeHeaders( _authRequest.headers, @@ -290,10 +290,7 @@ export class SessionsClient { logging: this._options.logging, }); if (_response.ok) { - return { - data: _response.body as AgentaApi.SessionStreamsResponseModel, - rawResponse: _response.rawResponse, - }; + return { data: _response.body as AgentaApi.SessionStreamsResponse, rawResponse: _response.rawResponse }; } if (_response.error.reason === "status-code") { @@ -316,7 +313,7 @@ export class SessionsClient { } /** - * @param {AgentaApi.SessionDetachRequestModel} request + * @param {AgentaApi.SessionDetachRequest} request * @param {SessionsClient.RequestOptions} requestOptions - Request-specific configuration. * * @throws {@link AgentaApi.UnprocessableEntityError} @@ -328,14 +325,14 @@ export class SessionsClient { * }) */ public detachSessionStream( - request: AgentaApi.SessionDetachRequestModel, + request: AgentaApi.SessionDetachRequest, requestOptions?: SessionsClient.RequestOptions, ): core.HttpResponsePromise> { return core.HttpResponsePromise.fromPromise(this.__detachSessionStream(request, requestOptions)); } private async __detachSessionStream( - request: AgentaApi.SessionDetachRequestModel, + request: AgentaApi.SessionDetachRequest, requestOptions?: SessionsClient.RequestOptions, ): Promise>> { const _authRequest: core.AuthRequest = await this._options.authProvider.getAuthRequest(); @@ -388,7 +385,7 @@ export class SessionsClient { } /** - * @param {AgentaApi.SessionHeartbeatRequestModel} request + * @param {AgentaApi.SessionHeartbeatRequest} request * @param {SessionsClient.RequestOptions} requestOptions - Request-specific configuration. * * @throws {@link AgentaApi.UnprocessableEntityError} @@ -400,16 +397,16 @@ export class SessionsClient { * }) */ public heartbeatSessionStream( - request: AgentaApi.SessionHeartbeatRequestModel, + request: AgentaApi.SessionHeartbeatRequest, requestOptions?: SessionsClient.RequestOptions, - ): core.HttpResponsePromise { + ): core.HttpResponsePromise { return core.HttpResponsePromise.fromPromise(this.__heartbeatSessionStream(request, requestOptions)); } private async __heartbeatSessionStream( - request: AgentaApi.SessionHeartbeatRequestModel, + request: AgentaApi.SessionHeartbeatRequest, requestOptions?: SessionsClient.RequestOptions, - ): Promise> { + ): Promise> { const _authRequest: core.AuthRequest = await this._options.authProvider.getAuthRequest(); const _headers: core.Fetcher.Args["headers"] = mergeHeaders( _authRequest.headers, @@ -437,10 +434,7 @@ export class SessionsClient { logging: this._options.logging, }); if (_response.ok) { - return { - data: _response.body as AgentaApi.SessionHeartbeatResponseModel, - rawResponse: _response.rawResponse, - }; + return { data: _response.body as AgentaApi.SessionHeartbeatResult, rawResponse: _response.rawResponse }; } if (_response.error.reason === "status-code") { @@ -462,6 +456,82 @@ export class SessionsClient { return handleNonStatusCodeError(_response.error, _response.rawResponse, "POST", "/sessions/streams/heartbeat"); } + /** + * @param {AgentaApi.SetSessionStreamHeaderRequest} request + * @param {SessionsClient.RequestOptions} requestOptions - Request-specific configuration. + * + * @throws {@link AgentaApi.UnprocessableEntityError} + * + * @example + * await client.sessions.setSessionStreamHeader({ + * session_id: "session_id", + * body: {} + * }) + */ + public setSessionStreamHeader( + request: AgentaApi.SetSessionStreamHeaderRequest, + requestOptions?: SessionsClient.RequestOptions, + ): core.HttpResponsePromise { + return core.HttpResponsePromise.fromPromise(this.__setSessionStreamHeader(request, requestOptions)); + } + + private async __setSessionStreamHeader( + request: AgentaApi.SetSessionStreamHeaderRequest, + requestOptions?: SessionsClient.RequestOptions, + ): Promise> { + const { session_id: sessionId, body: _body } = request; + const _queryParams: Record = { + session_id: sessionId, + }; + const _authRequest: core.AuthRequest = await this._options.authProvider.getAuthRequest(); + const _headers: core.Fetcher.Args["headers"] = mergeHeaders( + _authRequest.headers, + this._options?.headers, + requestOptions?.headers, + ); + const _response = await core.fetcher({ + url: core.url.join( + (await core.Supplier.get(this._options.baseUrl)) ?? + (await core.Supplier.get(this._options.environment)) ?? + environments.AgentaApiEnvironment.Default, + "sessions/streams/header", + ), + method: "PUT", + headers: _headers, + contentType: "application/json", + queryParameters: { ..._queryParams, ...requestOptions?.queryParams }, + requestType: "json", + body: _body, + timeoutMs: (requestOptions?.timeoutInSeconds ?? this._options?.timeoutInSeconds ?? 30) * 1000, + maxRetries: requestOptions?.maxRetries ?? this._options?.maxRetries, + withCredentials: true, + abortSignal: requestOptions?.abortSignal, + fetchFn: this._options?.fetch, + logging: this._options.logging, + }); + if (_response.ok) { + return { data: _response.body as AgentaApi.SessionStreamResponse, rawResponse: _response.rawResponse }; + } + + if (_response.error.reason === "status-code") { + switch (_response.error.statusCode) { + case 422: + throw new AgentaApi.UnprocessableEntityError( + _response.error.body as AgentaApi.HttpValidationError, + _response.rawResponse, + ); + default: + throw new errors.AgentaApiError({ + statusCode: _response.error.statusCode, + body: _response.error.body, + rawResponse: _response.rawResponse, + }); + } + } + + return handleNonStatusCodeError(_response.error, _response.rawResponse, "PUT", "/sessions/streams/header"); + } + /** * @param {AgentaApi.SessionInteractionCreateRequest} request * @param {SessionsClient.RequestOptions} requestOptions - Request-specific configuration. @@ -1511,27 +1581,308 @@ export class SessionsClient { } /** - * @param {AgentaApi.GetStateRequest} request + * @param {AgentaApi.SessionTurnAppendRequest} request + * @param {SessionsClient.RequestOptions} requestOptions - Request-specific configuration. + * + * @throws {@link AgentaApi.UnprocessableEntityError} + * + * @example + * await client.sessions.appendTurn({ + * session_id: "session_id", + * stream_id: "stream_id", + * turn_index: 1, + * harness_kind: "pi_core" + * }) + */ + public appendTurn( + request: AgentaApi.SessionTurnAppendRequest, + requestOptions?: SessionsClient.RequestOptions, + ): core.HttpResponsePromise { + return core.HttpResponsePromise.fromPromise(this.__appendTurn(request, requestOptions)); + } + + private async __appendTurn( + request: AgentaApi.SessionTurnAppendRequest, + requestOptions?: SessionsClient.RequestOptions, + ): Promise> { + const _authRequest: core.AuthRequest = await this._options.authProvider.getAuthRequest(); + const _headers: core.Fetcher.Args["headers"] = mergeHeaders( + _authRequest.headers, + this._options?.headers, + requestOptions?.headers, + ); + const _response = await core.fetcher({ + url: core.url.join( + (await core.Supplier.get(this._options.baseUrl)) ?? + (await core.Supplier.get(this._options.environment)) ?? + environments.AgentaApiEnvironment.Default, + "sessions/turns/", + ), + method: "POST", + headers: _headers, + contentType: "application/json", + queryParameters: requestOptions?.queryParams, + requestType: "json", + body: request, + timeoutMs: (requestOptions?.timeoutInSeconds ?? this._options?.timeoutInSeconds ?? 30) * 1000, + maxRetries: requestOptions?.maxRetries ?? this._options?.maxRetries, + withCredentials: true, + abortSignal: requestOptions?.abortSignal, + fetchFn: this._options?.fetch, + logging: this._options.logging, + }); + if (_response.ok) { + return { data: _response.body as AgentaApi.SessionTurnResponse, rawResponse: _response.rawResponse }; + } + + if (_response.error.reason === "status-code") { + switch (_response.error.statusCode) { + case 422: + throw new AgentaApi.UnprocessableEntityError( + _response.error.body as AgentaApi.HttpValidationError, + _response.rawResponse, + ); + default: + throw new errors.AgentaApiError({ + statusCode: _response.error.statusCode, + body: _response.error.body, + rawResponse: _response.rawResponse, + }); + } + } + + return handleNonStatusCodeError(_response.error, _response.rawResponse, "POST", "/sessions/turns/"); + } + + /** + * @param {AgentaApi.SessionTurnQueryRequest} request + * @param {SessionsClient.RequestOptions} requestOptions - Request-specific configuration. + * + * @throws {@link AgentaApi.UnprocessableEntityError} + * + * @example + * await client.sessions.queryTurns() + */ + public queryTurns( + request: AgentaApi.SessionTurnQueryRequest = {}, + requestOptions?: SessionsClient.RequestOptions, + ): core.HttpResponsePromise { + return core.HttpResponsePromise.fromPromise(this.__queryTurns(request, requestOptions)); + } + + private async __queryTurns( + request: AgentaApi.SessionTurnQueryRequest = {}, + requestOptions?: SessionsClient.RequestOptions, + ): Promise> { + const _authRequest: core.AuthRequest = await this._options.authProvider.getAuthRequest(); + const _headers: core.Fetcher.Args["headers"] = mergeHeaders( + _authRequest.headers, + this._options?.headers, + requestOptions?.headers, + ); + const _response = await core.fetcher({ + url: core.url.join( + (await core.Supplier.get(this._options.baseUrl)) ?? + (await core.Supplier.get(this._options.environment)) ?? + environments.AgentaApiEnvironment.Default, + "sessions/turns/query", + ), + method: "POST", + headers: _headers, + contentType: "application/json", + queryParameters: requestOptions?.queryParams, + requestType: "json", + body: request, + timeoutMs: (requestOptions?.timeoutInSeconds ?? this._options?.timeoutInSeconds ?? 30) * 1000, + maxRetries: requestOptions?.maxRetries ?? this._options?.maxRetries, + withCredentials: true, + abortSignal: requestOptions?.abortSignal, + fetchFn: this._options?.fetch, + logging: this._options.logging, + }); + if (_response.ok) { + return { data: _response.body as AgentaApi.SessionTurnsResponse, rawResponse: _response.rawResponse }; + } + + if (_response.error.reason === "status-code") { + switch (_response.error.statusCode) { + case 422: + throw new AgentaApi.UnprocessableEntityError( + _response.error.body as AgentaApi.HttpValidationError, + _response.rawResponse, + ); + default: + throw new errors.AgentaApiError({ + statusCode: _response.error.statusCode, + body: _response.error.body, + rawResponse: _response.rawResponse, + }); + } + } + + return handleNonStatusCodeError(_response.error, _response.rawResponse, "POST", "/sessions/turns/query"); + } + + /** + * @param {AgentaApi.FetchTurnRequest} request + * @param {SessionsClient.RequestOptions} requestOptions - Request-specific configuration. + * + * @throws {@link AgentaApi.UnprocessableEntityError} + * + * @example + * await client.sessions.fetchTurn({ + * turn_id: "turn_id" + * }) + */ + public fetchTurn( + request: AgentaApi.FetchTurnRequest, + requestOptions?: SessionsClient.RequestOptions, + ): core.HttpResponsePromise { + return core.HttpResponsePromise.fromPromise(this.__fetchTurn(request, requestOptions)); + } + + private async __fetchTurn( + request: AgentaApi.FetchTurnRequest, + requestOptions?: SessionsClient.RequestOptions, + ): Promise> { + const { turn_id: turnId } = request; + const _authRequest: core.AuthRequest = await this._options.authProvider.getAuthRequest(); + const _headers: core.Fetcher.Args["headers"] = mergeHeaders( + _authRequest.headers, + this._options?.headers, + requestOptions?.headers, + ); + const _response = await core.fetcher({ + url: core.url.join( + (await core.Supplier.get(this._options.baseUrl)) ?? + (await core.Supplier.get(this._options.environment)) ?? + environments.AgentaApiEnvironment.Default, + `sessions/turns/${core.url.encodePathParam(turnId)}`, + ), + method: "GET", + headers: _headers, + queryParameters: requestOptions?.queryParams, + timeoutMs: (requestOptions?.timeoutInSeconds ?? this._options?.timeoutInSeconds ?? 30) * 1000, + maxRetries: requestOptions?.maxRetries ?? this._options?.maxRetries, + withCredentials: true, + abortSignal: requestOptions?.abortSignal, + fetchFn: this._options?.fetch, + logging: this._options.logging, + }); + if (_response.ok) { + return { data: _response.body as AgentaApi.SessionTurnResponse, rawResponse: _response.rawResponse }; + } + + if (_response.error.reason === "status-code") { + switch (_response.error.statusCode) { + case 422: + throw new AgentaApi.UnprocessableEntityError( + _response.error.body as AgentaApi.HttpValidationError, + _response.rawResponse, + ); + default: + throw new errors.AgentaApiError({ + statusCode: _response.error.statusCode, + body: _response.error.body, + rawResponse: _response.rawResponse, + }); + } + } + + return handleNonStatusCodeError(_response.error, _response.rawResponse, "GET", "/sessions/turns/{turn_id}"); + } + + /** + * @param {AgentaApi.SessionQueryRequest} request * @param {SessionsClient.RequestOptions} requestOptions - Request-specific configuration. * * @throws {@link AgentaApi.UnprocessableEntityError} * * @example - * await client.sessions.getState({ + * await client.sessions.querySessions() + */ + public querySessions( + request: AgentaApi.SessionQueryRequest = {}, + requestOptions?: SessionsClient.RequestOptions, + ): core.HttpResponsePromise { + return core.HttpResponsePromise.fromPromise(this.__querySessions(request, requestOptions)); + } + + private async __querySessions( + request: AgentaApi.SessionQueryRequest = {}, + requestOptions?: SessionsClient.RequestOptions, + ): Promise> { + const _authRequest: core.AuthRequest = await this._options.authProvider.getAuthRequest(); + const _headers: core.Fetcher.Args["headers"] = mergeHeaders( + _authRequest.headers, + this._options?.headers, + requestOptions?.headers, + ); + const _response = await core.fetcher({ + url: core.url.join( + (await core.Supplier.get(this._options.baseUrl)) ?? + (await core.Supplier.get(this._options.environment)) ?? + environments.AgentaApiEnvironment.Default, + "sessions/query", + ), + method: "POST", + headers: _headers, + contentType: "application/json", + queryParameters: requestOptions?.queryParams, + requestType: "json", + body: request, + timeoutMs: (requestOptions?.timeoutInSeconds ?? this._options?.timeoutInSeconds ?? 30) * 1000, + maxRetries: requestOptions?.maxRetries ?? this._options?.maxRetries, + withCredentials: true, + abortSignal: requestOptions?.abortSignal, + fetchFn: this._options?.fetch, + logging: this._options.logging, + }); + if (_response.ok) { + return { data: _response.body as AgentaApi.SessionsResponse, rawResponse: _response.rawResponse }; + } + + if (_response.error.reason === "status-code") { + switch (_response.error.statusCode) { + case 422: + throw new AgentaApi.UnprocessableEntityError( + _response.error.body as AgentaApi.HttpValidationError, + _response.rawResponse, + ); + default: + throw new errors.AgentaApiError({ + statusCode: _response.error.statusCode, + body: _response.error.body, + rawResponse: _response.rawResponse, + }); + } + } + + return handleNonStatusCodeError(_response.error, _response.rawResponse, "POST", "/sessions/query"); + } + + /** + * @param {AgentaApi.DeleteSessionRequest} request + * @param {SessionsClient.RequestOptions} requestOptions - Request-specific configuration. + * + * @throws {@link AgentaApi.UnprocessableEntityError} + * + * @example + * await client.sessions.deleteSession({ * session_id: "session_id" * }) */ - public getState( - request: AgentaApi.GetStateRequest, + public deleteSession( + request: AgentaApi.DeleteSessionRequest, requestOptions?: SessionsClient.RequestOptions, - ): core.HttpResponsePromise { - return core.HttpResponsePromise.fromPromise(this.__getState(request, requestOptions)); + ): core.HttpResponsePromise> { + return core.HttpResponsePromise.fromPromise(this.__deleteSession(request, requestOptions)); } - private async __getState( - request: AgentaApi.GetStateRequest, + private async __deleteSession( + request: AgentaApi.DeleteSessionRequest, requestOptions?: SessionsClient.RequestOptions, - ): Promise> { + ): Promise>> { const { session_id: sessionId } = request; const _queryParams: Record = { session_id: sessionId, @@ -1547,9 +1898,9 @@ export class SessionsClient { (await core.Supplier.get(this._options.baseUrl)) ?? (await core.Supplier.get(this._options.environment)) ?? environments.AgentaApiEnvironment.Default, - "sessions/states/", + "sessions/", ), - method: "GET", + method: "DELETE", headers: _headers, queryParameters: { ..._queryParams, ...requestOptions?.queryParams }, timeoutMs: (requestOptions?.timeoutInSeconds ?? this._options?.timeoutInSeconds ?? 30) * 1000, @@ -1560,7 +1911,7 @@ export class SessionsClient { logging: this._options.logging, }); if (_response.ok) { - return { data: _response.body as AgentaApi.SessionStateResponse, rawResponse: _response.rawResponse }; + return { data: _response.body as Record, rawResponse: _response.rawResponse }; } if (_response.error.reason === "status-code") { @@ -1579,33 +1930,32 @@ export class SessionsClient { } } - return handleNonStatusCodeError(_response.error, _response.rawResponse, "GET", "/sessions/states/"); + return handleNonStatusCodeError(_response.error, _response.rawResponse, "DELETE", "/sessions/"); } /** - * @param {AgentaApi.SetStateRequest} request + * @param {AgentaApi.ArchiveSessionRequest} request * @param {SessionsClient.RequestOptions} requestOptions - Request-specific configuration. * * @throws {@link AgentaApi.UnprocessableEntityError} * * @example - * await client.sessions.setState({ - * session_id: "session_id", - * body: {} + * await client.sessions.archiveSession({ + * session_id: "session_id" * }) */ - public setState( - request: AgentaApi.SetStateRequest, + public archiveSession( + request: AgentaApi.ArchiveSessionRequest, requestOptions?: SessionsClient.RequestOptions, - ): core.HttpResponsePromise { - return core.HttpResponsePromise.fromPromise(this.__setState(request, requestOptions)); + ): core.HttpResponsePromise { + return core.HttpResponsePromise.fromPromise(this.__archiveSession(request, requestOptions)); } - private async __setState( - request: AgentaApi.SetStateRequest, + private async __archiveSession( + request: AgentaApi.ArchiveSessionRequest, requestOptions?: SessionsClient.RequestOptions, - ): Promise> { - const { session_id: sessionId, body: _body } = request; + ): Promise> { + const { session_id: sessionId } = request; const _queryParams: Record = { session_id: sessionId, }; @@ -1620,14 +1970,83 @@ export class SessionsClient { (await core.Supplier.get(this._options.baseUrl)) ?? (await core.Supplier.get(this._options.environment)) ?? environments.AgentaApiEnvironment.Default, - "sessions/states/", + "sessions/archive", ), - method: "PUT", + method: "POST", + headers: _headers, + queryParameters: { ..._queryParams, ...requestOptions?.queryParams }, + timeoutMs: (requestOptions?.timeoutInSeconds ?? this._options?.timeoutInSeconds ?? 30) * 1000, + maxRetries: requestOptions?.maxRetries ?? this._options?.maxRetries, + withCredentials: true, + abortSignal: requestOptions?.abortSignal, + fetchFn: this._options?.fetch, + logging: this._options.logging, + }); + if (_response.ok) { + return { data: _response.body as AgentaApi.SessionResponse, rawResponse: _response.rawResponse }; + } + + if (_response.error.reason === "status-code") { + switch (_response.error.statusCode) { + case 422: + throw new AgentaApi.UnprocessableEntityError( + _response.error.body as AgentaApi.HttpValidationError, + _response.rawResponse, + ); + default: + throw new errors.AgentaApiError({ + statusCode: _response.error.statusCode, + body: _response.error.body, + rawResponse: _response.rawResponse, + }); + } + } + + return handleNonStatusCodeError(_response.error, _response.rawResponse, "POST", "/sessions/archive"); + } + + /** + * @param {AgentaApi.UnarchiveSessionRequest} request + * @param {SessionsClient.RequestOptions} requestOptions - Request-specific configuration. + * + * @throws {@link AgentaApi.UnprocessableEntityError} + * + * @example + * await client.sessions.unarchiveSession({ + * session_id: "session_id" + * }) + */ + public unarchiveSession( + request: AgentaApi.UnarchiveSessionRequest, + requestOptions?: SessionsClient.RequestOptions, + ): core.HttpResponsePromise { + return core.HttpResponsePromise.fromPromise(this.__unarchiveSession(request, requestOptions)); + } + + private async __unarchiveSession( + request: AgentaApi.UnarchiveSessionRequest, + requestOptions?: SessionsClient.RequestOptions, + ): Promise> { + const { session_id: sessionId } = request; + const _queryParams: Record = { + session_id: sessionId, + }; + const _authRequest: core.AuthRequest = await this._options.authProvider.getAuthRequest(); + const _headers: core.Fetcher.Args["headers"] = mergeHeaders( + _authRequest.headers, + this._options?.headers, + requestOptions?.headers, + ); + const _response = await core.fetcher({ + url: core.url.join( + (await core.Supplier.get(this._options.baseUrl)) ?? + (await core.Supplier.get(this._options.environment)) ?? + environments.AgentaApiEnvironment.Default, + "sessions/unarchive", + ), + method: "POST", headers: _headers, - contentType: "application/json", queryParameters: { ..._queryParams, ...requestOptions?.queryParams }, - requestType: "json", - body: _body, timeoutMs: (requestOptions?.timeoutInSeconds ?? this._options?.timeoutInSeconds ?? 30) * 1000, maxRetries: requestOptions?.maxRetries ?? this._options?.maxRetries, withCredentials: true, @@ -1636,7 +2055,7 @@ export class SessionsClient { logging: this._options.logging, }); if (_response.ok) { - return { data: _response.body as AgentaApi.SessionStateResponse, rawResponse: _response.rawResponse }; + return { data: _response.body as AgentaApi.SessionResponse, rawResponse: _response.rawResponse }; } if (_response.error.reason === "status-code") { @@ -1655,6 +2074,6 @@ export class SessionsClient { } } - return handleNonStatusCodeError(_response.error, _response.rawResponse, "PUT", "/sessions/states/"); + return handleNonStatusCodeError(_response.error, _response.rawResponse, "POST", "/sessions/unarchive"); } } diff --git a/web/packages/agenta-api-client/src/generated/api/resources/sessions/client/requests/ArchiveSessionRequest.ts b/web/packages/agenta-api-client/src/generated/api/resources/sessions/client/requests/ArchiveSessionRequest.ts new file mode 100644 index 0000000000..4f4ebe776c --- /dev/null +++ b/web/packages/agenta-api-client/src/generated/api/resources/sessions/client/requests/ArchiveSessionRequest.ts @@ -0,0 +1,11 @@ +// This file was auto-generated by Fern from our API Definition. + +/** + * @example + * { + * session_id: "session_id" + * } + */ +export interface ArchiveSessionRequest { + session_id: string; +} diff --git a/web/packages/agenta-api-client/src/generated/api/resources/sessions/client/requests/GetStateRequest.ts b/web/packages/agenta-api-client/src/generated/api/resources/sessions/client/requests/DeleteSessionRequest.ts similarity index 80% rename from web/packages/agenta-api-client/src/generated/api/resources/sessions/client/requests/GetStateRequest.ts rename to web/packages/agenta-api-client/src/generated/api/resources/sessions/client/requests/DeleteSessionRequest.ts index 2a6e0c5387..d5f6772b62 100644 --- a/web/packages/agenta-api-client/src/generated/api/resources/sessions/client/requests/GetStateRequest.ts +++ b/web/packages/agenta-api-client/src/generated/api/resources/sessions/client/requests/DeleteSessionRequest.ts @@ -6,6 +6,6 @@ * session_id: "session_id" * } */ -export interface GetStateRequest { +export interface DeleteSessionRequest { session_id: string; } diff --git a/web/packages/agenta-api-client/src/generated/api/resources/sessions/client/requests/FetchTurnRequest.ts b/web/packages/agenta-api-client/src/generated/api/resources/sessions/client/requests/FetchTurnRequest.ts new file mode 100644 index 0000000000..48053bec8b --- /dev/null +++ b/web/packages/agenta-api-client/src/generated/api/resources/sessions/client/requests/FetchTurnRequest.ts @@ -0,0 +1,11 @@ +// This file was auto-generated by Fern from our API Definition. + +/** + * @example + * { + * turn_id: "turn_id" + * } + */ +export interface FetchTurnRequest { + turn_id: string; +} diff --git a/web/packages/agenta-api-client/src/generated/api/resources/sessions/client/requests/SessionDetachRequestModel.ts b/web/packages/agenta-api-client/src/generated/api/resources/sessions/client/requests/SessionDetachRequest.ts similarity index 83% rename from web/packages/agenta-api-client/src/generated/api/resources/sessions/client/requests/SessionDetachRequestModel.ts rename to web/packages/agenta-api-client/src/generated/api/resources/sessions/client/requests/SessionDetachRequest.ts index 493e6b76b3..43431f3f4a 100644 --- a/web/packages/agenta-api-client/src/generated/api/resources/sessions/client/requests/SessionDetachRequestModel.ts +++ b/web/packages/agenta-api-client/src/generated/api/resources/sessions/client/requests/SessionDetachRequest.ts @@ -7,7 +7,7 @@ * watcher_id: "watcher_id" * } */ -export interface SessionDetachRequestModel { +export interface SessionDetachRequest { session_id: string; watcher_id: string; } diff --git a/web/packages/agenta-api-client/src/generated/api/resources/sessions/client/requests/SessionHeartbeatRequestModel.ts b/web/packages/agenta-api-client/src/generated/api/resources/sessions/client/requests/SessionHeartbeatRequest.ts similarity index 85% rename from web/packages/agenta-api-client/src/generated/api/resources/sessions/client/requests/SessionHeartbeatRequestModel.ts rename to web/packages/agenta-api-client/src/generated/api/resources/sessions/client/requests/SessionHeartbeatRequest.ts index f63c331196..2ba9057ba8 100644 --- a/web/packages/agenta-api-client/src/generated/api/resources/sessions/client/requests/SessionHeartbeatRequestModel.ts +++ b/web/packages/agenta-api-client/src/generated/api/resources/sessions/client/requests/SessionHeartbeatRequest.ts @@ -7,7 +7,7 @@ * replica_id: "replica_id" * } */ -export interface SessionHeartbeatRequestModel { +export interface SessionHeartbeatRequest { session_id: string; replica_id: string; turn_id?: string | null; diff --git a/web/packages/agenta-api-client/src/generated/api/resources/sessions/client/requests/SessionQueryRequest.ts b/web/packages/agenta-api-client/src/generated/api/resources/sessions/client/requests/SessionQueryRequest.ts new file mode 100644 index 0000000000..5967190be9 --- /dev/null +++ b/web/packages/agenta-api-client/src/generated/api/resources/sessions/client/requests/SessionQueryRequest.ts @@ -0,0 +1,12 @@ +// This file was auto-generated by Fern from our API Definition. + +import type * as AgentaApi from "../../../../index.js"; + +/** + * @example + * {} + */ +export interface SessionQueryRequest { + references?: AgentaApi.Reference[] | null; + windowing?: AgentaApi.Windowing | null; +} diff --git a/web/packages/agenta-api-client/src/generated/api/resources/sessions/client/requests/SessionRecordIngestRequest.ts b/web/packages/agenta-api-client/src/generated/api/resources/sessions/client/requests/SessionRecordIngestRequest.ts index 8474bc41e8..b6112eee70 100644 --- a/web/packages/agenta-api-client/src/generated/api/resources/sessions/client/requests/SessionRecordIngestRequest.ts +++ b/web/packages/agenta-api-client/src/generated/api/resources/sessions/client/requests/SessionRecordIngestRequest.ts @@ -14,4 +14,6 @@ export interface SessionRecordIngestRequest { record_type?: string | null; record_source?: string | null; attributes?: Record | null; + turn_id?: string | null; + span_id?: string | null; } diff --git a/web/packages/agenta-api-client/src/generated/api/resources/sessions/client/requests/SessionStreamCommandRequestModel.ts b/web/packages/agenta-api-client/src/generated/api/resources/sessions/client/requests/SessionStreamCommandRequest.ts similarity index 57% rename from web/packages/agenta-api-client/src/generated/api/resources/sessions/client/requests/SessionStreamCommandRequestModel.ts rename to web/packages/agenta-api-client/src/generated/api/resources/sessions/client/requests/SessionStreamCommandRequest.ts index 513497529d..5d66eef4a2 100644 --- a/web/packages/agenta-api-client/src/generated/api/resources/sessions/client/requests/SessionStreamCommandRequestModel.ts +++ b/web/packages/agenta-api-client/src/generated/api/resources/sessions/client/requests/SessionStreamCommandRequest.ts @@ -1,14 +1,16 @@ // This file was auto-generated by Fern from our API Definition. +import type * as AgentaApi from "../../../../index.js"; + /** * @example * { * session_id: "session_id" * } */ -export interface SessionStreamCommandRequestModel { +export interface SessionStreamCommandRequest { session_id: string; - prompt?: string | null; + data?: AgentaApi.WorkflowRequestData | null; force?: boolean; detached?: boolean; } diff --git a/web/packages/agenta-api-client/src/generated/api/resources/sessions/client/requests/SessionStreamQueryRequestModel.ts b/web/packages/agenta-api-client/src/generated/api/resources/sessions/client/requests/SessionStreamQueryRequest.ts similarity index 79% rename from web/packages/agenta-api-client/src/generated/api/resources/sessions/client/requests/SessionStreamQueryRequestModel.ts rename to web/packages/agenta-api-client/src/generated/api/resources/sessions/client/requests/SessionStreamQueryRequest.ts index 8597fb46d4..f9dcdaaf27 100644 --- a/web/packages/agenta-api-client/src/generated/api/resources/sessions/client/requests/SessionStreamQueryRequestModel.ts +++ b/web/packages/agenta-api-client/src/generated/api/resources/sessions/client/requests/SessionStreamQueryRequest.ts @@ -4,7 +4,7 @@ * @example * {} */ -export interface SessionStreamQueryRequestModel { +export interface SessionStreamQueryRequest { session_id?: string | null; is_alive?: boolean | null; is_running?: boolean | null; diff --git a/web/packages/agenta-api-client/src/generated/api/resources/sessions/client/requests/SessionTurnAppendRequest.ts b/web/packages/agenta-api-client/src/generated/api/resources/sessions/client/requests/SessionTurnAppendRequest.ts new file mode 100644 index 0000000000..9f3935de27 --- /dev/null +++ b/web/packages/agenta-api-client/src/generated/api/resources/sessions/client/requests/SessionTurnAppendRequest.ts @@ -0,0 +1,26 @@ +// This file was auto-generated by Fern from our API Definition. + +import type * as AgentaApi from "../../../../index.js"; + +/** + * @example + * { + * session_id: "session_id", + * stream_id: "stream_id", + * turn_index: 1, + * harness_kind: "pi_core" + * } + */ +export interface SessionTurnAppendRequest { + session_id: string; + stream_id: string; + turn_index: number; + harness_kind: AgentaApi.HarnessKind; + agent_session_id?: string | null; + sandbox_id?: string | null; + references?: AgentaApi.Reference[] | null; + trace_id?: string | null; + span_id?: string | null; + start_time?: string | null; + end_time?: string | null; +} diff --git a/web/packages/agenta-api-client/src/generated/api/resources/sessions/client/requests/SessionTurnQueryRequest.ts b/web/packages/agenta-api-client/src/generated/api/resources/sessions/client/requests/SessionTurnQueryRequest.ts new file mode 100644 index 0000000000..c49b5ebfc8 --- /dev/null +++ b/web/packages/agenta-api-client/src/generated/api/resources/sessions/client/requests/SessionTurnQueryRequest.ts @@ -0,0 +1,12 @@ +// This file was auto-generated by Fern from our API Definition. + +import type * as AgentaApi from "../../../../index.js"; + +/** + * @example + * {} + */ +export interface SessionTurnQueryRequest { + query?: AgentaApi.SessionTurnQuery | null; + windowing?: AgentaApi.Windowing | null; +} diff --git a/web/packages/agenta-api-client/src/generated/api/resources/sessions/client/requests/SetStateRequest.ts b/web/packages/agenta-api-client/src/generated/api/resources/sessions/client/requests/SetSessionStreamHeaderRequest.ts similarity index 72% rename from web/packages/agenta-api-client/src/generated/api/resources/sessions/client/requests/SetStateRequest.ts rename to web/packages/agenta-api-client/src/generated/api/resources/sessions/client/requests/SetSessionStreamHeaderRequest.ts index a0381b7d4e..c988bb7b2f 100644 --- a/web/packages/agenta-api-client/src/generated/api/resources/sessions/client/requests/SetStateRequest.ts +++ b/web/packages/agenta-api-client/src/generated/api/resources/sessions/client/requests/SetSessionStreamHeaderRequest.ts @@ -9,7 +9,7 @@ import type * as AgentaApi from "../../../../index.js"; * body: {} * } */ -export interface SetStateRequest { +export interface SetSessionStreamHeaderRequest { session_id: string; - body: AgentaApi.SessionStateUpsertRequest; + body: AgentaApi.SessionStreamHeaderEdit; } diff --git a/web/packages/agenta-api-client/src/generated/api/resources/sessions/client/requests/UnarchiveSessionRequest.ts b/web/packages/agenta-api-client/src/generated/api/resources/sessions/client/requests/UnarchiveSessionRequest.ts new file mode 100644 index 0000000000..218016d453 --- /dev/null +++ b/web/packages/agenta-api-client/src/generated/api/resources/sessions/client/requests/UnarchiveSessionRequest.ts @@ -0,0 +1,11 @@ +// This file was auto-generated by Fern from our API Definition. + +/** + * @example + * { + * session_id: "session_id" + * } + */ +export interface UnarchiveSessionRequest { + session_id: string; +} diff --git a/web/packages/agenta-api-client/src/generated/api/resources/sessions/client/requests/index.ts b/web/packages/agenta-api-client/src/generated/api/resources/sessions/client/requests/index.ts index 387628d226..865b11028c 100644 --- a/web/packages/agenta-api-client/src/generated/api/resources/sessions/client/requests/index.ts +++ b/web/packages/agenta-api-client/src/generated/api/resources/sessions/client/requests/index.ts @@ -1,22 +1,28 @@ +export type { ArchiveSessionRequest } from "./ArchiveSessionRequest.js"; export type { BodyUploadSessionMountFile } from "./BodyUploadSessionMountFile.js"; +export type { DeleteSessionRequest } from "./DeleteSessionRequest.js"; export type { DeleteSessionStreamRequest } from "./DeleteSessionStreamRequest.js"; export type { DownloadSessionMountFileRequest } from "./DownloadSessionMountFileRequest.js"; export type { FetchInteractionRequest } from "./FetchInteractionRequest.js"; export type { FetchSessionMountsRequest } from "./FetchSessionMountsRequest.js"; export type { FetchSessionStreamRequest } from "./FetchSessionStreamRequest.js"; +export type { FetchTurnRequest } from "./FetchTurnRequest.js"; export type { GetRecordEventRequest } from "./GetRecordEventRequest.js"; -export type { GetStateRequest } from "./GetStateRequest.js"; -export type { SessionDetachRequestModel } from "./SessionDetachRequestModel.js"; -export type { SessionHeartbeatRequestModel } from "./SessionHeartbeatRequestModel.js"; +export type { SessionDetachRequest } from "./SessionDetachRequest.js"; +export type { SessionHeartbeatRequest } from "./SessionHeartbeatRequest.js"; export type { SessionInteractionCancelStaleRequest } from "./SessionInteractionCancelStaleRequest.js"; export type { SessionInteractionCreateRequest } from "./SessionInteractionCreateRequest.js"; export type { SessionInteractionQueryRequest } from "./SessionInteractionQueryRequest.js"; export type { SessionInteractionRespondRequest } from "./SessionInteractionRespondRequest.js"; export type { SessionInteractionTransitionRequest } from "./SessionInteractionTransitionRequest.js"; export type { SessionMountQueryRequest } from "./SessionMountQueryRequest.js"; +export type { SessionQueryRequest } from "./SessionQueryRequest.js"; export type { SessionRecordIngestRequest } from "./SessionRecordIngestRequest.js"; export type { SessionRecordQueryRequest } from "./SessionRecordQueryRequest.js"; -export type { SessionStreamCommandRequestModel } from "./SessionStreamCommandRequestModel.js"; -export type { SessionStreamQueryRequestModel } from "./SessionStreamQueryRequestModel.js"; -export type { SetStateRequest } from "./SetStateRequest.js"; +export type { SessionStreamCommandRequest } from "./SessionStreamCommandRequest.js"; +export type { SessionStreamQueryRequest } from "./SessionStreamQueryRequest.js"; +export type { SessionTurnAppendRequest } from "./SessionTurnAppendRequest.js"; +export type { SessionTurnQueryRequest } from "./SessionTurnQueryRequest.js"; +export type { SetSessionStreamHeaderRequest } from "./SetSessionStreamHeaderRequest.js"; export type { SignSessionMountCredentialsRequest } from "./SignSessionMountCredentialsRequest.js"; +export type { UnarchiveSessionRequest } from "./UnarchiveSessionRequest.js"; diff --git a/web/packages/agenta-api-client/src/generated/api/types/CommandMode.ts b/web/packages/agenta-api-client/src/generated/api/types/CommandMode.ts index e64e623d23..6dd9d5a0e3 100644 --- a/web/packages/agenta-api-client/src/generated/api/types/CommandMode.ts +++ b/web/packages/agenta-api-client/src/generated/api/types/CommandMode.ts @@ -1,6 +1,6 @@ // This file was auto-generated by Fern from our API Definition. -/** Derived from the prompt × force matrix. */ +/** Derived from the inputs/data × force matrix. */ export const CommandMode = { Send: "send", Steer: "steer", diff --git a/web/packages/agenta-api-client/src/generated/api/types/HarnessKind.ts b/web/packages/agenta-api-client/src/generated/api/types/HarnessKind.ts new file mode 100644 index 0000000000..d71e859a21 --- /dev/null +++ b/web/packages/agenta-api-client/src/generated/api/types/HarnessKind.ts @@ -0,0 +1,14 @@ +// This file was auto-generated by Fern from our API Definition. + +/** + * The coding agent program a run drives. A backend declares which it supports. + * + * ``pi_core`` is plain Pi; ``pi_agenta`` is Pi with Agenta's forced skills, prompt, and + * policy. Both drive the same ``pi`` ACP agent in the runner; ``claude`` drives Claude Code. + */ +export const HarnessKind = { + PiCore: "pi_core", + Claude: "claude", + PiAgenta: "pi_agenta", +} as const; +export type HarnessKind = (typeof HarnessKind)[keyof typeof HarnessKind]; diff --git a/web/packages/agenta-api-client/src/generated/api/types/HarnessSessionRecord.ts b/web/packages/agenta-api-client/src/generated/api/types/HarnessSessionRecord.ts deleted file mode 100644 index cbffe4da76..0000000000 --- a/web/packages/agenta-api-client/src/generated/api/types/HarnessSessionRecord.ts +++ /dev/null @@ -1,11 +0,0 @@ -// This file was auto-generated by Fern from our API Definition. - -/** - * Per-harness resume state. Value shape of `data.harness_sessions[]`. - */ -export interface HarnessSessionRecord { - /** This harness's own agentSessionId, fed to session/load on resume. */ - agent_session_id?: (string | null) | undefined; - /** Conversation turn number this harness last ran at. Load-eligible only when equal to the conversation's latest_turn_index; otherwise this harness's session file is stale (another harness ran since). */ - turn_index?: (number | null) | undefined; -} diff --git a/web/packages/agenta-api-client/src/generated/api/types/Mount.ts b/web/packages/agenta-api-client/src/generated/api/types/Mount.ts index 54951c0432..782de5ec4f 100644 --- a/web/packages/agenta-api-client/src/generated/api/types/Mount.ts +++ b/web/packages/agenta-api-client/src/generated/api/types/Mount.ts @@ -15,6 +15,7 @@ export interface Mount { id?: (string | null) | undefined; project_id: string; session_id?: (string | null) | undefined; + agent_id?: (string | null) | undefined; data?: AgentaApi.MountData | undefined; flags?: AgentaApi.MountFlags | undefined; tags?: (Record | null) | undefined; diff --git a/web/packages/agenta-api-client/src/generated/api/types/MountCreate.ts b/web/packages/agenta-api-client/src/generated/api/types/MountCreate.ts index 49e362e25d..8480e6d653 100644 --- a/web/packages/agenta-api-client/src/generated/api/types/MountCreate.ts +++ b/web/packages/agenta-api-client/src/generated/api/types/MountCreate.ts @@ -7,6 +7,7 @@ export interface MountCreate { description?: (string | null) | undefined; slug?: (string | null) | undefined; session_id?: (string | null) | undefined; + agent_id?: (string | null) | undefined; flags?: AgentaApi.MountFlags | undefined; tags?: (Record | null) | undefined; meta?: (Record | null) | undefined; diff --git a/web/packages/agenta-api-client/src/generated/api/types/MountQuery.ts b/web/packages/agenta-api-client/src/generated/api/types/MountQuery.ts index 3f09a41603..d6a218f327 100644 --- a/web/packages/agenta-api-client/src/generated/api/types/MountQuery.ts +++ b/web/packages/agenta-api-client/src/generated/api/types/MountQuery.ts @@ -2,5 +2,6 @@ export interface MountQuery { session_id?: (string | null) | undefined; + agent_id?: (string | null) | undefined; include_archived?: boolean | undefined; } diff --git a/web/packages/agenta-api-client/src/generated/api/types/SessionHeartbeatResponseModel.ts b/web/packages/agenta-api-client/src/generated/api/types/SessionHeartbeatResponseModel.ts deleted file mode 100644 index 1525ba574c..0000000000 --- a/web/packages/agenta-api-client/src/generated/api/types/SessionHeartbeatResponseModel.ts +++ /dev/null @@ -1,8 +0,0 @@ -// This file was auto-generated by Fern from our API Definition. - -import type * as AgentaApi from "../index.js"; - -export interface SessionHeartbeatResponseModel { - stream?: (AgentaApi.SessionStream | null) | undefined; - replica_id: string; -} diff --git a/web/packages/agenta-api-client/src/generated/api/types/SessionHeartbeatResult.ts b/web/packages/agenta-api-client/src/generated/api/types/SessionHeartbeatResult.ts new file mode 100644 index 0000000000..9c79dbda1d --- /dev/null +++ b/web/packages/agenta-api-client/src/generated/api/types/SessionHeartbeatResult.ts @@ -0,0 +1,25 @@ +// This file was auto-generated by Fern from our API Definition. + +import type * as AgentaApi from "../index.js"; + +/** + * A heartbeat's outcome: the reconciled stream plus the session's actual owner replica. + * + * `replica_id` is the replica that currently holds the affinity key after the claim + * (this caller if it won or already held it, another replica otherwise). The runner reads + * it to refuse serving a local sandbox session it does not own. + * + * `stream` is None when a losing replica heartbeats a session that has no row yet: it may + * not create or stamp one, since that row belongs to the owner. + * + * `is_current_turn` (W7.4) is False when this turn_id's alive/running lock was gone or + * reassigned at the moment of this beat — i.e. a cancel/steer/kill interrupted this turn + * since the last heartbeat. The runner's watchdog reads this to abort the in-flight run; + * without it a cancel that raced a heartbeat's nx=True re-acquire would silently re-arm the + * SAME lock under the SAME turn_id and the interruption would never surface. + */ +export interface SessionHeartbeatResult { + stream?: (AgentaApi.SessionStream | null) | undefined; + replica_id: string; + is_current_turn?: boolean | undefined; +} diff --git a/web/packages/agenta-api-client/src/generated/api/types/SessionInteraction.ts b/web/packages/agenta-api-client/src/generated/api/types/SessionInteraction.ts index cca307742c..a2fdc456c0 100644 --- a/web/packages/agenta-api-client/src/generated/api/types/SessionInteraction.ts +++ b/web/packages/agenta-api-client/src/generated/api/types/SessionInteraction.ts @@ -3,13 +3,13 @@ import type * as AgentaApi from "../index.js"; export interface SessionInteraction { - id?: (string | null) | undefined; - created_at?: (unknown | null) | undefined; - updated_at?: (unknown | null) | undefined; - deleted_at?: (unknown | null) | undefined; + created_at?: (string | null) | undefined; + updated_at?: (string | null) | undefined; + deleted_at?: (string | null) | undefined; created_by_id?: (string | null) | undefined; updated_by_id?: (string | null) | undefined; deleted_by_id?: (string | null) | undefined; + id?: (string | null) | undefined; project_id?: (string | null) | undefined; session_id: string; turn_id?: (string | null) | undefined; diff --git a/web/packages/agenta-api-client/src/generated/api/types/SessionMount.ts b/web/packages/agenta-api-client/src/generated/api/types/SessionMount.ts index 16b27386f7..1556a1d8fc 100644 --- a/web/packages/agenta-api-client/src/generated/api/types/SessionMount.ts +++ b/web/packages/agenta-api-client/src/generated/api/types/SessionMount.ts @@ -15,6 +15,7 @@ export interface SessionMount { id?: (string | null) | undefined; project_id: string; session_id: string; + agent_id?: (string | null) | undefined; data?: AgentaApi.MountData | undefined; flags?: AgentaApi.MountFlags | undefined; tags?: (Record | null) | undefined; diff --git a/web/packages/agenta-api-client/src/generated/api/types/SessionMountQuery.ts b/web/packages/agenta-api-client/src/generated/api/types/SessionMountQuery.ts index 9c232acfbc..06ad6df822 100644 --- a/web/packages/agenta-api-client/src/generated/api/types/SessionMountQuery.ts +++ b/web/packages/agenta-api-client/src/generated/api/types/SessionMountQuery.ts @@ -2,5 +2,6 @@ export interface SessionMountQuery { session_id: string; + agent_id?: (string | null) | undefined; include_archived?: boolean | undefined; } diff --git a/web/packages/agenta-api-client/src/generated/api/types/SessionRecord.ts b/web/packages/agenta-api-client/src/generated/api/types/SessionRecord.ts index 2117e807c8..8f868ae176 100644 --- a/web/packages/agenta-api-client/src/generated/api/types/SessionRecord.ts +++ b/web/packages/agenta-api-client/src/generated/api/types/SessionRecord.ts @@ -1,6 +1,12 @@ // This file was auto-generated by Fern from our API Definition. export interface SessionRecord { + created_at?: (string | null) | undefined; + updated_at?: (string | null) | undefined; + deleted_at?: (string | null) | undefined; + created_by_id?: (string | null) | undefined; + updated_by_id?: (string | null) | undefined; + deleted_by_id?: (string | null) | undefined; record_id: string; session_id: string; project_id: string; @@ -9,5 +15,6 @@ export interface SessionRecord { record_type?: (string | null) | undefined; record_source?: (string | null) | undefined; attributes?: (Record | null) | undefined; - created_at?: (string | null) | undefined; + turn_id?: (string | null) | undefined; + span_id?: (string | null) | undefined; } diff --git a/web/packages/agenta-api-client/src/generated/api/types/SessionStateResponse.ts b/web/packages/agenta-api-client/src/generated/api/types/SessionResponse.ts similarity index 58% rename from web/packages/agenta-api-client/src/generated/api/types/SessionStateResponse.ts rename to web/packages/agenta-api-client/src/generated/api/types/SessionResponse.ts index 391e0fa210..f8ed41b31f 100644 --- a/web/packages/agenta-api-client/src/generated/api/types/SessionStateResponse.ts +++ b/web/packages/agenta-api-client/src/generated/api/types/SessionResponse.ts @@ -2,7 +2,7 @@ import type * as AgentaApi from "../index.js"; -export interface SessionStateResponse { +export interface SessionResponse { count?: number | undefined; - session_state?: (AgentaApi.SessionState | null) | undefined; + session?: (AgentaApi.SessionStream | null) | undefined; } diff --git a/web/packages/agenta-api-client/src/generated/api/types/SessionState.ts b/web/packages/agenta-api-client/src/generated/api/types/SessionState.ts deleted file mode 100644 index da60c8f41f..0000000000 --- a/web/packages/agenta-api-client/src/generated/api/types/SessionState.ts +++ /dev/null @@ -1,24 +0,0 @@ -// This file was auto-generated by Fern from our API Definition. - -import type * as AgentaApi from "../index.js"; - -export interface SessionState { - created_at?: (string | null) | undefined; - updated_at?: (string | null) | undefined; - deleted_at?: (string | null) | undefined; - created_by_id?: (string | null) | undefined; - updated_by_id?: (string | null) | undefined; - deleted_by_id?: (string | null) | undefined; - /** Own uuid7 pk (state_id). */ - id?: (string | null) | undefined; - project_id?: (string | null) | undefined; - /** Bare session correlator (not an FK). */ - session_id: string; - /** Durable continuity state (resume ids + staleness guard). */ - data?: (AgentaApi.SessionStateData | null) | undefined; - /** Remote sandbox id — the single source of truth resume pointer. */ - sandbox_id?: (string | null) | undefined; - flags?: AgentaApi.SessionStateFlags | undefined; - tags?: (Record | null) | undefined; - meta?: (Record | null) | undefined; -} diff --git a/web/packages/agenta-api-client/src/generated/api/types/SessionStateData.ts b/web/packages/agenta-api-client/src/generated/api/types/SessionStateData.ts deleted file mode 100644 index 93616b4aa3..0000000000 --- a/web/packages/agenta-api-client/src/generated/api/types/SessionStateData.ts +++ /dev/null @@ -1,19 +0,0 @@ -// This file was auto-generated by Fern from our API Definition. - -import type * as AgentaApi from "../index.js"; - -/** - * Typed shape of the `data` column: the session's durable continuity state. - * - * Stored in the existing `data` JSON column (no dedicated columns) — every field is - * read and compared in the runner, never queried server-side, so a typed DTO gives the - * contract without a schema change. - */ -export interface SessionStateData { - /** The latest-run harness's agentSessionId; a fast-path mirror of harness_sessions[].agent_session_id. */ - latest_agent_session_id?: (string | null) | undefined; - /** Conversation-level turn counter compared against each harness's turn_index. */ - latest_turn_index?: (number | null) | undefined; - /** Per-harness resume state, keyed by harness id (e.g. 'claude', 'pi'). Durable mirror of the staleness guard. */ - harness_sessions?: (Record | null) | undefined; -} diff --git a/web/packages/agenta-api-client/src/generated/api/types/SessionStateFlags.ts b/web/packages/agenta-api-client/src/generated/api/types/SessionStateFlags.ts deleted file mode 100644 index 7635753e07..0000000000 --- a/web/packages/agenta-api-client/src/generated/api/types/SessionStateFlags.ts +++ /dev/null @@ -1,3 +0,0 @@ -// This file was auto-generated by Fern from our API Definition. - -export type SessionStateFlags = {}; diff --git a/web/packages/agenta-api-client/src/generated/api/types/SessionStateUpsertRequest.ts b/web/packages/agenta-api-client/src/generated/api/types/SessionStateUpsertRequest.ts deleted file mode 100644 index 945214c2a1..0000000000 --- a/web/packages/agenta-api-client/src/generated/api/types/SessionStateUpsertRequest.ts +++ /dev/null @@ -1,12 +0,0 @@ -// This file was auto-generated by Fern from our API Definition. - -import type * as AgentaApi from "../index.js"; - -export interface SessionStateUpsertRequest { - /** Full replacement of the continuity state (resume ids + staleness guard). */ - data?: (AgentaApi.SessionStateData | null) | undefined; - /** Remote sandbox id to record alongside the continuity state. */ - sandbox_id?: (string | null) | undefined; - /** the writer's conversation turn index; the pointer write is applied only when it is >= the row's data.latest_turn_index. */ - sandbox_turn_index?: (number | null) | undefined; -} diff --git a/web/packages/agenta-api-client/src/generated/api/types/SessionStream.ts b/web/packages/agenta-api-client/src/generated/api/types/SessionStream.ts index 0a03b3046e..cc2360682d 100644 --- a/web/packages/agenta-api-client/src/generated/api/types/SessionStream.ts +++ b/web/packages/agenta-api-client/src/generated/api/types/SessionStream.ts @@ -3,13 +3,15 @@ import type * as AgentaApi from "../index.js"; export interface SessionStream { - id: string; created_at?: (string | null) | undefined; updated_at?: (string | null) | undefined; deleted_at?: (string | null) | undefined; created_by_id?: (string | null) | undefined; updated_by_id?: (string | null) | undefined; deleted_by_id?: (string | null) | undefined; + name?: (string | null) | undefined; + description?: (string | null) | undefined; + id?: (string | null) | undefined; project_id: string; session_id: string; flags?: AgentaApi.SessionStreamFlags | undefined; diff --git a/web/packages/agenta-api-client/src/generated/api/types/SessionStreamCommandResponseModel.ts b/web/packages/agenta-api-client/src/generated/api/types/SessionStreamCommandResponse.ts similarity index 84% rename from web/packages/agenta-api-client/src/generated/api/types/SessionStreamCommandResponseModel.ts rename to web/packages/agenta-api-client/src/generated/api/types/SessionStreamCommandResponse.ts index 2af3ebaf29..18d1acc503 100644 --- a/web/packages/agenta-api-client/src/generated/api/types/SessionStreamCommandResponseModel.ts +++ b/web/packages/agenta-api-client/src/generated/api/types/SessionStreamCommandResponse.ts @@ -2,7 +2,7 @@ import type * as AgentaApi from "../index.js"; -export interface SessionStreamCommandResponseModel { +export interface SessionStreamCommandResponse { mode: AgentaApi.CommandMode; session_id: string; turn_id?: (string | null) | undefined; diff --git a/web/packages/agenta-api-client/src/generated/api/types/SessionStreamHeaderEdit.ts b/web/packages/agenta-api-client/src/generated/api/types/SessionStreamHeaderEdit.ts new file mode 100644 index 0000000000..0fc8591908 --- /dev/null +++ b/web/packages/agenta-api-client/src/generated/api/types/SessionStreamHeaderEdit.ts @@ -0,0 +1,12 @@ +// This file was auto-generated by Fern from our API Definition. + +/** + * The rename edit: a full-PUT of the header fields only. + * + * Distinct from SessionStreamEdit (used by the flag-mirror/heartbeat paths) so the + * liveness-only writes can never carry name/description, and vice versa. + */ +export interface SessionStreamHeaderEdit { + name?: (string | null) | undefined; + description?: (string | null) | undefined; +} diff --git a/web/packages/agenta-api-client/src/generated/api/types/SessionStreamResponseModel.ts b/web/packages/agenta-api-client/src/generated/api/types/SessionStreamResponse.ts similarity index 79% rename from web/packages/agenta-api-client/src/generated/api/types/SessionStreamResponseModel.ts rename to web/packages/agenta-api-client/src/generated/api/types/SessionStreamResponse.ts index 316e1359dc..c94ce7ba5a 100644 --- a/web/packages/agenta-api-client/src/generated/api/types/SessionStreamResponseModel.ts +++ b/web/packages/agenta-api-client/src/generated/api/types/SessionStreamResponse.ts @@ -2,6 +2,6 @@ import type * as AgentaApi from "../index.js"; -export interface SessionStreamResponseModel { +export interface SessionStreamResponse { stream?: (AgentaApi.SessionStream | null) | undefined; } diff --git a/web/packages/agenta-api-client/src/generated/api/types/SessionStreamsResponseModel.ts b/web/packages/agenta-api-client/src/generated/api/types/SessionStreamsResponse.ts similarity index 78% rename from web/packages/agenta-api-client/src/generated/api/types/SessionStreamsResponseModel.ts rename to web/packages/agenta-api-client/src/generated/api/types/SessionStreamsResponse.ts index 4a65cf8d99..aa69417c77 100644 --- a/web/packages/agenta-api-client/src/generated/api/types/SessionStreamsResponseModel.ts +++ b/web/packages/agenta-api-client/src/generated/api/types/SessionStreamsResponse.ts @@ -2,7 +2,7 @@ import type * as AgentaApi from "../index.js"; -export interface SessionStreamsResponseModel { +export interface SessionStreamsResponse { count: number; streams: AgentaApi.SessionStream[]; } diff --git a/web/packages/agenta-api-client/src/generated/api/types/SessionTurn.ts b/web/packages/agenta-api-client/src/generated/api/types/SessionTurn.ts new file mode 100644 index 0000000000..3e579fd46f --- /dev/null +++ b/web/packages/agenta-api-client/src/generated/api/types/SessionTurn.ts @@ -0,0 +1,25 @@ +// This file was auto-generated by Fern from our API Definition. + +import type * as AgentaApi from "../index.js"; + +export interface SessionTurn { + created_at?: (string | null) | undefined; + updated_at?: (string | null) | undefined; + deleted_at?: (string | null) | undefined; + created_by_id?: (string | null) | undefined; + updated_by_id?: (string | null) | undefined; + deleted_by_id?: (string | null) | undefined; + id?: (string | null) | undefined; + project_id: string; + session_id: string; + stream_id: string; + turn_index: number; + harness_kind: AgentaApi.HarnessKind; + agent_session_id?: (string | null) | undefined; + sandbox_id?: (string | null) | undefined; + references?: (AgentaApi.Reference[] | null) | undefined; + trace_id?: (string | null) | undefined; + span_id?: (string | null) | undefined; + start_time?: (string | null) | undefined; + end_time?: (string | null) | undefined; +} diff --git a/web/packages/agenta-api-client/src/generated/api/types/SessionTurnQuery.ts b/web/packages/agenta-api-client/src/generated/api/types/SessionTurnQuery.ts new file mode 100644 index 0000000000..ce85a78b68 --- /dev/null +++ b/web/packages/agenta-api-client/src/generated/api/types/SessionTurnQuery.ts @@ -0,0 +1,10 @@ +// This file was auto-generated by Fern from our API Definition. + +import type * as AgentaApi from "../index.js"; + +export interface SessionTurnQuery { + session_id?: (string | null) | undefined; + stream_id?: (string | null) | undefined; + harness_kind?: (AgentaApi.HarnessKind | null) | undefined; + references?: (AgentaApi.Reference[] | null) | undefined; +} diff --git a/web/packages/agenta-api-client/src/generated/api/types/SessionTurnResponse.ts b/web/packages/agenta-api-client/src/generated/api/types/SessionTurnResponse.ts new file mode 100644 index 0000000000..e4aeb68119 --- /dev/null +++ b/web/packages/agenta-api-client/src/generated/api/types/SessionTurnResponse.ts @@ -0,0 +1,8 @@ +// This file was auto-generated by Fern from our API Definition. + +import type * as AgentaApi from "../index.js"; + +export interface SessionTurnResponse { + count?: number | undefined; + turn?: (AgentaApi.SessionTurn | null) | undefined; +} diff --git a/web/packages/agenta-api-client/src/generated/api/types/SessionTurnsResponse.ts b/web/packages/agenta-api-client/src/generated/api/types/SessionTurnsResponse.ts new file mode 100644 index 0000000000..d76907c197 --- /dev/null +++ b/web/packages/agenta-api-client/src/generated/api/types/SessionTurnsResponse.ts @@ -0,0 +1,8 @@ +// This file was auto-generated by Fern from our API Definition. + +import type * as AgentaApi from "../index.js"; + +export interface SessionTurnsResponse { + count?: number | undefined; + turns?: AgentaApi.SessionTurn[] | undefined; +} diff --git a/web/packages/agenta-api-client/src/generated/api/types/SessionsResponse.ts b/web/packages/agenta-api-client/src/generated/api/types/SessionsResponse.ts new file mode 100644 index 0000000000..f7a494729c --- /dev/null +++ b/web/packages/agenta-api-client/src/generated/api/types/SessionsResponse.ts @@ -0,0 +1,8 @@ +// This file was auto-generated by Fern from our API Definition. + +import type * as AgentaApi from "../index.js"; + +export interface SessionsResponse { + count?: number | undefined; + sessions?: AgentaApi.SessionStream[] | undefined; +} diff --git a/web/packages/agenta-api-client/src/generated/api/types/SpanInput.ts b/web/packages/agenta-api-client/src/generated/api/types/SpanInput.ts index 59b58e8e62..ea48899f0e 100644 --- a/web/packages/agenta-api-client/src/generated/api/types/SpanInput.ts +++ b/web/packages/agenta-api-client/src/generated/api/types/SpanInput.ts @@ -20,6 +20,9 @@ export interface SpanInput { end_time?: (SpanInput.EndTime | null) | undefined; status_code?: (AgentaApi.OTelStatusCode | null) | undefined; status_message?: (string | null) | undefined; + session_id?: (string | null) | undefined; + user_id?: (string | null) | undefined; + agent_id?: (string | null) | undefined; attributes?: (Record | null) | undefined; references?: (AgentaApi.OTelReferenceInput[] | null) | undefined; links?: (AgentaApi.OTelLinkInput[] | null) | undefined; diff --git a/web/packages/agenta-api-client/src/generated/api/types/SpanOutput.ts b/web/packages/agenta-api-client/src/generated/api/types/SpanOutput.ts index fe97744187..946e8d7de2 100644 --- a/web/packages/agenta-api-client/src/generated/api/types/SpanOutput.ts +++ b/web/packages/agenta-api-client/src/generated/api/types/SpanOutput.ts @@ -20,6 +20,9 @@ export interface SpanOutput { end_time?: (SpanOutput.EndTime | null) | undefined; status_code?: (AgentaApi.OTelStatusCode | null) | undefined; status_message?: (string | null) | undefined; + session_id?: (string | null) | undefined; + user_id?: (string | null) | undefined; + agent_id?: (string | null) | undefined; attributes?: (Record | null) | undefined; references?: (AgentaApi.OTelReferenceOutput[] | null) | undefined; links?: (AgentaApi.OTelLinkOutput[] | null) | undefined; diff --git a/web/packages/agenta-api-client/src/generated/api/types/SpansNodeInput.ts b/web/packages/agenta-api-client/src/generated/api/types/SpansNodeInput.ts index 46f1cbc944..4333b85943 100644 --- a/web/packages/agenta-api-client/src/generated/api/types/SpansNodeInput.ts +++ b/web/packages/agenta-api-client/src/generated/api/types/SpansNodeInput.ts @@ -21,6 +21,9 @@ export interface SpansNodeInput { end_time?: (SpansNodeInput.EndTime | null) | undefined; status_code?: (AgentaApi.OTelStatusCode | null) | undefined; status_message?: (string | null) | undefined; + session_id?: (string | null) | undefined; + user_id?: (string | null) | undefined; + agent_id?: (string | null) | undefined; attributes?: (Record | null) | undefined; references?: (AgentaApi.OTelReferenceInput[] | null) | undefined; links?: (AgentaApi.OTelLinkInput[] | null) | undefined; diff --git a/web/packages/agenta-api-client/src/generated/api/types/SpansNodeOutput.ts b/web/packages/agenta-api-client/src/generated/api/types/SpansNodeOutput.ts index 9d98390ae4..c983aaefa0 100644 --- a/web/packages/agenta-api-client/src/generated/api/types/SpansNodeOutput.ts +++ b/web/packages/agenta-api-client/src/generated/api/types/SpansNodeOutput.ts @@ -21,6 +21,9 @@ export interface SpansNodeOutput { end_time?: (SpansNodeOutput.EndTime | null) | undefined; status_code?: (AgentaApi.OTelStatusCode | null) | undefined; status_message?: (string | null) | undefined; + session_id?: (string | null) | undefined; + user_id?: (string | null) | undefined; + agent_id?: (string | null) | undefined; attributes?: (Record | null) | undefined; references?: (AgentaApi.OTelReferenceOutput[] | null) | undefined; links?: (AgentaApi.OTelLinkOutput[] | null) | undefined; diff --git a/web/packages/agenta-api-client/src/generated/api/types/WorkflowRequestData.ts b/web/packages/agenta-api-client/src/generated/api/types/WorkflowRequestData.ts new file mode 100644 index 0000000000..eb3ed4e686 --- /dev/null +++ b/web/packages/agenta-api-client/src/generated/api/types/WorkflowRequestData.ts @@ -0,0 +1,10 @@ +// This file was auto-generated by Fern from our API Definition. + +export interface WorkflowRequestData { + revision?: (Record | null) | undefined; + parameters?: (Record | null) | undefined; + testcase?: (Record | null) | undefined; + inputs?: (Record | null) | undefined; + trace?: (Record | null) | undefined; + outputs?: (unknown | null) | undefined; +} diff --git a/web/packages/agenta-api-client/src/generated/api/types/index.ts b/web/packages/agenta-api-client/src/generated/api/types/index.ts index 2f6c7f440f..5da931664c 100644 --- a/web/packages/agenta-api-client/src/generated/api/types/index.ts +++ b/web/packages/agenta-api-client/src/generated/api/types/index.ts @@ -264,7 +264,7 @@ export * from "./Formatting.js"; export * from "./FullJsonInput.js"; export * from "./FullJsonOutput.js"; export * from "./GatewayToolConfig.js"; -export * from "./HarnessSessionRecord.js"; +export * from "./HarnessKind.js"; export * from "./Header.js"; export * from "./HttpValidationError.js"; export * from "./InviteRequest.js"; @@ -358,7 +358,7 @@ export * from "./SecretDto.js"; export * from "./SecretKind.js"; export * from "./SecretResponseDto.js"; export * from "./Selector.js"; -export * from "./SessionHeartbeatResponseModel.js"; +export * from "./SessionHeartbeatResult.js"; export * from "./SessionIdsResponse.js"; export * from "./SessionInteraction.js"; export * from "./SessionInteractionData.js"; @@ -375,16 +375,18 @@ export * from "./SessionMountsResponse.js"; export * from "./SessionRecord.js"; export * from "./SessionRecordResponse.js"; export * from "./SessionRecordsQueryResponse.js"; -export * from "./SessionState.js"; -export * from "./SessionStateData.js"; -export * from "./SessionStateFlags.js"; -export * from "./SessionStateResponse.js"; -export * from "./SessionStateUpsertRequest.js"; +export * from "./SessionResponse.js"; export * from "./SessionStream.js"; -export * from "./SessionStreamCommandResponseModel.js"; +export * from "./SessionStreamCommandResponse.js"; export * from "./SessionStreamFlags.js"; -export * from "./SessionStreamResponseModel.js"; -export * from "./SessionStreamsResponseModel.js"; +export * from "./SessionStreamHeaderEdit.js"; +export * from "./SessionStreamResponse.js"; +export * from "./SessionStreamsResponse.js"; +export * from "./SessionsResponse.js"; +export * from "./SessionTurn.js"; +export * from "./SessionTurnQuery.js"; +export * from "./SessionTurnResponse.js"; +export * from "./SessionTurnsResponse.js"; export * from "./SimpleApplication.js"; export * from "./SimpleApplicationAdditionalContext.js"; export * from "./SimpleApplicationCreate.js"; @@ -641,6 +643,7 @@ export * from "./WorkflowCatalogTypesResponse.js"; export * from "./WorkflowCreate.js"; export * from "./WorkflowEdit.js"; export * from "./WorkflowFlags.js"; +export * from "./WorkflowRequestData.js"; export * from "./WorkflowResponse.js"; export * from "./WorkflowRevisionCommit.js"; export * from "./WorkflowRevisionCreate.js"; diff --git a/web/packages/agenta-entities/src/session/api/api.ts b/web/packages/agenta-entities/src/session/api/api.ts index 7775ebfd28..ca30dfbbf1 100644 --- a/web/packages/agenta-entities/src/session/api/api.ts +++ b/web/packages/agenta-entities/src/session/api/api.ts @@ -15,7 +15,6 @@ import { sessionInteractionResponseSchema, sessionInteractionsResponseSchema, sessionRecordsQueryResponseSchema, - sessionStateResponseSchema, sessionStreamCommandResponseSchema, sessionMountsResponseSchema, sessionStreamResponseSchema, @@ -26,7 +25,6 @@ import { type SessionInteractionKind, type SessionInteractionStatusCode, type SessionRecord, - type SessionState, type SessionStream, type SessionStreamCommandResponse, } from "../core/schema" @@ -88,32 +86,6 @@ export interface SessionScopedParams { abortSignal?: AbortSignal } -/** - * Read a session's durable state: the opaque SDK `SessionRecord` + the `sandbox_id` resume - * pointer. Read-only from the FE — the record is owned and written by the runner/SDK, so the - * FE never calls `setState(data)` (it would clobber the runner's record). Returns `null` when - * absent (no run has persisted state yet) or on failure. - */ -export async function getSessionState({ - sessionId, - projectId, - appId, - abortSignal, -}: SessionScopedParams): Promise { - if (!projectId || !sessionId) return null - - const data = await callFern("[getSessionState]", () => - getSessionsClient().getState( - {session_id: sessionId}, - projectScopedRequest(projectId, appId, abortSignal), - ), - ) - if (!data) return null - - const validated = safeParseWithLogging(sessionStateResponseSchema, data, "[getSessionState]") - return validated?.session_state ?? null -} - export interface QueryInteractionsParams extends SessionScopedParams { kind?: SessionInteractionKind status?: SessionInteractionStatusCode @@ -289,7 +261,6 @@ export async function fetchSessionStream({ } export interface CommandSessionStreamParams extends SessionScopedParams { - prompt?: string /** Steal the run lock from whoever holds it. */ force?: boolean /** Fire-and-forget: start the run without holding a connection. */ @@ -313,15 +284,15 @@ export async function commandSessionStream({ projectId, appId, abortSignal, - prompt, force, detached, }: CommandSessionStreamParams): Promise { if (!projectId || !sessionId) return null + // The prompt→request.data (inputs) mapping is defined by the sessions feature owner when the send path gets wired (see PR #5375 body). const data = await callFern("[commandSessionStream]", () => getSessionsClient().setSessionStream( - {session_id: sessionId, prompt, force, detached}, + {session_id: sessionId, force, detached}, projectScopedRequest(projectId, appId, abortSignal), ), ) diff --git a/web/packages/agenta-entities/src/session/core/liveness.ts b/web/packages/agenta-entities/src/session/core/liveness.ts index e74dbd0df9..32871468f0 100644 --- a/web/packages/agenta-entities/src/session/core/liveness.ts +++ b/web/packages/agenta-entities/src/session/core/liveness.ts @@ -75,8 +75,8 @@ export interface SandboxLiveness { * `cold` (disk alive, not warm), or `dead` (disk gone). With no sandbox info the coarse `cold` * stands, so existing callers are unaffected until they start passing sandbox data. * - * FOLLOWUP(sessions,#5197): no caller passes `sandbox` yet — wire a sandbox-liveness signal from - * `getSessionState` through the dot (`sessionDotStatusAtomFamily`) when #5197 exposes it. See + * FOLLOWUP(sessions,#5197): no caller passes `sandbox` yet — wire a sandbox-liveness signal + * through the dot (`sessionDotStatusAtomFamily`) when #5197 exposes it. See * docs/designs/sessions/frontend-integration.md. */ export function refineLifecycleWithSandbox( diff --git a/web/packages/agenta-entities/src/session/core/schema.ts b/web/packages/agenta-entities/src/session/core/schema.ts index 4d4fd3cff6..5e3a9e086c 100644 --- a/web/packages/agenta-entities/src/session/core/schema.ts +++ b/web/packages/agenta-entities/src/session/core/schema.ts @@ -47,24 +47,6 @@ export const sessionRecordsQueryResponseSchema = z.object({ export type SessionRecord = z.infer export type SessionRecordsQueryResponse = z.infer -/** Durable SDK record + sandbox resume pointer. `data` is the opaque SDK `SessionRecord`. */ -export const sessionStateSchema = z.object({ - session_id: z.string(), - data: z.record(z.string(), z.unknown()).nullish(), - sandbox_id: z.string().nullish(), - id: z.string().nullish(), - project_id: z.string().nullish(), - created_at: z.string().nullish(), - updated_at: z.string().nullish(), -}) - -export const sessionStateResponseSchema = z.object({ - count: z.number().nullish(), - session_state: sessionStateSchema.nullish(), -}) - -export type SessionState = z.infer - /** A HITL request raised mid-run. `status` is the lifecycle enum (pending/responded/…). */ export const sessionInteractionSchema = z.object({ id: z.string().nullish(), diff --git a/web/packages/agenta-entities/src/session/index.ts b/web/packages/agenta-entities/src/session/index.ts index 004b715ffb..5c06ba107a 100644 --- a/web/packages/agenta-entities/src/session/index.ts +++ b/web/packages/agenta-entities/src/session/index.ts @@ -7,7 +7,6 @@ */ export { querySessionRecords, - getSessionState, queryInteractions, fetchInteraction, respondInteraction, @@ -35,12 +34,10 @@ export { export { sessionRecordSchema, sessionRecordsQueryResponseSchema, - sessionStateSchema, sessionInteractionSchema, sessionStreamSchema, type SessionRecord, type SessionRecordsQueryResponse, - type SessionState, type SessionInteraction, type SessionInteractionKind, type SessionInteractionStatusCode,