diff --git a/app/Ai/Tools/AttachmentCollector.php b/app/Ai/Tools/AttachmentCollector.php new file mode 100644 index 00000000..b86f91e2 --- /dev/null +++ b/app/Ai/Tools/AttachmentCollector.php @@ -0,0 +1,44 @@ +all() after the agent finishes to persist + * them onto the resulting AiMessage. + */ +class AttachmentCollector +{ + /** + * @var array> + */ + private array $attachments = []; + + /** + * @param array $attachment + */ + public function push(array $attachment): void + { + $this->attachments[] = $attachment; + } + + /** + * @return array> + */ + public function all(): array + { + return $this->attachments; + } + + public function clear(): void + { + $this->attachments = []; + } +} diff --git a/app/Providers/AppServiceProvider.php b/app/Providers/AppServiceProvider.php index 281c1246..794a1939 100644 --- a/app/Providers/AppServiceProvider.php +++ b/app/Providers/AppServiceProvider.php @@ -5,6 +5,7 @@ namespace App\Providers; use App\Ai\Providers\ExtendedGeminiProvider; +use App\Ai\Tools\AttachmentCollector; use App\Listeners\StripeEventListener; use App\Models\Account; use App\Models\AiMessage; @@ -80,6 +81,8 @@ public function register(): void default => new GeminiTextGenerationService, }, ); + + $this->app->scoped(AttachmentCollector::class); } /** diff --git a/tests/Feature/Ai/Tools/AttachmentCollectorTest.php b/tests/Feature/Ai/Tools/AttachmentCollectorTest.php new file mode 100644 index 00000000..3e77b16e --- /dev/null +++ b/tests/Feature/Ai/Tools/AttachmentCollectorTest.php @@ -0,0 +1,41 @@ +all())->toBeEmpty(); +}); + +test('collector records attachments in push order', function () { + $collector = new AttachmentCollector; + + $collector->push(['id' => 'a', 'type' => 'image']); + $collector->push(['id' => 'b', 'type' => 'video']); + + expect($collector->all())->toHaveCount(2); + expect($collector->all()[0]['id'])->toBe('a'); + expect($collector->all()[1]['id'])->toBe('b'); +}); + +test('collector clear removes all attachments', function () { + $collector = new AttachmentCollector; + + $collector->push(['id' => 'a', 'type' => 'image']); + $collector->clear(); + + expect($collector->all())->toBeEmpty(); +}); + +test('collector resolves as a scoped singleton within the container', function () { + $a = app(AttachmentCollector::class); + $b = app(AttachmentCollector::class); + + $a->push(['id' => 'same', 'type' => 'image']); + + expect($b->all())->toHaveCount(1); + expect($b->all()[0]['id'])->toBe('same'); +});