diff --git a/src/Loop.php b/src/Loop.php index 5521a3a..50ccb78 100644 --- a/src/Loop.php +++ b/src/Loop.php @@ -4,7 +4,6 @@ use Illuminate\Support\Collection; use Illuminate\Support\Facades\Auth; -use Kirschbaum\Loop\Collections\ToolCollection; use Kirschbaum\Loop\Contracts\Tool; use Kirschbaum\Loop\Contracts\Toolkit; use Prism\Prism\Enums\Provider; @@ -16,14 +15,9 @@ class Loop { - protected ToolCollection $tools; - protected string $context = ''; - public function __construct() - { - $this->tools = new ToolCollection; - } + public function __construct(protected LoopTools $loopTools) {} public function setup(): void {} @@ -36,16 +30,14 @@ public function context(string $context): static public function tool(Tool $tool): static { - $this->tools->push($tool); + $this->loopTools->registerTool($tool); return $this; } public function toolkit(Toolkit $toolkit): static { - foreach ($toolkit->getTools() as $tool) { - $this->tool($tool); - } + $this->loopTools->registerToolkit($toolkit); return $this; } @@ -99,13 +91,17 @@ public function ask(string $question, Collection $messages): Response public function getPrismTools(): Collection { - return $this->tools + return $this->loopTools + ->getTools() ->toBase() ->map(fn (Tool $tool) => $tool->build()); } public function getPrismTool(string $name): PrismTool { - return $this->tools->getTool($name)->build(); + return $this->loopTools + ->getTools() + ->getTool($name) + ->build(); } } diff --git a/src/LoopServiceProvider.php b/src/LoopServiceProvider.php index 811a34e..5f14abe 100644 --- a/src/LoopServiceProvider.php +++ b/src/LoopServiceProvider.php @@ -35,8 +35,12 @@ public function configurePackage(Package $package): void public function packageBooted(): void { + $this->app->singleton(LoopTools::class, function () { + return new LoopTools; + }); + $this->app->scoped(Loop::class, function ($app) { - $loop = new Loop; + $loop = new Loop($app->make(LoopTools::class)); $loop->setup(); return $loop; diff --git a/src/LoopTools.php b/src/LoopTools.php new file mode 100644 index 0000000..7b5e55b --- /dev/null +++ b/src/LoopTools.php @@ -0,0 +1,61 @@ + */ + protected array $toolkits = []; + + public function __construct() + { + $this->tools = new ToolCollection; + } + + public function registerTool(Tool $tool): void + { + $this->tools->push($tool); + } + + public function registerToolkit(Toolkit $toolkit): void + { + $this->toolkits[] = $toolkit; + + foreach ($toolkit->getTools() as $tool) { + $this->registerTool($tool); + } + } + + /** + * Get all registered tools + */ + public function getTools(): ToolCollection + { + return $this->tools; + } + + /** + * Get all registered toolkits + * + * @return array + */ + public function getToolkits(): array + { + return $this->toolkits; + } + + /** + * Clear all registrations + */ + public function clear(): void + { + $this->tools = new ToolCollection; + $this->toolkits = []; + } +} diff --git a/tests/Feature/LoopToolsTest.php b/tests/Feature/LoopToolsTest.php new file mode 100644 index 0000000..4296b3c --- /dev/null +++ b/tests/Feature/LoopToolsTest.php @@ -0,0 +1,88 @@ +forgetInstance(Loop::class); + + if (app()->bound(LoopTools::class)) { + app(LoopTools::class)->clear(); + } +}); + +test('tools persist across loop instances', function () { + $tool = CustomTool::make('test_tool', 'A test tool') + ->using(fn () => 'Test tool response'); + + LoopFacade::tool($tool); + + $loop1 = app(Loop::class); + $tools1 = $loop1->getPrismTools(); + + expect($tools1) + ->toHaveCount(1) + ->and($tools1->first()->name()) + ->toEqual('test_tool'); + + app()->forgetInstance(Loop::class); + + $loop2 = app(Loop::class); + $tools2 = $loop2->getPrismTools(); + + expect($tools2) + ->toHaveCount(1) + ->and($tools2->first()->name()) + ->toEqual('test_tool'); +}); + +test('toolkit registrations persist across loop instances', function () { + LoopFacade::toolkit(LaravelModelToolkit::make([User::class])); + + $loop1 = app(Loop::class); + $tools1 = $loop1->getPrismTools(); + + expect($tools1->count()) + ->toBeGreaterThan(0); + + app()->forgetInstance(Loop::class); + + $loop2 = app(Loop::class); + $tools2 = $loop2->getPrismTools(); + + expect($tools2->count()) + ->toEqual($tools1->count()); +}); + +test('multiple tool registrations persist', function () { + LoopFacade::tool(CustomTool::make('tool1', 'Tool 1')->using(fn () => 'Response 1')); + LoopFacade::tool(CustomTool::make('tool2', 'Tool 2')->using(fn () => 'Response 2')); + + $loop1 = app(Loop::class); + + expect($loop1->getPrismTools()) + ->toHaveCount(2); + + app()->forgetInstance(Loop::class); + + $loop2 = app(Loop::class); + + expect($loop2->getPrismTools()) + ->toHaveCount(2); +}); + +test('loop tools registry is a singleton', function () { + $registry1 = app(LoopTools::class); + $registry2 = app(LoopTools::class); + + expect($registry1) + ->toBe($registry2); +});