1import type * as Effect from "effect/Effect";
2import * as Binding from "../../Binding.ts";
3import type { BotAlias } from "./BotAlias.ts";
4
5/**
6 * The intent inside a code hook event's `sessionState` — the intent Lex is
7 * currently eliciting or fulfilling.
8 */
9export interface CodeHookIntent {
10 /** Name of the intent. */
11 name: string;
12 /** Slot values gathered so far (`slotName` → value or `null`). */
13 slots?: Record<string, unknown>;
14 /** `InProgress` | `ReadyForFulfillment` | `Fulfilled` | `Failed` | ... */
15 state?: string;
16 /** How the intent was matched (`Confirmed`/`Denied`/`None`). */
17 confirmationState?: string;
18}
19
20/**
21 * The `sessionState` envelope shared by code hook events and responses —
22 * the conversation's active intent, dialog action, and session attributes.
23 */
24export interface CodeHookSessionState {
25 /** The active intent and its slots. */
26 intent?: CodeHookIntent;
27 /** What Lex should do next (`Close`, `ElicitSlot`, `Delegate`, ...). */
28 dialogAction?: {
29 type: string;
30 slotToElicit?: string;
31 };
32 /** Application-specific session attributes. */
33 sessionAttributes?: Record<string, string>;
34 /** Contexts active for the current turn. */
35 activeContexts?: unknown[];
36 /** The locale and other original request metadata. */
37 [key: string]: unknown;
38}
39
40/**
41 * The event Amazon Lex V2 delivers to a dialog or fulfillment Lambda code
42 * hook (the `messageVersion: "1.0"` envelope).
43 */
44export interface CodeHookEvent {
45 /** Always `"1.0"`. */
46 messageVersion: string;
47 /** Which hook fired: `DialogCodeHook` or `FulfillmentCodeHook`. */
48 invocationSource: "DialogCodeHook" | "FulfillmentCodeHook";
49 /** How the user provided input (`Text`, `Speech`, `DTMF`). */
50 inputMode: string;
51 /** The bot, alias, and locale the conversation runs against. */
52 bot: {
53 id: string;
54 name: string;
55 aliasId: string;
56 aliasName?: string;
57 localeId: string;
58 version: string;
59 };
60 /** Identifier of the conversation session. */
61 sessionId: string;
62 /** The user's raw input for this turn. */
63 inputTranscript?: string;
64 /** The interpretations Lex considered for this turn. */
65 interpretations?: unknown[];
66 /** Request attributes sent by the client. */
67 requestAttributes?: Record<string, string>;
68 /** The conversation state at the time of invocation. */
69 sessionState: CodeHookSessionState;
70 /** Additional event fields (the schema grows over time). */
71 [key: string]: unknown;
72}
73
74/**
75 * The response a code hook returns to Amazon Lex V2 — the (usually mutated)
76 * `sessionState` plus optional messages to relay to the user.
77 */
78export interface CodeHookResponse {
79 /** The new conversation state (dialog action, intent state, slots). */
80 sessionState: CodeHookSessionState;
81 /** Messages to relay to the user. */
82 messages?: {
83 contentType: "PlainText" | "SSML" | "CustomPayload" | "ImageResponseCard";
84 content?: string;
85 }[];
86 /** Request attributes to echo back. */
87 requestAttributes?: Record<string, string>;
88}
89
90export interface CodeHookProps {
91 /**
92 * The alias locale whose code hook this function handles, e.g. `en_US`.
93 * The alias's `botAliasLocaleSettings` for this locale are pointed at the
94 * hosting Lambda.
95 */
96 localeId: string;
97}
98
99/**
100 * A code hook handler receives the typed Lex event and returns the full
101 * response Lex expects (`sessionState` + optional `messages`). Use
102 * {@link fulfillIntent} to build a valid close response.
103 */
104export type CodeHookHandler<Req = never> = (
105 event: CodeHookEvent,
106) => Effect.Effect<CodeHookResponse, never, Req>;
107
108export type CodeHookEventSourceService = <Req = never>(
109 alias: BotAlias,
110 props: CodeHookProps,
111 handler: CodeHookHandler<Req>,
112) => Effect.Effect<void, never, never>;
113
114/**
115 * Event source connecting an Amazon Lex V2 bot alias's Lambda code hook
116 * (dialog and fulfillment) to the hosting Lambda function.
117 *
118 * At deploy time the implementation (`LexV2.LambdaCodeHookEventSource`)
119 * injects the function ARN into the alias's `botAliasLocaleSettings`
120 * (through the alias's binding contract) and creates the
121 * `lambda:InvokeFunction` Permission for `lexv2.amazonaws.com`; at runtime
122 * it dispatches matching code hook events to the handler and returns the
123 * handler's response to Lex.
124 *
125 * Enable the hook per intent with the `dialogCodeHook` /
126 * `fulfillmentCodeHook` props on `LexV2.Intent`.
127 *
128 * Use the {@link onCodeHook} helper rather than the service directly, and
129 * provide `LexV2.LambdaCodeHookEventSource` on the hosting function.
130 * ### Handling Code Hooks
131 * **Example:** Fulfill an Intent from a Lambda Function
132 * ```typescript
133 * export default BotFunction.make(
134 * { main: import.meta.url },
135 * Effect.gen(function* () {
136 * const alias = yield* AWS.LexV2.BotAlias("Live", {
137 * botId: version.botId,
138 * botVersion: version.botVersion,
139 * });
140 *
141 * // deploy: wires the alias's en_US code hook + invoke Permission
142 * // runtime: dispatches code hook events to this handler
143 * yield* AWS.LexV2.onCodeHook(alias, { localeId: "en_US" }, (event) =>
144 * Effect.succeed(
145 * AWS.LexV2.fulfillIntent(event, { message: "Order placed!" }),
146 * ),
147 * );
148 *
149 * return {};
150 * }).pipe(Effect.provide(AWS.LexV2.LambdaCodeHookEventSource)),
151 * );
152 * ```
153 *
154 * @binding
155 */
156export interface CodeHookEventSource extends Binding.Service<
157 CodeHookEventSource,
158 "AWS.LexV2.CodeHookEventSource",
159 CodeHookEventSourceService
160> {}
161
162export const CodeHookEventSource = Binding.Service<CodeHookEventSource>(
163 "AWS.LexV2.CodeHookEventSource",
164);
165
166/**
167 * Handle an Amazon Lex V2 code hook (dialog + fulfillment) for a bot alias
168 * locale with the current Lambda function.
169 *
170 * Provide `LexV2.LambdaCodeHookEventSource` on the hosting function to
171 * satisfy the requirement.
172 *
173 * @param alias The bot alias whose code hook to handle.
174 * @param props The alias locale to attach to.
175 * @param handler Invoked once per code hook event; the returned value is
176 * the response Lex receives.
177 *
178 * @example Close every fulfilled intent with a message
179 * ```typescript
180 * yield* LexV2.onCodeHook(alias, { localeId: "en_US" }, (event) =>
181 * Effect.succeed(LexV2.fulfillIntent(event, { message: "Done!" })),
182 * );
183 * ```
184 */
185export function onCodeHook<Req = never>(
186 alias: BotAlias,
187 props: CodeHookProps,
188 handler: CodeHookHandler<Req>,
189): Effect.Effect<void, never, CodeHookEventSource> {
190 return CodeHookEventSource.use((source) => source(alias, props, handler));
191}
192
193/**
194 * Build a valid code hook response that closes the event's active intent as
195 * `Fulfilled` (or `Failed`), optionally relaying a plain-text message —
196 * the common shape for a fulfillment code hook.
197 *
198 * @example Fulfillment hook that confirms the order
199 * ```typescript
200 * yield* LexV2.onCodeHook(alias, { localeId: "en_US" }, (event) =>
201 * Effect.succeed(
202 * LexV2.fulfillIntent(event, { message: "Your order is placed." }),
203 * ),
204 * );
205 * ```
206 */
207export const fulfillIntent = (
208 event: CodeHookEvent,
209 options?: {
210 /** Plain-text message to relay to the user. */
211 message?: string;
212 /** The intent's final state. @default "Fulfilled" */
213 state?: "Fulfilled" | "Failed";
214 },
215): CodeHookResponse => ({
216 sessionState: {
217 ...event.sessionState,
218 dialogAction: { type: "Close" },
219 intent: {
220 ...event.sessionState.intent!,
221 state: options?.state ?? "Fulfilled",
222 },
223 },
224 ...(options?.message !== undefined
225 ? {
226 messages: [
227 { contentType: "PlainText" as const, content: options.message },
228 ],
229 }
230 : {}),
231});