UNPKG

ai

Version:

AI SDK by Vercel - build apps like ChatGPT, Claude, Gemini, and more with a single interface for any model using the Vercel AI Gateway or go direct to OpenAI, Anthropic, Google, or any other model provider.

124 lines (113 loc) 4.03 kB
import { asSchema, isExecutableTool, safeValidateTypes, type Context, type InferToolSetContext, type ModelMessage, type ToolSet, } from '@ai-sdk/provider-utils'; import { InvalidToolApprovalSignatureError } from '../error/invalid-tool-approval-signature-error'; import { InvalidToolInputError } from '../error/invalid-tool-input-error'; import { getOwn } from '../util/get-own'; import type { CollectedToolApprovals } from './collect-tool-approvals'; import { resolveToolApproval } from './resolve-tool-approval'; import { verifyToolApprovalSignature } from './tool-approval-signature'; import type { ToolApprovalConfiguration } from './tool-approval-configuration'; /** * Re-validates approved tool approvals reconstructed from client-supplied * message history before they are executed. Checks HMAC signature (when * configured), input schema, and approval policy. */ export async function validateApprovedToolApprovals< TOOLS extends ToolSet, RUNTIME_CONTEXT extends Context | unknown | never, >({ approvedToolApprovals, tools, toolApproval, messages, toolsContext, runtimeContext, toolApprovalSecret, }: { approvedToolApprovals: Array<CollectedToolApprovals<TOOLS>>; tools: TOOLS | undefined; toolApproval: ToolApprovalConfiguration<TOOLS, RUNTIME_CONTEXT> | undefined; messages: ModelMessage[]; toolsContext: InferToolSetContext<TOOLS>; runtimeContext: RUNTIME_CONTEXT; toolApprovalSecret?: string | Uint8Array; }): Promise<{ approvedToolApprovals: Array<CollectedToolApprovals<TOOLS>>; deniedToolApprovals: Array<CollectedToolApprovals<TOOLS>>; }> { const approved: Array<CollectedToolApprovals<TOOLS>> = []; const denied: Array<CollectedToolApprovals<TOOLS>> = []; for (const approval of approvedToolApprovals) { const { toolCall, approvalRequest } = approval; // Look up the tool by own property only: `toolName` comes from // client-supplied history, so a name matching an inherited object property // (e.g. `constructor`, `toString`) must resolve to "no such tool" rather // than a prototype value that would silently skip input validation below. const tool = getOwn(tools, toolCall.toolName); if (toolApprovalSecret != null) { if (approvalRequest.signature == null) { throw new InvalidToolApprovalSignatureError({ approvalId: approvalRequest.approvalId, toolCallId: toolCall.toolCallId, reason: 'missing signature', }); } const valid = await verifyToolApprovalSignature({ secret: toolApprovalSecret, signature: approvalRequest.signature, approvalId: approvalRequest.approvalId, toolCallId: toolCall.toolCallId, toolName: toolCall.toolName, input: toolCall.input, }); if (!valid) { throw new InvalidToolApprovalSignatureError({ approvalId: approvalRequest.approvalId, toolCallId: toolCall.toolCallId, reason: 'invalid signature', }); } } if (isExecutableTool(tool) && tool.inputSchema != null) { const validation = await safeValidateTypes({ value: toolCall.input, schema: asSchema(tool.inputSchema), }); if (!validation.success) { throw new InvalidToolInputError({ toolName: toolCall.toolName, toolInput: JSON.stringify(toolCall.input), cause: validation.error, }); } } const approvalStatus = await resolveToolApproval({ tools, toolApproval, toolCall, messages, toolsContext, runtimeContext, }); if (approvalStatus.type === 'denied') { denied.push({ ...approval, approvalResponse: { ...approval.approvalResponse, approved: false, reason: approvalStatus.reason ?? approval.approvalResponse.reason, }, }); } else { approved.push(approval); } } return { approvedToolApprovals: approved, deniedToolApprovals: denied }; }