diff --git a/public/assets/docs/demo-platform/06-module-06.md b/public/assets/docs/demo-platform/06-module-06.md index 203d6bb..57ee593 100644 --- a/public/assets/docs/demo-platform/06-module-06.md +++ b/public/assets/docs/demo-platform/06-module-06.md @@ -375,14 +375,45 @@ Return an instance of `auth0AI.withAsyncUserConfirmation` that: 3. Add the custom scope (aka 'permission') we created earlier to the existing scopes array. -4. Ensure the userID parameter is a promise that returns the user’s ID. + > [!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 + 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. +
> [!NOTE] @@ -399,23 +430,13 @@ 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.*
> [!TIP] + > **Advanced Error Handling Options:** > - > *Not sure what to do here?* + > *Not sure what to do here?* > > This wrapper returns to a *tool*, which then returns to the *streaming function*. > @@ -435,12 +456,6 @@ Return an instance of `auth0AI.withAsyncUserConfirmation` that: - // ...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); - ``` - --- #### Congrats! *You have completed Task 6.* @@ -472,12 +487,18 @@ 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. 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. 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. @@ -486,26 +507,46 @@ This will be accomplished by requiring Aiya to fetch a *fresh and ephemeral* acc
+ > [!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. Be sure to pass bindingMessage to withAsyncAuthorization: + > [!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. +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 -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. - -
+
- > [!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 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. + ``` -
+ > [!TIP] + > 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! @@ -523,14 +564,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 +665,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 +703,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(). 🙌 - ```diff - - transferFunds - + transferFunds() +2. Find the line with `transferFunds` and update it to call the function: + ```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 ``` +
+ + > [!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* - ```diff - - // transferFunds: transferFunds(), - + transferFunds: transferFunds(), +3. Open `app/(chat)/api/chat/[id]/_handlers/post.ts` +4. Find the commented line in the initial tools setup and **remove** the comment: + ```ts + - // 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 +738,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 + ```ts - 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 +786,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)