mirror of
https://github.com/langchain-ai/deepagents.git
synced 2026-07-21 17:25:26 -04:00
977e11006c
Added a `/timestamps` toggle for message timestamp footers in `deepagents-code`. _Opened collaboratively by Mason Daugherty and open-swe._ --------- Co-authored-by: open-swe[bot] <open-swe@users.noreply.github.com> Co-authored-by: Mason Daugherty <61371264+mdrxy@users.noreply.github.com> Co-authored-by: Mason Daugherty <github@mdrxy.com> Co-authored-by: Mason Daugherty <mason@langchain.dev>
136 lines
4.7 KiB
Python
136 lines
4.7 KiB
Python
"""Lightweight text-formatting helpers.
|
|
|
|
Keep this module free of heavy dependencies so it can be imported anywhere
|
|
in the app without pulling in large frameworks.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import locale
|
|
import logging
|
|
import subprocess # noqa: S404
|
|
import sys
|
|
from datetime import UTC, datetime
|
|
from functools import cache
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
def format_duration(seconds: float) -> str:
|
|
"""Format a duration in seconds into a human-readable string.
|
|
|
|
Args:
|
|
seconds: Duration in seconds.
|
|
|
|
Returns:
|
|
Formatted string like `"5s"`, `"2.3s"`, `"5m 12s"`, or `"1h 23m 4s"`.
|
|
"""
|
|
rounded = round(seconds, 1)
|
|
if rounded < 60: # noqa: PLR2004
|
|
if rounded % 1 == 0:
|
|
return f"{int(rounded)}s"
|
|
return f"{rounded:.1f}s"
|
|
minutes, secs = divmod(int(rounded), 60)
|
|
if minutes < 60: # noqa: PLR2004
|
|
return f"{minutes}m {secs}s"
|
|
hours, minutes = divmod(minutes, 60)
|
|
return f"{hours}h {minutes}m {secs}s"
|
|
|
|
|
|
def macos_force_24_hour_time() -> bool | None:
|
|
"""Read macOS's "24-Hour Time" preference (`AppleICUForce24HourTime`).
|
|
|
|
macOS exposes the clock style as a global preference that does not surface
|
|
through the POSIX `LC_TIME` locale (libc `%X` is 24-hour for every macOS
|
|
locale), so it must be read separately.
|
|
|
|
Returns:
|
|
`True`/`False` when the preference is explicitly set, or `None` when it
|
|
is unset (the user has never toggled it) or cannot be read.
|
|
"""
|
|
try:
|
|
result = subprocess.run(
|
|
["/usr/bin/defaults", "read", "-g", "AppleICUForce24HourTime"],
|
|
capture_output=True,
|
|
text=True,
|
|
timeout=1,
|
|
check=False,
|
|
)
|
|
except (OSError, subprocess.SubprocessError) as exc:
|
|
logger.debug("Could not read AppleICUForce24HourTime: %s", exc)
|
|
return None
|
|
if result.returncode != 0:
|
|
# Preference unset (key absent) — the user has never toggled it.
|
|
return None
|
|
return result.stdout.strip() == "1"
|
|
|
|
|
|
@cache
|
|
def uses_24_hour_clock() -> bool:
|
|
"""Whether the system is configured for a 24-hour clock.
|
|
|
|
On macOS the clock style lives in the `AppleICUForce24HourTime` system
|
|
preference rather than the POSIX locale, so that is consulted first (see
|
|
`macos_force_24_hour_time`). When that preference is unset we intentionally
|
|
default to a 24-hour clock: macOS resolves the region's 12-/24-hour default
|
|
via ICU/CFLocale, which POSIX cannot read (libc `%X` is 24-hour for every
|
|
macOS locale), so the locale probe below would be meaningless there.
|
|
|
|
On every other platform the active `LC_TIME` locale's time representation
|
|
is probed instead.
|
|
|
|
The result is cached because resolving it mutates process-global locale
|
|
state via `locale.setlocale(locale.LC_TIME, "")` (scoped to the time
|
|
category, and only ever performed once).
|
|
|
|
Returns:
|
|
`True` for a 24-hour clock (also the fallback when the macOS preference
|
|
is unset or no source can be resolved, matching the C locale's 24-hour
|
|
default), `False` for 12-hour.
|
|
"""
|
|
if sys.platform == "darwin":
|
|
forced = macos_force_24_hour_time()
|
|
if forced is not None:
|
|
return forced
|
|
# Preference unset: the region default lives in ICU/CFLocale, which the
|
|
# POSIX `%X` probe cannot see. Default to 24-hour rather than misread it.
|
|
return True
|
|
try:
|
|
locale.setlocale(locale.LC_TIME, "")
|
|
# 13:00 renders as "13:..." only where the locale's %X is 24-hour; a
|
|
# 12-hour locale renders "01:..." (often with an AM/PM marker).
|
|
probe = datetime(2000, 1, 1, 13, 0, 0).strftime("%X") # noqa: DTZ001 # naive probe; tz irrelevant to clock style
|
|
except (locale.Error, ValueError) as exc:
|
|
logger.debug(
|
|
"Could not resolve LC_TIME locale; defaulting to 24-hour clock: %s",
|
|
exc,
|
|
)
|
|
return True
|
|
return "13" in probe
|
|
|
|
|
|
def format_message_timestamp(timestamp: float) -> str | None:
|
|
"""Format a message timestamp for display.
|
|
|
|
Shows only the time of day for messages from the current local date and
|
|
prefixes the date otherwise. The 12- versus 24-hour clock follows the
|
|
system configuration (see `uses_24_hour_clock`).
|
|
|
|
Args:
|
|
timestamp: Unix epoch timestamp.
|
|
|
|
Returns:
|
|
A formatted timestamp, or `None` when invalid.
|
|
"""
|
|
try:
|
|
dt = datetime.fromtimestamp(timestamp, tz=UTC).astimezone()
|
|
except (ValueError, OSError, OverflowError, TypeError):
|
|
return None
|
|
if uses_24_hour_clock():
|
|
time_str = f"{dt:%H:%M:%S}"
|
|
else:
|
|
time_str = f"{dt.hour % 12 or 12}:{dt:%M:%S} {dt:%p}"
|
|
if dt.date() == datetime.now(tz=dt.tzinfo).date():
|
|
return time_str
|
|
return f"{dt:%b} {dt.day}, {time_str}"
|