aegra-api 0.1.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (64) hide show
  1. aegra_api/__init__.py +3 -0
  2. aegra_api/api/__init__.py +1 -0
  3. aegra_api/api/assistants.py +235 -0
  4. aegra_api/api/runs.py +1110 -0
  5. aegra_api/api/store.py +200 -0
  6. aegra_api/api/threads.py +761 -0
  7. aegra_api/config.py +204 -0
  8. aegra_api/constants.py +5 -0
  9. aegra_api/core/__init__.py +0 -0
  10. aegra_api/core/app_loader.py +91 -0
  11. aegra_api/core/auth_ctx.py +65 -0
  12. aegra_api/core/auth_deps.py +186 -0
  13. aegra_api/core/auth_handlers.py +248 -0
  14. aegra_api/core/auth_middleware.py +331 -0
  15. aegra_api/core/database.py +123 -0
  16. aegra_api/core/health.py +131 -0
  17. aegra_api/core/orm.py +165 -0
  18. aegra_api/core/route_merger.py +69 -0
  19. aegra_api/core/serializers/__init__.py +7 -0
  20. aegra_api/core/serializers/base.py +22 -0
  21. aegra_api/core/serializers/general.py +54 -0
  22. aegra_api/core/serializers/langgraph.py +102 -0
  23. aegra_api/core/sse.py +178 -0
  24. aegra_api/main.py +303 -0
  25. aegra_api/middleware/__init__.py +4 -0
  26. aegra_api/middleware/double_encoded_json.py +74 -0
  27. aegra_api/middleware/logger_middleware.py +95 -0
  28. aegra_api/models/__init__.py +76 -0
  29. aegra_api/models/assistants.py +81 -0
  30. aegra_api/models/auth.py +62 -0
  31. aegra_api/models/enums.py +29 -0
  32. aegra_api/models/errors.py +29 -0
  33. aegra_api/models/runs.py +124 -0
  34. aegra_api/models/store.py +67 -0
  35. aegra_api/models/threads.py +152 -0
  36. aegra_api/observability/__init__.py +1 -0
  37. aegra_api/observability/base.py +88 -0
  38. aegra_api/observability/otel.py +133 -0
  39. aegra_api/observability/setup.py +27 -0
  40. aegra_api/observability/targets/__init__.py +11 -0
  41. aegra_api/observability/targets/base.py +18 -0
  42. aegra_api/observability/targets/langfuse.py +33 -0
  43. aegra_api/observability/targets/otlp.py +38 -0
  44. aegra_api/observability/targets/phoenix.py +24 -0
  45. aegra_api/services/__init__.py +0 -0
  46. aegra_api/services/assistant_service.py +569 -0
  47. aegra_api/services/base_broker.py +59 -0
  48. aegra_api/services/broker.py +141 -0
  49. aegra_api/services/event_converter.py +157 -0
  50. aegra_api/services/event_store.py +196 -0
  51. aegra_api/services/graph_streaming.py +433 -0
  52. aegra_api/services/langgraph_service.py +456 -0
  53. aegra_api/services/streaming_service.py +362 -0
  54. aegra_api/services/thread_state_service.py +128 -0
  55. aegra_api/settings.py +124 -0
  56. aegra_api/utils/__init__.py +3 -0
  57. aegra_api/utils/assistants.py +23 -0
  58. aegra_api/utils/run_utils.py +60 -0
  59. aegra_api/utils/setup_logging.py +122 -0
  60. aegra_api/utils/sse_utils.py +26 -0
  61. aegra_api/utils/status_compat.py +57 -0
  62. aegra_api-0.1.0.dist-info/METADATA +244 -0
  63. aegra_api-0.1.0.dist-info/RECORD +64 -0
  64. aegra_api-0.1.0.dist-info/WHEEL +4 -0
