-
Notifications
You must be signed in to change notification settings - Fork 5
Expand file tree
/
Copy pathclass-wp-agent-workflow-spec.php
More file actions
176 lines (159 loc) · 5.29 KB
/
Copy pathclass-wp-agent-workflow-spec.php
File metadata and controls
176 lines (159 loc) · 5.29 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
<?php
/**
* Immutable value object representing a parsed and validated workflow spec.
*
* The spec is the wire-level shape of a workflow:
*
* [
* 'id' => 'my-plugin/triage-comments',
* 'version' => '1.0.0',
* 'inputs' => [ 'comment_id' => [ 'type' => 'integer', 'required' => true ] ],
* 'steps' => [
* [ 'id' => 'classify', 'type' => 'agent', 'agent' => 'ops-triage', ... ],
* [ 'id' => 'create-issue', 'type' => 'ability', 'ability' => 'linear/create-issue', ... ],
* ],
* 'triggers' => [
* [ 'type' => 'on_demand' ],
* [ 'type' => 'wp_action', 'hook' => 'comment_post' ],
* ],
* ]
*
* Construct via {@see from_array()}, which validates and freezes the input.
* Direct construction is allowed for callers that have already validated
* the array elsewhere (e.g. the registry restoring a previously-validated
* spec from its in-memory map).
*
* Specs are deliberately storage-agnostic — they have no opinion about
* where they came from (PHP file, CPT, custom table, REST upload) or how
* they're serialised. Storage adapters live in
* {@see WP_Agent_Workflow_Store} implementations.
*
* @package AgentsAPI
* @since 0.103.0
*/
namespace AgentsAPI\AI\Workflows;
use WP_Error;
defined( 'ABSPATH' ) || exit;
final class WP_Agent_Workflow_Spec {
/**
* @since 0.103.0
*
* @param string $id Stable, namespaced workflow identifier (`my-plugin/triage-comments`).
* @param string $version Caller-defined version string (semver suggested but not enforced).
* @param array<string, mixed> $inputs Map of `<input_name> => <schema>`. Each schema is a JSON-Schema-like
* fragment with `type`, optional `required`, optional `default`.
* @param array<int, array<string, mixed>> $steps Ordered list of step definitions. v0 supports `ability` and `agent`
* types; consumers may register additional types via a step handler map.
* @param array<int, array<string, mixed>> $triggers List of trigger definitions. v0 supports `on_demand`, `wp_action`, `cron`.
* @param array<mixed> $meta Free-form metadata for the registering plugin. Opaque to the runner.
* @param array<mixed> $raw The full raw array the spec was constructed from. Used for round-tripping
* a spec back to its caller-supplied shape.
*/
public function __construct(
private string $id,
private string $version,
private array $inputs,
private array $steps,
private array $triggers,
private array $meta,
private array $raw
) {}
/**
* Build a Spec from a raw array, returning a `WP_Error` on validation
* failure. Use {@see WP_Agent_Workflow_Spec_Validator::validate()} when
* you need detailed structured errors instead of just the first one.
*
* @since 0.103.0
*
* @param array<mixed> $raw Raw spec input.
* @return self|WP_Error
*/
public static function from_array( array $raw ) {
$errors = WP_Agent_Workflow_Spec_Validator::validate( $raw );
if ( ! empty( $errors ) ) {
$first = $errors[0];
return new WP_Error(
'workflow_spec_invalid',
$first['message'],
array(
'errors' => $errors,
'path' => $first['path'],
'code' => $first['code'],
)
);
}
$id = is_string( $raw['id'] ?? null ) ? $raw['id'] : '';
$version = isset( $raw['version'] ) && is_scalar( $raw['version'] ) ? (string) $raw['version'] : '0.0.0';
return new self(
$id,
$version,
isset( $raw['inputs'] ) && is_array( $raw['inputs'] ) ? self::string_keyed_array( $raw['inputs'] ) : array(),
isset( $raw['steps'] ) && is_array( $raw['steps'] ) ? self::list_of_arrays( $raw['steps'] ) : array(),
isset( $raw['triggers'] ) && is_array( $raw['triggers'] ) ? self::list_of_arrays( $raw['triggers'] ) : array(),
isset( $raw['meta'] ) && is_array( $raw['meta'] ) ? $raw['meta'] : array(),
$raw
);
}
public function get_id(): string {
return $this->id;
}
public function get_version(): string {
return $this->version;
}
/**
* @return array<string, mixed> Input schemas keyed by input name.
*/
public function get_inputs(): array {
return $this->inputs;
}
/**
* @return array<int, array<string, mixed>> Step definitions in order.
*/
public function get_steps(): array {
return $this->steps;
}
/**
* @return array<int, array<string, mixed>> Trigger definitions.
*/
public function get_triggers(): array {
return $this->triggers;
}
/**
* @return array<mixed>
*/
public function get_meta(): array {
return $this->meta;
}
/**
* @param array<mixed> $items
* @return array<string, mixed>
*/
private static function string_keyed_array( array $items ): array {
$out = array();
foreach ( $items as $key => $value ) {
if ( is_string( $key ) ) {
$out[ $key ] = $value;
}
}
return $out;
}
/**
* @param array<mixed> $items
* @return array<int, array<string, mixed>>
*/
private static function list_of_arrays( array $items ): array {
$out = array();
foreach ( $items as $value ) {
if ( is_array( $value ) ) {
$out[] = self::string_keyed_array( $value );
}
}
return $out;
}
/**
* @return array<mixed> The raw spec array as supplied to {@see from_array()}.
*/
public function to_array(): array {
return $this->raw;
}
}