@adonis-agora/agent
Version:
Governed, durable-backed AI agent (chat + tool-calling + governance) for AdonisJS — part of the Agora ecosystem.
82 lines • 3.38 kB
JavaScript
function validTimeZone(zone) {
if (zone === undefined || zone.trim() === '')
return 'UTC';
try {
new Intl.DateTimeFormat('en-US', { timeZone: zone });
return zone;
}
catch {
return 'UTC';
}
}
/**
* `[sent 2026-10-23T11:17-03:00]` — ISO 8601 to the minute, with the zone's offset (`Z` in UTC):
* locale-neutral, and unambiguous to the model whatever language the conversation is in.
*/
export function defaultMessageTimestampFormat(sentAt, ctx) {
const parts = new Intl.DateTimeFormat('en-US', {
timeZone: validTimeZone(ctx.timeZone),
year: 'numeric',
month: '2-digit',
day: '2-digit',
hour: '2-digit',
minute: '2-digit',
hourCycle: 'h23',
timeZoneName: 'longOffset',
}).formatToParts(sentAt);
const part = (type) => parts.find((candidate) => candidate.type === type)?.value ?? '';
// `GMT-03:00` → `-03:00`; a zero offset (`GMT`, `GMT+00:00`) → `Z`.
const shift = part('timeZoneName').replace(/^GMT/, '');
const offset = shift === '' || /^[+-]00:00$/.test(shift) ? 'Z' : shift;
return `[sent ${part('year')}-${part('month')}-${part('day')}T${part('hour')}:${part('minute')}${offset}]`;
}
/**
* A message's send time: the one recorded with it (`sentAt` — a channel's provider timestamp, a
* send that named one, the moment a queued send was queued), else when it was stored.
*/
export function messageSentAt(message) {
for (const value of [message.sentAt, message.createdAt]) {
if (value === undefined)
continue;
const at = new Date(value);
if (!Number.isNaN(at.getTime()))
return at;
}
return null;
}
/** Builds the per-turn labeller: resolves the zone once, then labels each user message. */
export function messageTimestampLabeller(config, turn) {
const zone = validTimeZone(typeof config.timeZone === 'function' ? config.timeZone(turn.actor) : config.timeZone);
const format = config.format ?? defaultMessageTimestampFormat;
return (stored, model) => {
// A person's message, not the tool-results half of an exchange.
if (model.role !== 'user' || (model.toolResults?.length ?? 0) > 0)
return model;
const sentAt = messageSentAt(stored);
if (sentAt === null)
return model;
const label = format(sentAt, {
actor: turn.actor,
threadId: turn.threadId,
timeZone: zone,
message: { role: model.role, content: model.content },
});
if (label === '')
return model;
return { ...model, content: model.content === '' ? label : `${label} ${model.content}` };
};
}
/**
* A send time as the store keeps it (ISO 8601), from what a caller handed over: a `Date`, an ISO
* string or epoch milliseconds. `undefined` for anything unreadable; a time in the future (a client
* clock ahead of ours) is taken as `now`.
*/
export function normalizeSentAt(value, now = Date.now()) {
if (value === undefined || value === null || value === '')
return undefined;
const at = value instanceof Date ? value.getTime() : new Date(value).getTime();
if (!Number.isFinite(at) || at <= 0)
return undefined;
return new Date(Math.min(at, now)).toISOString();
}
//# sourceMappingURL=message-timestamps.js.map