feat: structured errors with hint, ToolResult, cursor pagination
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01At3V6SjwbMe6eMzVGW3Hs7
This commit is contained in:
@@ -0,0 +1,188 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace LIT\MCP\Abilities;
|
||||
|
||||
use InvalidArgumentException;
|
||||
|
||||
/**
|
||||
* Uniform success result of every writing ability (spec §8.1).
|
||||
* The adapter returns it as structuredContent plus JSON text.
|
||||
*/
|
||||
final class ToolResult
|
||||
{
|
||||
private const RESERVED = [
|
||||
'ok', 'summary', 'changed', 'outline', 'rev', 'warnings',
|
||||
'revision_id', 'snapshot_id', 'log_id', 'preview_url', 'edit_url',
|
||||
];
|
||||
|
||||
/** @var list<array{id: string, type: string, action: string, label: string}> */
|
||||
private array $changed = [];
|
||||
|
||||
/** @var list<string> */
|
||||
private array $warnings = [];
|
||||
|
||||
private ?string $outline = null;
|
||||
private ?string $rev = null;
|
||||
private ?int $revisionId = null;
|
||||
private ?string $snapshotId = null;
|
||||
private ?int $logId = null;
|
||||
private ?string $previewUrl = null;
|
||||
private ?string $editUrl = null;
|
||||
|
||||
/** @var array<string, mixed> */
|
||||
private array $extra = [];
|
||||
|
||||
private function __construct(private string $summary)
|
||||
{
|
||||
}
|
||||
|
||||
public static function ok(string $summary): self
|
||||
{
|
||||
return new self($summary);
|
||||
}
|
||||
|
||||
public function changed(string $id, string $type, string $action, string $label = ''): self
|
||||
{
|
||||
$this->changed[] = ['id' => $id, 'type' => $type, 'action' => $action, 'label' => $label];
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function warning(string $warning): self
|
||||
{
|
||||
$this->warnings[] = $warning;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/** @param list<string> $warnings */
|
||||
public function warnings(array $warnings): self
|
||||
{
|
||||
foreach ($warnings as $warning) {
|
||||
$this->warning($warning);
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function outline(?string $outline): self
|
||||
{
|
||||
$this->outline = $outline;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function rev(?string $rev): self
|
||||
{
|
||||
$this->rev = $rev;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function revisionId(?int $revisionId): self
|
||||
{
|
||||
$this->revisionId = $revisionId;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function snapshotId(?string $snapshotId): self
|
||||
{
|
||||
$this->snapshotId = $snapshotId;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function logId(?int $logId): self
|
||||
{
|
||||
$this->logId = $logId;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function urls(?string $previewUrl, ?string $editUrl): self
|
||||
{
|
||||
$this->previewUrl = $previewUrl === '' ? null : $previewUrl;
|
||||
$this->editUrl = $editUrl === '' ? null : $editUrl;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function with(string $key, mixed $value): self
|
||||
{
|
||||
if (in_array($key, self::RESERVED, true)) {
|
||||
throw new InvalidArgumentException(sprintf('ToolResult key "%s" is reserved.', $key));
|
||||
}
|
||||
|
||||
$this->extra[$key] = $value;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/** @return array<string, mixed> */
|
||||
public function toArray(): array
|
||||
{
|
||||
return array_merge(
|
||||
[
|
||||
'ok' => true,
|
||||
'summary' => $this->summary,
|
||||
'changed' => $this->changed,
|
||||
'outline' => $this->outline,
|
||||
'rev' => $this->rev,
|
||||
'warnings' => $this->warnings,
|
||||
'revision_id' => $this->revisionId,
|
||||
'snapshot_id' => $this->snapshotId,
|
||||
'log_id' => $this->logId,
|
||||
'preview_url' => $this->previewUrl,
|
||||
'edit_url' => $this->editUrl,
|
||||
],
|
||||
$this->extra
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string, array<string, mixed>> $extraProperties
|
||||
* @param list<string> $extraRequired
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
public static function schema(array $extraProperties = [], array $extraRequired = []): array
|
||||
{
|
||||
return [
|
||||
'type' => 'object',
|
||||
'properties' => array_merge(
|
||||
[
|
||||
'ok' => ['type' => 'boolean'],
|
||||
'summary' => ['type' => 'string'],
|
||||
'changed' => [
|
||||
'type' => 'array',
|
||||
'items' => [
|
||||
'type' => 'object',
|
||||
'properties' => [
|
||||
'id' => ['type' => 'string'],
|
||||
'type' => ['type' => 'string'],
|
||||
'action' => ['type' => 'string'],
|
||||
'label' => ['type' => 'string'],
|
||||
],
|
||||
'required' => ['id', 'type', 'action', 'label'],
|
||||
],
|
||||
],
|
||||
'outline' => ['type' => ['string', 'null']],
|
||||
'rev' => ['type' => ['string', 'null']],
|
||||
'warnings' => ['type' => 'array', 'items' => ['type' => 'string']],
|
||||
'revision_id' => ['type' => ['integer', 'null']],
|
||||
'snapshot_id' => ['type' => ['string', 'null']],
|
||||
'log_id' => ['type' => ['integer', 'null']],
|
||||
'preview_url' => ['type' => ['string', 'null']],
|
||||
'edit_url' => ['type' => ['string', 'null']],
|
||||
],
|
||||
$extraProperties
|
||||
),
|
||||
'required' => array_merge(
|
||||
['ok', 'summary', 'changed', 'warnings', 'rev', 'revision_id', 'log_id'],
|
||||
$extraRequired
|
||||
),
|
||||
];
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user