Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
91 changes: 91 additions & 0 deletions src/Controller/A2AController.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
<?php
/*
* Fusio - Self-Hosted API Management for Builders.
* For the current version and information visit <https://www.fusio-project.org/>
*
* Copyright (c) Christoph Kappestein <christoph.kappestein@gmail.com>
*
* 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\Controller;

use Fusio\Impl\Service\A2A;
use Fusio\Impl\Service\System\FrameworkConfig;
use JsonException;
use PSX\Api\Attribute\Incoming;
use PSX\Api\Attribute\Outgoing;
use PSX\Api\Attribute\Path;
use PSX\Api\Attribute\Post;
use PSX\Framework\Controller\ControllerAbstract;
use PSX\Http\Exception as StatusCode;
use PSX\Http\FilterChainInterface;
use PSX\Http\RequestInterface;
use PSX\Http\ResponseInterface;
use PSX\Http\Stream\StringStream;
use PSX\Json\Parser;
use PSX\Json\Rpc\Context;
use PSX\Json\Rpc\Server;
use PSX\Schema\ContentType;

/**
* A2AController
*
* @author Christoph Kappestein <christoph.kappestein@gmail.com>
* @license http://www.apache.org/licenses/LICENSE-2.0
* @link https://www.fusio-project.org
*/
class A2AController extends ControllerAbstract
{
public function __construct(
private readonly A2A $server,
private readonly FrameworkConfig $frameworkConfig,
) {
}

public function getPreFilter(): array
{
$filter = parent::getPreFilter();
$filter[] = Filter\Tenant::class;
$filter[] = Filter\Firewall::class;

return $filter;
}

#[Post]
#[Path('/a2a/v1')]
#[Incoming(ContentType::JSON)]
#[Outgoing(200, ContentType::JSON)]
public function handle(RequestInterface $request, ResponseInterface $response, FilterChainInterface $filterChain): void
{
if (!$this->frameworkConfig->isA2AEnabled()) {
throw new StatusCode\ServiceUnavailableException('A2A service is not enabled');
}

$body = (string) $request->getBody();

try {
$data = Parser::decode($body);
} catch (JsonException) {
throw new StatusCode\BadRequestException('Provided an invalid request payload, must be an JSON object or array');
}

$return = new Server($this->server)->invoke($data, new Context());

$response->setStatus(200);
$response->setHeader('Content-Type', 'application/json');
$response->setBody(new StringStream(Parser::encode($return)));

$filterChain->handle($request, $response);
}
}
2 changes: 1 addition & 1 deletion src/Controller/JsonRPCController.php
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ public function handle(RequestInterface $request, ResponseInterface $response, F
throw new StatusCode\BadRequestException('Provided an invalid request payload, must be an JSON object or array');
}

$return = (new Server($this->server))->invoke($data, new Context());
$return = new Server($this->server)->invoke($data, new Context());

$response->setStatus(200);
$response->setHeader('Content-Type', 'application/json');
Expand Down
9 changes: 9 additions & 0 deletions src/Controller/WellKnownController.php
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@

namespace Fusio\Impl\Controller;

