Skip to content

Commit 36e915a

Browse files
committed
Rename ListCompletionResponse to ArrayCompletionResponse and update related methods to include hasMore flag
1 parent f79bd81 commit 36e915a

11 files changed

+74
-139
lines changed

src/Server/Completions/ListCompletionResponse.php renamed to src/Server/Completions/ArrayCompletionResponse.php

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

55
namespace Laravel\Mcp\Server\Completions;
66

7-
final class ListCompletionResponse extends CompletionResponse
7+
class ArrayCompletionResponse extends CompletionResponse
88
{
99
/**
1010
* @param array<int, string> $items
@@ -18,8 +18,10 @@ public function resolve(string $value): DirectCompletionResponse
1818
{
1919
$filtered = CompletionHelper::filterByPrefix($this->items, $value);
2020

21+
$hasMore = count($filtered) > self::MAX_VALUES;
22+
2123
$truncated = array_slice($filtered, 0, self::MAX_VALUES);
2224

23-
return new DirectCompletionResponse($truncated);
25+
return new DirectCompletionResponse($truncated, $hasMore);
2426
}
2527
}

src/Server/Completions/CallbackCompletionResponse.php

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66

77
use Illuminate\Support\Arr;
88

9-
final class CallbackCompletionResponse extends CompletionResponse
9+
class CallbackCompletionResponse extends CompletionResponse
1010
{
1111
/**
1212
* @param callable(string): (CompletionResponse|array<int, string>|string) $callback
@@ -24,8 +24,12 @@ public function resolve(string $value): CompletionResponse
2424
return $result;
2525
}
2626

27-
$truncated = array_slice(Arr::wrap($result), 0, self::MAX_VALUES);
27+
$items = Arr::wrap($result);
2828

29-
return new DirectCompletionResponse($truncated);
29+
$hasMore = count($items) > self::MAX_VALUES;
30+
31+
$truncated = array_slice($items, 0, self::MAX_VALUES);
32+
33+
return new DirectCompletionResponse($truncated, $hasMore);
3034
}
3135
}

src/Server/Completions/CompletionHelper.php

Lines changed: 3 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -14,31 +14,15 @@ class CompletionHelper
1414
*/
1515
public static function filterByPrefix(array $items, string $prefix): array
1616
{
17-
return self::filterByStringComparison(
18-
$items,
19-
$prefix,
20-
fn (string $item, string $needle) => Str::startsWith($item, $needle)
21-
);
22-
}
23-
24-
/**
25-
* @param array<string> $items
26-
* @return array<string>
27-
*/
28-
private static function filterByStringComparison(
29-
array $items,
30-
string $needle,
31-
callable $comparator
32-
): array {
33-
if ($needle === '') {
17+
if ($prefix === '') {
3418
return $items;
3519
}
3620

37-
$needleLower = Str::lower($needle);
21+
$prefixLower = Str::lower($prefix);
3822

3923
return array_values(array_filter(
4024
$items,
41-
fn (string $item) => $comparator(Str::lower($item), $needleLower)
25+
fn (string $item) => Str::startsWith(Str::lower($item), $prefixLower)
4226
));
4327
}
4428
}

src/Server/Completions/CompletionResponse.php

Lines changed: 9 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,6 @@ abstract class CompletionResponse implements Arrayable
2121
*/
2222
public function __construct(
2323
protected array $values,
24-
protected ?int $total = null,
2524
protected bool $hasMore = false,
2625
) {
2726
if (count($values) > self::MAX_VALUES) {
@@ -38,11 +37,13 @@ public static function from(array|string $values): CompletionResponse
3837
{
3938
$values = Arr::wrap($values);
4039

41-
if (count($values) > self::MAX_VALUES) {
40+
$hasMore = count($values) > self::MAX_VALUES;
41+
42+
if ($hasMore) {
4243
$values = array_slice($values, 0, self::MAX_VALUES);
4344
}
4445

45-
return new DirectCompletionResponse($values);
46+
return new DirectCompletionResponse($values, $hasMore);
4647
}
4748

4849
public static function empty(): CompletionResponse
@@ -55,7 +56,7 @@ public static function empty(): CompletionResponse
5556
*/
5657
public static function fromArray(array $items): CompletionResponse
5758
{
58-
return new ListCompletionResponse($items);
59+
return new ArrayCompletionResponse($items);
5960
}
6061

6162
/**
@@ -81,31 +82,20 @@ public function values(): array
8182
return $this->values;
8283
}
8384

84-
public function total(): ?int
85-
{
86-
return $this->total;
87-
}
88-
8985
public function hasMore(): bool
9086
{
9187
return $this->hasMore;
9288
}
9389

9490
/**
95-
* @return array{values: array<int, string>, total?: int, hasMore: bool}
91+
* @return array{values: array<int, string>, total: int, hasMore: bool}
9692
*/
9793
public function toArray(): array
9894
{
99-
$result = [
95+
return [
10096
'values' => $this->values,
97+
'total' => count($this->values),
98+
'hasMore' => $this->hasMore,
10199
];
102-
103-
if (! is_null($this->total)) {
104-
$result['total'] = $this->total;
105-
}
106-
107-
$result['hasMore'] = $this->hasMore;
108-
109-
return $result;
110100
}
111101
}

src/Server/Completions/DirectCompletionResponse.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44

55
namespace Laravel\Mcp\Server\Completions;
66

7-
final class DirectCompletionResponse extends CompletionResponse
7+
class DirectCompletionResponse extends CompletionResponse
88
{
99
public function resolve(string $value): DirectCompletionResponse
1010
{

src/Server/Completions/EnumCompletionResponse.php

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
use InvalidArgumentException;
99
use UnitEnum;
1010

11-
final class EnumCompletionResponse extends CompletionResponse
11+
class EnumCompletionResponse extends CompletionResponse
1212
{
1313
/**
1414
* @param class-string<UnitEnum> $enumClass
@@ -31,8 +31,10 @@ public function resolve(string $value): DirectCompletionResponse
3131

3232
$filtered = CompletionHelper::filterByPrefix($enumValues, $value);
3333

34+
$hasMore = count($filtered) > self::MAX_VALUES;
35+
3436
$truncated = array_slice($filtered, 0, self::MAX_VALUES);
3537

36-
return new DirectCompletionResponse($truncated);
38+
return new DirectCompletionResponse($truncated, $hasMore);
3739
}
3840
}

tests/Unit/Completions/CallbackCompletionResponseTest.php

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -45,18 +45,18 @@
4545
expect($resolved->values())->toBe(['single-item']);
4646
});
4747

48-
it('truncates callback results to 100 items', function (): void {
48+
it('truncates callback results to 100 items and sets hasMore', function (): void {
4949
$result = new CallbackCompletionResponse(fn (string $value): array => array_map(fn ($i): string => "item{$i}", range(1, 150)));
5050

5151
$resolved = $result->resolve('');
5252

53-
expect($resolved->values())->toHaveCount(100);
53+
expect($resolved->values())->toHaveCount(100)
54+
->and($resolved->hasMore())->toBeTrue();
5455
});
5556

5657
it('starts with empty values until resolved', function (): void {
5758
$result = new CallbackCompletionResponse(fn (string $value): array => ['result']);
5859

5960
expect($result->values())->toBe([])
60-
->and($result->total())->toBeNull()
6161
->and($result->hasMore())->toBeFalse();
6262
});

tests/Unit/Completions/CompletionResponseTest.php

Lines changed: 18 additions & 67 deletions
Original file line numberDiff line numberDiff line change
@@ -1,61 +1,40 @@
11
<?php
22

3+
use Laravel\Mcp\Server\Completions\ArrayCompletionResponse;
4+
use Laravel\Mcp\Server\Completions\CallbackCompletionResponse;
35
use Laravel\Mcp\Server\Completions\CompletionResponse;
4-
use Laravel\Mcp\Server\Completions\DirectCompletionResponse;
6+
use Laravel\Mcp\Server\Completions\EnumCompletionResponse;
57

68
it('creates a completion result with values', function (): void {
79
$result = CompletionResponse::from(['php', 'python', 'javascript']);
810

911
expect($result->values())->toBe(['php', 'python', 'javascript'])
10-
->and($result->hasMore())->toBeFalse()
11-
->and($result->total())->toBeNull();
12+
->and($result->hasMore())->toBeFalse();
1213
});
1314

1415
it('creates an empty completion result', function (): void {
1516
$result = CompletionResponse::empty();
1617

1718
expect($result->values())->toBe([])
18-
->and($result->hasMore())->toBeFalse()
19-
->and($result->total())->toBeNull();
19+
->and($result->hasMore())->toBeFalse();
2020
});
2121

2222
it('converts to array format', function (): void {
2323
$result = CompletionResponse::from(['php', 'python']);
2424

2525
expect($result->toArray())->toBe([
2626
'values' => ['php', 'python'],
27+
'total' => 2,
2728
'hasMore' => false,
2829
]);
2930
});
3031

31-
it('includes total in an array when provided', function (): void {
32-
$result = new DirectCompletionResponse(['php', 'python'], total: 5, hasMore: true);
33-
34-
expect($result->toArray())->toBe([
35-
'values' => ['php', 'python'],
36-
'total' => 5,
37-
'hasMore' => true,
38-
]);
39-
});
40-
41-
it('auto-truncates values to 100 items', function (): void {
32+
it('auto-truncates values to 100 items and sets hasMore', function (): void {
4233
$values = array_map(fn ($i): string => "item{$i}", range(1, 150));
4334
$result = CompletionResponse::from($values);
4435

45-
expect($result->values())->toHaveCount(100);
46-
});
47-
48-
it('throws exception when constructor receives more than 100 items', function (): void {
49-
$values = array_map(fn ($i): string => "item{$i}", range(1, 101));
50-
51-
new DirectCompletionResponse($values);
52-
})->throws(InvalidArgumentException::class, 'Completion values cannot exceed 100 items');
53-
54-
it('allows exactly 100 items', function (): void {
55-
$values = array_map(fn ($i): string => "item{$i}", range(1, 100));
56-
$result = new DirectCompletionResponse($values);
57-
58-
expect($result->values())->toHaveCount(100);
36+
expect($result->values())->toHaveCount(100)
37+
->and($result->hasMore())->toBeTrue();
5938
});
6039

6140
it('supports single string in from', function (): void {
@@ -64,55 +43,27 @@
6443
expect($result->values())->toBe(['single-value']);
6544
});
6645

67-
it('creates fromArray result', function (): void {
46+
it('fromArray creates ArrayCompletionResponse', function (): void {
6847
$result = CompletionResponse::fromArray(['php', 'python', 'javascript']);
69-
$resolved = $result->resolve('py');
7048

71-
expect($resolved->values())->toBe(['python']);
49+
expect($result)->toBeInstanceOf(ArrayCompletionResponse::class);
7250
});
7351

74-
it('creates fromEnum result with backed enum', function (): void {
75-
enum TestBackedEnum: string
52+
it('fromEnum creates EnumCompletionResponse', function (): void {
53+
enum FactoryTestEnum: string
7654
{
7755
case One = 'value-one';
78-
case Two = 'value-two';
7956
}
8057

81-
$result = CompletionResponse::fromEnum(TestBackedEnum::class);
82-
$resolved = $result->resolve('value-o');
83-
84-
expect($resolved->values())->toBe(['value-one']);
85-
});
86-
87-
it('creates fromEnum result with non-backed enum', function (): void {
88-
enum TestEnum
89-
{
90-
case Active;
91-
case Inactive;
92-
}
93-
94-
$result = CompletionResponse::fromEnum(TestEnum::class);
95-
$resolved = $result->resolve('act');
96-
97-
expect($resolved->values())->toBe(['Active']);
98-
});
99-
100-
it('throws an exception for an invalid enum class', function (): void {
101-
CompletionResponse::fromEnum('NotAnEnum');
102-
})->throws(InvalidArgumentException::class, 'is not an enum');
103-
104-
it('creates fromCallback result', function (): void {
105-
$result = CompletionResponse::fromCallback(fn (string $value): \Laravel\Mcp\Server\Completions\CompletionResponse => CompletionResponse::from(['test-value']));
106-
$resolved = $result->resolve('test');
58+
$result = CompletionResponse::fromEnum(FactoryTestEnum::class);
10759

108-
expect($resolved->values())->toBe(['test-value']);
60+
expect($result)->toBeInstanceOf(EnumCompletionResponse::class);
10961
});
11062

111-
it('callback can return an array', function (): void {
112-
$result = CompletionResponse::fromCallback(fn (string $value): array => ['value1', 'value2']);
113-
$resolved = $result->resolve('val');
63+
it('fromCallback creates CallbackCompletionResponse', function (): void {
64+
$result = CompletionResponse::fromCallback(fn (string $value): array => ['test']);
11465

115-
expect($resolved->values())->toBe(['value1', 'value2']);
66+
expect($result)->toBeInstanceOf(CallbackCompletionResponse::class);
11667
});
11768

11869
it('resolve returns self for direct type', function (): void {

tests/Unit/Completions/DirectCompletionResponseTest.php

Lines changed: 15 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -16,29 +16,32 @@
1616
expect($result->values())->toBe(['php', 'python', 'javascript']);
1717
});
1818

19-
it('works with metadata', function (): void {
20-
$result = new DirectCompletionResponse(['php', 'python'], total: 10, hasMore: true);
19+
it('works with hasMore flag', function (): void {
20+
$result = new DirectCompletionResponse(['php', 'python'], hasMore: true);
2121

2222
expect($result->values())->toBe(['php', 'python'])
23-
->and($result->total())->toBe(10)
2423
->and($result->hasMore())->toBeTrue();
2524
});
2625

27-
it('converts to array correctly', function (): void {
28-
$result = new DirectCompletionResponse(['php', 'python'], total: 5, hasMore: true);
26+
it('converts to array with hasMore true', function (): void {
27+
$result = new DirectCompletionResponse(['php', 'python'], hasMore: true);
2928

3029
expect($result->toArray())->toBe([
3130
'values' => ['php', 'python'],
32-
'total' => 5,
31+
'total' => 2,
3332
'hasMore' => true,
3433
]);
3534
});
3635

37-
it('converts to array without total when null', function (): void {
38-
$result = new DirectCompletionResponse(['php', 'python']);
36+
it('throws exception when constructor receives more than 100 items', function (): void {
37+
$values = array_map(fn ($i): string => "item{$i}", range(1, 101));
3938

40-
expect($result->toArray())->toBe([
41-
'values' => ['php', 'python'],
42-
'hasMore' => false,
43-
]);
39+
new DirectCompletionResponse($values);
40+
})->throws(InvalidArgumentException::class, 'Completion values cannot exceed 100 items');
41+
42+
it('allows exactly 100 items', function (): void {
43+
$values = array_map(fn ($i): string => "item{$i}", range(1, 100));
44+
$result = new DirectCompletionResponse($values);
45+
46+
expect($result->values())->toHaveCount(100);
4447
});

tests/Unit/Completions/EnumCompletionResponseTest.php

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,5 @@ enum PlainEnumForTest
5858
$result = new EnumCompletionResponse(BackedEnumForTest::class);
5959

6060
expect($result->values())->toBe([])
61-
->and($result->total())->toBeNull()
6261
->and($result->hasMore())->toBeFalse();
6362
});

0 commit comments

Comments
 (0)