@wp-typia/project-tools 0.22.2 → 0.22.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (62) hide show
  1. package/dist/runtime/built-in-block-code-templates/interactivity.d.ts +1 -1
  2. package/dist/runtime/built-in-block-code-templates/interactivity.js +4 -2
  3. package/dist/runtime/cli-add-block-json.d.ts +31 -0
  4. package/dist/runtime/cli-add-block-json.js +65 -0
  5. package/dist/runtime/cli-add-collision.d.ts +129 -0
  6. package/dist/runtime/cli-add-collision.js +293 -0
  7. package/dist/runtime/cli-add-filesystem.d.ts +29 -0
  8. package/dist/runtime/cli-add-filesystem.js +77 -0
  9. package/dist/runtime/cli-add-help.d.ts +4 -0
  10. package/dist/runtime/cli-add-help.js +41 -0
  11. package/dist/runtime/cli-add-shared.d.ts +6 -255
  12. package/dist/runtime/cli-add-shared.js +6 -391
  13. package/dist/runtime/cli-add-types.d.ts +247 -0
  14. package/dist/runtime/cli-add-types.js +64 -0
  15. package/dist/runtime/cli-add-validation.d.ts +87 -0
  16. package/dist/runtime/cli-add-validation.js +147 -0
  17. package/dist/runtime/cli-add-workspace-ability-scaffold.d.ts +5 -0
  18. package/dist/runtime/cli-add-workspace-ability-scaffold.js +366 -0
  19. package/dist/runtime/cli-add-workspace-ability-templates.d.ts +34 -0
  20. package/dist/runtime/cli-add-workspace-ability-templates.js +500 -0
  21. package/dist/runtime/cli-add-workspace-ability-types.d.ts +27 -0
  22. package/dist/runtime/cli-add-workspace-ability-types.js +14 -0
  23. package/dist/runtime/cli-add-workspace-ability.js +12 -852
  24. package/dist/runtime/cli-add-workspace-admin-view-scaffold.js +35 -61
  25. package/dist/runtime/cli-add-workspace-ai-scaffold.d.ts +21 -0
  26. package/dist/runtime/cli-add-workspace-ai-scaffold.js +87 -0
  27. package/dist/runtime/cli-add-workspace-ai-templates.d.ts +4 -0
  28. package/dist/runtime/cli-add-workspace-ai-templates.js +607 -0
  29. package/dist/runtime/cli-add-workspace-ai.js +15 -688
  30. package/dist/runtime/cli-add-workspace-assets.js +7 -4
  31. package/dist/runtime/cli-add-workspace-mutation.d.ts +30 -0
  32. package/dist/runtime/cli-add-workspace-mutation.js +60 -0
  33. package/dist/runtime/cli-add-workspace.js +2 -98
  34. package/dist/runtime/cli-add.d.ts +2 -2
  35. package/dist/runtime/cli-add.js +2 -2
  36. package/dist/runtime/cli-doctor-workspace-bindings.d.ts +11 -0
  37. package/dist/runtime/cli-doctor-workspace-bindings.js +134 -0
  38. package/dist/runtime/cli-doctor-workspace-blocks.d.ts +11 -0
  39. package/dist/runtime/cli-doctor-workspace-blocks.js +439 -0
  40. package/dist/runtime/cli-doctor-workspace-features.d.ts +11 -0
  41. package/dist/runtime/cli-doctor-workspace-features.js +383 -0
  42. package/dist/runtime/cli-doctor-workspace-package.d.ts +18 -0
  43. package/dist/runtime/cli-doctor-workspace-package.js +59 -0
  44. package/dist/runtime/cli-doctor-workspace-shared.d.ts +69 -0
  45. package/dist/runtime/cli-doctor-workspace-shared.js +87 -0
  46. package/dist/runtime/cli-doctor-workspace.js +25 -1062
  47. package/dist/runtime/index.d.ts +2 -0
  48. package/dist/runtime/index.js +1 -0
  49. package/dist/runtime/migration-utils.d.ts +2 -1
  50. package/dist/runtime/migration-utils.js +3 -11
  51. package/dist/runtime/package-managers.d.ts +19 -0
  52. package/dist/runtime/package-managers.js +62 -0
  53. package/dist/runtime/template-source-cache.d.ts +59 -0
  54. package/dist/runtime/template-source-cache.js +160 -0
  55. package/dist/runtime/ts-source-masking.d.ts +28 -0
  56. package/dist/runtime/ts-source-masking.js +104 -0
  57. package/dist/runtime/typia-llm.d.ts +9 -1
  58. package/dist/runtime/typia-llm.js +20 -5
  59. package/dist/runtime/workspace-inventory.js +116 -59
  60. package/dist/runtime/workspace-project.d.ts +1 -1
  61. package/dist/runtime/workspace-project.js +2 -10
  62. package/package.json +4 -4