use Fusio\Impl\Service\WellKnown\AgentCard;
use Fusio\Impl\Service\WellKnown\APICatalog;
use Fusio\Impl\Service\WellKnown\OAuthAuthorizationServer;
use Fusio\Impl\Service\WellKnown\OAuthProtectedResource;
Expand All @@ -42,6 +43,7 @@
class WellKnownController extends ControllerAbstract
{
public function __construct(
private readonly AgentCard $agentCard,
private readonly APICatalog $apiCatalog,
private readonly OAuthAuthorizationServer $oauthAuthorizationServer,
private readonly OAuthProtectedResource $oauthProtectedResource,
Expand All @@ -50,6 +52,13 @@ public function __construct(
) {
}

#[Get]
#[Path('/.well-known/agent-card.json')]
public function getAgentCard(): mixed
{
return $this->agentCard->get();
}

#[Get]
#[Path('/.well-known/api-catalog')]
public function getAPICatalog(): HttpResponse
Expand Down
49 changes: 49 additions & 0 deletions src/Messenger/InvokeAgent.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
<?php
/*
* Fusio - Self-Hosted API Management for Builders.
* For the current version and information visit <https://www.fusio-project.org/>
*
* Copyright (c) Christoph Kappestein <christoph.kappestein@gmail.com>
*
* 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\Messenger;

use Fusio\Model\Agent\Input;

/**
* InvokeAgent
*
* @author Christoph Kappestein <christoph.kappestein@gmail.com>
* @license http://www.apache.org/licenses/LICENSE-2.0
* @link https://www.fusio-project.org
*/
readonly class InvokeAgent
{
public function __construct(
private int $agentId,
private Input $input,
) {
}

public function getAgentId(): int
{
return $this->agentId;
}

public function getInput(): Input
{
return $this->input;
}
}
56 changes: 56 additions & 0 deletions src/MessengerHandler/InvokeAgentHandler.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
<?php
/*
* Fusio - Self-Hosted API Management for Builders.
* For the current version and information visit <https://www.fusio-project.org/>
*
* Copyright (c) Christoph Kappestein <christoph.kappestein@gmail.com>
*
* 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\MessengerHandler;

use Fusio\Impl\Messenger\InvokeAgent;
use Fusio\Impl\Service\Agent\Sender;
use Fusio\Impl\Service\System\FrameworkConfig;
use Fusio\Impl\Table;
use Fusio\Model;
use PSX\Json\Rpc\Exception\InvalidRequestException;
use Symfony\Component\Messenger\Attribute\AsMessageHandler;

/**
* InvokeAgentHandler
*
* @author Christoph Kappestein <christoph.kappestein@gmail.com>
* @license http://www.apache.org/licenses/LICENSE-2.0
* @link https://www.fusio-project.org
*/
#[AsMessageHandler]
readonly class InvokeAgentHandler
{
public function __construct(private Sender $sender, private Table\Agent $agentTable, private FrameworkConfig $frameworkConfig)
{
}

public function __invoke(InvokeAgent $agent): void
{
$row = $this->agentTable->findOneByTenantAndId($this->frameworkConfig->getTenantId(), null, $agent->getAgentId());
if (!$row instanceof Table\Generated\AgentRow) {
throw new InvalidRequestException('Provided an invalid agent id');
}

$output = $this->sender->send($row->getId(), $agent->getInput(), $context);

Check failure on line 52 in src/MessengerHandler/InvokeAgentHandler.php

View workflow job for this annotation

GitHub Actions / PHPStan

Undefined variable: $context

Check failure on line 52 in src/MessengerHandler/InvokeAgentHandler.php

View workflow job for this annotation

GitHub Actions / PHPStan

Undefined variable: $context

// @TODO handle output
}
}
22 changes: 21 additions & 1 deletion src/Migrations/Version20230508210151.php
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,22 @@ public function up(Schema $schema) : void
$agentMessageTable->addIndex(['agent_id', 'user_id', 'child']);
}

if (!$schema->hasTable('fusio_agent_task')) {
$agentTaskTable = $schema->createTable('fusio_agent_task');
$agentTaskTable->addColumn('id', 'integer', ['autoincrement' => true]);
$agentTaskTable->addColumn('agent_id', 'integer');
$agentTaskTable->addColumn('user_id', 'integer');
$agentTaskTable->addColumn('context_id', 'string', ['length' => 64, 'notnull' => false]);
$agentTaskTable->addColumn('status', 'integer', ['default' => 1]); // 1: submitted, 2: working, 3: requires_action, 4: completed, 5: failed
$agentTaskTable->addColumn('input', 'text', ['notnull' => false]);
$agentTaskTable->addColumn('output', 'text', ['notnull' => false]);
$agentTaskTable->addColumn('pending_data', 'text', ['notnull' => false]);
$agentTaskTable->addColumn('update_date', 'datetime');
$agentTaskTable->addColumn('insert_date', 'datetime');
$agentTaskTable->setPrimaryKey(['id']);
$agentTaskTable->addIndex(['context_id']);
}

if (!$schema->hasTable('fusio_app')) {
$appTable = $schema->createTable('fusio_app');
$appTable->addColumn('id', 'integer', ['autoincrement' => true]);
Expand Down Expand Up @@ -547,7 +563,6 @@ public function up(Schema $schema) : void
$testTable->addColumn('body', 'text', ['notnull' => false]);
$testTable->setPrimaryKey(['id']);
$testTable->addUniqueIndex(['operation_id']);

}

if (!$schema->hasTable('fusio_token')) {
Expand Down Expand Up @@ -703,6 +718,11 @@ public function up(Schema $schema) : void
$agentMessageTable->addForeignKeyConstraint($schema->getTable('fusio_user'), ['user_id'], ['id'], [], 'agent_message_user_id');
}

if (isset($agentTaskTable)) {
$agentTaskTable->addForeignKeyConstraint($schema->getTable('fusio_agent'), ['agent_id'], ['id'], [], 'agent_task_agent_id');
$agentTaskTable->addForeignKeyConstraint($schema->getTable('fusio_user'), ['user_id'], ['id'], [], 'agent_task_user_id');
}

if (isset($appTable)) {
$appTable->addForeignKeyConstraint($schema->getTable('fusio_user'), ['user_id'], ['id'], [], 'app_user_id');
}
Expand Down
50 changes: 50 additions & 0 deletions src/Migrations/Version20260507203029.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
<?php

declare(strict_types=1);

namespace Fusio\Impl\Migrations;

use Doctrine\DBAL\Schema\Schema;
use Doctrine\Migrations\AbstractMigration;

/**
* Auto-generated Migration: Please modify to your needs!
*/
final class Version20260507203029 extends AbstractMigration
{
public function getDescription(): string
{
return '';
}

public function up(Schema $schema): void
{
if (!$schema->hasTable('fusio_agent_task')) {
$agentTaskTable = $schema->createTable('fusio_agent_task');
$agentTaskTable->addColumn('id', 'integer', ['autoincrement' => true]);
$agentTaskTable->addColumn('agent_id', 'integer');
$agentTaskTable->addColumn('user_id', 'integer');
$agentTaskTable->addColumn('context_id', 'string', ['length' => 64, 'notnull' => false]);
$agentTaskTable->addColumn('status', 'integer', ['default' => 1]); // 1: submitted, 2: working, 3: requires_action, 4: completed, 5: failed
$agentTaskTable->addColumn('input', 'text', ['notnull' => false]);
$agentTaskTable->addColumn('output', 'text', ['notnull' => false]);
$agentTaskTable->addColumn('pending_data', 'text', ['notnull' => false]);
$agentTaskTable->addColumn('update_date', 'datetime');
$agentTaskTable->addColumn('insert_date', 'datetime');
$agentTaskTable->setPrimaryKey(['id']);
$agentTaskTable->addIndex(['context_id']);

$agentTaskTable->addForeignKeyConstraint($schema->getTable('fusio_agent'), ['agent_id'], ['id'], [], 'agent_task_agent_id');
$agentTaskTable->addForeignKeyConstraint($schema->getTable('fusio_user'), ['user_id'], ['id'], [], 'agent_task_user_id');
}
}

public function down(Schema $schema): void
{
}

public function isTransactional(): bool
{
return false;
}
}
Loading
Loading