mirror of
https://github.com/escalante29/WealthySmart.git
synced 2026-07-17 13:28:46 +02:00
Agent: resolve "today" in the browser's timezone, not hardcoded CR
The CR-hardcoded today broke two scenarios: the owner traveling, and any future non-CR user. Now the provider sends the browser's IANA timezone (X-Client-Timezone) with every CopilotKit request — the runtime forwards x-* headers to the agent on its own — and the backend binds it to a per-request ContextVar next to the DB session. get_current_date and the future-cuota bounds use it; the tool also reports the timezone so the model can echo it. Unknown or absent zones (n8n posts, tests) fall back to Costa Rica; comma-joined duplicate header values are tolerated. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -1,16 +1,54 @@
|
||||
from datetime import date, datetime, timedelta, timezone
|
||||
import contextvars
|
||||
from datetime import date, datetime, timedelta, timezone, tzinfo
|
||||
from zoneinfo import ZoneInfo
|
||||
|
||||
# Costa Rica has no DST; a fixed offset is exact year-round.
|
||||
# Costa Rica has no DST; a fixed offset is exact year-round. This is the
|
||||
# FALLBACK when the request doesn't declare a timezone (n8n posts, tests).
|
||||
CR_TZ = timezone(timedelta(hours=-6))
|
||||
|
||||
# Per-request client timezone, set by the agent middleware from the
|
||||
# X-Client-Timezone header (IANA name sent by the browser). Falls back to
|
||||
# Costa Rica so headless callers keep the owner's local semantics.
|
||||
_client_tz: contextvars.ContextVar[tzinfo] = contextvars.ContextVar(
|
||||
"client_tz", default=CR_TZ
|
||||
)
|
||||
|
||||
def today_cr() -> date:
|
||||
"""Current date in Costa Rica (UTC-6).
|
||||
|
||||
def set_client_timezone(name: str | None) -> contextvars.Token | None:
|
||||
"""Bind the request's IANA timezone; ignores unknown/absent names.
|
||||
|
||||
Proxies that see the header twice merge the values into a comma list
|
||||
("Zone, Zone") — take the first entry.
|
||||
"""
|
||||
if not name:
|
||||
return None
|
||||
try:
|
||||
return _client_tz.set(ZoneInfo(name.split(",")[0].strip()))
|
||||
except (KeyError, ValueError):
|
||||
return None
|
||||
|
||||
|
||||
def reset_client_timezone(token: contextvars.Token | None) -> None:
|
||||
if token is not None:
|
||||
_client_tz.reset(token)
|
||||
|
||||
|
||||
def client_tz() -> tzinfo:
|
||||
return _client_tz.get()
|
||||
|
||||
|
||||
def today_client() -> date:
|
||||
"""Current date where the USER is (request timezone, CR fallback).
|
||||
|
||||
The server clock is UTC — at 6 pm in Costa Rica it is already "tomorrow"
|
||||
in UTC. Anything user-facing that means "today" (agent answers, date
|
||||
defaults) must come from here, never date.today().
|
||||
"""
|
||||
return datetime.now(client_tz()).date()
|
||||
|
||||
|
||||
def today_cr() -> date:
|
||||
"""Current date in Costa Rica (UTC-6) — timezone-independent fallback."""
|
||||
return datetime.now(CR_TZ).date()
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user