From dfe3005fe6acaa48174dce09d725101d1c455208 Mon Sep 17 00:00:00 2001 From: Sergio Serrano <35855882+sdserranog@users.noreply.github.com> Date: Mon, 12 May 2025 14:02:11 -0300 Subject: [PATCH] Update example with the last version of arcade-js (#396) Update our example with the latest version of `arcade-js`. This means we can delete all the utility functions we created here, since we now have first-class `Zod` support and they are no longer needed. > [!WARNING] > Don't merge until this [PR](https://github.com/ArcadeAI/arcade-js/pull/127) is merged. --- examples/ai-sdk/README.md | 8 +-- examples/ai-sdk/arcade.js | 73 --------------------------- examples/ai-sdk/generateText.js | 48 ++++++++++++++++++ examples/ai-sdk/index.js | 71 +++++++++++++++++---------- examples/ai-sdk/package.json | 9 ++-- examples/ai-sdk/pnpm-lock.yaml | 87 +++++++++++++++++---------------- 6 files changed, 147 insertions(+), 149 deletions(-) delete mode 100644 examples/ai-sdk/arcade.js create mode 100644 examples/ai-sdk/generateText.js diff --git a/examples/ai-sdk/README.md b/examples/ai-sdk/README.md index d0ab68fe..7cf26c44 100644 --- a/examples/ai-sdk/README.md +++ b/examples/ai-sdk/README.md @@ -1,8 +1,10 @@

- + + + + Fallback image description +

Arcade AI SDK Example

diff --git a/examples/ai-sdk/arcade.js b/examples/ai-sdk/arcade.js deleted file mode 100644 index c1257d82..00000000 --- a/examples/ai-sdk/arcade.js +++ /dev/null @@ -1,73 +0,0 @@ -import { Arcade } from "@arcadeai/arcadejs" -import { PermissionDeniedError } from "@arcadeai/arcadejs" -import { ToolExecutionError } from "ai" -import { jsonSchema } from "ai" - -const arcadeClient = new Arcade({ - baseURL: "http://localhost:9099", -}) - -/** - * Retrieves and formats tools from Arcade.dev to the format required by the AI SDK. - * @param {Object} options - The options object - * @param {string} [options.toolkit] - Optional toolkit name to filter tools (e.g. "google", "slack") - * @param {string} options.user_id - The user ID from your application (e.g. an email, UUID, etc.) - */ -export const getArcadeTools = async ({ toolkit, user_id }) => { - const tools = await arcadeClient.tools.formatted.list({ - ...(toolkit && { toolkit }), - format: "openai", - }) - - return tools.items.reduce((acc, item) => { - if (!item.function.name) return acc - acc[item.function.name] = { - parameters: jsonSchema(item.function.parameters), - description: item.description, - execute: async (input) => - await arcadeClient.tools.execute({ - tool_name: item.function.name, - input, - user_id, - }), - } - return acc - }, {}) -} - -/** - * Determines if the error indicates that user authorization is needed for the tool - * @param {Error} error - The error caught during tool execution - * @returns {boolean} True if the error indicates authorization is required - */ -export const isAuthorizationRequiredError = (error) => { - return error instanceof ToolExecutionError && error.cause instanceof PermissionDeniedError -} - -/** - * Gets the authorization response for a tool that requires authentication - * @param {string} toolName - The name of the tool that needs authorization - * @param {string} user_id - The user ID from your application - * @returns {Promise<{url: string}>} The authorization response - */ -export const getAuthorizationResponse = async (toolName, user_id) => { - return await arcadeClient.tools.authorize({ - tool_name: toolName, - user_id, - }) -} - -/** - * Handles authorization errors by returning the authorization URL if needed, otherwise rethrows the error - * @param {Error} error - The error to handle - * @param {string} user_id - The user ID from your application - * @returns {Promise} The authorization URL if needed, otherwise the error is rethrown - */ -export const handleAuthorizationError = async (error, user_id) => { - if (isAuthorizationRequiredError(error)) { - const response = await getAuthorizationResponse(error.toolName, user_id) - return response.url - } - - throw error -} diff --git a/examples/ai-sdk/generateText.js b/examples/ai-sdk/generateText.js new file mode 100644 index 00000000..f4e5291a --- /dev/null +++ b/examples/ai-sdk/generateText.js @@ -0,0 +1,48 @@ +import { openai } from "@ai-sdk/openai" +import { Arcade } from "@arcadeai/arcadejs" +import { executeOrAuthorizeZodTool, toZodToolSet } from "@arcadeai/arcadejs/lib" +import { generateText } from "ai" + +const arcade = new Arcade() + +/** + * Get the Google toolkit. + */ +const googleToolkit = await arcade.tools.list({ + limit: 25, + toolkit: "google", +}) + +/** + * The Vercel AI SDK requires tools to be defined using Zod, a TypeScript-first schema validation library + * that has become the standard for runtime type checking. Zod is particularly valuable because it: + * - Provides runtime type safety and validation + * - Offers excellent TypeScript integration with automatic type inference + * - Has a simple, declarative API for defining schemas + * - Is widely adopted in the TypeScript ecosystem + * + * Arcade provides `toZodToolSet` to convert our tools into Zod format, making them compatible + * with the AI SDK. + * + * The `executeOrAuthorizeZodTool` helper function simplifies authorization. + * It checks if the tool requires authorization: if so, it returns an authorization URL, + * otherwise, it runs the tool directly without extra boilerplate. + * + * Learn more: https://docs.arcade.dev/home/use-tools/get-tool-definitions#get-zod-tool-definitions + */ +const googleTools = toZodToolSet({ + tools: googleToolkit.items, + client: arcade, + userId: "", // Your app's internal ID for the user (an email, UUID, etc). It's used internally to identify your user in Arcade + executeFactory: executeOrAuthorizeZodTool, // Checks if tool is authorized and executes it, or returns authorization URL if needed +}) + +const result = await generateText({ + model: openai("gpt-4o-mini"), + prompt: "Read my last email and summarize it in a few sentences", + tools: googleTools, + maxSteps: 5, +}) + +// Log the result +console.log(result.text) diff --git a/examples/ai-sdk/index.js b/examples/ai-sdk/index.js index 64b314f6..7ed6a5d6 100644 --- a/examples/ai-sdk/index.js +++ b/examples/ai-sdk/index.js @@ -1,31 +1,50 @@ -import { openai } from "@ai-sdk/openai"; -import { generateText } from "ai"; -import { getArcadeTools, handleAuthorizationError } from "./arcade.js"; +import { openai } from "@ai-sdk/openai" +import { Arcade } from "@arcadeai/arcadejs" +import { executeOrAuthorizeZodTool, toZodToolSet } from "@arcadeai/arcadejs/lib" +import { streamText } from "ai" -// Your app's internal ID for the user (an email, UUID, etc). It's used internally to identify your user in Arcade -const USER_ID = "USER_ID"; +const arcade = new Arcade() -const model = openai("gpt-4o-mini"); -const tools = await getArcadeTools({ toolkit: "google", user_id: USER_ID }); +/** + * Get the Google toolkit. + */ +const googleToolkit = await arcade.tools.list({ + limit: 25, + toolkit: "google", +}) -export const answerMyQuestion = async (prompt) => { - try { - const result = await generateText({ - model, - prompt, - tools, - maxSteps: 5, - }); +/** + * The Vercel AI SDK requires tools to be defined using Zod, a TypeScript-first schema validation library + * that has become the standard for runtime type checking. Zod is particularly valuable because it: + * - Provides runtime type safety and validation + * - Offers excellent TypeScript integration with automatic type inference + * - Has a simple, declarative API for defining schemas + * - Is widely adopted in the TypeScript ecosystem + * + * Arcade provides `toZodToolSet` to convert our tools into Zod format, making them compatible + * with the AI SDK. + * + * The `executeOrAuthorizeZodTool` helper function simplifies authorization. + * It checks if the tool requires authorization: if so, it returns an authorization URL, + * otherwise, it runs the tool directly without extra boilerplate. + * + * Learn more: https://docs.arcade.dev/home/use-tools/get-tool-definitions#get-zod-tool-definitions + */ +const googleTools = toZodToolSet({ + tools: googleToolkit.items, + client: arcade, + userId: "", // Your app's internal ID for the user (an email, UUID, etc). It's used internally to identify your user in Arcade + executeFactory: executeOrAuthorizeZodTool, +}) - return result.text; - } catch (error) { - const url = await handleAuthorizationError(error, USER_ID); - return `Authorization Required: Please visit this link to connect your Google account: ${url}`; - } -}; +const { textStream } = streamText({ + model: openai("gpt-4o-mini"), + prompt: "Read my last email and summarize it in a few sentences", + tools: googleTools, + maxSteps: 5, +}) -const answer = await answerMyQuestion( - "Read my last email and summarize it in a few sentences" -); - -console.log(answer); +// Stream the response to the console +for await (const chunk of textStream) { + process.stdout.write(chunk) +} diff --git a/examples/ai-sdk/package.json b/examples/ai-sdk/package.json index 36828938..c5da36c2 100644 --- a/examples/ai-sdk/package.json +++ b/examples/ai-sdk/package.json @@ -5,15 +5,16 @@ "main": "index.js", "type": "module", "scripts": { - "dev": "node --env-file=.env index.js" + "dev": "node --env-file=.env index.js", + "generateText": "node --env-file=.env generateText.js" }, "keywords": [], "author": "", "license": "ISC", "packageManager": "pnpm@10.6.5", "dependencies": { - "@ai-sdk/openai": "^1.3.6", - "@arcadeai/arcadejs": "^1.2.1", - "ai": "^4.2.11" + "@ai-sdk/openai": "^1.3.22", + "@arcadeai/arcadejs": "latest", + "ai": "^4.3.15" } } diff --git a/examples/ai-sdk/pnpm-lock.yaml b/examples/ai-sdk/pnpm-lock.yaml index cb394c4f..eac69a4e 100644 --- a/examples/ai-sdk/pnpm-lock.yaml +++ b/examples/ai-sdk/pnpm-lock.yaml @@ -9,35 +9,35 @@ importers: .: dependencies: '@ai-sdk/openai': - specifier: ^1.3.6 - version: 1.3.6(zod@3.24.2) + specifier: ^1.3.22 + version: 1.3.22(zod@3.24.2) '@arcadeai/arcadejs': - specifier: ^1.2.1 - version: 1.2.1 + specifier: latest + version: 1.4.2 ai: - specifier: ^4.2.11 - version: 4.2.11(react@19.1.0)(zod@3.24.2) + specifier: ^4.3.15 + version: 4.3.15(react@19.1.0)(zod@3.24.2) packages: - '@ai-sdk/openai@1.3.6': - resolution: {integrity: sha512-Lyp6W6dg+ERMJru3DI8/pWAjXLB0GbMMlXh4jxA3mVny8CJHlCAjlEJRuAdLg1/CFz4J1UDN2/4qBnIWtLFIqw==} + '@ai-sdk/openai@1.3.22': + resolution: {integrity: sha512-QwA+2EkG0QyjVR+7h6FE7iOu2ivNqAVMm9UJZkVxxTk5OIq5fFJDTEI/zICEMuHImTTXR2JjsL6EirJ28Jc4cw==} engines: {node: '>=18'} peerDependencies: zod: ^3.0.0 - '@ai-sdk/provider-utils@2.2.3': - resolution: {integrity: sha512-o3fWTzkxzI5Af7U7y794MZkYNEsxbjLam2nxyoUZSScqkacb7vZ3EYHLh21+xCcSSzEC161C7pZAGHtC0hTUMw==} + '@ai-sdk/provider-utils@2.2.8': + resolution: {integrity: sha512-fqhG+4sCVv8x7nFzYnFo19ryhAa3w096Kmc3hWxMQfW/TubPOmt3A6tYZhl4mUfQWWQMsuSkLrtjlWuXBVSGQA==} engines: {node: '>=18'} peerDependencies: zod: ^3.23.8 - '@ai-sdk/provider@1.1.0': - resolution: {integrity: sha512-0M+qjp+clUD0R1E5eWQFhxEvWLNaOtGQRUaBn8CUABnSKredagq92hUS9VjOzGsTm37xLfpaxl97AVtbeOsHew==} + '@ai-sdk/provider@1.1.3': + resolution: {integrity: sha512-qZMxYJ0qqX/RfnuIaab+zp8UAeJn/ygXXAffR5I4N0n1IrvA6qBsjc8hXLmBiMV2zoXlifkacF7sEFnYnjBcqg==} engines: {node: '>=18'} - '@ai-sdk/react@1.2.5': - resolution: {integrity: sha512-0jOop3S2WkDOdO4X5I+5fTGqZlNX8/h1T1eYokpkR9xh8Vmrxqw8SsovqGvrddTsZykH8uXRsvI+G4FTyy894A==} + '@ai-sdk/react@1.2.12': + resolution: {integrity: sha512-jK1IZZ22evPZoQW3vlkZ7wvjYGYF+tRBKXtrcolduIkQ/m/sOAVcVeVDUDvh1T91xCnWCdUGCPZg2avZ90mv3g==} engines: {node: '>=18'} peerDependencies: react: ^18 || ^19 || ^19.0.0-rc @@ -46,14 +46,14 @@ packages: zod: optional: true - '@ai-sdk/ui-utils@1.2.4': - resolution: {integrity: sha512-wLTxEZrKZRyBmlVZv8nGXgLBg5tASlqXwbuhoDu0MhZa467ZFREEnosH/OC/novyEHTQXko2zC606xoVbMrUcA==} + '@ai-sdk/ui-utils@1.2.11': + resolution: {integrity: sha512-3zcwCc8ezzFlwp3ZD15wAPjf2Au4s3vAbKsXQVyhxODHcmu0iyPO2Eua6D/vicq/AUm/BAo60r97O6HU+EI0+w==} engines: {node: '>=18'} peerDependencies: zod: ^3.23.8 - '@arcadeai/arcadejs@1.2.1': - resolution: {integrity: sha512-7ir38ylle6zmiM5nX1ENoIiDOp8stYEqEPbE/YDMVF7BvooD3N8ltMN9ZVmMj8DA8x0iubR/M3ewzA1nam7XMg==} + '@arcadeai/arcadejs@1.4.2': + resolution: {integrity: sha512-SpXPtqqS2djBDD4pujFc1xr/BgOhgZYmMDyjuQnq8B6e92j4mMHvrCHj8DQkqa1SNJaPxPzETwZ6kSfhHK0Ftw==} '@opentelemetry/api@1.9.0': resolution: {integrity: sha512-3giAOQvZiH5F9bMlMiv8+GSPMeqg0dbaeo58/0SlA9sxSqZhnUtxzX9/2FzyhS9sWQf5S0GJE0AKBrFqjpeYcg==} @@ -65,8 +65,8 @@ packages: '@types/node-fetch@2.6.12': resolution: {integrity: sha512-8nneRWKCg3rMtF69nLQJnOYUcbafYeFSjqkw3jCRLsqkWFlHaoQrr5mXmofFGOx3DKn7UfmBMyov8ySvLRVldA==} - '@types/node@18.19.86': - resolution: {integrity: sha512-fifKayi175wLyKyc5qUfyENhQ1dCNI1UNjp653d8kuYcPQN5JhX3dGuP/XmvPTg/xRBn1VTLpbmi+H/Mr7tLfQ==} + '@types/node@18.19.100': + resolution: {integrity: sha512-ojmMP8SZBKprc3qGrGk8Ujpo80AXkrP7G2tOT4VWr5jlr5DHjsJF+emXJz+Wm0glmy4Js62oKMdZZ6B9Y+tEcA==} abort-controller@3.0.0: resolution: {integrity: sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==} @@ -76,8 +76,8 @@ packages: resolution: {integrity: sha512-kja8j7PjmncONqaTsB8fQ+wE2mSU2DJ9D4XKoJ5PFWIdRMa6SLSN1ff4mOr4jCbfRSsxR4keIiySJU0N9T5hIQ==} engines: {node: '>= 8.0.0'} - ai@4.2.11: - resolution: {integrity: sha512-XvYFz+I2MNpkNeso/cEvm2q22cuU7B3EdUxGyhpa94gHbb3HEk73d42+UPJqweSBmoXA52mZ9qvb6jt3P4TITQ==} + ai@4.3.15: + resolution: {integrity: sha512-TYKRzbWg6mx/pmTadlAEIhuQtzfHUV0BbLY72+zkovXwq/9xhcH24IlQmkyBpElK6/4ArS0dHdOOtR1jOPVwtg==} engines: {node: '>=18'} peerDependencies: react: ^18 || ^19 || ^19.0.0-rc @@ -265,49 +265,50 @@ packages: snapshots: - '@ai-sdk/openai@1.3.6(zod@3.24.2)': + '@ai-sdk/openai@1.3.22(zod@3.24.2)': dependencies: - '@ai-sdk/provider': 1.1.0 - '@ai-sdk/provider-utils': 2.2.3(zod@3.24.2) + '@ai-sdk/provider': 1.1.3 + '@ai-sdk/provider-utils': 2.2.8(zod@3.24.2) zod: 3.24.2 - '@ai-sdk/provider-utils@2.2.3(zod@3.24.2)': + '@ai-sdk/provider-utils@2.2.8(zod@3.24.2)': dependencies: - '@ai-sdk/provider': 1.1.0 + '@ai-sdk/provider': 1.1.3 nanoid: 3.3.11 secure-json-parse: 2.7.0 zod: 3.24.2 - '@ai-sdk/provider@1.1.0': + '@ai-sdk/provider@1.1.3': dependencies: json-schema: 0.4.0 - '@ai-sdk/react@1.2.5(react@19.1.0)(zod@3.24.2)': + '@ai-sdk/react@1.2.12(react@19.1.0)(zod@3.24.2)': dependencies: - '@ai-sdk/provider-utils': 2.2.3(zod@3.24.2) - '@ai-sdk/ui-utils': 1.2.4(zod@3.24.2) + '@ai-sdk/provider-utils': 2.2.8(zod@3.24.2) + '@ai-sdk/ui-utils': 1.2.11(zod@3.24.2) react: 19.1.0 swr: 2.3.3(react@19.1.0) throttleit: 2.1.0 optionalDependencies: zod: 3.24.2 - '@ai-sdk/ui-utils@1.2.4(zod@3.24.2)': + '@ai-sdk/ui-utils@1.2.11(zod@3.24.2)': dependencies: - '@ai-sdk/provider': 1.1.0 - '@ai-sdk/provider-utils': 2.2.3(zod@3.24.2) + '@ai-sdk/provider': 1.1.3 + '@ai-sdk/provider-utils': 2.2.8(zod@3.24.2) zod: 3.24.2 zod-to-json-schema: 3.24.5(zod@3.24.2) - '@arcadeai/arcadejs@1.2.1': + '@arcadeai/arcadejs@1.4.2': dependencies: - '@types/node': 18.19.86 + '@types/node': 18.19.100 '@types/node-fetch': 2.6.12 abort-controller: 3.0.0 agentkeepalive: 4.6.0 form-data-encoder: 1.7.2 formdata-node: 4.4.1 node-fetch: 2.7.0 + zod: 3.24.2 transitivePeerDependencies: - encoding @@ -317,10 +318,10 @@ snapshots: '@types/node-fetch@2.6.12': dependencies: - '@types/node': 18.19.86 + '@types/node': 18.19.100 form-data: 4.0.2 - '@types/node@18.19.86': + '@types/node@18.19.100': dependencies: undici-types: 5.26.5 @@ -332,12 +333,12 @@ snapshots: dependencies: humanize-ms: 1.2.1 - ai@4.2.11(react@19.1.0)(zod@3.24.2): + ai@4.3.15(react@19.1.0)(zod@3.24.2): dependencies: - '@ai-sdk/provider': 1.1.0 - '@ai-sdk/provider-utils': 2.2.3(zod@3.24.2) - '@ai-sdk/react': 1.2.5(react@19.1.0)(zod@3.24.2) - '@ai-sdk/ui-utils': 1.2.4(zod@3.24.2) + '@ai-sdk/provider': 1.1.3 + '@ai-sdk/provider-utils': 2.2.8(zod@3.24.2) + '@ai-sdk/react': 1.2.12(react@19.1.0)(zod@3.24.2) + '@ai-sdk/ui-utils': 1.2.11(zod@3.24.2) '@opentelemetry/api': 1.9.0 jsondiffpatch: 0.6.0 zod: 3.24.2