feat(config): add envelopeDateFormat for custom envelope timestamp formatting
This commit is contained in:
parent
9a2be717b7
commit
17b78b406d
@ -28,9 +28,13 @@ export type EnvelopeFormatOptions = {
|
|||||||
*/
|
*/
|
||||||
includeElapsed?: boolean;
|
includeElapsed?: boolean;
|
||||||
/**
|
/**
|
||||||
* Optional user timezone used when timezone="user".
|
* Optional user timezone used when timezone = "user".
|
||||||
*/
|
*/
|
||||||
userTimezone?: string;
|
userTimezone?: string;
|
||||||
|
/**
|
||||||
|
* Custom date format string (e.g., "ddd MMM D HH:mm"). Overrides default timestamp format.
|
||||||
|
*/
|
||||||
|
dateFormat?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
type NormalizedEnvelopeOptions = {
|
type NormalizedEnvelopeOptions = {
|
||||||
@ -38,6 +42,7 @@ type NormalizedEnvelopeOptions = {
|
|||||||
includeTimestamp: boolean;
|
includeTimestamp: boolean;
|
||||||
includeElapsed: boolean;
|
includeElapsed: boolean;
|
||||||
userTimezone?: string;
|
userTimezone?: string;
|
||||||
|
dateFormat?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
type ResolvedEnvelopeTimezone =
|
type ResolvedEnvelopeTimezone =
|
||||||
@ -52,6 +57,7 @@ export function resolveEnvelopeFormatOptions(cfg?: ClawdbotConfig): EnvelopeForm
|
|||||||
includeTimestamp: defaults?.envelopeTimestamp !== "off",
|
includeTimestamp: defaults?.envelopeTimestamp !== "off",
|
||||||
includeElapsed: defaults?.envelopeElapsed !== "off",
|
includeElapsed: defaults?.envelopeElapsed !== "off",
|
||||||
userTimezone: defaults?.userTimezone,
|
userTimezone: defaults?.userTimezone,
|
||||||
|
dateFormat: defaults?.envelopeDateFormat,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -63,6 +69,7 @@ function normalizeEnvelopeOptions(options?: EnvelopeFormatOptions): NormalizedEn
|
|||||||
includeTimestamp,
|
includeTimestamp,
|
||||||
includeElapsed,
|
includeElapsed,
|
||||||
userTimezone: options?.userTimezone,
|
userTimezone: options?.userTimezone,
|
||||||
|
dateFormat: options?.dateFormat,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -132,11 +139,80 @@ function formatTimestamp(
|
|||||||
const date = ts instanceof Date ? ts : new Date(ts);
|
const date = ts instanceof Date ? ts : new Date(ts);
|
||||||
if (Number.isNaN(date.getTime())) return undefined;
|
if (Number.isNaN(date.getTime())) return undefined;
|
||||||
const zone = resolveEnvelopeTimezone(resolved);
|
const zone = resolveEnvelopeTimezone(resolved);
|
||||||
|
if (resolved.dateFormat) {
|
||||||
|
let tz: string | undefined;
|
||||||
|
if (zone.mode === "iana") tz = zone.timeZone;
|
||||||
|
else if (zone.mode === "utc") tz = "UTC";
|
||||||
|
return formatDate(date, resolved.dateFormat, tz);
|
||||||
|
}
|
||||||
if (zone.mode === "utc") return formatUtcTimestamp(date);
|
if (zone.mode === "utc") return formatUtcTimestamp(date);
|
||||||
if (zone.mode === "local") return formatZonedTimestamp(date);
|
if (zone.mode === "local") return formatZonedTimestamp(date);
|
||||||
return formatZonedTimestamp(date, zone.timeZone);
|
return formatZonedTimestamp(date, zone.timeZone);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function formatDate(date: Date, format: string, checkTimeZone?: string): string {
|
||||||
|
// We need to extract parts in the correct timezone
|
||||||
|
const timeZone = checkTimeZone || undefined; // undefined = local
|
||||||
|
const parts = new Intl.DateTimeFormat("en-US", {
|
||||||
|
timeZone,
|
||||||
|
weekday: "long",
|
||||||
|
year: "numeric",
|
||||||
|
month: "long",
|
||||||
|
day: "numeric",
|
||||||
|
hour: "numeric",
|
||||||
|
minute: "numeric",
|
||||||
|
second: "numeric",
|
||||||
|
hour12: false,
|
||||||
|
timeZoneName: "short",
|
||||||
|
}).formatToParts(date);
|
||||||
|
|
||||||
|
const getPart = (type: string) => parts.find((p) => p.type === type)?.value || "";
|
||||||
|
|
||||||
|
// Also get 12h hour and day period/ampm
|
||||||
|
const parts12 = new Intl.DateTimeFormat("en-US", {
|
||||||
|
timeZone,
|
||||||
|
hour: "numeric",
|
||||||
|
hour12: true,
|
||||||
|
}).formatToParts(date);
|
||||||
|
const hour12 = parts12.find((p) => p.type === "hour")?.value || "";
|
||||||
|
const dayPeriod = parts12.find((p) => p.type === "dayPeriod")?.value || "";
|
||||||
|
|
||||||
|
const Y = getPart("year");
|
||||||
|
const M = getPart("month"); // Long month
|
||||||
|
const D = getPart("day");
|
||||||
|
const d = getPart("weekday"); // Long weekday
|
||||||
|
const H = getPart("hour").padStart(2, "0"); // 0-23
|
||||||
|
const m = getPart("minute").padStart(2, "0");
|
||||||
|
const s = getPart("second").padStart(2, "0");
|
||||||
|
const z = parts.find((p) => p.type === "timeZoneName")?.value || "";
|
||||||
|
|
||||||
|
// Helper for short month/weekday
|
||||||
|
const MMM = M.slice(0, 3);
|
||||||
|
const MM = new Intl.DateTimeFormat("en-US", { timeZone, month: "2-digit" }).format(date);
|
||||||
|
const DD = D.padStart(2, "0");
|
||||||
|
const ddd = d.slice(0, 3);
|
||||||
|
const h = hour12.padStart(2, "0");
|
||||||
|
|
||||||
|
const map: Record<string, string> = {
|
||||||
|
YYYY: Y,
|
||||||
|
MMM: MMM,
|
||||||
|
MM: MM,
|
||||||
|
DD: DD,
|
||||||
|
D: D,
|
||||||
|
dddd: d,
|
||||||
|
ddd: ddd,
|
||||||
|
HH: H,
|
||||||
|
hh: h,
|
||||||
|
mm: m,
|
||||||
|
ss: s,
|
||||||
|
a: dayPeriod.toLowerCase(),
|
||||||
|
A: dayPeriod.toUpperCase(),
|
||||||
|
z: z,
|
||||||
|
};
|
||||||
|
|
||||||
|
return format.replace(/YYYY|MMM|MM|DD|D|dddd|ddd|HH|hh|mm|ss|a|A|z/g, (match) => map[match]);
|
||||||
|
}
|
||||||
|
|
||||||
function formatElapsedTime(currentMs: number, previousMs: number): string | undefined {
|
function formatElapsedTime(currentMs: number, previousMs: number): string | undefined {
|
||||||
const elapsedMs = currentMs - previousMs;
|
const elapsedMs = currentMs - previousMs;
|
||||||
if (!Number.isFinite(elapsedMs) || elapsedMs < 0) return undefined;
|
if (!Number.isFinite(elapsedMs) || elapsedMs < 0) return undefined;
|
||||||
@ -161,11 +237,11 @@ export function formatAgentEnvelope(params: AgentEnvelopeParams): string {
|
|||||||
const elapsed =
|
const elapsed =
|
||||||
resolved.includeElapsed && params.timestamp && params.previousTimestamp
|
resolved.includeElapsed && params.timestamp && params.previousTimestamp
|
||||||
? formatElapsedTime(
|
? formatElapsedTime(
|
||||||
params.timestamp instanceof Date ? params.timestamp.getTime() : params.timestamp,
|
params.timestamp instanceof Date ? params.timestamp.getTime() : params.timestamp,
|
||||||
params.previousTimestamp instanceof Date
|
params.previousTimestamp instanceof Date
|
||||||
? params.previousTimestamp.getTime()
|
? params.previousTimestamp.getTime()
|
||||||
: params.previousTimestamp,
|
: params.previousTimestamp,
|
||||||
)
|
)
|
||||||
: undefined;
|
: undefined;
|
||||||
if (params.from?.trim()) {
|
if (params.from?.trim()) {
|
||||||
const from = params.from.trim();
|
const from = params.from.trim();
|
||||||
|
|||||||
@ -114,6 +114,11 @@ export type AgentDefaultsConfig = {
|
|||||||
* Envelope timestamp timezone: "utc" (default), "local", "user", or an IANA timezone string.
|
* Envelope timestamp timezone: "utc" (default), "local", "user", or an IANA timezone string.
|
||||||
*/
|
*/
|
||||||
envelopeTimezone?: string;
|
envelopeTimezone?: string;
|
||||||
|
/**
|
||||||
|
* Custom date format string (e.g., "ddd MMM D HH:mm"). Overrides default timestamp format.
|
||||||
|
* Supported tokens: YYYY, MMM, MM, DD, D, ddd, dddd, HH, hh, mm, ss, a, A, z
|
||||||
|
*/
|
||||||
|
envelopeDateFormat?: string;
|
||||||
/**
|
/**
|
||||||
* Include absolute timestamps in message envelopes ("on" | "off", default: "on").
|
* Include absolute timestamps in message envelopes ("on" | "off", default: "on").
|
||||||
*/
|
*/
|
||||||
|
|||||||
@ -48,6 +48,7 @@ export const AgentDefaultsSchema = z
|
|||||||
userTimezone: z.string().optional(),
|
userTimezone: z.string().optional(),
|
||||||
timeFormat: z.union([z.literal("auto"), z.literal("12"), z.literal("24")]).optional(),
|
timeFormat: z.union([z.literal("auto"), z.literal("12"), z.literal("24")]).optional(),
|
||||||
envelopeTimezone: z.string().optional(),
|
envelopeTimezone: z.string().optional(),
|
||||||
|
envelopeDateFormat: z.string().optional(),
|
||||||
envelopeTimestamp: z.union([z.literal("on"), z.literal("off")]).optional(),
|
envelopeTimestamp: z.union([z.literal("on"), z.literal("off")]).optional(),
|
||||||
envelopeElapsed: z.union([z.literal("on"), z.literal("off")]).optional(),
|
envelopeElapsed: z.union([z.literal("on"), z.literal("off")]).optional(),
|
||||||
contextTokens: z.number().int().positive().optional(),
|
contextTokens: z.number().int().positive().optional(),
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user