fix(agent99): deliver visual lifecycle cards
This commit is contained in:
@@ -2,10 +2,15 @@
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import base64
|
||||
import binascii
|
||||
import hashlib
|
||||
import secrets
|
||||
import struct
|
||||
from datetime import datetime
|
||||
from typing import Literal
|
||||
|
||||
from pydantic import BaseModel, ConfigDict, Field
|
||||
from pydantic import BaseModel, ConfigDict, Field, model_validator
|
||||
|
||||
|
||||
class Agent99TelegramReceipt(BaseModel):
|
||||
@@ -75,6 +80,50 @@ class Agent99CompletionCallbackRequest(BaseModel):
|
||||
occurred_at: datetime
|
||||
|
||||
|
||||
class Agent99TelegramVisual(BaseModel):
|
||||
"""Bounded Agent99-rendered PNG; never accepts paths or arbitrary media."""
|
||||
|
||||
model_config = ConfigDict(extra="forbid", str_strip_whitespace=True)
|
||||
|
||||
schema_version: Literal["agent99_telegram_visual_v1"]
|
||||
media_type: Literal["image/png"]
|
||||
generated_by: Literal["agent99_system_drawing_v1"]
|
||||
width: Literal[1200]
|
||||
height: Literal[760]
|
||||
sha256: str = Field(pattern=r"^[0-9a-f]{64}$")
|
||||
content_base64: str = Field(
|
||||
min_length=32,
|
||||
max_length=1_400_000,
|
||||
pattern=r"^[A-Za-z0-9+/]+={0,2}$",
|
||||
repr=False,
|
||||
)
|
||||
|
||||
@model_validator(mode="after")
|
||||
def validate_bounded_png(self) -> Agent99TelegramVisual:
|
||||
try:
|
||||
raw = base64.b64decode(self.content_base64, validate=True)
|
||||
except (ValueError, binascii.Error) as exc:
|
||||
raise ValueError("agent99_visual_base64_invalid") from exc
|
||||
if len(raw) > 1024 * 1024:
|
||||
raise ValueError("agent99_visual_png_exceeds_1_mib")
|
||||
if (
|
||||
len(raw) < 24
|
||||
or raw[:8] != b"\x89PNG\r\n\x1a\n"
|
||||
or raw[12:16] != b"IHDR"
|
||||
):
|
||||
raise ValueError("agent99_visual_png_header_invalid")
|
||||
width, height = struct.unpack(">II", raw[16:24])
|
||||
if width != self.width or height != self.height:
|
||||
raise ValueError("agent99_visual_dimensions_invalid")
|
||||
digest = hashlib.sha256(raw).hexdigest()
|
||||
if not secrets.compare_digest(digest, self.sha256):
|
||||
raise ValueError("agent99_visual_sha256_mismatch")
|
||||
return self
|
||||
|
||||
def png_bytes(self) -> bytes:
|
||||
return base64.b64decode(self.content_base64, validate=True)
|
||||
|
||||
|
||||
class Agent99TelegramLifecycleRequest(BaseModel):
|
||||
"""Bounded Agent99 event card handed to the canonical Telegram gateway."""
|
||||
|
||||
@@ -119,4 +168,5 @@ class Agent99TelegramLifecycleRequest(BaseModel):
|
||||
occurrences: int = Field(default=1, ge=1, le=1_000_000)
|
||||
next_update: str = Field(min_length=1, max_length=300)
|
||||
reply_to_message_id: int | None = Field(default=None, ge=1)
|
||||
visual: Agent99TelegramVisual | None = None
|
||||
occurred_at: datetime
|
||||
|
||||
Reference in New Issue
Block a user