@@ -0,0 +1,76 @@
1
+ """Agent Protocol Pydantic models"""
2
+
3
+ from aegra_api.models.assistants import (
4
+ AgentSchemas,
5
+ Assistant,
6
+ AssistantCreate,
7
+ AssistantList,
8
+ AssistantSearchRequest,
9
+ AssistantUpdate,
10
+ )
11
+ from aegra_api.models.auth import AuthContext, TokenPayload, User
12
+ from aegra_api.models.errors import AgentProtocolError, get_error_type
13
+ from aegra_api.models.runs import Run, RunCreate, RunStatus
14
+ from aegra_api.models.store import (
15
+ StoreDeleteRequest,
16
+ StoreGetResponse,
17
+ StoreItem,
18
+ StorePutRequest,
19
+ StoreSearchRequest,
20
+ StoreSearchResponse,
21
+ )
22
+ from aegra_api.models.threads import (
23
+ Thread,
24
+ ThreadCheckpoint,
25
+ ThreadCheckpointPostRequest,
26
+ ThreadCreate,
27
+ ThreadHistoryRequest,
28
+ ThreadList,
29
+ ThreadSearchRequest,
30
+ ThreadSearchResponse,
31
+ ThreadState,
32
+ ThreadStateUpdate,
33
+ ThreadStateUpdateResponse,
34
+ ThreadUpdate,
35
+ )
36
+
37
+ __all__ = [
38
+ # Assistants
39
+ "Assistant",
40
+ "AssistantCreate",
41
+ "AssistantList",
42
+ "AssistantSearchRequest",
43
+ "AssistantUpdate",
44
+ "AgentSchemas",
45
+ # Threads
46
+ "Thread",
47
+ "ThreadCreate",
48
+ "ThreadList",
49
+ "ThreadSearchRequest",
50
+ "ThreadSearchResponse",
51
+ "ThreadState",
52
+ "ThreadStateUpdate",
53
+ "ThreadStateUpdateResponse",
54
+ "ThreadCheckpoint",
55
+ "ThreadCheckpointPostRequest",
56
+ "ThreadHistoryRequest",
57
+ # Runs
58
+ "Run",
59
+ "RunCreate",
60
+ "RunStatus",
61
+ # Store
62
+ "StorePutRequest",
63
+ "StoreGetResponse",
64
+ "StoreSearchRequest",
65
+ "StoreSearchResponse",
66
+ "StoreItem",
67
+ "StoreDeleteRequest",
68
+ # Errors
69
+ "AgentProtocolError",
70
+ "get_error_type",
71
+ # Auth
72
+ "User",
73
+ "AuthContext",
74
+ "TokenPayload",
75
+ "ThreadUpdate",
76
+ ]
@@ -0,0 +1,81 @@
1
+ """Assistant-related Pydantic models for Agent Protocol"""
2
+
3
+ from datetime import datetime
4
+ from typing import Any
5
+
6
+ from pydantic import BaseModel, ConfigDict, Field
7
+
8
+
9
+ class AssistantCreate(BaseModel):
10
+ """Request model for creating assistants"""
11
+
12
+ assistant_id: str | None = Field(None, description="Unique assistant identifier (auto-generated if not provided)")
13
+ name: str | None = Field(
14
+ None,
15
+ description="Human-readable assistant name (auto-generated if not provided)",
16
+ )
17
+ description: str | None = Field(None, description="Assistant description")
18
+ config: dict[str, Any] | None = Field({}, description="Assistant configuration")
19
+ context: dict[str, Any] | None = Field({}, description="Assistant context")
20
+ graph_id: str = Field(..., description="LangGraph graph ID from aegra.json")
21
+ metadata: dict[str, Any] | None = Field({}, description="Metadata to use for searching and filtering assistants.")
22
+ if_exists: str | None = Field("error", description="What to do if assistant exists: error or do_nothing")
23
+
24
+
25
+ class Assistant(BaseModel):
26
+ """Assistant entity model"""
27
+
28
+ assistant_id: str
29
+ name: str
30
+ description: str | None = None
31
+ config: dict[str, Any] = Field(default_factory=dict)
32
+ context: dict[str, Any] = Field(default_factory=dict)
33
+ graph_id: str
34
+ user_id: str
35
+ version: int = Field(..., description="The version of the assistant.")
36
+ metadata: dict[str, Any] = Field(default_factory=dict, alias="metadata_dict")
37
+ created_at: datetime
38
+ updated_at: datetime
39
+
40
+ model_config = ConfigDict(from_attributes=True, populate_by_name=True)
41
+
42
+
43
+ class AssistantUpdate(BaseModel):
44
+ """Request model for creating assistants"""
45
+
46
+ name: str | None = Field(None, description="The name of the assistant (auto-generated if not provided)")
47
+ description: str | None = Field(None, description="The description of the assistant. Defaults to null.")
48
+ config: dict[str, Any] | None = Field({}, description="Configuration to use for the graph.")
49
+ graph_id: str = Field("agent", description="The ID of the graph")
50
+ context: dict[str, Any] | None = Field(
51
+ {},
52
+ description="The context to use for the graph. Useful when graph is configurable.",
53
+ )
54
+ metadata: dict[str, Any] | None = Field({}, description="Metadata to use for searching and filtering assistants.")
55
+
56
+
57
+ class AssistantList(BaseModel):
58
+ """Response model for listing assistants"""
59
+
60
+ assistants: list[Assistant]
61
+ total: int
62
+
63
+
64
+ class AssistantSearchRequest(BaseModel):
65
+ """Request model for assistant search"""
66
+
67
+ name: str | None = Field(None, description="Filter by assistant name")
68
+ description: str | None = Field(None, description="Filter by assistant description")
69
+ graph_id: str | None = Field(None, description="Filter by graph ID")
70
+ limit: int | None = Field(20, le=100, ge=1, description="Maximum results")
71
+ offset: int | None = Field(0, ge=0, description="Results offset")
72
+ metadata: dict[str, Any] | None = Field({}, description="Metadata to use for searching and filtering assistants.")
73
+
74
+
75
+ class AgentSchemas(BaseModel):
76
+ """Agent schema definitions for client integration"""
77
+
78
+ input_schema: dict[str, Any] = Field(..., description="JSON Schema for agent inputs")
79
+ output_schema: dict[str, Any] = Field(..., description="JSON Schema for agent outputs")
80
+ state_schema: dict[str, Any] = Field(..., description="JSON Schema for agent state")
81
+ config_schema: dict[str, Any] = Field(..., description="JSON Schema for agent config")
@@ -0,0 +1,62 @@
1
+ """Authentication and user context models"""
2
+
3
+ from typing import Any
4
+
5
+ from pydantic import BaseModel, ConfigDict
6
+
7
+
8
+ class User(BaseModel):
9
+ """User model that accepts any auth fields.
10
+
11
+ This model uses ConfigDict(extra="allow") to accept any additional fields
12
+ from auth handlers (e.g., subscription_tier, team_id) while maintaining
13
+ type hints for common fields.
14
+ """
15
+
16
+ model_config = ConfigDict(extra="allow")
17
+
18
+ # Required
19
+ identity: str
20
+
21
+ # Optional with defaults
22
+ is_authenticated: bool = True
23
+ permissions: list[str] = []
24
+ display_name: str | None = None
25
+
26
+ # Common optional fields (for IDE hints)
27
+ org_id: str | None = None
28
+ email: str | None = None
29
+
30
+ def to_dict(self) -> dict[str, Any]:
31
+ """Convert to dict including all extra fields."""
32
+ return self.model_dump()
33
+
34
+ def __getattr__(self, name: str) -> Any:
35
+ """Allow attribute access to extra fields."""
36
+ try:
37
+ extra = object.__getattribute__(self, "__pydantic_extra__") or {}
38
+ except AttributeError:
39
+ extra = {}
40
+ if name in extra:
41
+ return extra[name]
42
+ raise AttributeError(f"'{type(self).__name__}' has no attribute '{name}'")
43
+
44
+
45
+ class AuthContext(BaseModel):
46
+ """Authentication context for request processing"""
47
+
48
+ model_config = ConfigDict(arbitrary_types_allowed=True)
49
+
50
+ user: User
51
+ request_id: str | None = None
52
+
53
+
54
+ class TokenPayload(BaseModel):
55
+ """JWT token payload structure"""
56
+
57
+ sub: str # subject (user ID)
58
+ name: str | None = None
59
+ scopes: list[str] = []
60
+ org: str | None = None
61
+ exp: int | None = None
62
+ iat: int | None = None
@@ -0,0 +1,29 @@
1
+ """Status enums for Aegra API specification."""
2
+
3
+ from typing import Literal
4
+
5
+ # Run status enum
6
+ RunStatus = Literal[
7
+ "pending",
8
+ "running",
9
+ "error",
10
+ "success",
11
+ "timeout",
12
+ "interrupted",
13
+ ]
14
+
15
+ # Thread status enum
16
+ ThreadStatus = Literal[
17
+ "idle",
18
+ "busy",
19
+ "interrupted",
20
+ "error",
21
+ ]
22
+
23
+ # Multitask strategy enum
24
+ MultitaskStrategy = Literal[
25
+ "reject",
26
+ "rollback",
27
+ "interrupt",
28
+ "enqueue",
29
+ ]
@@ -0,0 +1,29 @@
1
+ """Error response models for Agent Protocol"""
2
+
3
+ from typing import Any
4
+
5
+ from pydantic import BaseModel, Field
6
+
7
+
8
+ class AgentProtocolError(BaseModel):
9
+ """Standard Agent Protocol error response"""
10
+
11
+ error: str = Field(..., description="Error type")
12
+ message: str = Field(..., description="Human-readable error message")
13
+ details: dict[str, Any] | None = Field(None, description="Additional error details")
14
+
15
+
16
+ def get_error_type(status_code: int) -> str:
17
+ """Map HTTP status codes to error types"""
18
+ error_map = {
19
+ 400: "bad_request",
20
+ 401: "unauthorized",
21
+ 403: "forbidden",
22
+ 404: "not_found",
23
+ 409: "conflict",
24
+ 422: "validation_error",
25
+ 500: "internal_error",
26
+ 501: "not_implemented",
27
+ 503: "service_unavailable",
28
+ }
29
+ return error_map.get(status_code, "unknown_error")
@@ -0,0 +1,124 @@
1
+ """Run-related Pydantic models for Agent Protocol"""
2
+
3
+ from datetime import datetime
4
+ from typing import Any, Self
5
+
6
+ from pydantic import (
7
+ BaseModel,
8
+ ConfigDict,
9
+ Field,
10
+ field_validator,
11
+ model_validator,
12
+ )
13
+
14
+ from aegra_api.utils.status_compat import validate_run_status
15
+
16
+
17
+ class RunCreate(BaseModel):
18
+ """Request model for creating runs"""
19
+
20
+ assistant_id: str = Field(..., description="Assistant to execute")
21
+ input: dict[str, Any] | None = Field(
22
+ None,
23
+ description="Input data for the run. Optional when resuming from a checkpoint.",
24
+ )
25
+ config: dict[str, Any] | None = Field({}, description="Execution config")
26
+ context: dict[str, Any] | None = Field({}, description="Execution context")
27
+ checkpoint: dict[str, Any] | None = Field(
28
+ None,
29
+ description="Checkpoint configuration (e.g., {'checkpoint_id': '...', 'checkpoint_ns': ''})",
30
+ )
31
+ stream: bool = Field(False, description="Enable streaming response")
32
+ stream_mode: str | list[str] | None = Field(None, description="Requested stream mode(s)")
33
+ on_disconnect: str | None = Field(
34
+ None,
35
+ description="Behavior on client disconnect: 'cancel' (default) or 'continue'.",
36
+ )
37
+
38
+ multitask_strategy: str | None = Field(
39
+ None,
40
+ description="Strategy for handling concurrent runs on same thread: 'reject', 'interrupt', 'rollback', or 'enqueue'.",
41
+ )
42
+
43
+ # Human-in-the-loop fields (core HITL functionality)
44
+ command: dict[str, Any] | None = Field(
45
+ None,
46
+ description="Command for resuming interrupted runs with state updates or navigation",
47
+ )
48
+ interrupt_before: str | list[str] | None = Field(
49
+ None,
50
+ description="Nodes to interrupt immediately before they get executed. Use '*' for all nodes.",
51
+ )
52
+ interrupt_after: str | list[str] | None = Field(
53
+ None,
54
+ description="Nodes to interrupt immediately after they get executed. Use '*' for all nodes.",
55
+ )
56
+
57
+ # Subgraph configuration
58
+ stream_subgraphs: bool | None = Field(
59
+ False,
60
+ description="Whether to include subgraph events in streaming. When True, includes events from all subgraphs. When False (default when None), excludes subgraph events. Defaults to False for backwards compatibility.",
61
+ )
62
+
63
+ # Request metadata (top-level in payload)
64
+ metadata: dict[str, Any] | None = Field(
65
+ None,
66
+ description="Request metadata (e.g., from_studio flag)",
67
+ )
68
+
69
+ @model_validator(mode="after")
70
+ def validate_input_command_exclusivity(self) -> Self:
71
+ """Ensure input and command are mutually exclusive"""
72
+ # Allow empty input dict when command is present (frontend compatibility)
73
+ if self.input is not None and self.command is not None:
74
+ # If input is just an empty dict, treat it as None for compatibility
75
+ if self.input == {}:
76
+ self.input = None
77
+ else:
78
+ raise ValueError("Cannot specify both 'input' and 'command' - they are mutually exclusive")
79
+ if self.input is None and self.command is None:
80
+ if self.checkpoint is not None:
81
+ # Allow checkpoint-only requests by treating input as empty dict
82
+ self.input = {}
83
+ else:
84
+ raise ValueError("Must specify either 'input' or 'command'")
85
+ return self
86
+
87
+
88
+ class Run(BaseModel):
89
+ """Run entity model
90
+
91
+ Status values: pending, running, error, success, timeout, interrupted
92
+ """
93
+
94
+ model_config = ConfigDict(from_attributes=True)
95
+
96
+ run_id: str
97
+ thread_id: str
98
+ assistant_id: str
99
+ status: str = "pending" # Valid values: pending, running, error, success, timeout, interrupted
100
+ input: dict[str, Any]
101
+ output: dict[str, Any] | None = None
102
+ error_message: str | None = None
103
+ config: dict[str, Any] | None = {}
104
+ context: dict[str, Any] | None = {}
105
+ user_id: str
106
+ created_at: datetime
107
+ updated_at: datetime
108
+
109
+ @field_validator("status", mode="before")
110
+ @classmethod
111
+ def validate_status(cls, v: str) -> str:
112
+ """Validate status conforms to API specification."""
113
+ if not isinstance(v, str):
114
+ raise ValueError(f"Status must be a string, got {type(v)}")
115
+ return validate_run_status(v)
116
+
117
+
118
+ class RunStatus(BaseModel):
119
+ """Simple run status response"""
120
+
121
+ run_id: str
122
+ status: str # Standard status value
123
+
124
+ message: str | None = None
@@ -0,0 +1,67 @@
1
+ """Store-related Pydantic models for Agent Protocol"""
2
+
3
+ from typing import Any
4
+
5
+ from pydantic import BaseModel, Field, field_validator
6
+
7
+
8
+ class StorePutRequest(BaseModel):
9
+ """Request model for storing items"""
10
+
11
+ namespace: list[str] = Field(..., description="Storage namespace")
12
+ key: str = Field(..., description="Item key")
13
+ value: dict[str, Any] = Field(..., description="Item value (must be a JSON object)")
14
+
15
+ @field_validator("value", mode="before")
16
+ @classmethod
17
+ def validate_value_is_dict(cls, v: Any) -> dict[str, Any]:
18
+ """Validate that value is a dictionary.
19
+
20
+ LangGraph store requires values to be dictionaries for proper
21
+ serialization and search functionality.
22
+ """
23
+ if not isinstance(v, dict):
24
+ raise ValueError(f"Value must be a dictionary (JSON object), got {type(v).__name__}")
25
+ return v
26
+
27
+
28
+ class StoreGetResponse(BaseModel):
29
+ """Response model for getting items"""
30
+
31
+ key: str
32
+ value: Any
33
+ namespace: list[str]
34
+
35
+
36
+ class StoreSearchRequest(BaseModel):
37
+ """Request model for searching store items"""
38
+
39
+ namespace_prefix: list[str] = Field(..., description="Namespace prefix to search")
40
+ filter: dict[str, Any] | None = Field(None, description="Optional dictionary of key-value pairs to filter results.")
41
+ query: str | None = Field(None, description="Search query")
42
+ limit: int | None = Field(20, le=100, ge=1, description="Maximum results")
43
+ offset: int | None = Field(0, ge=0, description="Results offset")
44
+
45
+
46
+ class StoreItem(BaseModel):
47
+ """Store item model"""
48
+
49
+ key: str
50
+ value: Any
51
+ namespace: list[str]
52
+
53
+
54
+ class StoreSearchResponse(BaseModel):
55
+ """Response model for store search"""
56
+
57
+ items: list[StoreItem]
58
+ total: int
59
+ limit: int
60
+ offset: int
61
+
62
+
63
+ class StoreDeleteRequest(BaseModel):
64
+ """Request body for deleting store items (SDK-compatible)."""
65
+
66
+ namespace: list[str]
67
+ key: str
@@ -0,0 +1,152 @@
1
+ """Thread-related Pydantic models for Agent Protocol"""
2
+
3
+ from datetime import datetime
4
+ from typing import Any
5
+
6
+ from pydantic import BaseModel, ConfigDict, Field, field_validator
7
+
8
+ from aegra_api.utils.status_compat import validate_thread_status
9
+
10
+
11
+ class ThreadCreate(BaseModel):
12
+ """Request model for creating threads"""
13
+
14
+ model_config = ConfigDict(populate_by_name=True)
15
+
16
+ metadata: dict[str, Any] | None = Field(None, description="Thread metadata")
17
+ initial_state: dict[str, Any] | None = Field(None, description="LangGraph initial state")
18
+ thread_id: str | None = Field(
19
+ None,
20
+ alias="threadId",
21
+ description="Optional client-provided thread ID for idempotent creation",
22
+ )
23
+ if_exists: str | None = Field(
24
+ "raise",
25
+ alias="ifExists",
26
+ description="Behavior when thread exists: 'raise' (default) or 'do_nothing'",
27
+ )
28
+
29
+
30
+ class ThreadUpdate(BaseModel):
31
+ """Request model for updating threads"""
32
+
33
+ metadata: dict[str, Any] | None = Field(None, description="Thread metadata to update")
34
+
35
+
36
+ class Thread(BaseModel):
37
+ """Thread entity model
38
+
39
+ Status values: idle, busy, interrupted, error
40
+ """
41
+
42
+ model_config = ConfigDict(from_attributes=True)
43
+
44
+ thread_id: str
45
+ status: str = "idle" # Valid values: idle, busy, interrupted, error
46
+ metadata: dict[str, Any] = Field(default_factory=dict)
47
+ user_id: str
48
+ created_at: datetime
49
+ updated_at: datetime
50
+
51
+ @field_validator("status", mode="before")
52
+ @classmethod
53
+ def validate_status(cls, v: str) -> str:
54
+ """Validate status conforms to API specification."""
55
+ if not isinstance(v, str):
56
+ raise ValueError(f"Status must be a string, got {type(v)}")
57
+ return validate_thread_status(v)
58
+
59
+
60
+ class ThreadList(BaseModel):
61
+ """Response model for listing threads"""
62
+
63
+ threads: list[Thread]
64
+ total: int
65
+
66
+
67
+ class ThreadSearchRequest(BaseModel):
68
+ """Request model for thread search"""
69
+
70
+ metadata: dict[str, Any] | None = Field(None, description="Metadata filters")
71
+ status: str | None = Field(None, description="Thread status filter (idle, busy, interrupted, error)")
72
+ limit: int | None = Field(20, le=100, ge=1, description="Maximum results")
73
+ offset: int | None = Field(0, ge=0, description="Results offset")
74
+ order_by: str | None = Field("created_at DESC", description="Sort order")
75
+
76
+ @field_validator("status")
77
+ @classmethod
78
+ def validate_status(cls, v: str | None) -> str | None:
79
+ """Validate status filter conforms to API specification."""
80
+ if v is not None:
81
+ return validate_thread_status(v)
82
+ return v
83
+
84
+
85
+ class ThreadSearchResponse(BaseModel):
86
+ """Response model for thread search"""
87
+
88
+ threads: list[Thread]
89
+ total: int
90
+ limit: int
91
+ offset: int
92
+
93
+
94
+ class ThreadCheckpoint(BaseModel):
95
+ """Checkpoint identifier for thread history"""
96
+
97
+ checkpoint_id: str | None = None
98
+ thread_id: str | None = None
99
+ checkpoint_ns: str | None = ""
100
+
101
+
102
+ class ThreadCheckpointPostRequest(BaseModel):
103
+ """Request model for fetching thread checkpoint"""
104
+
105
+ checkpoint: ThreadCheckpoint = Field(description="Checkpoint to fetch")
106
+ subgraphs: bool | None = Field(False, description="Include subgraph states")
107
+
108
+
109
+ class ThreadState(BaseModel):
110
+ """Thread state model for history endpoint"""
111
+
112
+ values: dict[str, Any] = Field(description="Channel values (messages, etc.)")
113
+ next: list[str] = Field(default_factory=list, description="Next nodes to execute")
114
+ tasks: list[dict[str, Any]] = Field(default_factory=list, description="Tasks to execute")
115
+ interrupts: list[dict[str, Any]] = Field(default_factory=list, description="Interrupt data")
116
+ metadata: dict[str, Any] = Field(default_factory=dict, description="Checkpoint metadata")
117
+ created_at: datetime | None = Field(None, description="Timestamp of state creation")
118
+ checkpoint: ThreadCheckpoint = Field(description="Current checkpoint")
119
+ parent_checkpoint: ThreadCheckpoint | None = Field(None, description="Parent checkpoint")
120
+ checkpoint_id: str | None = Field(None, description="Checkpoint ID (for backward compatibility)")
121
+ parent_checkpoint_id: str | None = Field(None, description="Parent checkpoint ID (for backward compatibility)")
122
+
123
+
124
+ class ThreadStateUpdate(BaseModel):
125
+ """Request model for updating thread state"""
126
+
127
+ values: dict[str, Any] | list[dict[str, Any]] | None = Field(
128
+ None, description="The values to update the state with"
129
+ )
130
+ checkpoint: dict[str, Any] | None = Field(None, description="The checkpoint to update the state of")
131
+ checkpoint_id: str | None = Field(None, description="Optional checkpoint ID to update from")
132
+ as_node: str | None = Field(None, description="Update the state as if this node had just executed")
133
+ # Also support query-like parameters for GET-like behavior via POST
134
+ subgraphs: bool | None = Field(False, description="Include states from subgraphs")
135
+ checkpoint_ns: str | None = Field(None, description="Checkpoint namespace")
136
+
137
+
138
+ class ThreadStateUpdateResponse(BaseModel):
139
+ """Response model for thread state update"""
140
+
141
+ checkpoint: dict[str, Any] = Field(description="The checkpoint that was created/updated")
142
+
143
+
144
+ class ThreadHistoryRequest(BaseModel):
145
+ """Request model for thread history endpoint"""
146
+
147
+ limit: int | None = Field(10, ge=1, le=1000, description="Number of states to return")
148
+ before: str | None = Field(None, description="Return states before this checkpoint ID")
149
+ metadata: dict[str, Any] | None = Field(None, description="Filter by metadata")
150
+ checkpoint: dict[str, Any] | None = Field(None, description="Checkpoint for subgraph filtering")
151
+ subgraphs: bool | None = Field(False, description="Include states from subgraphs")
152
+ checkpoint_ns: str | None = Field(None, description="Checkpoint namespace")
@@ -0,0 +1 @@
1
+ """Observability module for the agent server."""