@@ -0,0 +1,607 @@
1
+ import { quotePhpString } from "./php-utils.js";
2
+ import { toTitleCase } from "./string-case.js";
3
+ /**
4
+ * Build the generated PHP controller and hook surface for a workspace AI feature.
5
+ */
6
+ export function buildAiFeaturePhpSource(aiFeatureSlug, namespace, phpPrefix, textDomain) {
7
+ const aiFeatureTitle = toTitleCase(aiFeatureSlug);
8
+ const aiFeaturePhpId = aiFeatureSlug.replace(/-/g, "_");
9
+ const loadSchemaFunctionName = `${phpPrefix}_${aiFeaturePhpId}_load_ai_feature_schema`;
10
+ const loadAiSchemaFunctionName = `${phpPrefix}_${aiFeaturePhpId}_load_ai_response_schema`;
11
+ const normalizeSchemaFunctionName = `${phpPrefix}_${aiFeaturePhpId}_sanitize_ai_feature_schema`;
12
+ const validatePayloadFunctionName = `${phpPrefix}_${aiFeaturePhpId}_validate_ai_feature_payload`;
13
+ const canManageFunctionName = `${phpPrefix}_${aiFeaturePhpId}_can_manage_ai_feature`;
14
+ const normalizePromptPayloadFunctionName = `${phpPrefix}_${aiFeaturePhpId}_normalize_ai_feature_prompt_payload`;
15
+ const buildPromptFunctionName = `${phpPrefix}_${aiFeaturePhpId}_build_ai_feature_prompt`;
16
+ const resolvePromptOptionsFunctionName = `${phpPrefix}_${aiFeaturePhpId}_resolve_ai_feature_prompt_options`;
17
+ const normalizeProviderTypeFunctionName = `${phpPrefix}_${aiFeaturePhpId}_normalize_provider_type`;
18
+ const buildTelemetryFunctionName = `${phpPrefix}_${aiFeaturePhpId}_build_ai_feature_telemetry`;
19
+ const resolveUnavailableMessageFunctionName = `${phpPrefix}_${aiFeaturePhpId}_resolve_ai_feature_unavailable_message`;
20
+ const isSupportedFunctionName = `${phpPrefix}_${aiFeaturePhpId}_is_ai_feature_supported`;
21
+ const adminNoticeFunctionName = `${phpPrefix}_${aiFeaturePhpId}_ai_feature_admin_notice`;
22
+ const handlerFunctionName = `${phpPrefix}_${aiFeaturePhpId}_handle_run_ai_feature`;
23
+ const registerRoutesFunctionName = `${phpPrefix}_${aiFeaturePhpId}_register_ai_feature_routes`;
24
+ const permissionFilterHook = `${phpPrefix}_${aiFeaturePhpId}_ai_feature_permission`;
25
+ const promptPayloadFilterHook = `${phpPrefix}_${aiFeaturePhpId}_ai_feature_prompt_payload`;
26
+ const promptFilterHook = `${phpPrefix}_${aiFeaturePhpId}_ai_feature_prompt`;
27
+ const promptOptionsFilterHook = `${phpPrefix}_${aiFeaturePhpId}_ai_feature_prompt_options`;
28
+ const adminNoticeMessageFilterHook = `${phpPrefix}_${aiFeaturePhpId}_ai_feature_admin_notice_message`;
29
+ const unavailableMessageFilterHook = `${phpPrefix}_${aiFeaturePhpId}_ai_feature_unavailable_message`;
30
+ const telemetryFilterHook = `${phpPrefix}_${aiFeaturePhpId}_ai_feature_telemetry`;
31
+ return `<?php
32
+ if ( ! defined( 'ABSPATH' ) ) {
33
+ \treturn;
34
+ }
35
+
36
+ /*
37
+ * Customization hooks for the ${aiFeatureTitle} AI feature:
38
+ *
39
+ * - ${quotePhpString(permissionFilterHook)} filters the default current_user_can( 'edit_posts' ) capability check.
40
+ * - ${quotePhpString(promptPayloadFilterHook)} filters the validated request payload array before prompt serialization.
41
+ * - ${quotePhpString(promptFilterHook)} filters the final prompt string after payload normalization.
42
+ * - ${quotePhpString(promptOptionsFilterHook)} filters prompt options with \`temperature\` and \`modelPreference\` keys.
43
+ * - ${quotePhpString(adminNoticeMessageFilterHook)} filters the wp-admin notice shown when AI support is unavailable.
44
+ * - ${quotePhpString(unavailableMessageFilterHook)} filters REST-facing unavailable messages by reason code.
45
+ * - ${quotePhpString(telemetryFilterHook)} filters the response telemetry array before schema validation. Return a schema-compatible array.
46
+ *
47
+ * Compatibility note: this server-only endpoint avoids WordPress script-module enqueue APIs so older sites can load the generated feature file safely.
48
+ */
49
+
50
+ if ( ! function_exists( '${loadSchemaFunctionName}' ) ) {
51
+ \tfunction ${loadSchemaFunctionName}( $schema_name ) {
52
+ \t\t$project_root = dirname( __DIR__, 2 );
53
+ \t\t$schema_path = $project_root . '/src/ai-features/${aiFeatureSlug}/api-schemas/' . $schema_name . '.schema.json';
54
+ \t\tif ( ! file_exists( $schema_path ) ) {
55
+ \t\t\treturn null;
56
+ \t\t}
57
+
58
+ \t\t$decoded = json_decode( file_get_contents( $schema_path ), true );
59
+ \t\treturn is_array( $decoded ) ? $decoded : null;
60
+ \t}
61
+ }
62
+
63
+ if ( ! function_exists( '${loadAiSchemaFunctionName}' ) ) {
64
+ \tfunction ${loadAiSchemaFunctionName}() {
65
+ \t\t$project_root = dirname( __DIR__, 2 );
66
+ \t\t$schema_path = $project_root . '/src/ai-features/${aiFeatureSlug}/ai-schemas/feature-result.ai.schema.json';
67
+ \t\tif ( ! file_exists( $schema_path ) ) {
68
+ \t\t\treturn null;
69
+ \t\t}
70
+
71
+ \t\t$decoded = json_decode( file_get_contents( $schema_path ), true );
72
+ \t\treturn is_array( $decoded ) ? $decoded : null;
73
+ \t}
74
+ }
75
+
76
+ if ( ! function_exists( '${normalizeSchemaFunctionName}' ) ) {
77
+ \tfunction ${normalizeSchemaFunctionName}( $schema ) {
78
+ \t\tif ( ! is_array( $schema ) ) {
79
+ \t\t\treturn $schema;
80
+ \t\t}
81
+
82
+ \t\tunset( $schema['$schema'], $schema['title'] );
83
+
84
+ \t\tif ( isset( $schema['properties'] ) && is_array( $schema['properties'] ) ) {
85
+ \t\t\tforeach ( $schema['properties'] as $key => $property_schema ) {
86
+ \t\t\t\t$schema['properties'][ $key ] = ${normalizeSchemaFunctionName}( $property_schema );
87
+ \t\t\t}
88
+ \t\t}
89
+
90
+ \t\tif ( isset( $schema['items'] ) && is_array( $schema['items'] ) ) {
91
+ \t\t\t$schema['items'] = ${normalizeSchemaFunctionName}( $schema['items'] );
92
+ \t\t}
93
+
94
+ \t\treturn $schema;
95
+ \t}
96
+ }
97
+
98
+ if ( ! function_exists( '${validatePayloadFunctionName}' ) ) {
99
+ \tfunction ${validatePayloadFunctionName}( $value, $schema_name, $param_name ) {
100
+ \t\t$schema = ${loadSchemaFunctionName}( $schema_name );
101
+ \t\tif ( ! is_array( $schema ) ) {
102
+ \t\t\treturn new WP_Error( 'missing_schema', 'Missing AI feature schema.', array( 'status' => 500 ) );
103
+ \t\t}
104
+
105
+ \t\t$rest_schema = ${normalizeSchemaFunctionName}( $schema );
106
+ \t\t$validation = rest_validate_value_from_schema( $value, $rest_schema, $param_name );
107
+ \t\tif ( is_wp_error( $validation ) ) {
108
+ \t\t\treturn $validation;
109
+ \t\t}
110
+
111
+ \t\treturn rest_sanitize_value_from_schema( $value, $rest_schema, $param_name );
112
+ \t}
113
+ }
114
+
115
+ if ( ! function_exists( '${canManageFunctionName}' ) ) {
116
+ \tfunction ${canManageFunctionName}( WP_REST_Request $request = null ) {
117
+ \t\t$permission = apply_filters(
118
+ \t\t\t${quotePhpString(permissionFilterHook)},
119
+ \t\t\tcurrent_user_can( 'edit_posts' ),
120
+ \t\t\t$request
121
+ \t\t);
122
+ \t\tif ( is_wp_error( $permission ) ) {
123
+ \t\t\treturn $permission;
124
+ \t\t}
125
+ \t\treturn (bool) $permission;
126
+ \t}
127
+ }
128
+
129
+ if ( ! function_exists( '${normalizePromptPayloadFunctionName}' ) ) {
130
+ \tfunction ${normalizePromptPayloadFunctionName}( array $payload ) {
131
+ \t\t$normalized_payload = apply_filters(
132
+ \t\t\t${quotePhpString(promptPayloadFilterHook)},
133
+ \t\t\t$payload
134
+ \t\t);
135
+ \t\treturn is_array( $normalized_payload ) ? $normalized_payload : $payload;
136
+ \t}
137
+ }
138
+
139
+ if ( ! function_exists( '${buildPromptFunctionName}' ) ) {
140
+ \tfunction ${buildPromptFunctionName}( array $payload ) {
141
+ \t\t$normalized_payload = ${normalizePromptPayloadFunctionName}( $payload );
142
+ \t\t$prompt = sprintf(
143
+ \t\t\t'You are helping with the %1$s AI workflow. Read the JSON request payload and return JSON that matches the provided schema. Request payload: %2$s',
144
+ \t\t\t${quotePhpString(aiFeatureTitle)},
145
+ \t\t\twp_json_encode( $normalized_payload )
146
+ \t\t);
147
+ \t\t$filtered_prompt = apply_filters(
148
+ \t\t\t${quotePhpString(promptFilterHook)},
149
+ \t\t\t$prompt,
150
+ \t\t\t$normalized_payload,
151
+ \t\t\t$payload
152
+ \t\t);
153
+ \t\treturn is_string( $filtered_prompt ) && '' !== $filtered_prompt ? $filtered_prompt : $prompt;
154
+ \t}
155
+ }
156
+
157
+ if ( ! function_exists( '${resolvePromptOptionsFunctionName}' ) ) {
158
+ \tfunction ${resolvePromptOptionsFunctionName}( array $payload = array() ) {
159
+ \t\t$options = apply_filters(
160
+ \t\t\t${quotePhpString(promptOptionsFilterHook)},
161
+ \t\t\tarray(
162
+ \t\t\t\t'modelPreference' => array(),
163
+ \t\t\t\t'temperature' => 0.2,
164
+ \t\t\t),
165
+ \t\t\t$payload
166
+ \t\t);
167
+ \t\tif ( ! is_array( $options ) ) {
168
+ \t\t\t$options = array();
169
+ \t\t}
170
+
171
+ \t\t$temperature = 0.2;
172
+ \t\tif ( array_key_exists( 'temperature', $options ) ) {
173
+ \t\t\tif ( null === $options['temperature'] ) {
174
+ \t\t\t\t$temperature = null;
175
+ \t\t\t} elseif ( is_numeric( $options['temperature'] ) ) {
176
+ \t\t\t\t$temperature = (float) $options['temperature'];
177
+ \t\t\t}
178
+ \t\t}
179
+
180
+ \t\t$model_preferences = array();
181
+ \t\tif ( isset( $options['modelPreference'] ) ) {
182
+ \t\t\t$raw_model_preferences = $options['modelPreference'];
183
+ \t\t\tif ( is_string( $raw_model_preferences ) && '' !== $raw_model_preferences ) {
184
+ \t\t\t\t$model_preferences = array( $raw_model_preferences );
185
+ \t\t\t} elseif ( is_array( $raw_model_preferences ) ) {
186
+ \t\t\t\t$model_preferences = array_values(
187
+ \t\t\t\t\tarray_filter(
188
+ \t\t\t\t\t\tarray_map(
189
+ \t\t\t\t\t\t\tstatic function ( $candidate ) {
190
+ \t\t\t\t\t\t\t\tif ( is_string( $candidate ) && '' !== $candidate ) {
191
+ \t\t\t\t\t\t\t\t\treturn $candidate;
192
+ \t\t\t\t\t\t\t\t}
193
+ \t\t\t\t\t\t\t\tif ( ! is_array( $candidate ) ) {
194
+ \t\t\t\t\t\t\t\t\treturn null;
195
+ \t\t\t\t\t\t\t\t}
196
+
197
+ \t\t\t\t\t\t\t\t$normalized = array_values(
198
+ \t\t\t\t\t\t\t\t\tarray_filter(
199
+ \t\t\t\t\t\t\t\t\t\t$candidate,
200
+ \t\t\t\t\t\t\t\t\t\tstatic function ( $value ) {
201
+ \t\t\t\t\t\t\t\t\t\t\treturn is_string( $value ) && '' !== $value;
202
+ \t\t\t\t\t\t\t\t\t\t}
203
+ \t\t\t\t\t\t\t\t\t)
204
+ \t\t\t\t\t\t\t\t);
205
+
206
+ \t\t\t\t\t\t\t\treturn count( $normalized ) > 0 ? $normalized : null;
207
+ \t\t\t\t\t\t\t},
208
+ \t\t\t\t\t\t\t$raw_model_preferences
209
+ \t\t\t\t\t\t),
210
+ \t\t\t\t\t\tstatic function ( $candidate ) {
211
+ \t\t\t\t\t\t\treturn null !== $candidate;
212
+ \t\t\t\t\t\t}
213
+ \t\t\t\t\t)
214
+ \t\t\t\t);
215
+ \t\t\t}
216
+ \t\t}
217
+
218
+ \t\treturn array(
219
+ \t\t\t'modelPreference' => $model_preferences,
220
+ \t\t\t'temperature' => $temperature,
221
+ \t\t);
222
+ \t}
223
+ }
224
+
225
+ if ( ! function_exists( '${normalizeProviderTypeFunctionName}' ) ) {
226
+ \tfunction ${normalizeProviderTypeFunctionName}( $provider_type ) {
227
+ \t\tif ( is_object( $provider_type ) && isset( $provider_type->value ) && is_string( $provider_type->value ) ) {
228
+ \t\t\treturn $provider_type->value;
229
+ \t\t}
230
+
231
+ \t\treturn is_string( $provider_type ) && '' !== $provider_type ? $provider_type : 'cloud';
232
+ \t}
233
+ }
234
+
235
+ if ( ! function_exists( '${buildTelemetryFunctionName}' ) ) {
236
+ \tfunction ${buildTelemetryFunctionName}( $result, array $payload = array(), array $normalized_result = array() ) {
237
+ \t\tif (
238
+ \t\t\t! is_object( $result ) ||
239
+ \t\t\t! method_exists( $result, 'getId' ) ||
240
+ \t\t\t! method_exists( $result, 'getModelMetadata' ) ||
241
+ \t\t\t! method_exists( $result, 'getProviderMetadata' ) ||
242
+ \t\t\t! method_exists( $result, 'getTokenUsage' )
243
+ \t\t) {
244
+ \t\t\treturn new WP_Error(
245
+ \t\t\t\t'ai_client_result_shape',
246
+ \t\t\t\t'The current WordPress AI Client result object is missing telemetry helpers.',
247
+ \t\t\t\tarray( 'status' => 502 )
248
+ \t\t\t);
249
+ \t\t}
250
+
251
+ \t\t$model_metadata = $result->getModelMetadata();
252
+ \t\t$provider_metadata = $result->getProviderMetadata();
253
+ \t\t$token_usage = $result->getTokenUsage();
254
+
255
+ \t\tif (
256
+ \t\t\t! is_object( $model_metadata ) ||
257
+ \t\t\t! method_exists( $model_metadata, 'getId' ) ||
258
+ \t\t\t! method_exists( $model_metadata, 'getName' ) ||
259
+ \t\t\t! is_object( $provider_metadata ) ||
260
+ \t\t\t! method_exists( $provider_metadata, 'getId' ) ||
261
+ \t\t\t! method_exists( $provider_metadata, 'getName' ) ||
262
+ \t\t\t! method_exists( $provider_metadata, 'getType' ) ||
263
+ \t\t\t! is_object( $token_usage ) ||
264
+ \t\t\t! method_exists( $token_usage, 'getCompletionTokens' ) ||
265
+ \t\t\t! method_exists( $token_usage, 'getPromptTokens' ) ||
266
+ \t\t\t! method_exists( $token_usage, 'getTotalTokens' )
267
+ \t\t) {
268
+ \t\t\treturn new WP_Error(
269
+ \t\t\t\t'ai_client_result_shape',
270
+ \t\t\t\t'The current WordPress AI Client telemetry objects are missing expected getters.',
271
+ \t\t\t\tarray( 'status' => 502 )
272
+ \t\t\t);
273
+ \t\t}
274
+
275
+ \t\t$telemetry = array(
276
+ \t\t\t'modelId' => (string) $model_metadata->getId(),
277
+ \t\t\t'modelName' => (string) $model_metadata->getName(),
278
+ \t\t\t'providerId' => (string) $provider_metadata->getId(),
279
+ \t\t\t'providerName' => (string) $provider_metadata->getName(),
280
+ \t\t\t'providerType' => ${normalizeProviderTypeFunctionName}( $provider_metadata->getType() ),
281
+ \t\t\t'resultId' => (string) $result->getId(),
282
+ \t\t\t'tokenUsage' => array(
283
+ \t\t\t\t'completionTokens' => (int) $token_usage->getCompletionTokens(),
284
+ \t\t\t\t'promptTokens' => (int) $token_usage->getPromptTokens(),
285
+ \t\t\t\t'totalTokens' => (int) $token_usage->getTotalTokens(),
286
+ \t\t\t),
287
+ \t\t);
288
+
289
+ \t\tif ( method_exists( $token_usage, 'getThoughtTokens' ) ) {
290
+ \t\t\t$thought_tokens = $token_usage->getThoughtTokens();
291
+ \t\t\tif ( null !== $thought_tokens ) {
292
+ \t\t\t\t$telemetry['tokenUsage']['thoughtTokens'] = (int) $thought_tokens;
293
+ \t\t\t}
294
+ \t\t}
295
+
296
+ \t\t$filtered_telemetry = apply_filters(
297
+ \t\t\t${quotePhpString(telemetryFilterHook)},
298
+ \t\t\t$telemetry,
299
+ \t\t\t$result,
300
+ \t\t\t$payload,
301
+ \t\t\t$normalized_result
302
+ \t\t);
303
+ \t\treturn is_array( $filtered_telemetry ) ? $filtered_telemetry : $telemetry;
304
+ \t}
305
+ }
306
+
307
+ if ( ! function_exists( '${resolveUnavailableMessageFunctionName}' ) ) {
308
+ \tfunction ${resolveUnavailableMessageFunctionName}( $message, $reason, array $context = array() ) {
309
+ \t\t$filtered_message = apply_filters(
310
+ \t\t\t${quotePhpString(unavailableMessageFilterHook)},
311
+ \t\t\t$message,
312
+ \t\t\t$reason,
313
+ \t\t\t$context
314
+ \t\t);
315
+ \t\treturn is_string( $filtered_message ) && '' !== $filtered_message ? $filtered_message : $message;
316
+ \t}
317
+ }
318
+
319
+ if ( ! function_exists( '${isSupportedFunctionName}' ) ) {
320
+ \tfunction ${isSupportedFunctionName}( array $payload = array(), $cache_result = true ) {
321
+ \t\tstatic $is_supported = null;
322
+ \t\t$use_cache = $cache_result && count( $payload ) === 0;
323
+ \t\tif ( $use_cache && null !== $is_supported ) {
324
+ \t\t\treturn $is_supported;
325
+ \t\t}
326
+
327
+ \t\tif ( ! function_exists( 'wp_ai_client_prompt' ) ) {
328
+ \t\t\tif ( $use_cache ) {
329
+ \t\t\t\t$is_supported = false;
330
+ \t\t\t}
331
+ \t\t\treturn false;
332
+ \t\t}
333
+
334
+ \t\t$schema = ${loadAiSchemaFunctionName}();
335
+ \t\tif ( ! is_array( $schema ) ) {
336
+ \t\t\tif ( $use_cache ) {
337
+ \t\t\t\t$is_supported = false;
338
+ \t\t\t}
339
+ \t\t\treturn false;
340
+ \t\t}
341
+
342
+ \t\t$prompt = wp_ai_client_prompt( 'AI feature support probe.' );
343
+ \t\tif ( ! is_object( $prompt ) || ! method_exists( $prompt, 'as_json_response' ) ) {
344
+ \t\t\tif ( $use_cache ) {
345
+ \t\t\t\t$is_supported = false;
346
+ \t\t\t}
347
+ \t\t\treturn false;
348
+ \t\t}
349
+ \t\t$prompt_options = ${resolvePromptOptionsFunctionName}( $payload );
350
+ \t\tif (
351
+ \t\t\tarray_key_exists( 'temperature', $prompt_options ) &&
352
+ \t\t\tnull !== $prompt_options['temperature'] &&
353
+ \t\t\tmethod_exists( $prompt, 'using_temperature' )
354
+ \t\t) {
355
+ \t\t\t$prompt = $prompt->using_temperature( $prompt_options['temperature'] );
356
+ \t\t}
357
+ \t\tif (
358
+ \t\t\t! empty( $prompt_options['modelPreference'] ) &&
359
+ \t\t\tmethod_exists( $prompt, 'using_model_preference' )
360
+ \t\t) {
361
+ \t\t\t$prompt = $prompt->using_model_preference( ...$prompt_options['modelPreference'] );
362
+ \t\t}
363
+
364
+ \t\t$structured_prompt = $prompt->as_json_response( $schema );
365
+ \t\tif ( ! is_object( $structured_prompt ) ) {
366
+ \t\t\tif ( $use_cache ) {
367
+ \t\t\t\t$is_supported = false;
368
+ \t\t\t}
369
+ \t\t\treturn false;
370
+ \t\t}
371
+
372
+ \t\tif ( method_exists( $structured_prompt, 'is_supported_for_text_generation' ) ) {
373
+ \t\t\t$supported = (bool) $structured_prompt->is_supported_for_text_generation();
374
+ \t\t\tif ( $use_cache ) {
375
+ \t\t\t\t$is_supported = $supported;
376
+ \t\t\t}
377
+ \t\t\treturn $supported;
378
+ \t\t}
379
+
380
+ \t\t$supported = method_exists( $structured_prompt, 'generate_text_result' );
381
+ \t\tif ( $use_cache ) {
382
+ \t\t\t$is_supported = $supported;
383
+ \t\t}
384
+ \t\treturn $supported;
385
+ \t}
386
+ }
387
+
388
+ if ( ! function_exists( '${adminNoticeFunctionName}' ) ) {
389
+ \tfunction ${adminNoticeFunctionName}() {
390
+ \t\tif ( ! current_user_can( 'manage_options' ) || ${isSupportedFunctionName}() ) {
391
+ \t\t\treturn;
392
+ \t\t}
393
+
394
+ \t\t$message = sprintf(
395
+ \t\t\t/* translators: %s: AI feature name. */
396
+ \t\t\t__( 'The %s AI feature is optional and remains disabled until the WordPress AI Client is available with structured text generation support for the generated schema.', ${quotePhpString(textDomain)} ),
397
+ \t\t\t${quotePhpString(aiFeatureTitle)}
398
+ \t\t);
399
+ \t\t$filtered_message = apply_filters(
400
+ \t\t\t${quotePhpString(adminNoticeMessageFilterHook)},
401
+ \t\t\t$message,
402
+ \t\t\tarray(
403
+ \t\t\t\t'featureSlug' => ${quotePhpString(aiFeatureSlug)},
404
+ \t\t\t\t'featureTitle' => ${quotePhpString(aiFeatureTitle)},
405
+ \t\t\t\t'namespace' => ${quotePhpString(namespace)},
406
+ \t\t\t)
407
+ \t\t);
408
+ \t\tif ( is_string( $filtered_message ) && '' !== $filtered_message ) {
409
+ \t\t\t$message = $filtered_message;
410
+ \t\t}
411
+ \t\tprintf( '<div class="notice notice-warning"><p>%s</p></div>', esc_html( $message ) );
412
+ \t}
413
+ }
414
+
415
+ if ( ! function_exists( '${handlerFunctionName}' ) ) {
416
+ \tfunction ${handlerFunctionName}( WP_REST_Request $request ) {
417
+ \t\t$payload = ${validatePayloadFunctionName}( $request->get_json_params(), 'feature-request', 'body' );
418
+ \t\tif ( is_wp_error( $payload ) ) {
419
+ \t\t\treturn $payload;
420
+ \t\t}
421
+
422
+ \t\tif ( ! ${isSupportedFunctionName}( $payload, false ) ) {
423
+ \t\t\treturn new WP_Error(
424
+ \t\t\t\t'ai_client_unavailable',
425
+ \t\t\t\t${resolveUnavailableMessageFunctionName}(
426
+ \t\t\t\t\t'The WordPress AI Client is unavailable or does not support this feature endpoint.',
427
+ \t\t\t\t\t'support_probe_failed',
428
+ \t\t\t\t\tarray(
429
+ \t\t\t\t\t\t'featureSlug' => ${quotePhpString(aiFeatureSlug)},
430
+ \t\t\t\t\t)
431
+ \t\t\t\t),
432
+ \t\t\t\tarray( 'status' => 501 )
433
+ \t\t\t);
434
+ \t\t}
435
+
436
+ \t\t$ai_schema = ${loadAiSchemaFunctionName}();
437
+ \t\tif ( ! is_array( $ai_schema ) ) {
438
+ \t\t\treturn new WP_Error(
439
+ \t\t\t\t'ai_client_schema_missing',
440
+ \t\t\t\t'The generated AI response schema is missing for this feature endpoint.',
441
+ \t\t\t\tarray( 'status' => 500 )
442
+ \t\t\t);
443
+ \t\t}
444
+
445
+ \t\t$prompt_options = ${resolvePromptOptionsFunctionName}( $payload );
446
+ \t\t$prompt = wp_ai_client_prompt( ${buildPromptFunctionName}( $payload ) );
447
+ \t\tif ( ! is_object( $prompt ) ) {
448
+ \t\t\treturn new WP_Error(
449
+ \t\t\t\t'ai_client_unavailable',
450
+ \t\t\t\t${resolveUnavailableMessageFunctionName}(
451
+ \t\t\t\t\t'The WordPress AI Client prompt builder is unavailable on this site.',
452
+ \t\t\t\t\t'prompt_builder_missing',
453
+ \t\t\t\t\tarray(
454
+ \t\t\t\t\t\t'featureSlug' => ${quotePhpString(aiFeatureSlug)},
455
+ \t\t\t\t\t)
456
+ \t\t\t\t),
457
+ \t\t\t\tarray( 'status' => 501 )
458
+ \t\t\t);
459
+ \t\t}
460
+
461
+ \t\tif (
462
+ \t\t\tarray_key_exists( 'temperature', $prompt_options ) &&
463
+ \t\t\tnull !== $prompt_options['temperature'] &&
464
+ \t\t\tmethod_exists( $prompt, 'using_temperature' )
465
+ \t\t) {
466
+ \t\t\t$prompt = $prompt->using_temperature( $prompt_options['temperature'] );
467
+ \t\t}
468
+ \t\tif (
469
+ \t\t\t! empty( $prompt_options['modelPreference'] ) &&
470
+ \t\t\tmethod_exists( $prompt, 'using_model_preference' )
471
+ \t\t) {
472
+ \t\t\t$prompt = $prompt->using_model_preference( ...$prompt_options['modelPreference'] );
473
+ \t\t}
474
+ \t\tif ( ! method_exists( $prompt, 'as_json_response' ) ) {
475
+ \t\t\treturn new WP_Error(
476
+ \t\t\t\t'ai_client_unavailable',
477
+ \t\t\t\t${resolveUnavailableMessageFunctionName}(
478
+ \t\t\t\t\t'The current WordPress AI Client does not expose as_json_response().',
479
+ \t\t\t\t\t'as_json_response_missing',
480
+ \t\t\t\t\tarray(
481
+ \t\t\t\t\t\t'featureSlug' => ${quotePhpString(aiFeatureSlug)},
482
+ \t\t\t\t\t)
483
+ \t\t\t\t),
484
+ \t\t\t\tarray( 'status' => 501 )
485
+ \t\t\t);
486
+ \t\t}
487
+
488
+ \t\t$structured_prompt = $prompt->as_json_response( $ai_schema );
489
+ \t\tif ( ! is_object( $structured_prompt ) ) {
490
+ \t\t\treturn new WP_Error(
491
+ \t\t\t\t'ai_client_unavailable',
492
+ \t\t\t\t${resolveUnavailableMessageFunctionName}(
493
+ \t\t\t\t\t'The current WordPress AI Client could not prepare a structured-output prompt.',
494
+ \t\t\t\t\t'structured_prompt_missing',
495
+ \t\t\t\t\tarray(
496
+ \t\t\t\t\t\t'featureSlug' => ${quotePhpString(aiFeatureSlug)},
497
+ \t\t\t\t\t)
498
+ \t\t\t\t),
499
+ \t\t\t\tarray( 'status' => 501 )
500
+ \t\t\t);
501
+ \t\t}
502
+
503
+ \t\tif (
504
+ \t\t\tmethod_exists( $structured_prompt, 'is_supported_for_text_generation' ) &&
505
+ \t\t\t! $structured_prompt->is_supported_for_text_generation()
506
+ \t\t) {
507
+ \t\t\treturn new WP_Error(
508
+ \t\t\t\t'ai_client_unavailable',
509
+ \t\t\t\t${resolveUnavailableMessageFunctionName}(
510
+ \t\t\t\t\t'The current WordPress AI Client provider or model does not support this structured-output feature.',
511
+ \t\t\t\t\t'text_generation_unsupported',
512
+ \t\t\t\t\tarray(
513
+ \t\t\t\t\t\t'featureSlug' => ${quotePhpString(aiFeatureSlug)},
514
+ \t\t\t\t\t)
515
+ \t\t\t\t),
516
+ \t\t\t\tarray( 'status' => 501 )
517
+ \t\t\t);
518
+ \t\t}
519
+ \t\tif ( ! method_exists( $structured_prompt, 'generate_text_result' ) ) {
520
+ \t\t\treturn new WP_Error(
521
+ \t\t\t\t'ai_client_unavailable',
522
+ \t\t\t\t${resolveUnavailableMessageFunctionName}(
523
+ \t\t\t\t\t'The current WordPress AI Client does not expose generate_text_result() after as_json_response().',
524
+ \t\t\t\t\t'generate_text_result_missing',
525
+ \t\t\t\t\tarray(
526
+ \t\t\t\t\t\t'featureSlug' => ${quotePhpString(aiFeatureSlug)},
527
+ \t\t\t\t\t)
528
+ \t\t\t\t),
529
+ \t\t\t\tarray( 'status' => 501 )
530
+ \t\t\t);
531
+ \t\t}
532
+
533
+ \t\t$result = $structured_prompt->generate_text_result();
534
+ \t\tif ( is_wp_error( $result ) ) {
535
+ \t\t\treturn $result;
536
+ \t\t}
537
+ \t\tif ( ! is_object( $result ) || ! method_exists( $result, 'toText' ) ) {
538
+ \t\t\treturn new WP_Error(
539
+ \t\t\t\t'ai_client_result_shape',
540
+ \t\t\t\t'The current WordPress AI Client result does not expose toText().',
541
+ \t\t\t\tarray( 'status' => 502 )
542
+ \t\t\t);
543
+ \t\t}
544
+
545
+ \t\t$decoded_result = json_decode( $result->toText(), true );
546
+ \t\tif ( ! is_array( $decoded_result ) ) {
547
+ \t\t\treturn new WP_Error(
548
+ \t\t\t\t'ai_client_invalid_json',
549
+ \t\t\t\t'The AI feature response did not decode to a JSON object.',
550
+ \t\t\t\tarray( 'status' => 502 )
551
+ \t\t\t);
552
+ \t\t}
553
+
554
+ \t\t$normalized_result = ${validatePayloadFunctionName}( $decoded_result, 'feature-result', 'result' );
555
+ \t\tif ( is_wp_error( $normalized_result ) ) {
556
+ \t\t\treturn new WP_Error(
557
+ \t\t\t\t'ai_client_invalid_response',
558
+ \t\t\t\t$normalized_result->get_error_message(),
559
+ \t\t\t\tarray( 'status' => 502 )
560
+ \t\t\t);
561
+ \t\t}
562
+
563
+ \t\t$telemetry = ${buildTelemetryFunctionName}( $result, $payload, $normalized_result );
564
+ \t\tif ( is_wp_error( $telemetry ) ) {
565
+ \t\t\treturn $telemetry;
566
+ \t\t}
567
+
568
+ \t\t$response = ${validatePayloadFunctionName}(
569
+ \t\t\tarray(
570
+ \t\t\t\t'result' => $normalized_result,
571
+ \t\t\t\t'telemetry' => $telemetry,
572
+ \t\t\t),
573
+ \t\t\t'feature-response',
574
+ \t\t\t'response'
575
+ \t\t);
576
+ \t\tif ( is_wp_error( $response ) ) {
577
+ \t\t\treturn new WP_Error(
578
+ \t\t\t\t'ai_client_invalid_response',
579
+ \t\t\t\t$response->get_error_message(),
580
+ \t\t\t\tarray( 'status' => 502 )
581
+ \t\t\t);
582
+ \t\t}
583
+
584
+ \t\treturn rest_ensure_response( $response );
585
+ \t}
586
+ }
587
+
588
+ if ( ! function_exists( '${registerRoutesFunctionName}' ) ) {
589
+ \tfunction ${registerRoutesFunctionName}() {
590
+ \t\tregister_rest_route(
591
+ \t\t\t${quotePhpString(namespace)},
592
+ \t\t\t'/ai/${aiFeatureSlug}',
593
+ \t\t\tarray(
594
+ \t\t\t\tarray(
595
+ \t\t\t\t\t'methods' => WP_REST_Server::CREATABLE,
596
+ \t\t\t\t\t'callback' => '${handlerFunctionName}',
597
+ \t\t\t\t\t'permission_callback' => '${canManageFunctionName}',
598
+ \t\t\t\t)
599
+ \t\t\t)
600
+ \t\t);
601
+ \t}
602
+ }
603
+
604
+ add_action( 'admin_notices', '${adminNoticeFunctionName}' );
605
+ add_action( 'rest_api_init', '${registerRoutesFunctionName}' );
606
+ `;
607
+ }