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
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
2 changes: 1 addition & 1 deletion daemon/src/daemon/contracts/memory.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ class MemoryMatch(ContractModel):


class MemoryResult(ContractModel):
matches: list[MemoryMatch] = Field(default_factory=list)
matches: list[MemoryMatch] = Field(default_factory=list[MemoryMatch])


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