-
Notifications
You must be signed in to change notification settings - Fork 5
Expand file tree
/
Copy pathclass-wp-agent-tool-execution-core.php
More file actions
195 lines (172 loc) · 7.93 KB
/
Copy pathclass-wp-agent-tool-execution-core.php
File metadata and controls
195 lines (172 loc) · 7.93 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
<?php
/**
* Generic tool-call mediation core.
*
* @package AgentsAPI
*/
namespace AgentsAPI\AI\Tools;
defined( 'ABSPATH' ) || exit;
/**
* Validates and mediates product-neutral tool calls.
*/
class WP_Agent_Tool_Execution_Core {
public const EXECUTOR_CLIENT = WP_Agent_Tool_Declaration::EXECUTOR_CLIENT;
/**
* Per-target executor registry, or null until first resolved.
*
* @var WP_Agent_Tool_Executor_Registry|null
*/
private ?WP_Agent_Tool_Executor_Registry $executor_registry;
/**
* @param WP_Agent_Tool_Executor_Registry|null $executor_registry Optional registry of
* target-keyed executors. When omitted, it is resolved lazily from the
* `agents_api_tool_executors` filter on first use so callers that never
* register a target keep the single-executor path unchanged.
*/
public function __construct( ?WP_Agent_Tool_Executor_Registry $executor_registry = null ) {
$this->executor_registry = $executor_registry;
}
/**
* Prepare a tool call for a caller-supplied execution adapter.
*
* @param string $tool_name Tool identifier.
* @param array<mixed> $tool_parameters Runtime tool-call parameters.
* @param array<mixed> $available_tools Tool declarations keyed by name.
* @param array<mixed> $context Host runtime context for this invocation.
* @return array<string, mixed> Prepared call or normalized error result.
*/
public function prepareWP_Agent_Tool_Call( string $tool_name, array $tool_parameters, array $available_tools, array $context = array() ): array {
$provider_tool_name = $tool_name;
$tool_name = WP_Agent_Tool_Declaration::canonicalNameForProviderToolName( $tool_name, $available_tools );
$tool_definition = $available_tools[ $tool_name ] ?? null;
if ( ! is_array( $tool_definition ) ) {
return array_merge(
array( 'ready' => false ),
WP_Agent_Tool_Result::error( $provider_tool_name, "Tool '{$provider_tool_name}' not found", array( 'error_type' => 'tool_not_found' ) )
);
}
$runtime = WP_Agent_Tool_Declaration::normalizeRuntimeMetadata( $tool_definition['runtime'] ?? array() );
$parameters = WP_Agent_Tool_Parameters::buildParameters( $tool_parameters, $context, $tool_definition );
$validation = WP_Agent_Tool_Parameters::validateRequiredParameters( $parameters, $tool_definition );
if ( ! $validation['valid'] ) {
return array_merge(
array( 'ready' => false ),
WP_Agent_Tool_Result::error(
$tool_name,
sprintf( 'Tool "%s" requires the following parameters: %s.', $tool_name, implode( ', ', $validation['missing'] ) ),
array(
'error_type' => 'missing_required_parameters',
'missing_parameters' => $validation['missing'],
),
$runtime
)
);
}
$tool_call = array(
'tool_name' => $tool_name,
'parameters' => $parameters,
'metadata' => array(
'source' => $tool_definition['source'] ?? WP_Agent_Tool_Declaration::sourceFromName( $tool_name ),
'provider_tool_name' => $provider_tool_name,
),
);
$tool_call_id = $context['tool_call_id'] ?? '';
if ( is_string( $tool_call_id ) && '' !== trim( $tool_call_id ) ) {
$tool_call['id'] = trim( $tool_call_id );
}
return array(
'ready' => true,
'tool_call' => WP_Agent_Tool_Call::normalize( $tool_call ),
'tool_def' => $tool_definition,
);
}
/**
* Execute a prepared tool call through a caller-supplied adapter.
*
* @param array<mixed> $tool_call Prepared tool call.
* @param array<mixed> $tool_definition Normalized tool declaration.
* @param WP_Agent_Tool_Executor $executor Host runtime execution adapter.
* @param array<mixed> $context Host runtime context for this invocation.
* @return array<string, mixed> Normalized execution result.
*/
public function executePreparedTool( array $tool_call, array $tool_definition, WP_Agent_Tool_Executor $executor, array $context = array() ): array {
$tool_call = WP_Agent_Tool_Call::normalize( $tool_call );
$executor = $this->resolveExecutorForTool( $tool_definition, $executor, $context );
try {
$result = $executor->executeWP_Agent_Tool_Call( $tool_call, $tool_definition, $context );
} catch ( \Throwable $throwable ) {
$tool_name = is_string( $tool_call['tool_name'] ?? null ) ? $tool_call['tool_name'] : '';
return WP_Agent_Tool_Result::error(
$tool_name,
$throwable->getMessage(),
array( 'error_type' => 'executor_exception' ),
WP_Agent_Tool_Declaration::normalizeRuntimeMetadata( $tool_definition['runtime'] ?? array() )
);
}
$runtime = array_merge(
WP_Agent_Tool_Declaration::normalizeRuntimeMetadata( $tool_definition['runtime'] ?? array() ),
WP_Agent_Tool_Declaration::normalizeRuntimeMetadata( $result['runtime'] ?? array() )
);
if ( ! array_key_exists( 'success', $result ) ) {
$result = array(
'success' => true,
'tool_name' => is_string( $tool_call['tool_name'] ?? null ) ? $tool_call['tool_name'] : '',
'result' => $result,
);
}
$result['tool_name'] = is_string( $result['tool_name'] ?? null ) ? $result['tool_name'] : ( is_string( $tool_call['tool_name'] ?? null ) ? $tool_call['tool_name'] : '' );
if ( ! empty( $runtime ) ) {
$result['runtime'] = $runtime;
}
return WP_Agent_Tool_Result::normalize( $result );
}
/**
* Prepare and execute a tool call through a caller-supplied adapter.
*
* @param string $tool_name Tool identifier.
* @param array<mixed> $tool_parameters Runtime tool-call parameters.
* @param array<mixed> $available_tools Tool declarations keyed by name.
* @param WP_Agent_Tool_Executor $executor Host runtime execution adapter.
* @param array<mixed> $context Host runtime context for this invocation.
* @return array<string, mixed> Normalized execution result.
*/
public function executeTool( string $tool_name, array $tool_parameters, array $available_tools, WP_Agent_Tool_Executor $executor, array $context = array() ): array {
$prepared = $this->prepareWP_Agent_Tool_Call( $tool_name, $tool_parameters, $available_tools, $context );
if ( empty( $prepared['ready'] ) ) {
unset( $prepared['ready'] );
return $prepared;
}
$tool_call = $prepared['tool_call'] ?? null;
$tool_def = $prepared['tool_def'] ?? null;
if ( ! is_array( $tool_call ) || ! is_array( $tool_def ) ) {
return WP_Agent_Tool_Result::error( $tool_name, 'Prepared tool call is invalid.', array( 'error_type' => 'invalid_prepared_tool_call' ) );
}
return $this->executePreparedTool( $tool_call, $tool_def, $executor, $context );
}
/**
* Resolve the executor for a single tool call.
*
* When the tool declaration names an execution target (`runtime.executor_target`)
* that a consumer registered through the `agents_api_tool_executors` filter, the
* registered executor handles the call. Any tool with no target, or a target with
* no registered executor, dispatches through the caller-provided default executor
* exactly as before — the single-executor path and existing ability-backed tools
* are unchanged.
*
* @param array<mixed> $tool_definition Tool declaration selected for the call.
* @param WP_Agent_Tool_Executor $default_executor Caller-provided default executor.
* @param array<mixed> $context Host runtime context for this invocation.
* @return WP_Agent_Tool_Executor Executor to dispatch the tool call to.
*/
private function resolveExecutorForTool( array $tool_definition, WP_Agent_Tool_Executor $default_executor, array $context ): WP_Agent_Tool_Executor {
// Skip registry resolution entirely when the tool names no target, so the
// common ability-backed path never builds or consults a registry.
if ( '' === WP_Agent_Tool_Executor_Registry::targetIdFromDeclaration( $tool_definition ) ) {
return $default_executor;
}
if ( null === $this->executor_registry ) {
$this->executor_registry = WP_Agent_Tool_Executor_Registry::fromFilters( $context );
}
return $this->executor_registry->resolveForTool( $tool_definition, $default_executor );
}
}