diff --git a/composer.json b/composer.json index 277eef488..cfe5a5d6a 100644 --- a/composer.json +++ b/composer.json @@ -36,7 +36,7 @@ "fusio/adapter-util": "^6.0", "fusio/adapter-worker": "^0.2", "fusio/marketplace": "^0.2", - "firebase/php-jwt": "^6.0", + "firebase/php-jwt": "^7.0", "symfony/filesystem": "^6.0|^7.0", "symfony/mailer": "^6.0|^7.0", "symfony/doctrine-messenger": "^6.0|^7.0", diff --git a/src/Backend/Action/Connection/Agent/Reset.php b/src/Backend/Action/Agent/Create.php similarity index 61% rename from src/Backend/Action/Connection/Agent/Reset.php rename to src/Backend/Action/Agent/Create.php index 06748b3e1..1cb98c7ab 100644 --- a/src/Backend/Action/Connection/Agent/Reset.php +++ b/src/Backend/Action/Agent/Create.php @@ -18,45 +18,45 @@ * limitations under the License. */ -namespace Fusio\Impl\Backend\Action\Connection\Agent; +namespace Fusio\Impl\Backend\Action\Agent; -use Fusio\Engine\Connector; +use Fusio\Engine\ActionInterface; use Fusio\Engine\ContextInterface; use Fusio\Engine\ParametersInterface; use Fusio\Engine\RequestInterface; -use Fusio\Impl\Service\System\FrameworkConfig; -use Fusio\Impl\Table; +use Fusio\Impl\Service\Agent; +use Fusio\Impl\Service\System\ContextFactory; +use Fusio\Model\Backend\AgentCreate; use PSX\Http\Environment\HttpResponse; -use PSX\Http\Exception\BadRequestException; /** - * Reset + * Create * * @author Christoph Kappestein * @license http://www.apache.org/licenses/LICENSE-2.0 * @link https://www.fusio-project.org */ -readonly class Reset extends AgentAbstract +readonly class Create implements ActionInterface { - public function __construct(private Table\Agent $agentTable, Connector $connector, FrameworkConfig $frameworkConfig) + public function __construct(private Agent $agentService, private ContextFactory $contextFactory) { - parent::__construct($connector, $frameworkConfig); } public function handle(RequestInterface $request, ParametersInterface $configuration, ContextInterface $context): mixed { - $this->assertConnectionEnabled(); + $body = $request->getPayload(); - $connectionId = (int) $request->get('connection_id'); - if (empty($connectionId)) { - throw new BadRequestException('Provided no connection'); - } + assert($body instanceof AgentCreate); - $this->agentTable->reset($context->getUser()->getId(), $connectionId); + $id = $this->agentService->create( + $body, + $this->contextFactory->newActionContext($context) + ); - return new HttpResponse(200, [], [ + return new HttpResponse(201, [], [ 'success' => true, - 'message' => 'Chat successfully reset', + 'message' => 'Agent successfully created', + 'id' => '' . $id, ]); } } diff --git a/src/Backend/Action/Agent/Delete.php b/src/Backend/Action/Agent/Delete.php new file mode 100644 index 000000000..e1f59e7a1 --- /dev/null +++ b/src/Backend/Action/Agent/Delete.php @@ -0,0 +1,56 @@ + + * + * Copyright (c) Christoph Kappestein + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +namespace Fusio\Impl\Backend\Action\Agent; + +use Fusio\Engine\ActionInterface; +use Fusio\Engine\ContextInterface; +use Fusio\Engine\ParametersInterface; +use Fusio\Engine\RequestInterface; +use Fusio\Impl\Service\Agent; +use Fusio\Impl\Service\System\ContextFactory; + +/** + * Delete + * + * @author Christoph Kappestein + * @license http://www.apache.org/licenses/LICENSE-2.0 + * @link https://www.fusio-project.org + */ +readonly class Delete implements ActionInterface +{ + public function __construct(private Agent $agentService, private ContextFactory $contextFactory) + { + } + + public function handle(RequestInterface $request, ParametersInterface $configuration, ContextInterface $context): mixed + { + $id = $this->agentService->delete( + $request->get('agent_id'), + $this->contextFactory->newActionContext($context) + ); + + return [ + 'success' => true, + 'message' => 'Agent successfully deleted', + 'id' => '' . $id, + ]; + } +} diff --git a/src/Backend/Action/Connection/Agent/Get.php b/src/Backend/Action/Agent/Get.php similarity index 59% rename from src/Backend/Action/Connection/Agent/Get.php rename to src/Backend/Action/Agent/Get.php index 2b56e61cd..1bef00aa5 100644 --- a/src/Backend/Action/Connection/Agent/Get.php +++ b/src/Backend/Action/Agent/Get.php @@ -18,17 +18,15 @@ * limitations under the License. */ -namespace Fusio\Impl\Backend\Action\Connection\Agent; +namespace Fusio\Impl\Backend\Action\Agent; -use Fusio\Engine\Connector; +use Fusio\Engine\ActionInterface; use Fusio\Engine\ContextInterface; use Fusio\Engine\ParametersInterface; use Fusio\Engine\RequestInterface; use Fusio\Impl\Backend\View; -use Fusio\Impl\Service\Agent\Intent; -use Fusio\Impl\Service\System\FrameworkConfig; -use PSX\Http\Environment\HttpResponse; -use PSX\Http\Exception\BadRequestException; +use Fusio\Impl\Table; +use PSX\Http\Exception as StatusCode; /** * Get @@ -37,24 +35,27 @@ * @license http://www.apache.org/licenses/LICENSE-2.0 * @link https://www.fusio-project.org */ -readonly class Get extends AgentAbstract +readonly class Get implements ActionInterface { - public function __construct(private View\Connection\Agent $view, Connector $connector, FrameworkConfig $frameworkConfig) + public function __construct(private View\Agent $view) { - parent::__construct($connector, $frameworkConfig); } public function handle(RequestInterface $request, ParametersInterface $configuration, ContextInterface $context): mixed { - $this->assertConnectionEnabled(); + $agent = $this->view->getEntity( + $request->get('agent_id'), + $context + ); - $connectionId = (int) $request->get('connection_id'); - if (empty($connectionId)) { - throw new BadRequestException('Provided no connection'); + if (empty($agent)) { + throw new StatusCode\NotFoundException('Could not find agent'); } - $intent = Intent::tryFrom($request->get('intent') ?? ''); + if ($agent['status'] == Table\Agent::STATUS_DELETED) { + throw new StatusCode\GoneException('Agent was deleted'); + } - return new HttpResponse(200, [], $this->view->getCollection($connectionId, $intent, $context)); + return $agent; } } diff --git a/src/Service/Agent/IntentFactory.php b/src/Backend/Action/Agent/GetAll.php similarity index 60% rename from src/Service/Agent/IntentFactory.php rename to src/Backend/Action/Agent/GetAll.php index cb5329c0d..18c0f31a6 100644 --- a/src/Service/Agent/IntentFactory.php +++ b/src/Backend/Action/Agent/GetAll.php @@ -18,32 +18,33 @@ * limitations under the License. */ -namespace Fusio\Impl\Service\Agent; +namespace Fusio\Impl\Backend\Action\Agent; + +use Fusio\Engine\ActionInterface; +use Fusio\Engine\ContextInterface; +use Fusio\Engine\ParametersInterface; +use Fusio\Engine\RequestInterface; +use Fusio\Impl\Backend\Filter\QueryFilter; +use Fusio\Impl\Backend\View; /** - * IntentFactory + * GetAll * * @author Christoph Kappestein * @license http://www.apache.org/licenses/LICENSE-2.0 * @link https://www.fusio-project.org */ -readonly class IntentFactory +class GetAll implements ActionInterface { - public function __construct( - private Intent\ActionIntent $actionIntent, - private Intent\SchemaIntent $schemaIntent, - private Intent\ArchitectIntent $architectIntent, - private Intent\GeneralIntent $generalIntent - ) { + public function __construct(private View\Agent $view) + { } - public function factory(?Intent $intent): IntentInterface + public function handle(RequestInterface $request, ParametersInterface $configuration, ContextInterface $context): mixed { - return match($intent) { - Intent::ACTION => $this->actionIntent, - Intent::SCHEMA => $this->schemaIntent, - Intent::ARCHITECT => $this->architectIntent, - default => $this->generalIntent, - }; + return $this->view->getCollection( + QueryFilter::from($request), + $context + ); } } diff --git a/src/Backend/Action/Agent/GetTools.php b/src/Backend/Action/Agent/GetTools.php new file mode 100644 index 000000000..f60a581e0 --- /dev/null +++ b/src/Backend/Action/Agent/GetTools.php @@ -0,0 +1,63 @@ + + * + * Copyright (c) Christoph Kappestein + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +namespace Fusio\Impl\Backend\Action\Agent; + +use Fusio\Engine\ActionInterface; +use Fusio\Engine\Agent\ToolsInterface; +use Fusio\Engine\ContextInterface; +use Fusio\Engine\ParametersInterface; +use Fusio\Engine\RequestInterface; + +/** + * GetTools + * + * @author Christoph Kappestein + * @license http://www.apache.org/licenses/LICENSE-2.0 + * @link https://www.fusio-project.org + */ +readonly class GetTools implements ActionInterface +{ + public function __construct(private ToolsInterface $tools) + { + } + + public function handle(RequestInterface $request, ParametersInterface $configuration, ContextInterface $context): mixed + { + return [ + 'tools' => $this->getTools(), + ]; + } + + private function getTools(): array + { + $toolbox = $this->tools->resolve(); + + $result = []; + foreach ($toolbox->getTools() as $tool) { + $result[] = [ + 'name' => $tool->getName(), + 'description' => $tool->getDescription(), + ]; + } + + return $result; + } +} diff --git a/src/Backend/Action/Agent/Message/GetAll.php b/src/Backend/Action/Agent/Message/GetAll.php new file mode 100644 index 000000000..204449008 --- /dev/null +++ b/src/Backend/Action/Agent/Message/GetAll.php @@ -0,0 +1,50 @@ + + * + * Copyright (c) Christoph Kappestein + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +namespace Fusio\Impl\Backend\Action\Agent\Message; + +use Fusio\Engine\ActionInterface; +use Fusio\Engine\ContextInterface; +use Fusio\Engine\ParametersInterface; +use Fusio\Engine\RequestInterface; +use Fusio\Impl\Backend\View; + +/** + * GetAll + * + * @author Christoph Kappestein + * @license http://www.apache.org/licenses/LICENSE-2.0 + * @link https://www.fusio-project.org + */ +readonly class GetAll implements ActionInterface +{ + public function __construct(private View\Agent\Message $view) + { + } + + public function handle(RequestInterface $request, ParametersInterface $configuration, ContextInterface $context): mixed + { + return $this->view->getCollection( + (int) $request->get('agent_id'), + (int) $request->get('parent'), + $context + ); + } +} diff --git a/src/Backend/Action/Agent/Message/Submit.php b/src/Backend/Action/Agent/Message/Submit.php new file mode 100644 index 000000000..5e8198c42 --- /dev/null +++ b/src/Backend/Action/Agent/Message/Submit.php @@ -0,0 +1,58 @@ + + * + * Copyright (c) Christoph Kappestein + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +namespace Fusio\Impl\Backend\Action\Agent\Message; + +use Fusio\Engine\ActionInterface; +use Fusio\Engine\ContextInterface; +use Fusio\Engine\ParametersInterface; +use Fusio\Engine\RequestInterface; +use Fusio\Impl\Service\Agent; +use Fusio\Model\Backend\AgentContent; +use PSX\Http\Environment\HttpResponse; + +/** + * Submit + * + * @author Christoph Kappestein + * @license http://www.apache.org/licenses/LICENSE-2.0 + * @link https://www.fusio-project.org + */ +readonly class Submit implements ActionInterface +{ + public function __construct(private Agent\Sender $sender) + { + } + + public function handle(RequestInterface $request, ParametersInterface $configuration, ContextInterface $context): mixed + { + $body = $request->getPayload(); + + assert($body instanceof AgentContent); + + $message = $this->sender->send( + $request->get('agent_id'), + $body, + $context, + ); + + return new HttpResponse(201, [], $message); + } +} diff --git a/src/Backend/Action/Agent/Update.php b/src/Backend/Action/Agent/Update.php new file mode 100644 index 000000000..d2f6723ec --- /dev/null +++ b/src/Backend/Action/Agent/Update.php @@ -0,0 +1,62 @@ + + * + * Copyright (c) Christoph Kappestein + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +namespace Fusio\Impl\Backend\Action\Agent; + +use Fusio\Engine\ActionInterface; +use Fusio\Engine\ContextInterface; +use Fusio\Engine\ParametersInterface; +use Fusio\Engine\RequestInterface; +use Fusio\Impl\Service\Agent; +use Fusio\Impl\Service\System\ContextFactory; +use Fusio\Model\Backend\AgentUpdate; + +/** + * Update + * + * @author Christoph Kappestein + * @license http://www.apache.org/licenses/LICENSE-2.0 + * @link https://www.fusio-project.org + */ +readonly class Update implements ActionInterface +{ + public function __construct(private Agent $agentService, private ContextFactory $contextFactory) + { + } + + public function handle(RequestInterface $request, ParametersInterface $configuration, ContextInterface $context): mixed + { + $body = $request->getPayload(); + + assert($body instanceof AgentUpdate); + + $id = $this->agentService->update( + $request->get('agent_id'), + $body, + $this->contextFactory->newActionContext($context) + ); + + return [ + 'success' => true, + 'message' => 'Agent successfully updated', + 'id' => '' . $id, + ]; + } +} diff --git a/src/Backend/Action/Connection/Agent/AgentAbstract.php b/src/Backend/Action/Connection/Agent/AgentAbstract.php index 753fb39f3..bfb5f82e0 100644 --- a/src/Backend/Action/Connection/Agent/AgentAbstract.php +++ b/src/Backend/Action/Connection/Agent/AgentAbstract.php @@ -41,8 +41,13 @@ public function __construct(private Connector $connector, private FrameworkConfi { } - protected function getConnection(int $connectionId): AgentInterface + protected function getConnection(RequestInterface $request): AgentInterface { + $connectionId = $request->get('connection_id'); + if (empty($connectionId)) { + throw new BadRequestException('Provided no connection'); + } + $connection = $this->connector->getConnection($connectionId); if (!$connection instanceof AgentInterface) { throw new BadRequestException('Provided an invalid connection'); diff --git a/src/Backend/Action/Connection/Agent/Send.php b/src/Backend/Action/Connection/Agent/Send.php index 38058bcf6..e971ddf9a 100644 --- a/src/Backend/Action/Connection/Agent/Send.php +++ b/src/Backend/Action/Connection/Agent/Send.php @@ -24,9 +24,10 @@ use Fusio\Engine\ContextInterface; use Fusio\Engine\ParametersInterface; use Fusio\Engine\RequestInterface; -use Fusio\Impl\Service\Agent\Sender; +use Fusio\Impl\Service\Agent\Serializer\ResultSerializer; +use Fusio\Impl\Service\Agent\Unserializer\MessageUnserializer; use Fusio\Impl\Service\System\FrameworkConfig; -use Fusio\Model\Backend\AgentRequest; +use Fusio\Model\Backend\AgentContent; use PSX\Http\Environment\HttpResponse; use PSX\Http\Exception\BadRequestException; @@ -39,7 +40,7 @@ */ readonly class Send extends AgentAbstract { - public function __construct(private Sender $sender, Connector $connector, FrameworkConfig $frameworkConfig) + public function __construct(private MessageUnserializer $messageUnserializer, private ResultSerializer $resultSerializer, Connector $connector, FrameworkConfig $frameworkConfig) { parent::__construct($connector, $frameworkConfig); } @@ -48,18 +49,21 @@ public function handle(RequestInterface $request, ParametersInterface $configura { $this->assertConnectionEnabled(); - $connectionId = (int) $request->get('connection_id'); - if (empty($connectionId)) { - throw new BadRequestException('Provided no connection'); - } - - $agent = $this->getConnection($connectionId); + $agent = $this->getConnection($request); $payload = $request->getPayload(); - assert($payload instanceof AgentRequest); + assert($payload instanceof AgentContent); + + $messages = $this->messageUnserializer->unserialize($payload); + + $options = [ + 'temperature' => 0.4 + ]; + + $result = $agent->call($messages, $options); - $response = $this->sender->send($agent, $context->getUser()->getId(), $connectionId, $payload); + $output = $this->resultSerializer->serialize($result); - return new HttpResponse(200, [], $response); + return new HttpResponse(200, [], $output); } } diff --git a/src/Backend/View/Agent.php b/src/Backend/View/Agent.php new file mode 100644 index 000000000..1cfcee486 --- /dev/null +++ b/src/Backend/View/Agent.php @@ -0,0 +1,92 @@ + + * + * Copyright (c) Christoph Kappestein + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +namespace Fusio\Impl\Backend\View; + +use Fusio\Engine\ContextInterface; +use Fusio\Impl\Backend\Filter\QueryFilter; +use Fusio\Impl\Table; +use PSX\Nested\Builder; +use PSX\Sql\OrderBy; +use PSX\Sql\ViewAbstract; + +/** + * Agent + * + * @author Christoph Kappestein + * @license http://www.apache.org/licenses/LICENSE-2.0 + * @link https://www.fusio-project.org + */ +class Agent extends ViewAbstract +{ + public function getCollection(QueryFilter $filter, ContextInterface $context) + { + $startIndex = $filter->getStartIndex(); + $count = $filter->getCount(); + $sortBy = Table\Generated\AgentColumn::tryFrom($filter->getSortBy(Table\Generated\AgentTable::COLUMN_NAME) ?? ''); + $sortOrder = $filter->getSortOrder(OrderBy::ASC); + + $condition = $filter->getCondition([QueryFilter::COLUMN_SEARCH => Table\Generated\AgentTable::COLUMN_NAME]); + $condition->equals(Table\Generated\AgentTable::COLUMN_TENANT_ID, $context->getTenantId()); + $condition->in(Table\Generated\AgentTable::COLUMN_STATUS, [Table\Agent::STATUS_ACTIVE]); + + $builder = new Builder($this->connection); + + $definition = [ + 'totalResults' => $this->getTable(Table\Agent::class)->getCount($condition), + 'startIndex' => $startIndex, + 'itemsPerPage' => $count, + 'entry' => $builder->doCollection([$this->getTable(Table\Agent::class), 'findAll'], [$condition, $startIndex, $count, $sortBy, $sortOrder], [ + 'id' => $builder->fieldInteger(Table\Generated\AgentTable::COLUMN_ID), + 'status' => $builder->fieldInteger(Table\Generated\AgentTable::COLUMN_STATUS), + 'type' => $builder->fieldInteger(Table\Generated\AgentTable::COLUMN_TYPE), + 'name' => Table\Generated\AgentTable::COLUMN_NAME, + 'description' => Table\Generated\AgentTable::COLUMN_DESCRIPTION, + 'outgoing' => Table\Generated\AgentTable::COLUMN_OUTGOING, + 'action' => Table\Generated\AgentTable::COLUMN_ACTION, + 'metadata' => $builder->fieldJson(Table\Generated\AgentTable::COLUMN_METADATA), + 'insertDate' => $builder->fieldDateTime(Table\Generated\AgentTable::COLUMN_INSERT_DATE), + ]), + ]; + + return $builder->build($definition); + } + + public function getEntity(string $id, ContextInterface $context) + { + $builder = new Builder($this->connection); + + $definition = $builder->doEntity([$this->getTable(Table\Agent::class), 'findOneByIdentifier'], [$context->getTenantId(), $context->getUser()->getCategoryId(), $id], [ + 'id' => $builder->fieldInteger(Table\Generated\AgentTable::COLUMN_ID), + 'status' => $builder->fieldInteger(Table\Generated\AgentTable::COLUMN_STATUS), + 'type' => $builder->fieldInteger(Table\Generated\AgentTable::COLUMN_TYPE), + 'name' => Table\Generated\AgentTable::COLUMN_NAME, + 'description' => Table\Generated\AgentTable::COLUMN_DESCRIPTION, + 'introduction' => Table\Generated\AgentTable::COLUMN_INTRODUCTION, + 'tools' => $builder->fieldJson(Table\Generated\AgentTable::COLUMN_TOOLS), + 'outgoing' => Table\Generated\AgentTable::COLUMN_OUTGOING, + 'action' => Table\Generated\AgentTable::COLUMN_ACTION, + 'metadata' => $builder->fieldJson(Table\Generated\AgentTable::COLUMN_METADATA), + 'insertDate' => $builder->fieldDateTime(Table\Generated\AgentTable::COLUMN_INSERT_DATE), + ]); + + return $builder->build($definition); + } +} diff --git a/src/Backend/View/Connection/Agent.php b/src/Backend/View/Agent/Message.php similarity index 55% rename from src/Backend/View/Connection/Agent.php rename to src/Backend/View/Agent/Message.php index d57e4330a..94744f96d 100644 --- a/src/Backend/View/Connection/Agent.php +++ b/src/Backend/View/Agent/Message.php @@ -18,7 +18,7 @@ * limitations under the License. */ -namespace Fusio\Impl\Backend\View\Connection; +namespace Fusio\Impl\Backend\View\Agent; use Fusio\Engine\ContextInterface; use Fusio\Impl\Service; @@ -35,31 +35,42 @@ * @license http://www.apache.org/licenses/LICENSE-2.0 * @link https://www.fusio-project.org */ -class Agent extends ViewAbstract +class Message extends ViewAbstract { - public function getCollection(int $connectionId, ?Service\Agent\Intent $intent, ContextInterface $context) + public function getCollection(int $agentId, int $parentId, ContextInterface $context) { $condition = Condition::withAnd(); - $condition->equals(Table\Generated\AgentColumn::USER_ID, $context->getUser()->getId()); - $condition->equals(Table\Generated\AgentColumn::CONNECTION_ID, $connectionId); - $condition->equals(Table\Generated\AgentColumn::INTENT, $intent?->getInt() ?? 0); + $condition->equals(Table\Generated\AgentMessageColumn::AGENT_ID, $agentId); + $condition->equals(Table\Generated\AgentMessageColumn::USER_ID, $context->getUser()->getId()); - $count = $this->getTable(Table\Agent::class)->getCount($condition); + if ($parentId > 0) { + $condition->equals(Table\Generated\AgentMessageColumn::PARENT_ID, $parentId); + } else { + $condition->nil(Table\Generated\AgentMessageColumn::PARENT_ID); + } + + $count = $this->getTable(Table\Agent\Message::class)->getCount($condition); $startIndex = max(0, $count - Service\Agent\Sender::CONTEXT_MESSAGES_LENGTH); - $sortBy = Table\Generated\AgentColumn::ID; + $sortBy = Table\Generated\AgentMessageColumn::ID; $sortOrder = OrderBy::ASC; $builder = new Builder($this->connection); $definition = [ - 'totalResults' => Service\Agent\Sender::CONTEXT_MESSAGES_LENGTH, + 'totalResults' => $count, 'startIndex' => 0, 'itemsPerPage' => Service\Agent\Sender::CONTEXT_MESSAGES_LENGTH, - 'entry' => $builder->doCollection([$this->getTable(Table\Agent::class), 'findBy'], [$condition, $startIndex, $count, $sortBy, $sortOrder], [ - 'id' => $builder->fieldInteger(Table\Generated\AgentTable::COLUMN_ID), - 'origin' => $builder->fieldInteger(Table\Generated\AgentTable::COLUMN_ORIGIN), - 'message' => $builder->fieldJson(Table\Generated\AgentTable::COLUMN_MESSAGE), - 'insertDate' => $builder->fieldDateTime(Table\Generated\AgentTable::COLUMN_INSERT_DATE), + 'entry' => $builder->doCollection([$this->getTable(Table\Agent\Message::class), 'findBy'], [$condition, $startIndex, $count, $sortBy, $sortOrder], [ + 'id' => $builder->fieldInteger(Table\Generated\AgentMessageTable::COLUMN_ID), + 'role' => $builder->fieldCallback(Table\Generated\AgentMessageTable::COLUMN_ORIGIN, function ($value) { + return match ($value) { + Table\Agent\Message::ORIGIN_ASSISTANT => 'assistant', + Table\Agent\Message::ORIGIN_SYSTEM => 'system', + default => 'user', + }; + }), + 'content' => $builder->fieldJson(Table\Generated\AgentMessageTable::COLUMN_CONTENT), + 'insertDate' => $builder->fieldDateTime(Table\Generated\AgentMessageTable::COLUMN_INSERT_DATE), ]), ]; diff --git a/src/Service/Agent/IntentInterface.php b/src/Event/Agent/CreatedEvent.php similarity index 64% rename from src/Service/Agent/IntentInterface.php rename to src/Event/Agent/CreatedEvent.php index 031fdadca..c2ac25e4f 100644 --- a/src/Service/Agent/IntentInterface.php +++ b/src/Event/Agent/CreatedEvent.php @@ -18,26 +18,33 @@ * limitations under the License. */ -namespace Fusio\Impl\Service\Agent; +namespace Fusio\Impl\Event\Agent; -use Fusio\Model\Backend\AgentMessage; -use Symfony\AI\Platform\Result\ResultInterface; +use Fusio\Impl\Authorization\UserContext; +use Fusio\Impl\Event\EventAbstract; +use Fusio\Model\Backend\AgentCreate; +use Fusio\Model\Backend\CronjobCreate; /** - * An intent describes the intent of a user what he wants to achieve, depending on the intent we provide different - * messages, tools and response formats to the agent + * CreatedEvent * * @author Christoph Kappestein * @license http://www.apache.org/licenses/LICENSE-2.0 * @link https://www.fusio-project.org */ -interface IntentInterface +class CreatedEvent extends EventAbstract { - public function getMessage(): string; + private AgentCreate $agent; - public function getTools(): array; + public function __construct(AgentCreate $agent, UserContext $context) + { + parent::__construct($context); - public function getResponseSchema(): ?array; + $this->agent = $agent; + } - public function transformResult(ResultInterface $result): AgentMessage; + public function getAgent(): AgentCreate + { + return $this->agent; + } } diff --git a/src/Event/Agent/DeletedEvent.php b/src/Event/Agent/DeletedEvent.php new file mode 100644 index 000000000..e8c930e2c --- /dev/null +++ b/src/Event/Agent/DeletedEvent.php @@ -0,0 +1,49 @@ + + * + * Copyright (c) Christoph Kappestein + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +namespace Fusio\Impl\Event\Agent; + +use Fusio\Impl\Authorization\UserContext; +use Fusio\Impl\Event\EventAbstract; +use Fusio\Impl\Table\Generated\AgentRow; + +/** + * DeletedEvent + * + * @author Christoph Kappestein + * @license http://www.apache.org/licenses/LICENSE-2.0 + * @link https://www.fusio-project.org + */ +class DeletedEvent extends EventAbstract +{ + private AgentRow $existing; + + public function __construct(AgentRow $existing, UserContext $context) + { + parent::__construct($context); + + $this->existing = $existing; + } + + public function getExisting(): AgentRow + { + return $this->existing; + } +} diff --git a/src/Event/Agent/UpdatedEvent.php b/src/Event/Agent/UpdatedEvent.php new file mode 100644 index 000000000..bf1bb53bf --- /dev/null +++ b/src/Event/Agent/UpdatedEvent.php @@ -0,0 +1,57 @@ + + * + * Copyright (c) Christoph Kappestein + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +namespace Fusio\Impl\Event\Agent; + +use Fusio\Impl\Authorization\UserContext; +use Fusio\Impl\Event\EventAbstract; +use Fusio\Impl\Table\Generated\AgentRow; +use Fusio\Model\Backend\AgentUpdate; + +/** + * UpdatedEvent + * + * @author Christoph Kappestein + * @license http://www.apache.org/licenses/LICENSE-2.0 + * @link https://www.fusio-project.org + */ +class UpdatedEvent extends EventAbstract +{ + private AgentUpdate $agent; + private AgentRow $existing; + + public function __construct(AgentUpdate $agent, AgentRow $existing, UserContext $context) + { + parent::__construct($context); + + $this->agent = $agent; + $this->existing = $existing; + } + + public function getAgent(): AgentUpdate + { + return $this->agent; + } + + public function getExisting(): AgentRow + { + return $this->existing; + } +} diff --git a/src/Installation/DataBag.php b/src/Installation/DataBag.php index 0e78a7dba..f36503827 100644 --- a/src/Installation/DataBag.php +++ b/src/Installation/DataBag.php @@ -25,6 +25,7 @@ use Fusio\Impl\Backend; use Fusio\Impl\Consumer; use Fusio\Impl\Table; +use PSX\Json\Parser; use PSX\Schema\ContentType; use PSX\Schema\TypeInterface; @@ -55,6 +56,7 @@ public function __construct() 'fusio_connection' => [], 'fusio_cronjob' => [], 'fusio_event' => [], + 'fusio_agent' => [], 'fusio_log' => [], 'fusio_provider' => [], 'fusio_page' => [], @@ -82,6 +84,7 @@ public function __construct() 'fusio_role_scope' => [], 'fusio_action_commit' => [], 'fusio_schema_commit' => [], + 'fusio_agent_message' => [], ]; } @@ -229,6 +232,37 @@ public function addActionCommit(string $action, string $user, string $commitHash ]; } + public function addAgent(string $category, string $connection, int $type, string $name, string $description, string $introduction, array $tools, string $outgoing, string $action, int $status = Table\Agent::STATUS_ACTIVE, ?array $metadata = null, ?string $date = null, ?string $tenantId = null): void + { + $this->data['fusio_agent'][$name] = [ + 'tenant_id' => $tenantId, + 'category_id' => $this->getReference('fusio_category', $category, $tenantId), + 'connection_id' => $this->getReference('fusio_connection', $connection, $tenantId), + 'status' => $status, + 'type' => $type, + 'name' => $name, + 'description' => $description, + 'introduction' => $introduction, + 'tools' => Parser::encode($tools), + 'outgoing' => $outgoing, + 'action' => $action, + 'metadata' => $metadata !== null ? json_encode($metadata) : null, + 'insert_date' => (new \DateTime($date ?? 'now'))->format('Y-m-d H:i:s'), + ]; + } + + public function addAgentMessage(string $agent, string $user, int $origin, string $content, ?int $parentId = null, ?string $date = null, ?string $tenantId = null): void + { + $this->data['fusio_agent_message'][$content] = [ + 'agent_id' => $this->getReference('fusio_agent', $agent, $tenantId), + 'user_id' => $this->getReference('fusio_user', $user, $tenantId), + 'parent_id' => $parentId !== null ? $parentId : null, + 'origin' => $origin, + 'content' => Parser::encode(['type' => 'text', 'content' => $content]), + 'insert_date' => (new \DateTime($date ?? 'now'))->format('Y-m-d H:i:s'), + ]; + } + public function addApp(string $user, string $name, string $url, string $appKey, string $appSecret, int $status = Table\App::STATUS_ACTIVE, ?array $metadata = null, ?string $date = null, ?string $tenantId = null): void { $this->data['fusio_app'][$name] = [ diff --git a/src/Installation/NewInstallation.php b/src/Installation/NewInstallation.php index a592aca10..cfbbfeb78 100644 --- a/src/Installation/NewInstallation.php +++ b/src/Installation/NewInstallation.php @@ -32,7 +32,6 @@ use Fusio\Impl\Table; use Fusio\Marketplace; use Fusio\Model; -use Psr\Container\ContainerInterface; use PSX\Api\Model\Passthru; use PSX\Api\OperationInterface; use PSX\Schema\ContentType; @@ -275,6 +274,86 @@ private static function getOperations(): array throws: [999 => Model\Common\Message::class], description: 'Returns a paginated list of action commits', ), + 'agent.getAll' => new Operation( + action: Backend\Action\Agent\GetAll::class, + httpMethod: 'GET', + httpPath: '/agent', + httpCode: 200, + outgoing: Model\Backend\AgentCollection::class, + parameters: ['startIndex' => PropertyTypeFactory::getInteger(), 'count' => PropertyTypeFactory::getInteger(), 'search' => PropertyTypeFactory::getString()], + throws: [999 => Model\Common\Message::class], + description: 'Returns a paginated list of agents', + ), + 'agent.create' => new Operation( + action: Backend\Action\Agent\Create::class, + httpMethod: 'POST', + httpPath: '/agent', + httpCode: 201, + outgoing: Model\Common\Message::class, + incoming: Model\Backend\AgentCreate::class, + throws: [999 => Model\Common\Message::class], + eventName: 'fusio.agent.create', + description: 'Creates a new agent', + ), + 'agent.get' => new Operation( + action: Backend\Action\Agent\Get::class, + httpMethod: 'GET', + httpPath: '/agent/$agent_id<[0-9]+|^~>', + httpCode: 200, + outgoing: Model\Backend\Agent::class, + throws: [999 => Model\Common\Message::class], + description: 'Returns a specific agent', + ), + 'agent.update' => new Operation( + action: Backend\Action\Agent\Update::class, + httpMethod: 'PUT', + httpPath: '/agent/$agent_id<[0-9]+|^~>', + httpCode: 200, + outgoing: Model\Common\Message::class, + incoming: Model\Backend\AgentUpdate::class, + throws: [999 => Model\Common\Message::class], + eventName: 'fusio.agent.update', + description: 'Updates an existing agent', + ), + 'agent.delete' => new Operation( + action: Backend\Action\Agent\Delete::class, + httpMethod: 'DELETE', + httpPath: '/agent/$agent_id<[0-9]+|^~>', + httpCode: 200, + outgoing: Model\Common\Message::class, + throws: [999 => Model\Common\Message::class], + eventName: 'fusio.agent.delete', + description: 'Deletes an existing agent', + ), + 'agent.getTools' => new Operation( + action: Backend\Action\Agent\GetTools::class, + httpMethod: 'GET', + httpPath: '/agent/tools', + httpCode: 200, + outgoing: Model\Backend\AgentTools::class, + throws: [999 => Model\Common\Message::class], + description: 'Returns available tools for an agent', + ), + 'agent.message.getAll' => new Operation( + action: Backend\Action\Agent\Message\GetAll::class, + httpMethod: 'GET', + httpPath: '/agent/$agent_id<[0-9]+|^~>/message', + httpCode: 200, + outgoing: Model\Backend\AgentMessageCollection::class, + parameters: ['parent' => PropertyTypeFactory::getInteger()], + throws: [999 => Model\Common\Message::class], + description: 'Returns a paginated list of agent messages', + ), + 'agent.message.submit' => new Operation( + action: Backend\Action\Agent\Message\Submit::class, + httpMethod: 'POST', + httpPath: '/agent/$agent_id<[0-9]+|^~>/message', + httpCode: 201, + outgoing: Model\Backend\AgentMessage::class, + incoming: Model\Backend\AgentContent::class, + throws: [999 => Model\Common\Message::class], + description: 'Submits a new agent message', + ), 'app.getAll' => new Operation( action: Backend\Action\App\GetAll::class, httpMethod: 'GET', @@ -514,35 +593,16 @@ private static function getOperations(): array throws: [999 => Model\Common\Message::class], description: 'Returns a redirect url to start the OAuth2 authorization flow for the given connection', ), - 'connection.agent.get' => new Operation( - action: Backend\Action\Connection\Agent\Get::class, - httpMethod: 'GET', - httpPath: '/connection/:connection_id/agent', - httpCode: 200, - outgoing: Model\Backend\AgentCollection::class, - parameters: ['intent' => PropertyTypeFactory::getString()], - throws: [999 => Model\Common\Message::class], - description: 'Returns all previous sent messages', - ), 'connection.agent.send' => new Operation( action: Backend\Action\Connection\Agent\Send::class, httpMethod: 'POST', httpPath: '/connection/:connection_id/agent', httpCode: 200, - outgoing: Model\Backend\AgentResponse::class, - incoming: Model\Backend\AgentRequest::class, + outgoing: Model\Backend\AgentContent::class, + incoming: Model\Backend\AgentContent::class, throws: [999 => Model\Common\Message::class], description: 'Sends a message to an agent', ), - 'connection.agent.reset' => new Operation( - action: Backend\Action\Connection\Agent\Reset::class, - httpMethod: 'DELETE', - httpPath: '/connection/:connection_id/agent', - httpCode: 200, - outgoing: Model\Common\Message::class, - throws: [999 => Model\Common\Message::class], - description: 'Resets all agent messages', - ), 'connection.database.getTables' => new Operation( action: Backend\Action\Connection\Database\Table\GetAll::class, httpMethod: 'GET', diff --git a/src/Migrations/Version20230508210151.php b/src/Migrations/Version20230508210151.php index 13f353525..b877b9f02 100644 --- a/src/Migrations/Version20230508210151.php +++ b/src/Migrations/Version20230508210151.php @@ -57,13 +57,32 @@ public function up(Schema $schema) : void $agentTable = $schema->createTable('fusio_agent'); $agentTable->addColumn('id', 'integer', ['autoincrement' => true]); $agentTable->addColumn('tenant_id', 'string', ['length' => 64, 'notnull' => false, 'default' => null]); - $agentTable->addColumn('user_id', 'integer'); + $agentTable->addColumn('category_id', 'integer', ['default' => 1]); $agentTable->addColumn('connection_id', 'integer'); - $agentTable->addColumn('origin', 'integer'); - $agentTable->addColumn('intent', 'integer'); - $agentTable->addColumn('message', 'text'); + $agentTable->addColumn('status', 'integer', ['default' => Table\Agent::STATUS_ACTIVE]); + $agentTable->addColumn('type', 'integer', ['default' => Table\Agent::TYPE_GENERAL]); + $agentTable->addColumn('name', 'string'); + $agentTable->addColumn('description', 'string'); + $agentTable->addColumn('introduction', 'text'); + $agentTable->addColumn('tools', 'text', ['notnull' => false, 'default' => null]); + $agentTable->addColumn('outgoing', 'string', ['length' => 255, 'notnull' => false, 'default' => null]); + $agentTable->addColumn('action', 'string', ['length' => 255, 'notnull' => false, 'default' => null]); + $agentTable->addColumn('metadata', 'text', ['notnull' => false]); $agentTable->addColumn('insert_date', 'datetime'); $agentTable->setPrimaryKey(['id']); + $agentTable->addUniqueIndex(['tenant_id', 'name']); + } + + if (!$schema->hasTable('fusio_agent_message')) { + $agentMessageTable = $schema->createTable('fusio_agent_message'); + $agentMessageTable->addColumn('id', 'integer', ['autoincrement' => true]); + $agentMessageTable->addColumn('agent_id', 'integer'); + $agentMessageTable->addColumn('user_id', 'integer'); + $agentMessageTable->addColumn('parent_id', 'integer', ['notnull' => false, 'default' => null]); + $agentMessageTable->addColumn('origin', 'integer'); + $agentMessageTable->addColumn('content', 'text'); + $agentMessageTable->addColumn('insert_date', 'datetime'); + $agentMessageTable->setPrimaryKey(['id']); } if (!$schema->hasTable('fusio_app')) { @@ -671,8 +690,14 @@ public function up(Schema $schema) : void } if (isset($agentTable)) { - $agentTable->addForeignKeyConstraint($schema->getTable('fusio_user'), ['user_id'], ['id'], [], 'agent_chat_user_id'); - $agentTable->addForeignKeyConstraint($schema->getTable('fusio_connection'), ['connection_id'], ['id'], [], 'agent_chat_connection_id'); + $agentTable->addForeignKeyConstraint($schema->getTable('fusio_category'), ['category_id'], ['id'], [], 'agent_category_id'); + $agentTable->addForeignKeyConstraint($schema->getTable('fusio_connection'), ['connection_id'], ['id'], [], 'agent_connection_id'); + } + + if (isset($agentMessageTable)) { + $agentMessageTable->addForeignKeyConstraint($schema->getTable('fusio_agent'), ['agent_id'], ['id'], [], 'agent_message_agent_id'); + $agentMessageTable->addForeignKeyConstraint($schema->getTable('fusio_user'), ['user_id'], ['id'], [], 'agent_message_user_id'); + $agentMessageTable->addForeignKeyConstraint($schema->getTable('fusio_agent_message'), ['parent_id'], ['id'], [], 'agent_message_parent_id'); } if (isset($appTable)) { diff --git a/src/Migrations/Version20260218181413.php b/src/Migrations/Version20260218181413.php new file mode 100644 index 000000000..bf13bc952 --- /dev/null +++ b/src/Migrations/Version20260218181413.php @@ -0,0 +1,88 @@ +hasTable('fusio_agent')) { + $agentTable = $schema->getTable('fusio_agent'); + if (!$agentTable->hasColumn('introduction')) { + $schema->dropTable('fusio_agent'); + $needsCreate = true; + } + } else { + $needsCreate = true; + } + + if ($needsCreate) { + $agentTable = $schema->createTable('fusio_agent'); + $agentTable->addColumn('id', 'integer', ['autoincrement' => true]); + $agentTable->addColumn('tenant_id', 'string', ['length' => 64, 'notnull' => false, 'default' => null]); + $agentTable->addColumn('category_id', 'integer', ['default' => 1]); + $agentTable->addColumn('connection_id', 'integer'); + $agentTable->addColumn('status', 'integer', ['default' => Table\Agent::STATUS_ACTIVE]); + $agentTable->addColumn('type', 'integer', ['default' => Table\Agent::TYPE_GENERAL]); + $agentTable->addColumn('name', 'string'); + $agentTable->addColumn('description', 'string'); + $agentTable->addColumn('introduction', 'text'); + $agentTable->addColumn('tools', 'text', ['notnull' => false, 'default' => null]); + $agentTable->addColumn('outgoing', 'string', ['length' => 255, 'notnull' => false, 'default' => null]); + $agentTable->addColumn('action', 'string', ['length' => 255, 'notnull' => false, 'default' => null]); + $agentTable->addColumn('metadata', 'text', ['notnull' => false]); + $agentTable->addColumn('insert_date', 'datetime'); + $agentTable->setPrimaryKey(['id']); + $agentTable->addUniqueIndex(['tenant_id', 'name']); + + $agentTable->addForeignKeyConstraint($schema->getTable('fusio_category'), ['category_id'], ['id'], [], 'agent_category_id'); + $agentTable->addForeignKeyConstraint($schema->getTable('fusio_connection'), ['connection_id'], ['id'], [], 'agent_connection_id'); + } + + if (!$schema->hasTable('fusio_agent_message')) { + $agentMessageTable = $schema->createTable('fusio_agent_message'); + $agentMessageTable->addColumn('id', 'integer', ['autoincrement' => true]); + $agentMessageTable->addColumn('agent_id', 'integer'); + $agentMessageTable->addColumn('user_id', 'integer'); + $agentMessageTable->addColumn('parent_id', 'integer', ['notnull' => false, 'default' => null]); + $agentMessageTable->addColumn('origin', 'integer'); + $agentMessageTable->addColumn('content', 'text'); + $agentMessageTable->addColumn('insert_date', 'datetime'); + $agentMessageTable->setPrimaryKey(['id']); + + $agentMessageTable->addForeignKeyConstraint($schema->getTable('fusio_agent'), ['agent_id'], ['id'], [], 'agent_message_agent_id'); + $agentMessageTable->addForeignKeyConstraint($schema->getTable('fusio_user'), ['user_id'], ['id'], [], 'agent_message_user_id'); + $agentMessageTable->addForeignKeyConstraint($schema->getTable('fusio_agent_message'), ['parent_id'], ['id'], [], 'agent_message_parent_id'); + } + } + + public function down(Schema $schema): void + { + } + + public function isTransactional(): bool + { + return false; + } + + public function postUp(Schema $schema): void + { + DataSyncronizer::sync($this->connection); + } +} diff --git a/src/Service/Agent.php b/src/Service/Agent.php new file mode 100644 index 000000000..a2d0a60d0 --- /dev/null +++ b/src/Service/Agent.php @@ -0,0 +1,137 @@ + + * + * Copyright (c) Christoph Kappestein + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +namespace Fusio\Impl\Service; + +use Fusio\Impl\Authorization\UserContext; +use Fusio\Impl\Event\Agent\CreatedEvent; +use Fusio\Impl\Event\Agent\DeletedEvent; +use Fusio\Impl\Event\Agent\UpdatedEvent; +use Fusio\Impl\Table; +use Fusio\Model\Backend\AgentCreate; +use Fusio\Model\Backend\AgentUpdate; +use Psr\EventDispatcher\EventDispatcherInterface; +use PSX\DateTime\LocalDateTime; +use PSX\Http\Exception as StatusCode; +use PSX\Json\Parser; + +/** + * Agent + * + * @author Christoph Kappestein + * @license http://www.apache.org/licenses/LICENSE-2.0 + * @link https://www.fusio-project.org + */ +readonly class Agent +{ + public function __construct( + private Table\Agent $agentTable, + private Agent\Validator $validator, + private EventDispatcherInterface $eventDispatcher + ) { + } + + public function create(AgentCreate $agent, UserContext $context): int + { + $this->validator->assert($agent, $context->getCategoryId(), $context->getTenantId()); + + // create agent + try { + $this->agentTable->beginTransaction(); + + $row = new Table\Generated\AgentRow(); + $row->setTenantId($context->getTenantId()); + $row->setCategoryId($context->getCategoryId()); + $row->setConnectionId($agent->getConnection()); + $row->setStatus(Table\Agent::STATUS_ACTIVE); + $row->setType($agent->getType() ?? Table\Agent::TYPE_GENERAL); + $row->setName($agent->getName()); + $row->setDescription($agent->getDescription()); + $row->setIntroduction($agent->getIntroduction()); + $row->setTools($agent->getTools() !== null ? Parser::encode($agent->getTools()) : null); + $row->setOutgoing($agent->getOutgoing()); + $row->setAction($agent->getAction()); + $row->setInsertDate(LocalDateTime::now()); + $row->setMetadata($agent->getMetadata() !== null ? Parser::encode($agent->getMetadata()) : null); + $this->agentTable->create($row); + + $agentId = $this->agentTable->getLastInsertId(); + $agent->setId($agentId); + + $this->agentTable->commit(); + } catch (\Throwable $e) { + $this->agentTable->rollBack(); + + throw $e; + } + + $this->eventDispatcher->dispatch(new CreatedEvent($agent, $context)); + + return $agentId; + } + + public function update(string $agentId, AgentUpdate $agent, UserContext $context): int + { + $existing = $this->agentTable->findOneByIdentifier($context->getTenantId(), $context->getCategoryId(), $agentId); + if (empty($existing)) { + throw new StatusCode\NotFoundException('Could not find agent'); + } + + if ($existing->getStatus() == Table\Agent::STATUS_DELETED) { + throw new StatusCode\GoneException('Agent was deleted'); + } + + $this->validator->assert($agent, $context->getCategoryId(), $context->getTenantId(), $existing); + + $existing->setConnectionId($agent->getConnection() ?? $existing->getConnectionId()); + $existing->setType($agent->getType() ?? $existing->getType()); + $existing->setName($agent->getName() ?? $existing->getName()); + $existing->setDescription($agent->getDescription() ?? $existing->getDescription()); + $existing->setIntroduction($agent->getIntroduction() ?? $existing->getIntroduction()); + $existing->setTools($agent->getTools() !== null ? Parser::encode($agent->getTools()) : $existing->getTools()); + $existing->setOutgoing($agent->getOutgoing() ?? $existing->getOutgoing()); + $existing->setAction($agent->getAction() ?? $existing->getAction()); + $existing->setMetadata($agent->getMetadata() !== null ? Parser::encode($agent->getMetadata()) : $existing->getMetadata()); + $this->agentTable->update($existing); + + $this->eventDispatcher->dispatch(new UpdatedEvent($agent, $existing, $context)); + + return $existing->getId(); + } + + public function delete(string $agentId, UserContext $context): int + { + $existing = $this->agentTable->findOneByIdentifier($context->getTenantId(), $context->getCategoryId(), $agentId); + if (empty($existing)) { + throw new StatusCode\NotFoundException('Could not find agent'); + } + + if ($existing->getStatus() == Table\Agent::STATUS_DELETED) { + throw new StatusCode\GoneException('Agent was deleted'); + } + + $existing->setStatus(Table\Agent::STATUS_DELETED); + $this->agentTable->update($existing); + + $this->eventDispatcher->dispatch(new DeletedEvent($existing, $context)); + + return $existing->getId(); + } +} diff --git a/src/Service/Agent/Intent/ActionIntent.php b/src/Service/Agent/Intent/ActionIntent.php deleted file mode 100644 index 792faf02d..000000000 --- a/src/Service/Agent/Intent/ActionIntent.php +++ /dev/null @@ -1,139 +0,0 @@ - - * - * Copyright (c) Christoph Kappestein - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -namespace Fusio\Impl\Service\Agent\Intent; - -use Fusio\Impl\Service\Agent\IntentInterface; -use Fusio\Impl\Service\Agent\Serializer\ResultSerializer; -use Fusio\Model\Backend\AgentMessage; -use Symfony\AI\Platform\Result\ResultInterface; - -/** - * ActionIntent - * - * @author Christoph Kappestein - * @license http://www.apache.org/licenses/LICENSE-2.0 - * @link https://www.fusio-project.org - */ -readonly class ActionIntent implements IntentInterface -{ - public function __construct(private ResultSerializer $resultSerializer) - { - } - - public function getMessage(): string - { - $hint = 'The user has the intent to develop a new action.' . "\n"; - $hint.= 'Therefor you need to transform the provided business logic of the user message into PHP code.' . "\n"; - $hint.= 'The resulting PHP code must be wrapped into the following code template:' . "\n"; - $hint.= "\n"; - $hint.= '