$agentTools Tool name patterns of the agent (`*`, `core/*`, `maze-graph/list-plugins`). */ public function __construct( public readonly int $userId, public readonly int $sessionId = 0, public readonly string $runId = '', public readonly int $step = 0, public readonly int $maxSteps = 0, public readonly string $callId = '', public readonly bool $tainted = false, public readonly string $approvedHash = '', public readonly array $agentTools = ['*'], public readonly string $via = self::MODEL, public readonly bool $surfaceOn = true, public readonly string $permissionMode = PermissionMode::ASK, ) { } /** * A write the person starts themselves (decision 045), recorded under `person:`. `$key` is unique per * attempt (the app sends one per click and reuses it on a retry), so a retried request never writes twice. * * @since 0.1.0 */ public static function forPerson(int $userId, string $key, int $sessionId = 0, bool $surfaceOn = true): self { return new self($userId, $sessionId, 'person', 0, 0, $key, false, '', ['*'], self::PERSON, $surfaceOn); } /** * The same context for one call. * * @since 0.1.0 */ public function forCall(string $callId, string $approvedHash = ''): self { return new self($this->userId, $this->sessionId, $this->runId, $this->step, $this->maxSteps, $callId, $this->tainted, $approvedHash, $this->agentTools, $this->via, $this->surfaceOn, $this->permissionMode); } /** * @since 0.1.0 */ public function idempotencyKey(): string { return $this->runId . ':' . $this->callId; } /** * Whether this is the run's last step (the model must answer with what it has). * * @since 0.1.0 */ public function lastStep(): bool { return $this->maxSteps > 0 && $this->step + 1 >= $this->maxSteps; } /** * A stable hash of a tool call: an approval covers these exact arguments and nothing else. * * @since 0.1.0 * @param array $args */ public static function hash(string $name, array $args): string { return hash('sha256', $name . "\0" . (string) json_encode(self::canonical($args), JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE)); } private static function canonical(mixed $value): mixed { if (!is_array($value)) { return $value; } if (!array_is_list($value)) { ksort($value); } return array_map([self::class, 'canonical'], $value); } }