Skip to content

Commit 0be3ff2

Browse files
committed
Refactor Loop class to use a LoopTools singleton for tool management
1 parent 89859b2 commit 0be3ff2

File tree

4 files changed

+163
-14
lines changed

4 files changed

+163
-14
lines changed

src/Loop.php

Lines changed: 9 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@
44

55
use Illuminate\Support\Collection;
66
use Illuminate\Support\Facades\Auth;
7-
use Kirschbaum\Loop\Collections\ToolCollection;
87
use Kirschbaum\Loop\Contracts\Tool;
98
use Kirschbaum\Loop\Contracts\Toolkit;
109
use Prism\Prism\Enums\Provider;
@@ -16,14 +15,9 @@
1615

1716
class Loop
1817
{
19-
protected ToolCollection $tools;
20-
2118
protected string $context = '';
2219

23-
public function __construct()
24-
{
25-
$this->tools = new ToolCollection;
26-
}
20+
public function __construct(protected LoopTools $loopTools) {}
2721

2822
public function setup(): void {}
2923

@@ -36,16 +30,14 @@ public function context(string $context): static
3630

3731
public function tool(Tool $tool): static
3832
{
39-
$this->tools->push($tool);
33+
$this->loopTools->registerTool($tool);
4034

4135
return $this;
4236
}
4337

4438
public function toolkit(Toolkit $toolkit): static
4539
{
46-
foreach ($toolkit->getTools() as $tool) {
47-
$this->tool($tool);
48-
}
40+
$this->loopTools->registerToolkit($toolkit);
4941

5042
return $this;
5143
}
@@ -99,13 +91,17 @@ public function ask(string $question, Collection $messages): Response
9991

10092
public function getPrismTools(): Collection
10193
{
102-
return $this->tools
94+
return $this->loopTools
95+
->getTools()
10396
->toBase()
10497
->map(fn (Tool $tool) => $tool->build());
10598
}
10699

107100
public function getPrismTool(string $name): PrismTool
108101
{
109-
return $this->tools->getTool($name)->build();
102+
return $this->loopTools
103+
->getTools()
104+
->getTool($name)
105+
->build();
110106
}
111107
}

src/LoopServiceProvider.php

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,8 +35,12 @@ public function configurePackage(Package $package): void
3535

3636
public function packageBooted(): void
3737
{
38+
$this->app->singleton(LoopTools::class, function () {
39+
return new LoopTools;
40+
});
41+
3842
$this->app->scoped(Loop::class, function ($app) {
39-
$loop = new Loop;
43+
$loop = new Loop($app->make(LoopTools::class));
4044
$loop->setup();
4145

4246
return $loop;

src/LoopTools.php

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
<?php
2+
3+
namespace Kirschbaum\Loop;
4+
5+
use Kirschbaum\Loop\Collections\ToolCollection;
6+
use Kirschbaum\Loop\Contracts\Tool;
7+
use Kirschbaum\Loop\Contracts\Toolkit;
8+
9+
class LoopTools
10+
{
11+
protected ToolCollection $tools;
12+
13+
/** @var array<int, Toolkit> */
14+
protected array $toolkits = [];
15+
16+
public function __construct()
17+
{
18+
$this->tools = new ToolCollection;
19+
}
20+
21+
public function registerTool(Tool $tool): void
22+
{
23+
$this->tools->push($tool);
24+
}
25+
26+
public function registerToolkit(Toolkit $toolkit): void
27+
{
28+
$this->toolkits[] = $toolkit;
29+
30+
foreach ($toolkit->getTools() as $tool) {
31+
$this->registerTool($tool);
32+
}
33+
}
34+
35+
/**
36+
* Get all registered tools
37+
*/
38+
public function getTools(): ToolCollection
39+
{
40+
return $this->tools;
41+
}
42+
43+
/**
44+
* Get all registered toolkits
45+
*
46+
* @return array<int, Toolkit>
47+
*/
48+
public function getToolkits(): array
49+
{
50+
return $this->toolkits;
51+
}
52+
53+
/**
54+
* Clear all registrations
55+
*/
56+
public function clear(): void
57+
{
58+
$this->tools = new ToolCollection;
59+
$this->toolkits = [];
60+
}
61+
}

tests/Feature/LoopToolsTest.php

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
use Kirschbaum\Loop\Facades\Loop as LoopFacade;
6+
use Kirschbaum\Loop\Loop;
7+
use Kirschbaum\Loop\LoopTools;
8+
use Kirschbaum\Loop\Toolkits\LaravelModelToolkit;
9+
use Kirschbaum\Loop\Tools\CustomTool;
10+
use Workbench\App\Models\User;
11+
12+
beforeEach(function () {
13+
LoopFacade::clearResolvedInstances();
14+
15+
app()->forgetInstance(Loop::class);
16+
17+
if (app()->bound(LoopTools::class)) {
18+
app(LoopTools::class)->clear();
19+
}
20+
});
21+
22+
test('tools persist across loop instances', function () {
23+
$tool = CustomTool::make('test_tool', 'A test tool')
24+
->using(fn () => 'Test tool response');
25+
26+
LoopFacade::tool($tool);
27+
28+
$loop1 = app(Loop::class);
29+
$tools1 = $loop1->getPrismTools();
30+
31+
expect($tools1)
32+
->toHaveCount(1)
33+
->and($tools1->first()->name())
34+
->toEqual('test_tool');
35+
36+
app()->forgetInstance(Loop::class);
37+
38+
$loop2 = app(Loop::class);
39+
$tools2 = $loop2->getPrismTools();
40+
41+
expect($tools2)
42+
->toHaveCount(1)
43+
->and($tools2->first()->name())
44+
->toEqual('test_tool');
45+
});
46+
47+
test('toolkit registrations persist across loop instances', function () {
48+
LoopFacade::toolkit(LaravelModelToolkit::make([User::class]));
49+
50+
$loop1 = app(Loop::class);
51+
$tools1 = $loop1->getPrismTools();
52+
53+
expect($tools1->count())
54+
->toBeGreaterThan(0);
55+
56+
app()->forgetInstance(Loop::class);
57+
58+
$loop2 = app(Loop::class);
59+
$tools2 = $loop2->getPrismTools();
60+
61+
expect($tools2->count())
62+
->toEqual($tools1->count());
63+
});
64+
65+
test('multiple tool registrations persist', function () {
66+
LoopFacade::tool(CustomTool::make('tool1', 'Tool 1')->using(fn () => 'Response 1'));
67+
LoopFacade::tool(CustomTool::make('tool2', 'Tool 2')->using(fn () => 'Response 2'));
68+
69+
$loop1 = app(Loop::class);
70+
71+
expect($loop1->getPrismTools())
72+
->toHaveCount(2);
73+
74+
app()->forgetInstance(Loop::class);
75+
76+
$loop2 = app(Loop::class);
77+
78+
expect($loop2->getPrismTools())
79+
->toHaveCount(2);
80+
});
81+
82+
test('loop tools registry is a singleton', function () {
83+
$registry1 = app(LoopTools::class);
84+
$registry2 = app(LoopTools::class);
85+
86+
expect($registry1)
87+
->toBe($registry2);
88+
});

0 commit comments

Comments
 (0)