Files
nuqloud-ai/lib/Service/ToolConfirmationService.php

103 lines
3.8 KiB
PHP
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<?php
declare(strict_types=1);
namespace OCA\EducAI\Service;
use OCA\EducAI\Db\PendingToolConfirmation;
use OCA\EducAI\Db\PendingToolConfirmationMapper;
use OCP\AppFramework\Db\DoesNotExistException;
class ToolConfirmationService {
private const TTL_SECONDS = 600;
public function __construct(private PendingToolConfirmationMapper $mapper) {
}
/** @param array<string,mixed> $arguments @param array<string,mixed>|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<string,mixed> $arguments @param array<string,mixed>|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<string,mixed> $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<string,mixed>|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<string,mixed>|null $context */
private function botId(?array $context): ?int {
return isset($context['bot_id']) && is_int($context['bot_id']) ? $context['bot_id'] : null;
}
/** @param array<string,mixed>|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<string,mixed>|null $context */
private function talkMessageId(?array $context): ?int {
$messageId = $context['talk_message_id'] ?? null;
return is_int($messageId) && $messageId > 0 ? $messageId : null;
}
/** @param array<string,mixed>|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;
}
}