Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 23 additions & 0 deletions cueapi/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,16 @@
InvalidScheduleError,
RateLimitError,
)
from cueapi.models.agent import Agent, AgentList
from cueapi.models.cue import Cue, CueList
from cueapi.models.execution import Execution, ExecutionList, OutcomeDetail
from cueapi.models.message import (
FromAgentRef,
Message,
MessageList,
StateTransitionResponse,
)
from cueapi.models.worker import Worker, WorkerList
from cueapi.payload import CuePayload
from cueapi.resources.agents import AgentsResource
from cueapi.resources.executions import ExecutionsResource
Expand All @@ -21,12 +31,25 @@
__version__ = "0.2.0"

__all__ = [
"Agent",
"AgentList",
"AgentsResource",
"Cue",
"CueAPI",
"CueList",
"CuePayload",
"Execution",
"ExecutionList",
"ExecutionsResource",
"FromAgentRef",
"Message",
"MessageList",
"MessagesResource",
"OutcomeDetail",
"StateTransitionResponse",
"UsageResource",
"Worker",
"WorkerList",
"WorkersResource",
"verify_webhook",
"CueAPIError",
Expand Down
49 changes: 49 additions & 0 deletions cueapi/models/agent.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
"""Agent Pydantic model — typed accessor for messaging-primitive agent responses.

Closes the Agent portion of cueapi-python #24's `model_drift` manifest.
``AgentsResource`` methods currently return raw dicts; callers can opt
into typed accessors via ``Agent.model_validate(client.agents.get(ref))``
or ``AgentList.model_validate(client.agents.list())``.
"""

from __future__ import annotations

from datetime import datetime
from typing import Any, Dict, List, Optional

from pydantic import BaseModel


class Agent(BaseModel):
"""Typed accessor for a messaging-primitive agent (Phase 12.1.5).

``webhook_secret`` is populated only on the response from
``client.agents.create()`` (when ``webhook_url`` was supplied) and
from ``client.agents.webhook_secret_regenerate()``. Subsequent reads
omit the secret.
"""

id: str
user_id: Optional[str] = None
api_key_id: Optional[str] = None
slug: str
display_name: str
webhook_url: Optional[str] = None
# One-time on create + on regenerate; None on subsequent reads.
webhook_secret: Optional[str] = None
metadata: Dict[str, Any] = {}
status: Optional[str] = None # online / offline / away
deleted_at: Optional[datetime] = None
created_at: Optional[datetime] = None
updated_at: Optional[datetime] = None
model_config = {"extra": "allow"}


class AgentList(BaseModel):
"""Typed accessor for ``client.agents.list()`` responses."""

agents: List[Agent]
total: int
limit: int
offset: int
model_config = {"extra": "allow"}
100 changes: 100 additions & 0 deletions cueapi/models/execution.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
"""Execution Pydantic model — typed accessor for execution dict responses.

Closes the Execution portion of cueapi-python #24's `model_drift` manifest.
``ExecutionsResource`` methods (`list`, `get`, `replay`) currently return
raw dicts; callers can opt into typed accessors via
``Execution.model_validate(client.executions.get(...))``. Returning the
typed object directly from resource methods is a separate breaking-change
PR (would bump major version).
"""

from __future__ import annotations

from datetime import datetime
from typing import Any, Dict, List, Optional

from pydantic import BaseModel, Field


class OutcomeDetail(BaseModel):
"""Outcome reported by the worker / handler. Set when the execution
has reached a terminal state and the handler has reported via
``POST /v1/executions/{id}/outcome``."""

success: bool
result: Optional[str] = None
error: Optional[str] = None
metadata: Optional[Dict[str, Any]] = None
recorded_at: Optional[datetime] = None
# Evidence fields (Phase 18 Gap 11 — outcome verification).
external_id: Optional[str] = None
result_url: Optional[str] = None
result_ref: Optional[str] = None
result_type: Optional[str] = None
summary: Optional[str] = None
artifacts: Optional[List[Any]] = None
validation_state: Optional[str] = None
assertions: Optional[Dict[str, Any]] = None
model_config = {"extra": "allow"}


class Execution(BaseModel):
"""Typed accessor for an execution response.

Mirrors the server's ``ExecutionResponse`` schema. Use as
``Execution.model_validate(client.executions.get(exec_id))`` or
``Execution.model_validate(item)`` over each item in
``client.executions.list()['executions']``.
"""

id: str
cue_id: str
scheduled_for: datetime
status: str
http_status: Optional[int] = None
response_body: Optional[str] = None
attempts: Optional[int] = None
next_retry: Optional[datetime] = None
error_message: Optional[str] = None
started_at: Optional[datetime] = None
delivered_at: Optional[datetime] = None
last_attempt_at: Optional[datetime] = None
claimed_by_worker: Optional[str] = None
claimed_at: Optional[datetime] = None
last_heartbeat_at: Optional[datetime] = None
# Hosted PR #589: effective payload the handler/webhook saw at delivery.
# `payload_override` if set on the execution, else parent cue's payload.
payload: Optional[Dict[str, Any]] = None
# Outcome — populated only after handler reports.
outcome: Optional[OutcomeDetail] = None
outcome_state: Optional[str] = Field(
default=None,
description=(
"Phase 18 Gap 11: enum tracking outcome verification state. "
"Values: reported_success / reported_failure / verified_success / "
"verification_pending / verification_failed / unknown."
),
)
triggered_by: Optional[str] = Field(
default=None,
description="scheduled / manual_fire / chain / replay",
)
# Chain support (Gap 1 — on_success_fire native chaining).
chain_parent_id: Optional[str] = None
chain_depth: Optional[int] = None
created_at: Optional[datetime] = None
updated_at: Optional[datetime] = None
# Forward-compat: server may grow the response over time without the
# SDK breaking. Same pattern as AlertConfig / VerificationConfig in
# the Cue model.
model_config = {"extra": "allow"}


class ExecutionList(BaseModel):
"""Typed accessor for ``client.executions.list()`` responses."""

executions: List[Execution]
total: int
limit: int
offset: int
model_config = {"extra": "allow"}
76 changes: 76 additions & 0 deletions cueapi/models/message.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
"""Message Pydantic model — typed accessor for messaging-primitive message responses.

Closes the Message portion of cueapi-python #24's `model_drift` manifest.
``MessagesResource`` methods currently return raw dicts; callers can opt
into typed accessors via ``Message.model_validate(client.messages.get(id))``
or ``MessageList.model_validate(client.agents.inbox(ref))``.
"""

from __future__ import annotations

from datetime import datetime
from typing import Any, Dict, List, Optional

from pydantic import BaseModel, Field


class FromAgentRef(BaseModel):
"""Inline agent reference rendered on incoming-message responses."""

agent_id: Optional[str] = None
slug: Optional[str] = None
model_config = {"extra": "allow"}


class StateTransitionResponse(BaseModel):
"""Response shape for ``mark_read`` and ``ack`` endpoints."""

delivery_state: str
read_at: Optional[datetime] = None
acked_at: Optional[datetime] = None
model_config = {"extra": "allow"}


class Message(BaseModel):
"""Typed accessor for a messaging-primitive message (Phase 12.1.5).

Mirrors the server's ``MessageResponse`` schema. Both inbox-fetched
and sent-fetched messages use this shape; the ``from`` / ``to`` slots
capture sender / recipient regardless of perspective.
"""

id: str
user_id: Optional[str] = None
# Sender — populated on inbox responses; may be self on sent responses.
# Pydantic treats `from` as a reserved keyword, but the server uses it
# in the response. Use alias for clean access via .from_agent.
from_agent: Optional[FromAgentRef] = Field(default=None, alias="from")
to: Optional[str] = None
body: Optional[str] = None
subject: Optional[str] = None
thread_id: Optional[str] = None
reply_to: Optional[str] = None
reply_to_agent: Optional[str] = None
priority: Optional[int] = None
expects_reply: Optional[bool] = None
delivery_state: Optional[str] = None
metadata: Optional[Dict[str, Any]] = None
expires_at: Optional[datetime] = None
queued_at: Optional[datetime] = None
delivered_at: Optional[datetime] = None
read_at: Optional[datetime] = None
acked_at: Optional[datetime] = None
failed_at: Optional[datetime] = None
created_at: Optional[datetime] = None
updated_at: Optional[datetime] = None
model_config = {"extra": "allow", "populate_by_name": True}


class MessageList(BaseModel):
"""Typed accessor for inbox / sent responses (lists of messages)."""

messages: List[Message]
total: Optional[int] = None
limit: Optional[int] = None
offset: Optional[int] = None
model_config = {"extra": "allow"}
44 changes: 44 additions & 0 deletions cueapi/models/worker.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
"""Worker Pydantic model — typed accessor for worker dict responses.

Closes the Worker portion of cueapi-python #24's `model_drift` manifest.
``WorkersResource.list()`` currently returns a raw dict; callers can opt
into typed accessors via ``Worker.model_validate(item)`` over each item
in ``client.workers.list()['workers']``.
"""

from __future__ import annotations

from datetime import datetime
from typing import Any, List, Optional

from pydantic import BaseModel


class Worker(BaseModel):
"""Typed accessor for a registered worker.

Mirrors the server's ``Worker`` model. ``heartbeat_status`` is
computed server-side from ``seconds_since_heartbeat``:
- ``online``: <60s since last heartbeat
- ``stale``: 60-360s since last heartbeat
- ``dead``: >360s since last heartbeat
"""

id: Optional[str] = None
user_id: Optional[str] = None
worker_id: str
handlers: Optional[List[str]] = None
last_heartbeat: Optional[datetime] = None
heartbeat_status: Optional[str] = None
seconds_since_heartbeat: Optional[int] = None
api_key_id: Optional[str] = None
created_at: Optional[datetime] = None
model_config = {"extra": "allow"}


class WorkerList(BaseModel):
"""Typed accessor for ``client.workers.list()`` responses."""

workers: List[Worker]
total: Optional[int] = None
model_config = {"extra": "allow"}
Loading
Loading