@@ -39,7 +39,7 @@ class Tool
3939 /** @var array <int, string> */
4040 protected array $ requiredParameters = [];
4141
42- /** @var Closure():mixed|callable():mixed */
42+ /** @var Closure():mixed|callable():mixed|null */
4343 protected $ fn ;
4444
4545 /** @var null|false|Closure(Throwable,array<int|string,mixed>):string */
@@ -68,6 +68,10 @@ public function for(string $description): self
6868
6969 public function using (Closure |callable $ fn ): self
7070 {
71+ if ($ fn === $ this ) {
72+ return $ this ;
73+ }
74+
7175 $ this ->fn = $ fn ;
7276
7377 return $ this ;
@@ -262,7 +266,9 @@ public function failedHandler(): null|false|Closure
262266 public function handle (...$ args ): string |ToolOutput |ToolError
263267 {
264268 try {
265- $ value = call_user_func ($ this ->fn , ...$ args );
269+ $ callable = $ this ->resolveHandler ();
270+
271+ $ value = call_user_func ($ callable , ...$ args );
266272
267273 if (is_string ($ value )) {
268274 return $ value ;
@@ -281,6 +287,35 @@ public function handle(...$args): string|ToolOutput|ToolError
281287 }
282288 }
283289
290+ /**
291+ * Resolve the callable handler for this tool.
292+ *
293+ * Priority: explicit $fn > invokable subclass (__invoke) > error.
294+ * Also unwraps SerializableClosure wrappers that break named arguments.
295+ */
296+ protected function resolveHandler (): callable
297+ {
298+ $ fn = $ this ->fn ;
299+
300+ if ($ fn === null && method_exists ($ this , '__invoke ' )) {
301+ $ fn = $ this ;
302+ }
303+
304+ if ($ fn === null ) {
305+ throw new PrismException ("Tool handler not defined for tool: {$ this ->name }" );
306+ }
307+
308+ // After ProcessDriver deserialization, $fn may become a
309+ // SerializableClosure\Serializers\Native whose __invoke doesn't
310+ // forward PHP 8 named arguments. Unwrap via getClosure() to
311+ // recover the real Closure so named-arg spreading works.
312+ if (is_object ($ fn ) && method_exists ($ fn , 'getClosure ' )) {
313+ return $ fn ->getClosure ();
314+ }
315+
316+ return $ fn ;
317+ }
318+
284319 protected function shouldHandleErrors (): bool
285320 {
286321 return $ this ->failedHandler !== false ;
0 commit comments