apps/web/src/lib/aiHandoff.ts

1import type { ChatMessage } from '@local/ai'
2import { HANDOFF_FAILURE_REPLY } from '@local/ai/components/workflows'
3
4/** Recover the displayed selection, excluding explicitly separate alternatives. */
5export function selectedAgendaUrls(history: ChatMessage[]): string[] {
6	const plan = [...history]
7		.reverse()
8		.find(
9			(turn) =>
10				turn.role === 'assistant' && turn.text.startsWith('Your UNLEASH in '),
11		)?.text
12	if (!plan) return []
13	const selected = plan.split(/\*\*Alternatives|Would you like me to email/)[0]
14	return [
15		...new Set(
16			[...selected.matchAll(/\]\(([^)]+agendaSession=[^)]+)\)/g)].map(
17				(match) => match[1],
18			),
19		),
20	]
21}
22
23export type HandoffKind = 'lead' | 'agenda-email'
24export type HandoffOutcome = {
25	kind: HandoffKind
26	ok: boolean
27	ticketEnquiry?: boolean
28	validationReply?: string
29}
30
31/** A generated promise is never evidence that a handoff happened. */
32export function verifiedHandoffReply(
33	text: string,
34	outcome?: HandoffOutcome,
35): string {
36	if (outcome?.validationReply && !outcome.ok) return outcome.validationReply
37	if (outcome)
38		return outcome.ok
39			? outcome.kind === 'agenda-email'
40				? 'Your selected agenda has been submitted for email delivery.'
41				: `Your enquiry has been sent to the team.${outcome.ticketEnquiry ? ' This is an enquiry, not a confirmed booking.' : ''}`
42			: HANDOFF_FAILURE_REPLY
43	if (
44		/\b(?:we(?:['’]ve| have)?|i(?:['’]ve| have)?|your (?:request|enquiry|agenda))[^.!?\n]{0,65}\b(?:sent|submitted|forwarded|passed|sending|will send|will be sent)\b|\b(?:done|team will be in touch|(?:request|enquiry|agenda) is on its way)\b/i.test(
45			text,
46		)
47	)
48		return HANDOFF_FAILURE_REPLY
49	return text
50}
51