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.
This commit is contained in:
Sergio Serrano 2025-05-12 14:02:11 -03:00 committed by GitHub
parent 419a0ac717
commit dfe3005fe6
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
6 changed files with 147 additions and 149 deletions

View file

@ -1,8 +1,10 @@
<h3 align="center">
<a name="readme-top"></a>
<img
src="https://docs.arcade.dev/images/logo/arcade-logo.png"
>
<picture>
<source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/ArcadeAI/.github/refs/heads/main/profile/assets/new_arcade_logo_white.svg" width="300">
<source media="(prefers-color-scheme: light)" srcset="https://raw.githubusercontent.com/ArcadeAI/.github/refs/heads/main/profile/assets/new_arcade_logo_black.svg" width="300">
<img alt="Fallback image description" src="https://raw.githubusercontent.com/ArcadeAI/.github/refs/heads/main/profile/assets/new_arcade_logo_black.svg" width="300" >
</picture>
</h3>
<div align="center">
<h3>Arcade AI SDK Example</h3>

View file

@ -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<string>} 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
}

View file

@ -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_USER_ID>", // 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)

View file

@ -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_USER_ID>", // 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)
}

View file

@ -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"
}
}

View file

@ -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