$arguments @param array|null $context */ public function request(string $toolName, array $arguments, ?array $context): array { $token = bin2hex(random_bytes(16)); $confirmation = new PendingToolConfirmation(); $confirmation->setToken($token); $confirmation->setUserId($this->userId($context)); $confirmation->setBotId($this->botId($context)); $confirmation->setRoomToken($this->roomToken($context)); $confirmation->setTalkMessageId($this->talkMessageId($context)); $confirmation->setToolName($toolName); $confirmation->setArgumentsHash($this->argumentsHash($arguments)); $confirmation->setCreatedAt(time()); $confirmation->setExpiresAt(time() + self::TTL_SECONDS); $this->mapper->insert($confirmation); return ['expires_in_seconds' => self::TTL_SECONDS]; } /** @param array $arguments @param array|null $context */ public function consume(string $toolName, array $arguments, ?array $context): bool { $confirmationMessageId = $this->talkMessageId($context); if ($confirmationMessageId === null || !$this->isAffirmativeConfirmationMessage($context)) { return false; } try { $confirmation = $this->mapper->findActiveForAction( $this->userId($context), $this->botId($context), $this->roomToken($context), $toolName, $this->argumentsHash($arguments), $confirmationMessageId, time() ); return $this->mapper->claim($confirmation, time()); } catch (DoesNotExistException) { return false; } } /** @param array $arguments */ private function argumentsHash(array $arguments): string { unset($arguments['confirmation_id']); ksort($arguments); return hash('sha256', json_encode($arguments, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE | JSON_THROW_ON_ERROR)); } /** @param array|null $context */ private function userId(?array $context): string { $userId = $context['user_id'] ?? null; if (!is_string($userId) || $userId === '') { throw new \RuntimeException('A trusted invoking user is required for confirmation.'); } return $userId; } /** @param array|null $context */ private function botId(?array $context): ?int { return isset($context['bot_id']) && is_int($context['bot_id']) ? $context['bot_id'] : null; } /** @param array|null $context */ private function roomToken(?array $context): ?string { return isset($context['room_token']) && is_string($context['room_token']) && $context['room_token'] !== '' ? $context['room_token'] : null; } /** @param array|null $context */ private function talkMessageId(?array $context): ?int { $messageId = $context['talk_message_id'] ?? null; return is_int($messageId) && $messageId > 0 ? $messageId : null; } /** @param array|null $context */ private function isAffirmativeConfirmationMessage(?array $context): bool { $message = $context['user_message'] ?? null; if (!is_string($message) || trim($message) === '') { return false; } $message = trim($message); if (preg_match('/\\b(?:no|nope|nah|cancel|stop|wait|hold(?:\\s+on)?|do\\s+not|don\'t|not\\s+now|never)\\b/i', $message) === 1) { return false; } return preg_match('/^(?:yes|y|yeah|yep|sure|ok(?:ay)?|affirmative|absolutely|certainly|definitely|of course|(?:i\\s+)?(?:confirm(?:ed)?|approv(?:e|ed))|(?:please\\s+)?(?:proceed|continue|go ahead|go for it|do it)|sounds good|all good|that(?:\'s| is) (?:fine|good|correct|ok(?:ay)?)|let(?:\'|’)s do it)\\b/i', $message) === 1; } }