Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 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
4 changes: 2 additions & 2 deletions daemon/src/daemon/adapters/camera/onvif_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

from collections.abc import Awaitable, Callable, Mapping
from dataclasses import dataclass
from typing import Protocol
from typing import Protocol, cast


@dataclass(frozen=True)
Expand Down Expand Up @@ -74,7 +74,7 @@ def parse_motion_event(payload: Mapping[str, object]) -> MotionEvent | None:
topic = str(payload.get("topic", ""))
message = payload.get("message")
if isinstance(message, Mapping):
state_source: Mapping[str, object] = message
state_source: Mapping[str, object] = cast("Mapping[str, object]", message)
else:
state_source = payload

Expand Down
15 changes: 9 additions & 6 deletions daemon/src/daemon/adapters/llm/event_parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

import json
from dataclasses import dataclass
from typing import TYPE_CHECKING
from typing import TYPE_CHECKING, cast

if TYPE_CHECKING:
from collections.abc import Iterable, Iterator, Mapping
Expand Down Expand Up @@ -216,21 +216,24 @@ def _parse_payload(raw_data: str) -> dict[str, JsonValue] | None:
payload: object = json.loads(raw_data)
if not isinstance(payload, dict):
return None
return _normalize_json_object(payload)
return _normalize_json_object(cast("dict[str, JsonValue]", payload))


def _parse_arguments(arguments_json: str) -> dict[str, JsonValue] | None:
payload: object = json.loads(arguments_json)
if not isinstance(payload, dict):
return None
return _normalize_json_object(payload)
return _normalize_json_object(cast("dict[str, JsonValue]", payload))


def _dict_field(payload: dict[str, JsonValue], key: str) -> dict[str, JsonValue] | None:
value = payload.get(key)
if not isinstance(value, dict):
return None
return _normalize_json_object(value)
normalized: dict[str, JsonValue] = {}
for nested_key, nested_value in cast("dict[object, object]", value).items():
normalized[str(nested_key)] = _normalize_json_value(nested_value)
return normalized


def _string_field(payload: dict[str, JsonValue], key: str) -> str | None:
Expand All @@ -255,9 +258,9 @@ def _normalize_json_value(value: object) -> JsonValue:
if value is None or isinstance(value, str | int | float | bool):
return value
if isinstance(value, list):
return _normalize_json_array(value)
return _normalize_json_array(cast("list[object]", value))
if isinstance(value, dict):
return _normalize_json_object(value)
return _normalize_json_object(cast("dict[str, JsonValue]", value))
return str(value)


Expand Down
6 changes: 4 additions & 2 deletions daemon/src/daemon/adapters/llm/openai_transport.py
Original file line number Diff line number Diff line change
Expand Up @@ -358,12 +358,14 @@ def _assert_clean_payload(value: object, *, prefix: str) -> None:
return

if isinstance(value, list):
for index, item in enumerate(value):
items = cast("list[object]", value)
for index, item in enumerate(items):
_assert_clean_payload(item, prefix=f"{prefix}[{index}]")
return

if isinstance(value, dict):
for key, item in value.items():
mapping = cast("dict[object, object]", value)
for key, item in mapping.items():
if key in SKIP_PRIVACY_KEYS and isinstance(item, str):
continue
_assert_clean_payload(item, prefix=f"{prefix}.{key}")
Expand Down
3 changes: 2 additions & 1 deletion daemon/src/daemon/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -527,7 +527,8 @@ async def post_debug_inject(payload: dict[str, object]) -> dict[str, object]:
raw_data = payload.get("data", {})
if not isinstance(raw_data, dict):
raise HTTPException(status_code=400, detail="data must be an object")
data = {str(key): value for key, value in raw_data.items()}
raw_mapping = cast("dict[object, object]", raw_data)
data: dict[str, object] = {str(key): value for key, value in raw_mapping.items()}
log_buffer.log("INFO", "debug", f"inject:{event_type}", {"payload": data})
await event_bus.publish(event_type, data)
return {"injected": True, "event_id": f"debug-{len(recent_events) + 1}"}
Expand Down
2 changes: 1 addition & 1 deletion daemon/src/daemon/audit/langfuse_export.py
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ class LangfuseConfig:
class LangfuseMirror:
client: LangfuseClientLike
logger: LoggerLike = field(default_factory=lambda: logging.getLogger("daemon.audit.langfuse"))
_llm_span_ids: dict[str, str] = field(default_factory=dict)
_llm_span_ids: dict[str, str] = field(default_factory=lambda: cast("dict[str, str]", {}))

