diff --git a/auth4genai/snippets/get-started/cloudflare-agents-js/call-your-api.mdx b/auth4genai/snippets/get-started/cloudflare-agents-js/call-your-api.mdx index 988d26329..4e57bea04 100644 --- a/auth4genai/snippets/get-started/cloudflare-agents-js/call-your-api.mdx +++ b/auth4genai/snippets/get-started/cloudflare-agents-js/call-your-api.mdx @@ -42,6 +42,27 @@ npm install hono \ In the root directory of your project, copy the `.dev.vars.example` into `.dev.vars` file and configure the Auth0 and OpenAI variables. +```bash .dev.vars wrap lines +# ... +# You can use any provider of your choice supported by Vercel AI +OPENAI_API_KEY="OPENAI API KEY" + +SESSION_STORE=cloudflare-kv +SESSION_STORE_NAMESPACE=Session + + +#auth0 +AUTH0_DOMAIN="YOUR-ACCOUNT.us.auth0.com" +AUTH0_CLIENT_ID="YOUR CLIENT ID" +AUTH0_CLIENT_SECRET="YOUR CLIENT SECRET" +AUTH0_SESSION_ENCRYPTION_KEY="RANDOM 32 CHARS" +AUTH0_AUDIENCE="YOUR AUDIENCE" + +BASE_URL="http://localhost:3000" +``` + +If you use another provider for your LLM, adjust the variable name in `.dev.vars` accordingly. + ### Define a tool to call your API In this step, you'll create a Vercel AI tool to make the first-party API call to the Auth0 API. You will do the same for third-party APIs. @@ -53,41 +74,159 @@ Since the Agent defined in the class Chat in `src/agent/chat.ts` uses the **Auth The tool we are defining here uses the same access token to call Auth0's [`/userinfo`](https://auth0.com/docs/api/authentication/user-profile/get-user-info) endpoint. ```tsx src/agent/tools.ts wrap lines -const getUserInfoTool = tool({ - description: "Get information about the current logged in user.", - parameters: z.object({}), - execute: async () => { - const { agent } = getCurrentAgent(); - const tokenSet = agent?.getCredentials(); - if (!tokenSet) { - return "There is no user logged in."; +/** + * Tool definitions for the AI chat agent + * Tools can either require human confirmation or execute automatically + */ +import { tool } from "ai"; +import { z } from "zod"; + +import { getCurrentAgent } from "agents"; +import { unstable_scheduleSchema } from "agents/schedule"; +import { format, toZonedTime } from "date-fns-tz"; +import { buyStock } from "./auth0-ai-sample-tools/buy-stock"; +import { checkUsersCalendar } from "./auth0-ai-sample-tools/check-user-calendar"; + +/** + * Weather information tool that requires human confirmation + * When invoked, this will present a confirmation dialog to the user + * The actual implementation is in the executions object below + */ +const getWeatherInformation = tool({ + description: "show the weather in a given city to the user", + inputSchema: z.object({ city: z.string() }), +}); + +/** + * Local time tool that executes automatically + * Since it includes an execute function, it will run without user confirmation + * This is suitable for low-risk operations that don't need oversight + */ +const getLocalTime = tool({ + description: "get the local time for a specified location", + inputSchema: z.object({ + timeZone: z.string().describe("IANA time zone name"), + }), + execute: async ({ timeZone: location }) => { + const now = new Date(); + const zonedDate = toZonedTime(now, location); + const output = format(zonedDate, "yyyy-MM-dd HH:mm:ssXXX", { + timeZone: location, + }); + return output; + }, +}); + +const scheduleTask = tool({ + description: "A tool to schedule a task to be executed at a later time", + inputSchema: unstable_scheduleSchema, + execute: async ({ when, description }) => { + const { agent } = getCurrentAgent(); + + function throwError(msg: string): string { + throw new Error(msg); + } + if (when.type === "no-schedule") { + return "Not a valid schedule input"; + } + const input = + when.type === "scheduled" + ? when.date // scheduled + : when.type === "delayed" + ? when.delayInSeconds // delayed + : when.type === "cron" + ? when.cron // cron + : throwError("not a valid schedule input"); + try { + agent!.schedule(input!, "executeTask" as keyof typeof agent, description); + } catch (error) { + console.error("error scheduling task", error); + return `Error scheduling task: ${error}`; } + return `Task scheduled for type "${when.type}" : ${input}`; + }, +}); - const response = await fetch( - `https://${process.env.AUTH0_DOMAIN}/userinfo`, - { - headers: { - Authorization: `Bearer ${tokenSet.access_token}`, - }, - } - ); +/** + * Tool to list all scheduled tasks + * This executes automatically without requiring human confirmation + */ +const getScheduledTasks = tool({ + description: "List all tasks that have been scheduled", + inputSchema: z.object({}), + execute: async () => { + const { agent } = getCurrentAgent(); - if (response.ok) { - return { result: await response.json() }; + try { + const tasks = agent!.getSchedules(); + if (!tasks || tasks.length === 0) { + return "No scheduled tasks found."; + } + return tasks; + } catch (error) { + console.error("Error listing scheduled tasks", error); + return `Error listing scheduled tasks: ${error}`; } + }, +}); - return "I couldn't verify your identity"; +/** + * Tool to cancel a scheduled task by its ID + * This executes automatically without requiring human confirmation + */ +const cancelScheduledTask = tool({ + description: "Cancel a scheduled task using its ID", + inputSchema: z.object({ + taskId: z.string().describe("The ID of the task to cancel"), + }), + execute: async ({ taskId }) => { + const { agent } = getCurrentAgent(); + try { + await agent!.cancelSchedule(taskId); + return `Task ${taskId} has been successfully canceled.`; + } catch (error) { + console.error("Error canceling scheduled task", error); + return `Error canceling task ${taskId}: ${error}`; + } }, }); + +/** + * Export all available tools + * These will be provided to the AI model to describe available capabilities + */ +export const tools = { + getWeatherInformation, + getLocalTime, + scheduleTask, + getScheduledTasks, + cancelScheduledTask, + checkUsersCalendar, + buyStock, +}; + +/** + * Implementation of confirmation-required tools + * This object contains the actual logic for tools that need human approval + * Each function here corresponds to a tool above that doesn't have an execute function + */ +export const executions = { + getWeatherInformation: async ({ city }: { city: string }) => { + console.log(`Getting weather information for ${city}`); + return `The weather in ${city} is sunny`; + }, +}; ``` -Then in the `tools` export of the `src/agent/chat.ts` file, add the `getUserInfoTool` to the tools array: +Then in the `tools` export of the `src/agent/chat.ts` file, add the `tools` to the `allTools` array: ```tsx src/agent/chat.ts wrap lines -export const tools = { - // Your other tools... - getUserInfoTool, -}; +async onChatMessage() { + const allTools = { + ...tools, + ...(this.mcp?.getAITools?.() ?? {}), + }; + ... // The rest of the code ``` ### Test your application diff --git a/auth4genai/snippets/how-tos/github/cloudflare-agents.mdx b/auth4genai/snippets/how-tos/github/cloudflare-agents.mdx index 9ca0f441b..2e7f3136e 100644 --- a/auth4genai/snippets/how-tos/github/cloudflare-agents.mdx +++ b/auth4genai/snippets/how-tos/github/cloudflare-agents.mdx @@ -19,30 +19,35 @@ npm install @auth0/ai-vercel @auth0/ai-cloudflare @auth0/ai Then, you need to initialize Auth0 AI and set up the connection to request access tokens with the required Github scopes. -```typescript ./src/lib/auth0-ai.ts wrap lines +```typescript ./src/agent/auth0-ai.ts wrap lines import { Auth0AI, setGlobalAIContext } from "@auth0/ai-vercel"; import { getCurrentAgent } from "agents"; -import type { Chat } from "./chat"; + +setGlobalAIContext(() => ({ threadID: getAgent().name })); + +const auth0AI = new Auth0AI({ + store: () => { + return (getAgent() as any).auth0AIStore; + }, +}); const getAgent = () => { - const { agent } = getCurrentAgent(); + const { agent } = getCurrentAgent(); if (!agent) { throw new Error("No agent found"); } return agent; }; -setGlobalAIContext(() => ({ threadID: getAgent().name })); - -const auth0AI = new Auth0AI(); +const refreshToken = async () => { + const credentials = (getAgent() as any).getCredentials(); + return credentials?.refresh_token; +}; export const withGitHub = auth0AI.withTokenVault({ + refreshToken, connection: "github", scopes: ["repo"], - refreshToken: async () => { - const credentials = getAgent().getCredentials(); - return credentials?.refresh_token; - }, }); ``` @@ -50,19 +55,19 @@ export const withGitHub = auth0AI.withTokenVault({ Wrap your tool using the Auth0 AI SDK to obtain an access token for the Github API. -```typescript ./src/agent/tools/listRepositories.ts wrap lines highlight={2-4,9,15,19-21,31-33} +```typescript ./src/agent/tools/listRepositories.ts wrap lines highlight={6-7,9,15,19-21,28-30} +import { tool } from "ai"; +import { z } from "zod/v3"; + import { Octokit, RequestError } from "octokit"; import { getAccessTokenFromTokenVault } from "@auth0/ai-vercel"; import { TokenVaultError } from "@auth0/ai/interrupts"; -import { withGitHub } from "@/lib/auth0-ai"; -import { tool } from "ai"; -import { z } from "zod"; - +import { withGitHub } from "@/agent/auth0-ai"; export const listRepositories = withGitHub( tool({ description: "List respositories for the current user on GitHub", - parameters: z.object({}), + inputSchema: z.object({}), execute: async () => { // Get the access token from Auth0 AI const accessToken = getAccessTokenFromTokenVault(); @@ -72,17 +77,14 @@ export const listRepositories = withGitHub( const octokit = new Octokit({ auth: accessToken, }); - const { data } = await octokit.rest.repos.listForAuthenticatedUser(); return data.map((repo) => repo.name); } catch (error) { - console.log("Error", error); - if (error instanceof RequestError) { if (error.status === 401) { throw new TokenVaultError( - `Authorization required to access the Token Vault connection` + `Authorization required to access the Token Vault` ); } } @@ -92,6 +94,7 @@ export const listRepositories = withGitHub( }, }) ); + ``` ### 3. Handle authentication redirects diff --git a/auth4genai/snippets/how-tos/google-calendar/cloudflare-agents.mdx b/auth4genai/snippets/how-tos/google-calendar/cloudflare-agents.mdx index de6e32c97..6e0665d3b 100644 --- a/auth4genai/snippets/how-tos/google-calendar/cloudflare-agents.mdx +++ b/auth4genai/snippets/how-tos/google-calendar/cloudflare-agents.mdx @@ -17,7 +17,7 @@ npm install @auth0/ai-vercel @auth0/ai-cloudflare @auth0/ai Then, you need to initialize Auth0 AI and set up the connection to request access tokens with the required Google Calendar scopes. -```typescript ./src/lib/auth0-ai.ts wrap lines +```typescript ./src/agent/auth0-ai.ts wrap lines import { Auth0AI, setGlobalAIContext } from "@auth0/ai-vercel"; import { getCurrentAgent } from "agents"; import type { Chat } from "./chat"; @@ -35,12 +35,12 @@ setGlobalAIContext(() => ({ threadID: getAgent().name })); const auth0AI = new Auth0AI(); export const withGoogleCalendar = auth0AI.withTokenVault({ - connection: "google-oauth2", - scopes: ["https://www.googleapis.com/auth/calendar.freebusy"], refreshToken: async () => { - const credentials = getAgent().getCredentials(); + const credentials = (getAgent() as any).getCredentials(); return credentials?.refresh_token; }, + connection: "google-oauth2", + scopes: ["https://www.googleapis.com/auth/calendar.freebusy"], }); ``` @@ -48,60 +48,57 @@ export const withGoogleCalendar = auth0AI.withTokenVault({ Wrap your tool using the Auth0 AI SDK to obtain an access token for the Google Calendar API. -```typescript ./src/agent/tools/checkUsersCalendar.ts wrap lines highlight={4-6,10,19,26-28.46-48} -import { addHours, formatISO } from "date-fns"; -import { GaxiosError } from "gaxios"; -import { google } from "googleapis"; +```typescript ./src/agent/auth0-ai-sample-tools/check-user-calendar.ts wrap lines highlight={1-2,6,8,17,37-39} import { getAccessTokenFromTokenVault } from "@auth0/ai-vercel"; import { TokenVaultError } from "@auth0/ai/interrupts"; -import { withGoogleCalendar } from "@/lib/auth0-ai"; import { tool } from "ai"; +import { addHours } from "date-fns"; import { z } from "zod"; +import { withGoogleCalendar } from "../auth0-ai"; export const checkUsersCalendar = withGoogleCalendar( tool({ description: "Check user availability on a given date time on their calendar", - parameters: z.object({ + inputSchema: z.object({ date: z.coerce.date(), }), execute: async ({ date }) => { // Get the access token from Auth0 AI const accessToken = getAccessTokenFromTokenVault(); - - // Google SDK - try { - const calendar = google.calendar("v3"); - const auth = new google.auth.OAuth2(); - - auth.setCredentials({ - access_token: accessToken, - }); - - const response = await calendar.freebusy.query({ - auth, - requestBody: { - timeMin: formatISO(date), - timeMax: addHours(date, 1).toISOString(), - timeZone: "UTC", - items: [{ id: "primary" }], - }, - }); - - return { - available: response.data?.calendars?.primary?.busy?.length === 0, - }; - } catch (error) { - if (error instanceof GaxiosError) { - if (error.status === 401) { - throw new TokenVaultError( - `Authorization required to access the Token Vault connection` - ); - } + const url = "https://www.googleapis.com/calendar/v3/freeBusy"; + const body = JSON.stringify({ + timeMin: date, + timeMax: addHours(date, 1), + timeZone: "UTC", + items: [{ id: "primary" }], + }); + + const response = await fetch(url, { + method: "POST", + headers: { + Authorization: `Bearer ${accessToken}`, + "Content-Type": "application/json", + }, + body, + }); + + if (!response.ok) { + if (response.status === 401) { + throw new TokenVaultError( + "Authorization required to access the Federated Connection" + ); } - - throw error; + throw new Error( + `Invalid response from Google Calendar API: ${ + response.status + } - ${await response.text()}` + ); } + + const busyResp = await response.json(); + + return { available: busyResp.calendars.primary.busy.length === 0 }; }, }) ); @@ -117,78 +114,129 @@ Interrupts are a way for the system to pause execution and prompt the user to ta On the Chat agent class, you need to set up the tool invocation and handle the interruption messaging via the `errorSerializer`. -```typescript ./src/agent/chat.ts wrap lines highlight={1-2,52-54,63-66} -import { setAIContext } from "@auth0/ai-vercel"; -import { errorSerializer, withInterruptions } from "@auth0/ai-vercel/interrupts"; -// Other dependencies -import { AuthAgent, OwnedAgent } from "@auth0/auth0-cloudflare-agents-api"; +```typescript ./src/agent/chat.ts wrap lines highlight={6-10,52,54-57,101} import { openai } from "@ai-sdk/openai"; +import { + AsyncUserConfirmationResumer, + CloudflareKVStore, +} from "@auth0/ai-cloudflare"; +import { + errorSerializer, + invokeTools, + withInterruptions, +} from "@auth0/ai-vercel/interrupts"; +import { AuthAgent, OwnedAgent } from "@auth0/auth0-cloudflare-agents-api"; import { AIChatAgent } from "agents/ai-chat-agent"; +import { + convertToModelMessages, + createUIMessageStream, + createUIMessageStreamResponse, + generateId, + stepCountIs, + streamText, + type UIMessage, +} from "ai"; +import { executions, tools } from "./tools"; +import { processToolCalls } from "./utils"; + +const model = openai("gpt-4o-2024-11-20"); + +class BaseChat extends AIChatAgent {} + +const AuthedChat = AuthAgent(BaseChat); +const OwnedAuthedChat = OwnedAgent(AuthedChat); +const ResumableOwnedAuthedChat = AsyncUserConfirmationResumer(OwnedAuthedChat); + +export class Chat extends ResumableOwnedAuthedChat { + messages: UIMessage[] = []; + + declare mcp?: + | { + unstable_getAITools?: () => Record; + } + | undefined; -const SuperAgent = OwnedAgent(AuthAgent(AIChatAgent)); - -export class Chat extends SuperAgent { - async onChatMessage( - onFinish: StreamTextOnFinishCallback, - options?: { abortSignal?: AbortSignal } - ) { - // Collect all tools, including MCP tools + async onChatMessage() { const allTools = { ...tools, - ...this.mcp.unstable_getAITools(), + ...(this.mcp?.unstable_getAITools?.() ?? {}), }; - const claims = this.getClaims(); - // Create a streaming response that handles both text and tool outputs - const dataStreamResponse = createDataStreamResponse({ - execute: withInterruptions(async (dataStream) => { - // Process any pending tool calls from previous messages - // This handles human-in-the-loop confirmations for tools - const processedMessages = await processToolCalls({ - messages: this.messages, - dataStream, - tools: allTools, - executions, - }); - - // Stream the AI response using GPT-4 - const result = streamText({ - model, - system: `You are a helpful assistant that can do various tasks... - -${unstable_getSchedulePrompt({ date: new Date() })} + + const claims = this.getClaims?.(); + + const stream = createUIMessageStream({ + originalMessages: this.messages, + execute: withInterruptions( + async ({ writer }) => { + await invokeTools({ + messages: convertToModelMessages(this.messages), + tools: allTools, + }); + + const processed = await processToolCalls({ + messages: this.messages, + dataStream: writer, + tools: allTools, + executions, + }); + + const result = streamText({ + model, + stopWhen: stepCountIs(10), + messages: convertToModelMessages(processed), + system: `You are a helpful assistant that can do various tasks... If the user asks to schedule a task, use the schedule tool to schedule the task. -The name of the user is ${claims?.name ?? "unknown"}. -`, - messages: processedMessages, - tools: allTools, - onFinish: async (args) => { - onFinish( - args as Parameters>[0] - ); - }, - onError: (error) => { - if (!Auth0Interrupt.isInterrupt(error)) { - return; - } - console.error("Error while streaming:", error); - }, - maxSteps: 10, - }); - - // Merge the AI response stream with tool execution outputs - result.mergeIntoDataStream(dataStream); - }), - onError: errorSerializer((err) => { - console.log(err); - return "Oops, an error occured!"; - }), +The name of the user is ${claims?.name ?? "unknown"}.`, + tools: allTools, + onStepFinish: (output) => { + if (output.finishReason === "tool-calls") { + const last = output.content[output.content.length - 1]; + if (last?.type === "tool-error") { + const { toolName, toolCallId, error, input } = last; + const serializableError = { + cause: error, + toolCallId, + toolName, + toolArgs: input, + }; + throw serializableError; + } + } + }, + }); + + writer.merge( + result.toUIMessageStream({ + sendReasoning: true, + }) + ); + }, + { messages: this.messages, tools: allTools } + ), + onError: errorSerializer(), }); - return dataStreamResponse; + return createUIMessageStreamResponse({ stream }); + } + + async executeTask(description: string) { + await this.saveMessages([ + ...this.messages, + { + id: generateId(), + role: "user", + parts: [{ type: "text", text: `Running scheduled task: ${description}` }], + }, + ]); + } + + get auth0AIStore() { + return new CloudflareKVStore({ kv: (this as any).env.Session }); } } + ``` #### Client Side diff --git a/auth4genai/snippets/how-tos/slack/cloudflare-agents.mdx b/auth4genai/snippets/how-tos/slack/cloudflare-agents.mdx index 15fa03e22..54f29b34c 100644 --- a/auth4genai/snippets/how-tos/slack/cloudflare-agents.mdx +++ b/auth4genai/snippets/how-tos/slack/cloudflare-agents.mdx @@ -17,7 +17,7 @@ npm install @auth0/ai-vercel @auth0/ai-cloudflare @auth0/ai Then, you need to initialize Auth0 AI and set up the connection to request access tokens with the required Github scopes. -```typescript ./src/lib/auth0-ai.ts wrap lines +```typescript ./src/agent/auth0-ai.ts wrap lines import { Auth0AI, setGlobalAIContext } from "@auth0/ai-vercel"; import { getCurrentAgent } from "agents"; import type { Chat } from "./chat"; @@ -35,12 +35,13 @@ setGlobalAIContext(() => ({ threadID: getAgent().name })); const auth0AI = new Auth0AI(); export const withSlack = auth0AI.withTokenVault({ - connection: "sign-in-with-slack", - scopes: ["channels:read", "groups:read"], refreshToken: async () => { - const credentials = getAgent().getCredentials(); - return credentials?.refresh_token; + const session = await auth0AI.getSession(); + const refreshToken = session?.tokenSet.refreshToken as string; + return refreshToken; }, + connection: "sign-in-with-slack", + scopes: ["channels:read", "groups:read"], }); ``` @@ -48,18 +49,19 @@ export const withSlack = auth0AI.withTokenVault({ Wrap your tool using the Auth0 AI SDK to obtain an access token for the Slack API. -```typescript ./src/agent/tools/listRepositories.ts wrap lines highlight={2-4,8,14,18,30-32} -import { ErrorCode, WebClient } from "@slack/web-api"; +```typescript ./src/agent/tools/listRepositories.ts wrap lines highlight={3-5,9,15,19,31-33} +import { tool } from "ai"; +import { z } from "zod/v3"; + import { getAccessTokenFromTokenVault } from "@auth0/ai-vercel"; import { TokenVaultError } from "@auth0/ai/interrupts"; -import { withSlack } from "@/lib/auth0-ai"; -import { tool } from "ai"; -import { z } from "zod"; +import { withSlack } from "@/agent/auth0-ai"; +import { ErrorCode, WebClient } from "@slack/web-api"; export const listChannels = withSlack( tool({ description: "List channels for the current user on Slack", - parameters: z.object({}), + inputSchema: z.object({}), execute: async () => { // Get the access token from Auth0 AI const accessToken = getAccessTokenFromTokenVault(); @@ -79,7 +81,7 @@ export const listChannels = withSlack( if (error && typeof error === "object" && "code" in error) { if (error.code === ErrorCode.HTTPError) { throw new TokenVaultError( - `Authorization required to access the Token Vault connection` + `Authorization required to access the Token Vault` ); } } @@ -101,76 +103,126 @@ Interrupts are a way for the system to pause execution and prompt the user to ta On the Chat agent class, you need to set up the tool invocation and handle the interruption messaging via the `errorSerializer`. -```typescript ./src/agent/chat.ts wrap lines highlight={1-2,52-54,63-66} -import { setAIContext } from "@auth0/ai-vercel"; -import { errorSerializer, withInterruptions } from "@auth0/ai-vercel/interrupts"; -// Other dependencies -import { AuthAgent, OwnedAgent } from "@auth0/auth0-cloudflare-agents-api"; +```typescript ./src/agent/chat.ts wrap lines highlight={1-10,52,54-57,101} import { openai } from "@ai-sdk/openai"; +import { + AsyncUserConfirmationResumer, + CloudflareKVStore, +} from "@auth0/ai-cloudflare"; +import { + errorSerializer, + invokeTools, + withInterruptions, +} from "@auth0/ai-vercel/interrupts"; +import { AuthAgent, OwnedAgent } from "@auth0/auth0-cloudflare-agents-api"; import { AIChatAgent } from "agents/ai-chat-agent"; +import { + convertToModelMessages, + createUIMessageStream, + createUIMessageStreamResponse, + generateId, + stepCountIs, + streamText, + type UIMessage, +} from "ai"; +import { executions, tools } from "./tools"; +import { processToolCalls } from "./utils"; + +const model = openai("gpt-4o-2024-11-20"); + +class BaseChat extends AIChatAgent {} + +const AuthedChat = AuthAgent(BaseChat); +const OwnedAuthedChat = OwnedAgent(AuthedChat); +const ResumableOwnedAuthedChat = AsyncUserConfirmationResumer(OwnedAuthedChat); + +export class Chat extends ResumableOwnedAuthedChat { + messages: UIMessage[] = []; + + declare mcp?: + | { + unstable_getAITools?: () => Record; + } + | undefined; -const SuperAgent = OwnedAgent(AuthAgent(AIChatAgent)); - -export class Chat extends SuperAgent { - async onChatMessage( - onFinish: StreamTextOnFinishCallback, - options?: { abortSignal?: AbortSignal } - ) { - // Collect all tools, including MCP tools + async onChatMessage() { const allTools = { ...tools, - ...this.mcp.unstable_getAITools(), + ...(this.mcp?.unstable_getAITools?.() ?? {}), }; - const claims = this.getClaims(); - // Create a streaming response that handles both text and tool outputs - const dataStreamResponse = createDataStreamResponse({ - execute: withInterruptions(async (dataStream) => { - // Process any pending tool calls from previous messages - // This handles human-in-the-loop confirmations for tools - const processedMessages = await processToolCalls({ - messages: this.messages, - dataStream, - tools: allTools, - executions, - }); - // Stream the AI response using GPT-4 - const result = streamText({ - model, - system: `You are a helpful assistant that can do various tasks... - -${unstable_getSchedulePrompt({ date: new Date() })} + const claims = this.getClaims?.(); + + const stream = createUIMessageStream({ + originalMessages: this.messages, + execute: withInterruptions( + async ({ writer }) => { + await invokeTools({ + messages: convertToModelMessages(this.messages), + tools: allTools, + }); + + const processed = await processToolCalls({ + messages: this.messages, + dataStream: writer, + tools: allTools, + executions, + }); + + const result = streamText({ + model, + stopWhen: stepCountIs(10), + messages: convertToModelMessages(processed), + system: `You are a helpful assistant that can do various tasks... If the user asks to schedule a task, use the schedule tool to schedule the task. -The name of the user is ${claims?.name ?? "unknown"}. -`, - messages: processedMessages, - tools: allTools, - onFinish: async (args) => { - onFinish( - args as Parameters>[0] - ); - }, - onError: (error) => { - if (!Auth0Interrupt.isInterrupt(error)) { - return; - } - console.error("Error while streaming:", error); - }, - maxSteps: 10, - }); - - // Merge the AI response stream with tool execution outputs - result.mergeIntoDataStream(dataStream); - }), - onError: errorSerializer((err) => { - console.log(err); - return "Oops, an error occured!"; - }), +The name of the user is ${claims?.name ?? "unknown"}.`, + tools: allTools, + onStepFinish: (output) => { + if (output.finishReason === "tool-calls") { + const last = output.content[output.content.length - 1]; + if (last?.type === "tool-error") { + const { toolName, toolCallId, error, input } = last; + const serializableError = { + cause: error, + toolCallId, + toolName, + toolArgs: input, + }; + throw serializableError; + } + } + }, + }); + + writer.merge( + result.toUIMessageStream({ + sendReasoning: true, + }) + ); + }, + { messages: this.messages, tools: allTools } + ), + onError: errorSerializer(), }); - return dataStreamResponse; + return createUIMessageStreamResponse({ stream }); + } + + async executeTask(description: string) { + await this.saveMessages([ + ...this.messages, + { + id: generateId(), + role: "user", + parts: [{ type: "text", text: `Running scheduled task: ${description}` }], + }, + ]); + } + + get auth0AIStore() { + return new CloudflareKVStore({ kv: (this as any).env.Session }); } } ```