How to Build an AI Agent That Asks Permission First (Nuxt + AI SDK 7) A developer built a Nuxt app with an AI agent powered by Anthropic's Claude via Amazon Bedrock, and integrated tool approval from AI SDK 7 to require user permission before executing file-deletion tools. The implementation includes a path-boundary check to prevent the agent from accessing files outside a designated fixtures directory. The project is available as a companion repository on GitHub. I did something stupid. I built a superhero-themed Nuxt app, connected it to an Anthropic model through Amazon Bedrock https://aws.amazon.com/bedrock/?trk=1ad04439-1c50-4fdd-a845-d07d2655fe7a&sc channel=el , and gave it a tool that deletes files from my computer. In fact, if I wasn't careful, it could delete all my files The first time I tried it, I didn't use any sort of approval mechanism. And as you expected it just deleted things. Then I looked into how my coding agent works, and I learned about tool approvals. I learned that AI SDK 7 https://vercel.com/blog/ai-sdk-7 has a tool approval at the model-call level. It works by pausing for an approval, showing an approval window, and then deleting it. I then put Kiro CLI https://kiro.dev/docs/cli/?trk=1ad04439-1c50-4fdd-a845-d07d2655fe7a&sc channel=el behind the same interface using Agent Client Protocol ACP . Watch the full video on YouTube https://www.youtube.com/watch?v=RktwtjobGI4 . You need: Create the project and install the versions used in the recorded demo: npx nuxi@latest init nuxt-agent-approval cd nuxt-agent-approval npm install \ nuxt@4.5.2 \ vue@3.5.41 \ ai@7.0.66 \ @ai-sdk/vue@4.0.66 \ @ai-sdk/amazon-bedrock@5.0.57 \ @aws-sdk/credential-providers@3.1111.0 \ @nuxt/ui@4.10.0 \ zod@4.4.3 npm install -D @iconify-json/lucide@1.2.123 Register Nuxt UI and expose the Amazon Bedrock settings through server-side runtime config: // nuxt.config.ts export default defineNuxtConfig { modules: '@nuxt/ui' , css: '~/assets/css/main.css' , runtimeConfig: { awsRegion: process.env.AWS REGION ?? 'us-west-2', bedrockModelId: process.env.NUXT BEDROCK MODEL ID } } Add the two Nuxt UI imports: / app/assets/css/main.css / @import "tailwindcss"; @import "@nuxt/ui"; You can compare your setup with the complete companion project https://github.com/ErikCH/blog-posts/tree/main/apps/ai-sdk-7-nuxt-agents . The video uses two fixture files, old-draft.md and keep-me.md . Create them before adding the tools: mkdir -p fixtures printf ' Old draft\n' fixtures/old-draft.md printf ' Keep me\n' fixtures/keep-me.md The agent can list or remove files in that directory, but it should not accept a path such as ../../package.json . Approval decides whether a tool runs. It does not decide what the tool can reach after it starts. The boundary belongs inside the tool: js // server/utils/file-tools.ts import { lstat, readdir, realpath, rm } from 'node:fs/promises' import { resolve, relative, isAbsolute } from 'node:path' import { tool } from 'ai' import as z from 'zod' function resolveInsideFixtures inputPath: string : string { const root = resolve process.cwd , 'fixtures' const target = resolve root, inputPath const rel = relative root, target if rel === '' || rel.startsWith '..' || isAbsolute rel { throw createError { statusCode: 400, statusMessage: Path escapes the fixtures directory: ${inputPath} } } return target } export const listFiles = tool { description: 'List the files in the project fixtures directory.', inputSchema: z.object {} , execute: async = { const entries = await readdir resolve process.cwd , 'fixtures' , { withFileTypes: true } return { files: entries.filter entry = entry.isFile .map entry = entry.name } } } export const deleteFile = tool { description: 'Permanently delete one file from the fixtures directory.', inputSchema: z.object { path: z.string } , execute: async { path } = { const target = resolveInsideFixtures path const info = await lstat target .catch = null if info?.isFile || info.isSymbolicLink { return { deleted: false, path, reason: 'Not a regular file' } } const root = await realpath resolve process.cwd , 'fixtures' const canonicalTarget = await realpath target const rel = relative root, canonicalTarget if rel === '' || rel.startsWith '..' || isAbsolute rel { throw createError { statusCode: 400, statusMessage: 'File resolves outside fixtures' } } await rm canonicalTarget return { deleted: true, path } } } The second containment check happens after realpath . That catches a path that looked local before resolution but points outside the fixture directory through a symbolic link. Create the provider in server/utils/bedrock.ts : js import { createAmazonBedrock } from '@ai-sdk/amazon-bedrock' import { fromNodeProviderChain } from '@aws-sdk/credential-providers' export function useBedrock { const { awsRegion } = useRuntimeConfig return createAmazonBedrock { region: awsRegion, credentialProvider: fromNodeProviderChain } } fromNodeProviderChain uses the AWS credentials already available to your process, including AWS IAM Identity Center https://aws.amazon.com/iam/identity-center/?trk=1ad04439-1c50-4fdd-a845-d07d2655fe7a&sc channel=el sessions, named profiles, environment variables, and instance roles. You do not need to put a long-lived access key in the Nuxt project. Do not copy a model ID from this post. Available IDs vary by account and AWS Region. List the active Amazon Bedrock inference profiles https://aws.amazon.com/blogs/machine-learning/getting-started-with-cross-region-inference-in-amazon-bedrock/?trk=1ad04439-1c50-4fdd-a845-d07d2655fe7a&sc channel=el for your account: aws bedrock list-inference-profiles \ --region us-west-2 \ --query 'inferenceProfileSummaries ?status== ACTIVE .inferenceProfileId' Set one of the returned profile IDs before starting Nuxt: export AWS REGION=us-west-2 read -r -p "Inference profile ID: " NUXT BEDROCK MODEL ID export NUXT BEDROCK MODEL ID npm run dev Now add an unguarded chat route. Starting without approval makes the failure visible: js // server/api/chat.post.ts import { streamText, stepCountIs, convertToModelMessages, toUIMessageStream, createUIMessageStreamResponse } from 'ai' import type { AmazonBedrockProvider } from '@ai-sdk/amazon-bedrock' type BedrockModelId = Parameters