async def export_record(self, *, record_type: str, payload: dict[str, JSONValue]) -> None:
trace_context = {
Expand Down
12 changes: 8 additions & 4 deletions daemon/src/daemon/audit/local_sink.py
Original file line number Diff line number Diff line change
Expand Up @@ -787,14 +787,16 @@ def _default_role(kind: str) -> str:

def _json_dict(value: object) -> dict[str, JSONValue]:
if isinstance(value, dict):
return {str(key): _coerce_json_value(item) for key, item in value.items()}
mapping = cast("dict[object, object]", value)
return {str(key): _coerce_json_value(item) for key, item in mapping.items()}
return {}


def _json_dict_list(value: object) -> list[dict[str, JSONValue]]:
if not isinstance(value, list):
return []
return [_json_dict(item) for item in value]
items = cast("list[object]", value)
return [_json_dict(item) for item in items]


def _optional_json(value: object) -> dict[str, JSONValue] | list[JSONValue] | None:
Expand All @@ -818,9 +820,11 @@ def _coerce_json_value(value: object) -> JSONValue:
if isinstance(value, bool | int | float | str):
return value
if isinstance(value, list):
return [_coerce_json_value(item) for item in value]
items = cast("list[object]", value)
return [_coerce_json_value(item) for item in items]
if isinstance(value, dict):
return {str(key): _coerce_json_value(item) for key, item in value.items()}
mapping = cast("dict[object, object]", value)
return {str(key): _coerce_json_value(item) for key, item in mapping.items()}
return str(value)


Expand Down
8 changes: 5 additions & 3 deletions daemon/src/daemon/audit/redactor.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
from __future__ import annotations

import re
from typing import TYPE_CHECKING
from typing import TYPE_CHECKING, cast

from daemon.privacy import anonymize, is_clean

Expand Down Expand Up @@ -55,11 +55,13 @@ def mask_langfuse_value(value: object) -> object:
if isinstance(value, str):
return mask_langfuse_text(value)
if isinstance(value, list):
return [mask_langfuse_value(item) for item in value]
items = cast("list[object]", value)
return [mask_langfuse_value(item) for item in items]
if isinstance(value, dict):
mapping = cast("dict[object, object]", value)
return {
str(mask_langfuse_text(str(key))): mask_langfuse_value(item)
for key, item in value.items()
for key, item in mapping.items()
}
return value

Expand Down
5 changes: 3 additions & 2 deletions daemon/src/daemon/config/loader.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import logging
from pathlib import Path
from typing import cast

import yaml
from pydantic import BaseModel
Expand Down Expand Up @@ -68,7 +69,7 @@ def parse_persona_document(text: str) -> PersonaConfigDocument:
_, raw_frontmatter, body = text.split("---\n", 2)
except ValueError as exc:
raise ValueError("persona.md frontmatter is malformed") from exc
frontmatter = yaml.safe_load(raw_frontmatter) or {}
frontmatter = cast("dict[str, object]", yaml.safe_load(raw_frontmatter) or {})
parsed_frontmatter = PersonaFrontmatter.model_validate(frontmatter)
return PersonaConfigDocument.model_validate({
"name": parsed_frontmatter.guardian_name,
Expand All @@ -85,7 +86,7 @@ def _load_yaml_document[YamlDocument: BaseModel](
path: Path,
model_cls: type[YamlDocument],
) -> YamlDocument:
payload = yaml.safe_load(path.read_text(encoding="utf-8")) or {}
payload = cast("dict[str, object]", yaml.safe_load(path.read_text(encoding="utf-8")) or {})
return model_cls.model_validate(payload)


Expand Down
10 changes: 7 additions & 3 deletions daemon/src/daemon/contracts/adapters.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,9 @@ class DialogueResponse(ContractModel):
text: str
model: str
finish_reason: str
tool_calls: list[DialogueToolCall] = Field(default_factory=list)
tool_calls: list[DialogueToolCall] = Field(
default_factory=lambda: cast("list[DialogueToolCall]", [])
)


def _freeze_json_value(value: JSONValue) -> object:
Expand All @@ -59,9 +61,11 @@ def _freeze_json_value(value: JSONValue) -> object:

def _thaw_json_value(value: object) -> JSONValue:
if isinstance(value, Mapping):
return {key: _thaw_json_value(item) for key, item in value.items()}
mapping = cast("Mapping[str, object]", value)
return {key: _thaw_json_value(item) for key, item in mapping.items()}
if isinstance(value, tuple):
return [_thaw_json_value(item) for item in value]
items = cast("tuple[object, ...]", value)
return [_thaw_json_value(item) for item in items]
if value is None or isinstance(value, str | int | float | bool):
return cast("JSONValue", value)
msg = f"Unsupported JSON-like value: {type(value)!r}"
Expand Down
4 changes: 2 additions & 2 deletions daemon/src/daemon/contracts/archive.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

from dataclasses import dataclass, field
from datetime import date, datetime
from typing import Protocol
from typing import Protocol, cast
from uuid import UUID


Expand All @@ -25,7 +25,7 @@ class DailyReportRecord:
place_id: str
summary_text: str
session_refs: tuple[dict[str, object], ...] = ()
fact_stats: dict[str, object] = field(default_factory=dict)
fact_stats: dict[str, object] = field(default_factory=lambda: cast("dict[str, object]", {}))


class ArchiveRepositoryLike(Protocol):
Expand Down
22 changes: 16 additions & 6 deletions daemon/src/daemon/contracts/audio.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

from datetime import datetime
from enum import StrEnum
from typing import Literal
from typing import Literal, cast

from pydantic import Field, field_validator

Expand All @@ -26,8 +26,10 @@ class ActivationEvent(ContractModel):
def _coerce_source_signals(cls, value: object) -> object:
if not isinstance(value, list):
return value
items = cast("list[object]", value)
return [
item if isinstance(item, ActivationSignal) else ActivationSignal(item) for item in value
item if isinstance(item, ActivationSignal) else ActivationSignal(str(item))
for item in items
]


Expand All @@ -47,7 +49,9 @@ class AudioCapability(StrEnum):
class AudioDeviceInfo(ContractModel):
device_id: str
device_type: str
capabilities: list[AudioCapability] = Field(default_factory=list)
capabilities: list[AudioCapability] = Field(
default_factory=lambda: cast("list[AudioCapability]", [])
)
sample_rate: int = Field(gt=0)
channels: int = Field(gt=0)

Expand All @@ -56,14 +60,18 @@ class AudioDeviceInfo(ContractModel):
def _coerce_capabilities(cls, value: object) -> object:
if not isinstance(value, list):
return value
items = cast("list[object]", value)
return [
item if isinstance(item, AudioCapability) else AudioCapability(item) for item in value
item if isinstance(item, AudioCapability) else AudioCapability(str(item))
for item in items
]


class AudioStatusResponse(ContractModel):
device: Literal["single_mic", "mic_array"]
capabilities: list[AudioCapability] = Field(default_factory=list)
capabilities: list[AudioCapability] = Field(
default_factory=lambda: cast("list[AudioCapability]", [])
)
sample_rate: int = Field(gt=0)
channels: int = Field(gt=0)
doa_available: bool
Expand All @@ -73,8 +81,10 @@ class AudioStatusResponse(ContractModel):
def _coerce_status_capabilities(cls, value: object) -> object:
if not isinstance(value, list):
return value
items = cast("list[object]", value)
return [
item if isinstance(item, AudioCapability) else AudioCapability(item) for item in value
item if isinstance(item, AudioCapability) else AudioCapability(str(item))
for item in items
]


Expand Down
5 changes: 4 additions & 1 deletion daemon/src/daemon/contracts/config_docs.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

from datetime import datetime
from enum import StrEnum
from typing import cast

from pydantic import Field

Expand Down Expand Up @@ -89,7 +90,9 @@ class ConfigDocumentWriteRequest[TDocument](ContractModel):

class ConfigDocumentWriteResponse(ContractModel):
status: ConfigWriteStatus = Field(strict=False)
validation_errors: list[ValidationErrorItem] = Field(default_factory=list)
validation_errors: list[ValidationErrorItem] = Field(
default_factory=lambda: cast("list[ValidationErrorItem]", [])
)
audit_id: str


Expand Down
3 changes: 2 additions & 1 deletion daemon/src/daemon/contracts/memory.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

from datetime import datetime
from enum import StrEnum
from typing import cast

from pydantic import Field

Expand Down Expand Up @@ -34,7 +35,7 @@ class MemoryMatch(ContractModel):


class MemoryResult(ContractModel):
matches: list[MemoryMatch] = Field(default_factory=list)
matches: list[MemoryMatch] = Field(default_factory=lambda: cast("list[MemoryMatch]", []))
Comment thread
ToaruPen marked this conversation as resolved.
Outdated


class ReviewDecision(StrEnum):
Expand Down
7 changes: 5 additions & 2 deletions daemon/src/daemon/core/logging.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import re
from collections import deque
from datetime import UTC, datetime
from typing import cast

import structlog

Expand All @@ -12,9 +13,11 @@

def _sanitize(value: object) -> object:
if isinstance(value, dict):
return {key: _sanitize(item) for key, item in value.items()}
mapping = cast("dict[str, object]", value)
return {key: _sanitize(item) for key, item in mapping.items()}
if isinstance(value, list):
return [_sanitize(item) for item in value]
items = cast("list[object]", value)
return [_sanitize(item) for item in items]
if isinstance(value, str):
redacted = PHONE_RE.sub("<PHONE>", value)
return JAPANESE_NAME_RE.sub("<PERSON>", redacted)
Expand Down
16 changes: 9 additions & 7 deletions daemon/src/daemon/harness/tool_loop.py
Original file line number Diff line number Diff line change
Expand Up @@ -970,14 +970,13 @@ def _truncate_json_value(value: JSONValue, *, max_bytes: int) -> JSONValue:
if isinstance(normalized, str):
return normalized[: max(1, max_bytes // 4)]
if isinstance(normalized, list):
items: list[JSONValue] = [cast("JSONValue", item) for item in normalized]
items = cast("list[JSONValue]", normalized)
while items and len(json.dumps(items, ensure_ascii=False).encode()) > max_bytes:
items.pop()
return cast("JSONValue", items)
if isinstance(normalized, dict):
trimmed: dict[str, JSONValue] = {
str(key): cast("JSONValue", nested_value) for key, nested_value in normalized.items()
}
mapping = cast("dict[str, JSONValue]", normalized)
trimmed = dict(mapping)
while trimmed and len(json.dumps(trimmed, ensure_ascii=False).encode()) > max_bytes:
last_key = next(reversed(trimmed.keys()))
trimmed.pop(last_key)
Expand All @@ -987,19 +986,22 @@ def _truncate_json_value(value: JSONValue, *, max_bytes: int) -> JSONValue:

def _normalize_json_value(value: object) -> object:
if isinstance(value, dict):
mapping = cast("dict[object, object]", value)
return {
str(key): cast("JSONValue", _normalize_json_value(nested_value))
for key, nested_value in list(value.items())[:10]
for key, nested_value in list(mapping.items())[:10]
}
if isinstance(value, list):
return [_normalize_json_value(item) for item in value[:20]]
items = cast("list[object]", value)
return [_normalize_json_value(item) for item in items[:20]]
return value


def _coerce_json_dict(value: object) -> dict[str, JSONValue]:
if not isinstance(value, dict):
return {}
mapping = cast("dict[object, object]", value)
return {
str(key): cast("JSONValue", _normalize_json_value(nested_value))
for key, nested_value in value.items()
for key, nested_value in mapping.items()
}
Loading
Loading