From d96d95451570648947ad0e80769e77b4cca8ca07 Mon Sep 17 00:00:00 2001 From: Jan Singer Date: Tue, 25 Nov 2025 10:43:02 +0100 Subject: [PATCH 01/18] docs: streamline module 06 walkthrough for better developer experience --- .../assets/docs/demo-platform/06-module-06.md | 339 +++++++++++++----- 1 file changed, 245 insertions(+), 94 deletions(-) diff --git a/public/assets/docs/demo-platform/06-module-06.md b/public/assets/docs/demo-platform/06-module-06.md index 203d6bb..3cd4c15 100644 --- a/public/assets/docs/demo-platform/06-module-06.md +++ b/public/assets/docs/demo-platform/06-module-06.md @@ -373,15 +373,57 @@ Return an instance of `auth0AI.withAsyncUserConfirmation` that: 2. ~~Guard against a missing Auth0AI client (*defensive coding*)~~ _← Done for you_ -3. Add the custom scope (aka 'permission') we created earlier to the existing scopes array. +3. **Add the Required Imports**: At the top of the file, add: + ```typescript + import type { AuthorizerToolParameter, TokenSet } from '@auth0/ai'; + import { + AccessDeniedInterrupt, + UserDoesNotHavePushNotificationsInterrupt, + } from '@auth0/ai/interrupts'; + ``` -4. Ensure the userID parameter is a promise that returns the user’s ID. +4. **Update the Guard Condition**: Change the return statement in the guard to return the tool instead of nothing: + ```typescript + if (!auth0AI) { + console.warn('Auth0AI client not initialized!'); + return tool; // Instead of just 'return;' + } + ``` - i.e. getUseruser.sub +5. **Add the Custom Scope**: Update the scopes array to include the transfer permission: + ```typescript + const scopes = ['openid', 'profile', 'email', 'create:transfer']; + ``` -5. Insert the audience value we created earlier. +
-6. Enhance onAuthorizationRequest by plugging in our custom handleOnAuthorize helper function. + > [!TIP] + > **Scope Configuration Best Practices:** + > + > - Always include baseline OIDC scopes: `openid`, `profile`, `email` + > - Add custom API scopes like `create:transfer` for specific permissions + > - In production, consider loading scopes from environment variables: + > ```typescript + > const scopes = process.env.AUTH0_API_SCOPES?.split(',') || ['openid', 'profile', 'email']; + > ``` + > - Scopes should match exactly what you configured in the Auth0 API + +6. **Implement the Full Configuration**: Replace the commented-out implementation with the complete `withAsyncUserConfirmation` call: + ```typescript + return auth0AI.withAsyncUserConfirmation({ + scopes, + userID: async () => { + const user = await getUser(); + return user.sub; + }, + audience: 'http://localhost:3000/api/accounts/transfers', + onAuthorizationRequest: handleOnAuthorize(writer), + onUnauthorized: (e) => { + // Handle different interrupt types... + }, + ...options, + })(tool); + ```
@@ -399,47 +441,73 @@ Return an instance of `auth0AI.withAsyncUserConfirmation` that:
-7. Ensure any errors (i.e. from `onUnauthorized`) are *normalized*. - - This callback provides a lot of opportunity to improve the UX even further. This is where you *could* handle cases such as where the user *denies* authorization. - - The handleOnAuthorize is meant to provide you with a *pattern* you can use to differentiate between denial, missing enrollment, or generic errors. - - The SDK provides normalized error codes to make this easier. For example: - - AccessDeniedInterrupt - - UserDoesNotHavePushNotificationsInterrupt - - *Check out the SDK types (`node_modules/@auth0/ai/dist/esm/interrupts/CIBAInterrupts.d.ts`) for more.* +7. **Implement Error Handling**: In the `onUnauthorized` callback, handle different interrupt types: + + ```typescript + onUnauthorized: (e) => { + if (e instanceof AccessDeniedInterrupt) { + return { + status: 'denied', + dataCount: 0, + message: '🚫 **Berechtigung verweigert**\n\nDie angeforderte Aktion wurde von Ihnen abgelehnt...', + error: e, + reason: 'user_denied_authorization', + canRetry: true, + nextSteps: [ + 'Stellen Sie sicher, dass Sie die Aktion wirklich durchführen möchten', + 'Versuchen Sie es erneut mit dem gleichen Befehl', + 'Kontaktieren Sie den Support, falls Sie Probleme haben', + ], + }; + } + + if (e instanceof UserDoesNotHavePushNotificationsInterrupt) { + return { + status: 'error', + dataCount: 0, + message: 'MFA Push-Benachrichtigungen sind nicht eingerichtet. Ich starte das Enrollment für Sie...', + error: e, + requiresMFAEnrollment: true, + forceAction: 'enroll-mfa-push', + autoExecute: true, + }; + } + + // Default error handling + return { + status: 'error', + dataCount: 0, + message: e.message, + error: e, + }; + } + ```
> [!TIP] + > **Advanced Error Handling Options:** > - > *Not sure what to do here?* - > - > This wrapper returns to a *tool*, which then returns to the *streaming function*. - > - > How does the *tool* handle/return errors? 🤔 + > - **Custom Error Messages**: You can customize messages based on the specific error or user context + > - **Retry Logic**: Add `canRetry: true` with custom retry mechanisms + > - **Conditional Actions**: Use different `forceAction` values based on error type + > - **User Guidance**: Provide `nextSteps` arrays with actionable instructions + > - **Streaming Updates**: Use the `writer` to stream real-time error explanations to the UI > - > Refer to **Step 4**: try passing an *async function* that returns an error object. - > - The goal is to ensure that an error is returned *gracefully* if it occurs and does not 'throw' or *halt* the current action entirely. - > - If the function halts processing the Agent will not be able to properly triage and find alternatives solutions. - -
- -8. Now, spread the incoming options so they are passed along to withAsyncUserConfirmation. - - Not sure what a "spread" is? *Ask Aiya*! - - ```diff - - // ...options, /** 👀 ✅ Step 8: The Auth0AI wrapper spreads the same options as our wrapper! TypeScript interface to the rescue? 🧐 */ - + ...options, - -9. Lastly, make sure the tool being wrapped is *actually injected*! - ```diff - - // })(tool) /** ✅ Step 9: Don't forget to inject the `tool` being wrapped! */; - + })(tool); - ``` + > **Error Response Structure:** + > ```typescript + > { + > status: 'error' | 'denied' | 'interrupted', + > dataCount: 0, + > message: 'User-facing error message', + > error: e, // Original error object + > reason?: 'error_code_for_logging', + > canRetry?: boolean, + > forceAction?: 'tool_name_to_execute', + > autoExecute?: boolean, + > nextSteps?: string[] + > } + > ``` --- #### Congrats! @@ -472,40 +540,82 @@ This will be accomplished by requiring Aiya to fetch a *fresh and ephemeral* acc Setup -- From your code editor, open `lib/auth0/ai/transfer-funds.ts`. +- From your code editor, open `lib/ai/tools/transfer-funds.ts`. Steps -1. Wrap transferFunds with withAsyncAuthorization. +1. **Add Required Imports**: At the top of the file, add the necessary imports: + ```typescript + import { withAsyncAuthorization } from '@/lib/auth0/ai/with-async-authorization'; + import { getCIBACredentials } from '@auth0/ai-vercel'; + ``` - You will need to: - - import the function from the `lib/auth0/ai` directory. - - *wrap the tool* -- instead of simply returning it, pass it as the tool parameter of withAsyncAuthorization. - - transferFunds should ultimately *still return* the original tool. - - bindingMessage can be a simple string like `Please approve the transfer.` +2. **Transform the Tool Export**: Currently, `transferFunds` is exported as a simple tool. You need to wrap it with `withAsyncAuthorization`. Replace the entire export from: + ```typescript + export const transferFunds = tool<...>({ + ``` + to: + ```typescript + export const transferFunds = (writer?: UIMessageStreamWriter) => + withAsyncAuthorization({ + tool: tool<...>({ + ```
+ > [!TIP] + > **Higher-Order Function Pattern:** + > + > This creates a "factory function" that: + > - Accepts an optional `writer` parameter for streaming updates + > - Returns a wrapped tool with authorization capabilities + > - Maintains the original tool's TypeScript interface + > + > **Why this pattern?** It allows the same tool to be used with or without streaming capabilities while maintaining type safety. + +3. **Add Authorization Parameters**: At the end of the `withAsyncAuthorization` call, add: + ```typescript + }), + writer, + bindingMessage: 'Please approve the transfer', + }); + ``` + > [!NOTE] > > ***What is bindingMessage?*** > > When using the Auth0 Guardian SDK this message can be displayed to the user in order to provide context about the request. It is *not* used in our demo but still required. -
- -2. Import getCIBACredentials and use it to retrieve an accessToken to be sent in the Authorization header of the API call. - -3. Update the tool description. - - Right now the tool states to always require confirmation. However, we are implementing confirmation via push notification so having Aiya confirm first would be very annoying. - - Read the description and modify the instructions so Aiya *never* asks for confirmation. +4. **Retrieve CIBA Credentials**: In the tool's `execute` function, replace the empty `Authorization` header with: + ```typescript + const token = getCIBACredentials(); + // ... + Authorization: `Bearer ${token?.accessToken}`, + ``` -
+
- > [!TIP] - > When instructing LLMs be explicit but concise. + > [!TIP] + > **Understanding CIBA Credentials:** + > + > - `getCIBACredentials()` returns the fresh access token obtained through the CIBA flow + > - This token contains the `create:transfer` scope we configured earlier + > - The token is ephemeral and specific to this authorization request + > - Always use optional chaining (`?.`) when accessing token properties for safety + +5. **Update the Tool Description**: Change the description to remove the manual confirmation requirement: + Change the text from: + ``` + Always confirm the details of the transfer with the user before continuing. + ``` + to: + ``` + DO NOT require confirmation from the user -- they will confirm via push notification. + ``` -
+ > [!TIP] + > When instructing LLMs be explicit but concise. The push notification will handle user confirmation automatically. --- #### Congrats! @@ -523,14 +633,12 @@ Feel free to give it a try, just know you'll be missing out on a better UX! ***W
-## Task 8: Enhance the UX +## Task 8: Enhance the UX (Higher-Order Function) #### Goal -To enhance the user's experience we need a way to *inject* the streaming writer into `withAsyncAuthorization` so the authorization portion of the flow (where the push notification gets sent) can stream *status messages* to the chat UI. - -*The plain exported tool has no place to accept that writer.* +In Task 7, you already transformed `transferFunds` into a higher-order function that accepts a `writer` parameter. This allows us to inject the streaming writer into `withAsyncAuthorization` so the authorization flow can stream status messages to the chat UI. -Although this is *not* a *requirement* to enable the core feature functionality, it sure does make for a better user experience! +*This pattern enables real-time progress updates during the authorization process.* #### What are we doing? @@ -626,23 +734,12 @@ Without being able to stream message updates, the user is left with a loading in ***How?*** - A “higher-order factory function” (*of course*)! We need to “wrap the wrapper”. - - It's easy. Just create a function that *returns* the wrapped tool. - -
- - > [!TIP] - > - > We will give you a hint... - > - > - > ```diff - > - export const transferFunds = /* ✅ TASK 7 - STEP 1: */ withAsyncAuthorization({ - > + export const transferFunds = /* ✅ TASK 8 */ () => withAsyncAuthorization({... - > ``` + This was already accomplished in Task 7! By transforming the export to: + ```typescript + export const transferFunds = (writer?: UIMessageStreamWriter) => withAsyncAuthorization({... + ``` -
+ You created a higher-order function that accepts a `writer` parameter and returns the authorized tool. --- #### Congrats! @@ -675,20 +772,32 @@ In the previous task we transformed transfer-funds into a higher-orde Steps 1. Open `lib/ai/tool-registry.ts`. -2. Simply change transferFundstransferFunds(). 🙌 +2. Find the line with `transferFunds` and update it to call the function: ```diff - - transferFunds - + transferFunds() + - transferFunds /* ⚠️ TASK 9: Modify to call higher-order function (see `transfer-funds.ts`) */, + + transferFunds: transferFunds(), // call the higher-order function to obtain the tool ``` +
+ + > [!TIP] + > **Tool Registry Patterns:** + > + > - **Simple tools**: `toolName` (direct reference) + > - **Factory functions**: `toolName()` (function call) + > - **Parameterized tools**: `toolName(config)` (with configuration) + > + > The registry calls `transferFunds()` without arguments, using the optional writer parameter pattern. + ***But wait, there's more!*** -3. Open ```app/(chat)/api/chat/[id]/_handlers/post.ts``` -4. Scroll to **line 81** and ***uncomment*** ```transferFunds()```. You *might* see a red squiggly, *this can be ignored* (for now). ```transferFunds()``` does not require an arg to compile and run. The writer arg is *optional* +3. Open `app/(chat)/api/chat/[id]/_handlers/post.ts` +4. Find the commented line in the initial tools setup and **remove** the comment: ```diff - - // transferFunds: transferFunds(), - + transferFunds: transferFunds(), + - // transferFunds: transferFunds(), /* ⚠️ TASK 9 */ + + transferFunds: transferFunds(), /* ⚠️ TASK 9 */ ``` + **Note:** This registers the tool without a writer initially, which is fine since the writer parameter is optional. ***But, where is the datastream writer? I thought we were injecting it?*** @@ -698,14 +807,34 @@ Once the datastream writer is available, in the actual createUIMessageStrea Nifty trick, *right*? 🤓 -5. Speaking of that, let's do it now! Scroll to **line 110** and ***uncomment*** ```transferFunds: transferFunds(dataStream)```, and remove the line above it. +5. Now find the section where tools are re-initialized with the dataStream writer (around line 110). **Uncomment** the line with `dataStream` and **comment out** the line above it: ```diff - transferFunds, - - // transferFunds: transferFunds(dataStream), - + transferFunds: transferFunds(dataStream), + - // transferFunds: transferFunds(dataStream), /* ⚠️ TASK 9 */ + + // transferFunds, + + transferFunds: transferFunds(dataStream), /* ⚠️ TASK 9 */ ``` + **Why both registrations?** + - First registration: Makes the tool available to the AI system initially (without writer) + - Second registration: Re-initializes the tool with the dataStream writer for real-time updates + +
+ + > [!TIP] + > **Dual Registration Pattern:** + > + > This pattern is useful for tools that need streaming capabilities: + > + > 1. **Initial registration**: `toolName()` - Basic tool availability + > 2. **Enhanced registration**: `toolName(writer)` - With streaming capabilities + > + > **Alternative approaches:** + > - Use dependency injection patterns + > - Implement lazy initialization within the tool + > - Create separate streaming and non-streaming variants + > [!NOTE] > > The code in the POST function is on the ***advanced*** side of the Vercel SDK implementation. @@ -726,26 +855,48 @@ Only one more to go...
-## Task 10: Try **it** +## Task 10: Test the Implementation #### Steps -1. Stop the application and restart it again with: +1. **Restart the Application**: If the application is running, restart it to pick up all changes: ```bash npm run dev ``` -2. Navigate to [http://localhost:3000](http://localhost:3000) +2. **Navigate to the Application**: Open [http://localhost:3000](http://localhost:3000) -3. Ask the Aiya to transfer funds from your checking to savings, (*or whichever accounts you would like*). +3. **Start a New Chat**: Click the "+" button to create a new chat session. +4. **Test the Transfer**: Ask Aiya to transfer funds, for example: ``` - Transfer $25 from checking to savings. + Transfer $25 from checking to savings ``` ![Aiya Init Transfer](./assets/Module06/images/init-transfer.png) +
+ + > [!TIP] + > **Troubleshooting Common Issues:** + > + > **If transfers fail:** + > - Check browser console for error messages + > - Verify Auth0 API configuration matches your audience URL + > - Ensure CIBA is enabled in your Auth0 application + > - Confirm push notifications are enabled in Auth0 Dashboard + > + > **If push notifications don't arrive:** + > - Check Guardian app enrollment status + > - Verify device has internet connectivity + > - Look for notifications in Auth0 Dashboard logs + > + > **If authorization hangs:** + > - Check network connectivity + > - Verify Auth0 API audience configuration + > - Ensure correct scopes are configured + 4. If you are ***NOT*** already enrolled in push notifications, you will be prompted to enroll. ![Aiya Poll](./assets/Module06/images/enroll-push.png) From 7803a6a59302bfe77113f62dd5fdcde7b9bd63e2 Mon Sep 17 00:00:00 2001 From: Jan Singer Date: Tue, 9 Dec 2025 11:56:09 +0100 Subject: [PATCH 02/18] Update public/assets/docs/demo-platform/06-module-06.md Co-authored-by: Danny Fuhriman --- .../assets/docs/demo-platform/06-module-06.md | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/public/assets/docs/demo-platform/06-module-06.md b/public/assets/docs/demo-platform/06-module-06.md index 3cd4c15..5e5f2f0 100644 --- a/public/assets/docs/demo-platform/06-module-06.md +++ b/public/assets/docs/demo-platform/06-module-06.md @@ -373,14 +373,28 @@ Return an instance of `auth0AI.withAsyncUserConfirmation` that: 2. ~~Guard against a missing Auth0AI client (*defensive coding*)~~ _← Done for you_ -3. **Add the Required Imports**: At the top of the file, add: +3. Add the custom scope (aka 'permission') we created earlier to the existing scopes array. + + > [!TIP] + > **Scope Configuration Best Practices:** + > + > - Always include baseline OIDC scopes: `openid`, `profile`, `email` + > - Add custom API scopes like `create:transfer` for specific permissions + > - In production, consider loading scopes from environment variables: + > ```typescript + > const scopes = process.env.AUTH0_API_SCOPES?.split(',') || ['openid', 'profile', 'email']; + > ``` + > - Scopes should match exactly what you configured in the Auth0 API + +====== Move to 1. (line 371) and adjust numbering accordingly. ===== +1. Ensure the necessary imports have been added to the file: ```typescript import type { AuthorizerToolParameter, TokenSet } from '@auth0/ai'; import { AccessDeniedInterrupt, + CIBAInterrupt, UserDoesNotHavePushNotificationsInterrupt, } from '@auth0/ai/interrupts'; - ``` 4. **Update the Guard Condition**: Change the return statement in the guard to return the tool instead of nothing: ```typescript From 93dd88f916b054882f8b622fa4d5fe138559e0ce Mon Sep 17 00:00:00 2001 From: Jan Singer Date: Tue, 9 Dec 2025 11:56:22 +0100 Subject: [PATCH 03/18] Update public/assets/docs/demo-platform/06-module-06.md Co-authored-by: Danny Fuhriman --- public/assets/docs/demo-platform/06-module-06.md | 11 +---------- 1 file changed, 1 insertion(+), 10 deletions(-) diff --git a/public/assets/docs/demo-platform/06-module-06.md b/public/assets/docs/demo-platform/06-module-06.md index 5e5f2f0..8b5b0ff 100644 --- a/public/assets/docs/demo-platform/06-module-06.md +++ b/public/assets/docs/demo-platform/06-module-06.md @@ -411,16 +411,7 @@ Return an instance of `auth0AI.withAsyncUserConfirmation` that:
- > [!TIP] - > **Scope Configuration Best Practices:** - > - > - Always include baseline OIDC scopes: `openid`, `profile`, `email` - > - Add custom API scopes like `create:transfer` for specific permissions - > - In production, consider loading scopes from environment variables: - > ```typescript - > const scopes = process.env.AUTH0_API_SCOPES?.split(',') || ['openid', 'profile', 'email']; - > ``` - > - Scopes should match exactly what you configured in the Auth0 API +6. Enhance onAuthorizationRequest by plugging in our custom handleOnAuthorize helper function. 6. **Implement the Full Configuration**: Replace the commented-out implementation with the complete `withAsyncUserConfirmation` call: ```typescript From 7e0f2d1ab11a5599a7adb395558598fe49b43c46 Mon Sep 17 00:00:00 2001 From: Jan Singer Date: Tue, 9 Dec 2025 11:56:34 +0100 Subject: [PATCH 04/18] Update public/assets/docs/demo-platform/06-module-06.md Co-authored-by: Danny Fuhriman --- public/assets/docs/demo-platform/06-module-06.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/public/assets/docs/demo-platform/06-module-06.md b/public/assets/docs/demo-platform/06-module-06.md index 8b5b0ff..cd0e2d1 100644 --- a/public/assets/docs/demo-platform/06-module-06.md +++ b/public/assets/docs/demo-platform/06-module-06.md @@ -409,7 +409,7 @@ Return an instance of `auth0AI.withAsyncUserConfirmation` that: const scopes = ['openid', 'profile', 'email', 'create:transfer']; ``` -
+5. Insert the audience value we created earlier. 6. Enhance onAuthorizationRequest by plugging in our custom handleOnAuthorize helper function. From b893bcdd6207e97afdc2e162f1045a689c5feb26 Mon Sep 17 00:00:00 2001 From: Jan Singer Date: Tue, 9 Dec 2025 11:57:11 +0100 Subject: [PATCH 05/18] Update public/assets/docs/demo-platform/06-module-06.md Co-authored-by: Danny Fuhriman --- .../assets/docs/demo-platform/06-module-06.md | 41 ------------------- 1 file changed, 41 deletions(-) diff --git a/public/assets/docs/demo-platform/06-module-06.md b/public/assets/docs/demo-platform/06-module-06.md index cd0e2d1..3567344 100644 --- a/public/assets/docs/demo-platform/06-module-06.md +++ b/public/assets/docs/demo-platform/06-module-06.md @@ -446,47 +446,6 @@ Return an instance of `auth0AI.withAsyncUserConfirmation` that:
-7. **Implement Error Handling**: In the `onUnauthorized` callback, handle different interrupt types: - - ```typescript - onUnauthorized: (e) => { - if (e instanceof AccessDeniedInterrupt) { - return { - status: 'denied', - dataCount: 0, - message: '🚫 **Berechtigung verweigert**\n\nDie angeforderte Aktion wurde von Ihnen abgelehnt...', - error: e, - reason: 'user_denied_authorization', - canRetry: true, - nextSteps: [ - 'Stellen Sie sicher, dass Sie die Aktion wirklich durchführen möchten', - 'Versuchen Sie es erneut mit dem gleichen Befehl', - 'Kontaktieren Sie den Support, falls Sie Probleme haben', - ], - }; - } - - if (e instanceof UserDoesNotHavePushNotificationsInterrupt) { - return { - status: 'error', - dataCount: 0, - message: 'MFA Push-Benachrichtigungen sind nicht eingerichtet. Ich starte das Enrollment für Sie...', - error: e, - requiresMFAEnrollment: true, - forceAction: 'enroll-mfa-push', - autoExecute: true, - }; - } - - // Default error handling - return { - status: 'error', - dataCount: 0, - message: e.message, - error: e, - }; - } - ```
From abbda572ec28cb306c11a6f92b57e8eea5eabaea Mon Sep 17 00:00:00 2001 From: Jan Singer Date: Tue, 9 Dec 2025 11:57:33 +0100 Subject: [PATCH 06/18] Update public/assets/docs/demo-platform/06-module-06.md Co-authored-by: Danny Fuhriman --- public/assets/docs/demo-platform/06-module-06.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/public/assets/docs/demo-platform/06-module-06.md b/public/assets/docs/demo-platform/06-module-06.md index 3567344..b02e075 100644 --- a/public/assets/docs/demo-platform/06-module-06.md +++ b/public/assets/docs/demo-platform/06-module-06.md @@ -579,7 +579,9 @@ This will be accomplished by requiring Aiya to fetch a *fresh and ephemeral* acc ``` > [!TIP] - > When instructing LLMs be explicit but concise. The push notification will handle user confirmation automatically. + > Instructing an LLM is like training someone to perform the task. Sometimes adding an explanation as to *why* the model should do/not do something can be helpful (i.e. *the user will be getting a push notification instead*). + > + > When instructing LLMs be explicit but concise. --- #### Congrats! From 80d2a48302119395280f21247d07bda1eba970e3 Mon Sep 17 00:00:00 2001 From: Jan Singer Date: Tue, 9 Dec 2025 11:57:41 +0100 Subject: [PATCH 07/18] Update public/assets/docs/demo-platform/06-module-06.md Co-authored-by: Danny Fuhriman --- public/assets/docs/demo-platform/06-module-06.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/public/assets/docs/demo-platform/06-module-06.md b/public/assets/docs/demo-platform/06-module-06.md index b02e075..a2287f1 100644 --- a/public/assets/docs/demo-platform/06-module-06.md +++ b/public/assets/docs/demo-platform/06-module-06.md @@ -739,7 +739,7 @@ In the previous task we transformed transfer-funds into a higher-orde 1. Open `lib/ai/tool-registry.ts`. 2. Find the line with `transferFunds` and update it to call the function: - ```diff + ```ts - transferFunds /* ⚠️ TASK 9: Modify to call higher-order function (see `transfer-funds.ts`) */, + transferFunds: transferFunds(), // call the higher-order function to obtain the tool ``` From f348405b173b6ad889f8f5ebb269f5ae04cd7415 Mon Sep 17 00:00:00 2001 From: Jan Singer Date: Tue, 9 Dec 2025 11:57:48 +0100 Subject: [PATCH 08/18] Update public/assets/docs/demo-platform/06-module-06.md Co-authored-by: Danny Fuhriman --- public/assets/docs/demo-platform/06-module-06.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/public/assets/docs/demo-platform/06-module-06.md b/public/assets/docs/demo-platform/06-module-06.md index a2287f1..be7935d 100644 --- a/public/assets/docs/demo-platform/06-module-06.md +++ b/public/assets/docs/demo-platform/06-module-06.md @@ -775,7 +775,7 @@ Nifty trick, *right*? 🤓 5. Now find the section where tools are re-initialized with the dataStream writer (around line 110). **Uncomment** the line with `dataStream` and **comment out** the line above it: - ```diff + ```ts - transferFunds, - // transferFunds: transferFunds(dataStream), /* ⚠️ TASK 9 */ + // transferFunds, From 7cf7960324166c05461151dbf1d4fb200796ccb8 Mon Sep 17 00:00:00 2001 From: Jan Singer Date: Tue, 9 Dec 2025 11:57:54 +0100 Subject: [PATCH 09/18] Update public/assets/docs/demo-platform/06-module-06.md Co-authored-by: Danny Fuhriman --- public/assets/docs/demo-platform/06-module-06.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/public/assets/docs/demo-platform/06-module-06.md b/public/assets/docs/demo-platform/06-module-06.md index be7935d..c6e9e38 100644 --- a/public/assets/docs/demo-platform/06-module-06.md +++ b/public/assets/docs/demo-platform/06-module-06.md @@ -759,7 +759,7 @@ In the previous task we transformed transfer-funds into a higher-orde 3. Open `app/(chat)/api/chat/[id]/_handlers/post.ts` 4. Find the commented line in the initial tools setup and **remove** the comment: - ```diff + ```ts - // transferFunds: transferFunds(), /* ⚠️ TASK 9 */ + transferFunds: transferFunds(), /* ⚠️ TASK 9 */ ``` From bf9df31e650ddd64348385883b55fee9495bb031 Mon Sep 17 00:00:00 2001 From: Jan Singer Date: Tue, 9 Dec 2025 11:58:24 +0100 Subject: [PATCH 10/18] Update public/assets/docs/demo-platform/06-module-06.md Co-authored-by: Danny Fuhriman --- public/assets/docs/demo-platform/06-module-06.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/public/assets/docs/demo-platform/06-module-06.md b/public/assets/docs/demo-platform/06-module-06.md index c6e9e38..c9b8f7a 100644 --- a/public/assets/docs/demo-platform/06-module-06.md +++ b/public/assets/docs/demo-platform/06-module-06.md @@ -452,11 +452,11 @@ Return an instance of `auth0AI.withAsyncUserConfirmation` that: > [!TIP] > **Advanced Error Handling Options:** > - > - **Custom Error Messages**: You can customize messages based on the specific error or user context - > - **Retry Logic**: Add `canRetry: true` with custom retry mechanisms - > - **Conditional Actions**: Use different `forceAction` values based on error type - > - **User Guidance**: Provide `nextSteps` arrays with actionable instructions - > - **Streaming Updates**: Use the `writer` to stream real-time error explanations to the UI + > *Not sure what to do here?* + > + > This wrapper returns to a *tool*, which then returns to the *streaming function*. + > + > How does the *tool* handle/return errors? 🤔 > > **Error Response Structure:** > ```typescript From 2ef175269e4ba2715b7501a1b1958d17bc62162c Mon Sep 17 00:00:00 2001 From: Jan Singer Date: Tue, 9 Dec 2025 11:58:42 +0100 Subject: [PATCH 11/18] Update public/assets/docs/demo-platform/06-module-06.md Co-authored-by: Danny Fuhriman --- .../assets/docs/demo-platform/06-module-06.md | 27 +++++++++---------- 1 file changed, 13 insertions(+), 14 deletions(-) diff --git a/public/assets/docs/demo-platform/06-module-06.md b/public/assets/docs/demo-platform/06-module-06.md index c9b8f7a..2e0fa43 100644 --- a/public/assets/docs/demo-platform/06-module-06.md +++ b/public/assets/docs/demo-platform/06-module-06.md @@ -458,20 +458,19 @@ Return an instance of `auth0AI.withAsyncUserConfirmation` that: > > How does the *tool* handle/return errors? 🤔 > - > **Error Response Structure:** - > ```typescript - > { - > status: 'error' | 'denied' | 'interrupted', - > dataCount: 0, - > message: 'User-facing error message', - > error: e, // Original error object - > reason?: 'error_code_for_logging', - > canRetry?: boolean, - > forceAction?: 'tool_name_to_execute', - > autoExecute?: boolean, - > nextSteps?: string[] - > } - > ``` + > Refer to **Step 4**: try passing an *async function* that returns an error object. + > - The goal is to ensure that an error is returned *gracefully* if it occurs and does not 'throw' or *halt* the current action entirely. + > - If the function halts processing the Agent will not be able to properly triage and find alternatives solutions. + +
+ +8. Now, spread the incoming options so they are passed along to withAsyncUserConfirmation. + + Not sure what a "spread" is? *Ask Aiya*! + + ```diff + - // ...options, /** 👀 ✅ Step 8: The Auth0AI wrapper spreads the same options as our wrapper! TypeScript interface to the rescue? 🧐 */ + + ...options, --- #### Congrats! From f87a9c2fc0ce9ea8fbe0d309db84e6e347ed443a Mon Sep 17 00:00:00 2001 From: Jan Singer Date: Tue, 9 Dec 2025 11:59:13 +0100 Subject: [PATCH 12/18] Update public/assets/docs/demo-platform/06-module-06.md Co-authored-by: Danny Fuhriman --- public/assets/docs/demo-platform/06-module-06.md | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/public/assets/docs/demo-platform/06-module-06.md b/public/assets/docs/demo-platform/06-module-06.md index 2e0fa43..9da0d69 100644 --- a/public/assets/docs/demo-platform/06-module-06.md +++ b/public/assets/docs/demo-platform/06-module-06.md @@ -507,11 +507,12 @@ This will be accomplished by requiring Aiya to fetch a *fresh and ephemeral* acc Steps -1. **Add Required Imports**: At the top of the file, add the necessary imports: - ```typescript - import { withAsyncAuthorization } from '@/lib/auth0/ai/with-async-authorization'; - import { getCIBACredentials } from '@auth0/ai-vercel'; - ``` + 1. Ensure the necessary imports have been added to the file: + ```typescript + import { withAsyncAuthorization } from '@/lib/auth0/ai/with-async-authorization'; + import { getCIBACredentials } from '@auth0/ai-vercel'; + import { tool, type UIMessageStreamWriter } from 'ai'; + ``` 2. **Transform the Tool Export**: Currently, `transferFunds` is exported as a simple tool. You need to wrap it with `withAsyncAuthorization`. Replace the entire export from: ```typescript From 8f81279621e4c6cf34873bc25392811974ecb8fd Mon Sep 17 00:00:00 2001 From: Jan Singer Date: Tue, 9 Dec 2025 11:59:41 +0100 Subject: [PATCH 13/18] Update public/assets/docs/demo-platform/06-module-06.md Co-authored-by: Danny Fuhriman --- public/assets/docs/demo-platform/06-module-06.md | 16 ---------------- 1 file changed, 16 deletions(-) diff --git a/public/assets/docs/demo-platform/06-module-06.md b/public/assets/docs/demo-platform/06-module-06.md index 9da0d69..eb0807a 100644 --- a/public/assets/docs/demo-platform/06-module-06.md +++ b/public/assets/docs/demo-platform/06-module-06.md @@ -413,22 +413,6 @@ Return an instance of `auth0AI.withAsyncUserConfirmation` that: 6. Enhance onAuthorizationRequest by plugging in our custom handleOnAuthorize helper function. -6. **Implement the Full Configuration**: Replace the commented-out implementation with the complete `withAsyncUserConfirmation` call: - ```typescript - return auth0AI.withAsyncUserConfirmation({ - scopes, - userID: async () => { - const user = await getUser(); - return user.sub; - }, - audience: 'http://localhost:3000/api/accounts/transfers', - onAuthorizationRequest: handleOnAuthorize(writer), - onUnauthorized: (e) => { - // Handle different interrupt types... - }, - ...options, - })(tool); - ```
From 83ff7c31288e926c974cb997721fc6c70bf8400e Mon Sep 17 00:00:00 2001 From: Jan Singer Date: Tue, 9 Dec 2025 12:00:08 +0100 Subject: [PATCH 14/18] Update public/assets/docs/demo-platform/06-module-06.md Co-authored-by: Danny Fuhriman --- public/assets/docs/demo-platform/06-module-06.md | 16 ++++++---------- 1 file changed, 6 insertions(+), 10 deletions(-) diff --git a/public/assets/docs/demo-platform/06-module-06.md b/public/assets/docs/demo-platform/06-module-06.md index eb0807a..52415bf 100644 --- a/public/assets/docs/demo-platform/06-module-06.md +++ b/public/assets/docs/demo-platform/06-module-06.md @@ -498,16 +498,12 @@ This will be accomplished by requiring Aiya to fetch a *fresh and ephemeral* acc import { tool, type UIMessageStreamWriter } from 'ai'; ``` -2. **Transform the Tool Export**: Currently, `transferFunds` is exported as a simple tool. You need to wrap it with `withAsyncAuthorization`. Replace the entire export from: - ```typescript - export const transferFunds = tool<...>({ - ``` - to: - ```typescript - export const transferFunds = (writer?: UIMessageStreamWriter) => - withAsyncAuthorization({ - tool: tool<...>({ - ``` +2. Wrap transferFunds with withAsyncAuthorization. + You will need to: + - import the function from the `lib/auth0/ai` directory. + - *wrap the tool* -- instead of simply returning it, pass it as the tool parameter of withAsyncAuthorization. + - transferFunds should ultimately *still return* the original tool. + - bindingMessage can be a simple string like `Please approve the transfer.`
From 4651e9f4484faafb97f21c4cad295acdf68972c1 Mon Sep 17 00:00:00 2001 From: Jan Singer Date: Tue, 9 Dec 2025 12:00:45 +0100 Subject: [PATCH 15/18] Update public/assets/docs/demo-platform/06-module-06.md Co-authored-by: Danny Fuhriman --- public/assets/docs/demo-platform/06-module-06.md | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/public/assets/docs/demo-platform/06-module-06.md b/public/assets/docs/demo-platform/06-module-06.md index 52415bf..1cdfdb3 100644 --- a/public/assets/docs/demo-platform/06-module-06.md +++ b/public/assets/docs/demo-platform/06-module-06.md @@ -517,13 +517,7 @@ This will be accomplished by requiring Aiya to fetch a *fresh and ephemeral* acc > > **Why this pattern?** It allows the same tool to be used with or without streaming capabilities while maintaining type safety. -3. **Add Authorization Parameters**: At the end of the `withAsyncAuthorization` call, add: - ```typescript - }), - writer, - bindingMessage: 'Please approve the transfer', - }); - ``` +3. Be sure to pass bindingMessage to withAsyncAuthorization: > [!NOTE] > From 8fbd9f6a827bce7b237f729656b005ee95690e88 Mon Sep 17 00:00:00 2001 From: Jan Singer Date: Tue, 9 Dec 2025 12:01:04 +0100 Subject: [PATCH 16/18] Update public/assets/docs/demo-platform/06-module-06.md Co-authored-by: Danny Fuhriman --- public/assets/docs/demo-platform/06-module-06.md | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/public/assets/docs/demo-platform/06-module-06.md b/public/assets/docs/demo-platform/06-module-06.md index 1cdfdb3..a48690f 100644 --- a/public/assets/docs/demo-platform/06-module-06.md +++ b/public/assets/docs/demo-platform/06-module-06.md @@ -525,12 +525,7 @@ This will be accomplished by requiring Aiya to fetch a *fresh and ephemeral* acc > > When using the Auth0 Guardian SDK this message can be displayed to the user in order to provide context about the request. It is *not* used in our demo but still required. -4. **Retrieve CIBA Credentials**: In the tool's `execute` function, replace the empty `Authorization` header with: - ```typescript - const token = getCIBACredentials(); - // ... - Authorization: `Bearer ${token?.accessToken}`, - ``` +4. In **step one** you imported getCIBACredentials. Now use it to retrieve an accessToken. Ensure the token is sent in the tool's fetch call as the Authorization header
From 047d4bf17cf5b0a6e80bf7662d177eb9fb0765fd Mon Sep 17 00:00:00 2001 From: Jan Singer Date: Tue, 9 Dec 2025 12:01:23 +0100 Subject: [PATCH 17/18] Update public/assets/docs/demo-platform/06-module-06.md Co-authored-by: Danny Fuhriman --- public/assets/docs/demo-platform/06-module-06.md | 4 ---- 1 file changed, 4 deletions(-) diff --git a/public/assets/docs/demo-platform/06-module-06.md b/public/assets/docs/demo-platform/06-module-06.md index a48690f..b2cb334 100644 --- a/public/assets/docs/demo-platform/06-module-06.md +++ b/public/assets/docs/demo-platform/06-module-06.md @@ -541,10 +541,6 @@ This will be accomplished by requiring Aiya to fetch a *fresh and ephemeral* acc Change the text from: ``` Always confirm the details of the transfer with the user before continuing. - ``` - to: - ``` - DO NOT require confirmation from the user -- they will confirm via push notification. ``` > [!TIP] From b94267644c4472ebee660afb6a58535ece94a0e7 Mon Sep 17 00:00:00 2001 From: Jan Singer Date: Tue, 9 Dec 2025 12:01:39 +0100 Subject: [PATCH 18/18] Update public/assets/docs/demo-platform/06-module-06.md Co-authored-by: Danny Fuhriman --- public/assets/docs/demo-platform/06-module-06.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/public/assets/docs/demo-platform/06-module-06.md b/public/assets/docs/demo-platform/06-module-06.md index b2cb334..57ee593 100644 --- a/public/assets/docs/demo-platform/06-module-06.md +++ b/public/assets/docs/demo-platform/06-module-06.md @@ -538,7 +538,7 @@ This will be accomplished by requiring Aiya to fetch a *fresh and ephemeral* acc > - Always use optional chaining (`?.`) when accessing token properties for safety 5. **Update the Tool Description**: Change the description to remove the manual confirmation requirement: - Change the text from: + Change the following text to explicitly **not** require confirmation. There is no "right" way to word this -- you decide. ``` Always confirm the details of the transfer with the user before continuing. ```