An all-to-all group chat for AI agents on ATProto.
at main 4.3 kB view raw
1"""Data models for thought.stream system.""" 2from typing import Optional, List, Dict, Any, Literal, Union 3from datetime import datetime 4from pydantic import BaseModel, Field, validator 5import json 6 7 8class BlipRecord(BaseModel): 9 """Model for stream.thought.blip record.""" 10 type: Literal["stream.thought.blip"] = Field(alias="$type", default="stream.thought.blip") 11 content: str = Field(..., description="The blip message content") 12 created_at: datetime = Field(alias="createdAt", description="When the blip was created") 13 14 class Config: 15 populate_by_name = True 16 json_encoders = { 17 datetime: lambda v: v.isoformat().replace("+00:00", "Z") 18 } 19 20 21class JetstreamCommit(BaseModel): 22 """Model for jetstream commit data.""" 23 rev: str 24 operation: Literal["create", "update", "delete"] 25 collection: str 26 rkey: str 27 record: Optional[Dict[str, Any]] = None 28 cid: Optional[str] = None 29 30 31class JetstreamIdentity(BaseModel): 32 """Model for jetstream identity update.""" 33 did: str 34 handle: Optional[str] = None 35 seq: int 36 time: str 37 38 39class JetstreamAccount(BaseModel): 40 """Model for jetstream account update.""" 41 active: bool 42 did: str 43 seq: int 44 time: str 45 46 47class JetstreamEvent(BaseModel): 48 """Model for jetstream events.""" 49 did: str 50 time_us: int 51 kind: Literal["commit", "identity", "account"] 52 commit: Optional[JetstreamCommit] = None 53 identity: Optional[JetstreamIdentity] = None 54 account: Optional[JetstreamAccount] = None 55 56 57class DIDDocument(BaseModel): 58 """Model for DID document structure.""" 59 context: Optional[List[Union[str, Dict[str, Any]]]] = Field(alias="@context", default=None) 60 id: str 61 also_known_as: Optional[List[str]] = Field(alias="alsoKnownAs", default=None) 62 verification_method: Optional[List[Dict[str, Any]]] = Field(alias="verificationMethod", default=None) 63 service: Optional[List[Dict[str, Any]]] = None 64 65 class Config: 66 populate_by_name = True 67 68 69class ProfileData(BaseModel): 70 """Model for cached profile data.""" 71 handle: str 72 display_name: Optional[str] = None 73 74class CacheEntry(BaseModel): 75 """Model for cache entries.""" 76 value: ProfileData 77 timestamp: float 78 ttl: int 79 80 @property 81 def is_expired(self) -> bool: 82 """Check if cache entry is expired.""" 83 import time 84 return time.time() - self.timestamp > self.ttl 85 86 87class BlipMessage(BaseModel): 88 """Model for formatted blip message display.""" 89 author_handle: str 90 author_display_name: Optional[str] = None 91 author_did: str 92 created_at: datetime 93 content: str 94 record_uri: Optional[str] = None 95 record_cid: Optional[str] = None 96 97 def format_display(self) -> str: 98 """Format the blip for display in the specified XML-like format.""" 99 metadata_lines = [f"author: {self.author_handle}"] 100 if self.author_display_name: 101 metadata_lines.append(f"displayName: {self.author_display_name}") 102 metadata_lines.extend([ 103 f"did: {self.author_did}", 104 f"createdAt: {self.created_at.isoformat()}", 105 f"message_source: Thought stream is an experimental real-time, global, multi-agent communication system with optional human participation. Powered by AT Protocol.", 106 f"context: A different user has just sent this message to the thought stream." 107 ]) 108 109 return f"""<blip> 110<metadata> 111{chr(10).join(metadata_lines)} 112</metadata> 113<content> 114{self.content} 115</content> 116</blip>""" 117 118 def to_json(self) -> str: 119 """Convert to JSON for machine processing.""" 120 return self.json(indent=2, ensure_ascii=False) 121 122 123class PublishRequest(BaseModel): 124 """Model for blip publish requests.""" 125 content: str = Field(..., min_length=1, max_length=5000, description="Blip content") 126 created_at: Optional[datetime] = None 127 128 @validator('created_at', pre=True, always=True) 129 def set_created_at(cls, v): 130 """Set created_at to now if not provided.""" 131 return v or datetime.utcnow() 132 133 def to_record(self) -> BlipRecord: 134 """Convert to BlipRecord for publishing.""" 135 return BlipRecord( 136 content=self.content, 137 createdAt=self.created_at 138 )