PluginProbe
Extendify / 3.1.4
Extendify v3.1.4
3.2.0 3.1.6 3.1.5 3.1.4 3.1.3 3.1.2 3.1.1 3.1.0 3.0.6 3.0.5 3.0.4 trunk 0.1.0 0.10.0 0.10.1 0.10.2 0.11.0 0.11.1 0.2.0 0.3.0 0.3.1 0.4.0 0.5.0 0.6.0 0.7.0 All 126 releases
extendify / src / Agent / api.js

api.js in Extendify 3.1.4, at src/Agent/api.js

217 lines 7.3 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 import { buildBlockSchema } from '@agent/lib/block-schema';
2 import {
3 classifyBlockEdit,
4 IGNORED_BLOCKS,
5 } from '@agent/lib/classify-block-edit';
6 import { getClientTools } from '@agent/lib/client-tools';
7 import { INSERTABLE_BLOCK_TYPES } from '@agent/lib/insertable-blocks';
8 import { ensureCoreBlocksRegistered } from '@agent/lib/register-blocks';
9 import {
10 buildSubtreeManifest,
11 buildSubtreeTree,
12 } from '@agent/lib/subtree-manifest';
13 import { useChatStore } from '@agent/state/chat';
14 import { useGlobalStore } from '@agent/state/global';
15 import { tools } from '@agent/workflows/workflows';
16 import { AI_HOST } from '@constants';
17 import { useQuickEditStore } from '@quick-edit/state/store';
18 import { digest } from '@shared/api/digest';
19 import { reqDataBasics } from '@shared/lib/data';
20 import { getBlockType } from '@wordpress/blocks';
21
22 const rootFor = (block) =>
23 block?.id && block?.target
24 ? document.querySelector(`[${block.target}="${block.id}"]`)
25 : null;
26
27 // If greater than 5 blocks the agent will narrow the scope
28 const EAGER_LOAD_MAX = 5;
29
30 // The staged selection's attribute schemas, with the install's palette baked in.
31 export const blockSchemasFor = async (block) => {
32 const root = rootFor(block);
33 const { bucket } = classifyBlockEdit({ block, root });
34 const types =
35 bucket === 'single'
36 ? [block.blockType]
37 : IGNORED_BLOCKS.has(block?.blockType)
38 ? []
39 : [...new Set(buildSubtreeManifest(root).map(({ type }) => type))];
40 if (!types.length || types.length > EAGER_LOAD_MAX) return [];
41 await ensureCoreBlocksRegistered();
42 return types
43 .map((type) => ({ type, schema: buildBlockSchema(getBlockType(type)) }))
44 .filter(({ schema }) => schema);
45 };
46
47 // Only consumed for a multi-block edit; inert for single/combo.
48 export const subtreeManifestFor = (block) =>
49 buildSubtreeManifest(rootFor(block));
50
51 // Selection hierarchy for the backend's positional edits.
52 export const subtreeTreeFor = (block) => buildSubtreeTree(rootFor(block));
53
54 const extra = () => {
55 const { x, y, width, height } = useGlobalStore.getState();
56 return {
57 userAgent: window?.navigator?.userAgent,
58 vendor: window?.navigator?.vendor || 'unknown',
59 platform:
60 window?.navigator?.userAgentData?.platform ||
61 window?.navigator?.platform ||
62 'unknown',
63 mobile: window?.navigator?.userAgentData?.mobile,
64 width: window.innerWidth,
65 height: window.innerHeight,
66 screenHeight: window.screen.height,
67 screenWidth: window.screen.width,
68 orientation: window.screen.orientation?.type,
69 touchSupport: 'ontouchstart' in window || navigator.maxTouchPoints > 0,
70 agentUI: { x, y, width, height },
71 };
72 };
73
74 export const pickWorkflow = async ({ workflows, options }) => {
75 const { failedWorkflows, context } = window.extAgentData;
76 const failed = failedWorkflows ?? new Set();
77 const filteredWorkflows = workflows.filter((wf) => !failed.has(wf.id));
78
79 const block = useQuickEditStore.getState().agentBlock;
80
81 const messages = useChatStore
82 .getState()
83 .getCurrentMessages({ includeTools: false });
84 const lastAssistantMessage = useChatStore
85 .getState()
86 .getLastAssistantMessage();
87
88 const response = await fetch(`${AI_HOST}/api/agent/find-agent`, {
89 method: 'POST',
90 headers: { 'Content-Type': 'application/json' },
91 signal: options?.signal,
92 body: JSON.stringify({
93 ...reqDataBasics,
94 workflows: filteredWorkflows,
95 previousWorkflow: {
96 workflowId: lastAssistantMessage?.details?.workflowId,
97 language: lastAssistantMessage?.details?.language,
98 lastMessage: lastAssistantMessage?.details?.content,
99 sessionId: lastAssistantMessage?.details?.sessionId,
100 },
101 context,
102 agentContext: window.extAgentData.agentContext,
103 wpAbilities: window.extAgentData.wpAbilities ?? [],
104 clientTools: getClientTools(),
105 messages: messages.slice(-5),
106 hasBlock: Boolean(block), // todo: remove this
107 blockDetails: block,
108 ...options,
109 extra: extra(),
110 }),
111 });
112
113 if (!response.ok) {
114 digest({
115 error: {
116 name: response.statusText,
117 messages: response.statusMessage,
118 },
119 details: { source: 'agent', caller: 'pick-workflow' },
120 });
121 const error = new Error('Bad response from server');
122 error.response = response;
123 throw error;
124 }
125 return await response.json();
126 };
127
128 export const handleWorkflow = async ({ workflow, workflowData, options }) => {
129 const { getCurrentMessages, getMessagesFor } = useChatStore.getState();
130 const block = useQuickEditStore.getState().agentBlock;
131 const isBlockPatching =
132 workflow?.id === 'block-patching' ||
133 (workflow?.id === 'select-block' && Boolean(block));
134 // Spent once the manifest flows: patching never reads this workflow's inputs.
135 const { blockSearch: _spent, ...carried } = workflowData ?? {};
136 // Schemas: pinned up front when the selection is small enough; otherwise
137 // empty until get-block-schemas fetches them into the same field.
138 const data = isBlockPatching
139 ? {
140 ...carried,
141 blockSchemas: workflowData?.blockSchemas?.length
142 ? workflowData.blockSchemas
143 : await blockSchemasFor(block),
144 }
145 : workflowData;
146 const response = await fetch(`${AI_HOST}/api/agent/handle-workflow`, {
147 method: 'POST',
148 headers: { 'Content-Type': 'application/json' },
149 signal: options?.signal,
150 body: JSON.stringify({
151 ...reqDataBasics,
152 workflow,
153 workflowData: data,
154 messages: getCurrentMessages(),
155 previousMessages: getMessagesFor(workflow?.id),
156 context: window.extAgentData.context,
157 agentContext: window.extAgentData.agentContext,
158 wpAbilities: window.extAgentData.wpAbilities ?? [],
159 clientTools: getClientTools(),
160 // The manifest is the backend's block-patching signal — never send it
161 // for other workflows or they'd be routed into the patching handler.
162 blockManifest: isBlockPatching ? subtreeManifestFor(block) : [],
163 blockTree: isBlockPatching ? subtreeTreeFor(block) : [],
164 // The plugin owns the add catalog so old fielded builds are never
165 // offered a type they can't build.
166 insertableBlockTypes: isBlockPatching ? INSERTABLE_BLOCK_TYPES : [],
167 retry: options?.retry || false,
168 features: ['qa-tool'],
169 extra: extra(),
170 }),
171 });
172
173 if (!response.ok) throw new Error('Bad response from server');
174 return await response.json();
175 };
176
177 export const rateAnswer = ({ answerId, rating }) =>
178 fetch(`${AI_HOST}/api/agent/rate-workflow`, {
179 method: 'POST',
180 headers: { 'Content-Type': 'application/json' },
181 body: JSON.stringify({ answerId, rating }),
182 }).catch((error) =>
183 digest({
184 error: error,
185 details: { source: 'agent', caller: 'rateAnswer', answerId, rating },
186 }),
187 );
188
189 export const callTool = async ({ tool, inputs }) => {
190 if (tools[tool]) return await tools[tool](inputs);
191 // Ability tools are named after the ability and have no file; the generic
192 // runner executes them. Key the result to its slot so the loop sees it filled.
193 const isAbility = (window.extAgentData?.wpAbilities ?? []).some((category) =>
194 category.abilities?.some((ability) => ability.name === tool),
195 );
196 if (isAbility) {
197 return {
198 [tool]: await tools['execute-ability']({ ability: tool, input: inputs }),
199 };
200 }
201 throw new Error(`Tool ${tool} not found`);
202 };
203
204 export const recordAgentActivity = ({ action, sessionId, value = {} }) => {
205 return fetch(`${AI_HOST}/api/agent/activities`, {
206 keepalive: true,
207 method: 'POST',
208 headers: { 'Content-Type': 'application/json' },
209 body: JSON.stringify({
210 ...reqDataBasics,
211 action,
212 sessionId,
213 value,
214 }),
215 });
216 };
217