@blokkli/editor 2.0.0-alpha.56 → 2.0.0-alpha.58

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 (53) hide show
  1. package/dist/module.d.mts +2 -2
  2. package/dist/module.json +1 -1
  3. package/dist/module.mjs +2 -2
  4. package/dist/modules/agent/index.d.mts +1 -1
  5. package/dist/modules/agent/index.mjs +19 -19
  6. package/dist/modules/agent/runtime/app/composables/index.d.ts +3 -1
  7. package/dist/modules/agent/runtime/app/composables/index.js +1 -0
  8. package/dist/modules/agent/runtime/app/composables/useAgent.d.ts +6 -0
  9. package/dist/modules/agent/runtime/app/composables/useAgent.js +11 -0
  10. package/dist/modules/agent/runtime/app/features/agent/Container.vue +83 -88
  11. package/dist/modules/agent/runtime/app/helpers/buildPageContext.d.ts +7 -0
  12. package/dist/modules/agent/runtime/app/helpers/buildPageContext.js +81 -0
  13. package/dist/modules/agent/runtime/app/helpers/index.d.ts +15 -1
  14. package/dist/modules/agent/runtime/app/helpers/index.js +16 -0
  15. package/dist/modules/agent/runtime/app/helpers/injections.d.ts +3 -0
  16. package/dist/modules/agent/runtime/app/helpers/injections.js +3 -0
  17. package/dist/modules/agent/runtime/app/providers/agentProvider.d.ts +33 -0
  18. package/dist/modules/agent/runtime/app/providers/agentProvider.js +315 -0
  19. package/dist/modules/agent/runtime/app/providers/conversationProvider.d.ts +50 -0
  20. package/dist/modules/agent/runtime/app/providers/conversationProvider.js +263 -0
  21. package/dist/modules/agent/runtime/app/providers/planProvider.d.ts +15 -0
  22. package/dist/modules/agent/runtime/app/providers/planProvider.js +34 -0
  23. package/dist/modules/agent/runtime/app/providers/socketProvider.d.ts +17 -0
  24. package/dist/modules/agent/runtime/app/providers/socketProvider.js +110 -0
  25. package/dist/modules/agent/runtime/app/providers/toolsProvider.d.ts +44 -0
  26. package/dist/modules/agent/runtime/app/providers/toolsProvider.js +298 -0
  27. package/dist/modules/agent/runtime/app/types/index.d.ts +47 -0
  28. package/dist/modules/agent/runtime/app/types/index.js +3 -1
  29. package/dist/modules/agent/runtime/server/helpers.js +2 -1
  30. package/dist/modules/charts/index.d.mts +1 -1
  31. package/dist/modules/drupal/index.d.mts +2 -2
  32. package/dist/modules/iframes/index.d.mts +1 -1
  33. package/dist/modules/index.d.mts +1 -1
  34. package/dist/modules/readability/index.d.mts +2 -2
  35. package/dist/modules/table-of-contents/index.d.mts +2 -2
  36. package/dist/runtime/editor/components/DiffViewer/State.vue +51 -67
  37. package/dist/runtime/editor/components/EditProvider.vue +30 -10
  38. package/dist/runtime/editor/components/PreviewProvider.vue +14 -8
  39. package/dist/runtime/editor/css/output.css +1 -1
  40. package/dist/runtime/editor/events/index.d.ts +4 -3
  41. package/dist/runtime/editor/features/add-list/index.vue +2 -1
  42. package/dist/runtime/editor/features/changelog/changelog.json +8 -0
  43. package/dist/runtime/editor/features/dev-mode/index.vue +1 -10
  44. package/dist/runtime/editor/features/responsive-preview/Frame/index.vue +2 -2
  45. package/dist/runtime/editor/features/translations/index.vue +1 -0
  46. package/dist/runtime/editor/plugins/Sidebar/index.vue +9 -2
  47. package/dist/runtime/editor/providers/state.js +4 -1
  48. package/dist/shared/{editor.BTOBvmaz.d.mts → editor.Bpw1EP57.d.mts} +3 -3
  49. package/dist/shared/{editor.9vf8ZnOp.mjs → editor.FygP6XeF.mjs} +2 -2
  50. package/dist/types.d.mts +1 -1
  51. package/package.json +30 -7
  52. package/dist/modules/agent/runtime/app/composables/agentProvider.d.ts +0 -62
  53. package/dist/modules/agent/runtime/app/composables/agentProvider.js +0 -1048
@@ -1,1048 +0,0 @@
1
- import { ref, shallowRef, readonly, reactive, watch } from "#imports";
2
- import {
3
- conversationItemSchema
4
- } from "#blokkli/agent/app/types";
5
- import {
6
- createToolMap,
7
- executeTool,
8
- getToolCategory,
9
- getToolInfoForServer,
10
- getToolDefinition,
11
- isMutationAction,
12
- isQueryResult,
13
- isToolError,
14
- resolveTools
15
- } from "#blokkli/agent/app/helpers";
16
- import { mcpTools, routeAgent, routeRoute } from "#blokkli-build/agent-client";
17
- import { generateUUID } from "#blokkli/editor/helpers/uuid";
18
- import { itemEntityType } from "#blokkli-build/config";
19
- export default function(app, adapter, agentName) {
20
- const { $t, state, ui, context } = app;
21
- let ws = null;
22
- let reconnectTimeout = null;
23
- let pingInterval = null;
24
- let reconnectAttempts = 0;
25
- const MAX_RECONNECT_ATTEMPTS = 10;
26
- const isConnected = ref(false);
27
- const isReady = ref(false);
28
- const hasBeenReady = ref(false);
29
- let pendingPrompt = null;
30
- let pendingInit = null;
31
- let toolMap = {};
32
- let sentToolNames = [];
33
- const storedPageContext = shallowRef(null);
34
- const isProcessing = ref(false);
35
- const isThinking = ref(false);
36
- const usageTurns = ref([]);
37
- const conversation = ref([]);
38
- const activeItem = ref(null);
39
- const autoApprove = ref(false);
40
- const pendingMutation = ref(null);
41
- const pendingToolCall = ref(null);
42
- let pendingToolCallResolve = null;
43
- const plan = ref(null);
44
- const transcriptContent = ref(null);
45
- const showTranscript = ref(false);
46
- const activeConversationId = ref(null);
47
- const conversationList = ref([]);
48
- const showConversationList = ref(false);
49
- const feedbackItemIds = ref(/* @__PURE__ */ new Set());
50
- const toolDetails = reactive(/* @__PURE__ */ new Map());
51
- async function saveCurrentConversation(serverState) {
52
- if (!adapter.agentConversations) return;
53
- if (!conversation.value.length) return;
54
- if (!activeConversationId.value) {
55
- activeConversationId.value = generateUUID();
56
- }
57
- const firstUser = conversation.value.find((item) => item.type === "user");
58
- const titleText = firstUser && "content" in firstUser ? firstUser.content : "";
59
- const title = titleText.length > 80 ? titleText.slice(0, 80) + "\u2026" : titleText;
60
- try {
61
- await adapter.agentConversations.upsert({
62
- uuid: activeConversationId.value,
63
- title,
64
- clientState: JSON.stringify({
65
- conversation: conversation.value.filter(
66
- (item) => item.type !== "error"
67
- ),
68
- usageTurns: usageTurns.value
69
- }),
70
- serverState: JSON.stringify({
71
- messages: serverState.messages,
72
- activatedLazyTools: serverState.activatedLazyTools
73
- }),
74
- hash: serverState.hash
75
- });
76
- } catch (e) {
77
- console.warn("[blokkli agent] Failed to save conversation:", e);
78
- }
79
- }
80
- function parseConversationData(data) {
81
- try {
82
- const parsed = JSON.parse(data.clientState);
83
- if (!parsed.conversation?.length) {
84
- return null;
85
- }
86
- const clientConversation = parsed.conversation.map(
87
- (item) => {
88
- const result = conversationItemSchema.safeParse(item);
89
- if (result.success) {
90
- return result.data;
91
- }
92
- return {
93
- type: "unknown",
94
- id: generateId(),
95
- timestamp: Date.now()
96
- };
97
- }
98
- );
99
- const serverParsed = JSON.parse(data.serverState);
100
- if (!serverParsed?.messages?.length) {
101
- return null;
102
- }
103
- return {
104
- conversation: clientConversation,
105
- usageTurns: parsed.usageTurns ?? [],
106
- feedbackItemIds: data.feedbackItemIds ?? [],
107
- serverState: {
108
- messages: serverParsed.messages,
109
- activatedLazyTools: serverParsed.activatedLazyTools,
110
- hash: data.hash
111
- }
112
- };
113
- } catch {
114
- return null;
115
- }
116
- }
117
- async function loadConversation(uuid) {
118
- if (!adapter.agentConversations) return null;
119
- try {
120
- const data = await adapter.agentConversations.load(uuid);
121
- if (!data) return null;
122
- return parseConversationData(data);
123
- } catch (e) {
124
- console.warn("[blokkli agent] Failed to load conversation:", e);
125
- return null;
126
- }
127
- }
128
- async function deleteConversation(id) {
129
- if (adapter.agentConversations) {
130
- try {
131
- await adapter.agentConversations.delete(id);
132
- } catch (e) {
133
- console.warn("[blokkli agent] Failed to delete conversation:", e);
134
- }
135
- }
136
- if (activeConversationId.value === id) {
137
- activeConversationId.value = null;
138
- conversation.value = [];
139
- activeItem.value = null;
140
- isProcessing.value = false;
141
- isThinking.value = false;
142
- send({ type: "new_conversation" });
143
- }
144
- await refreshConversationList();
145
- }
146
- async function switchConversation(id) {
147
- if (isProcessing.value) return;
148
- const loaded = await loadConversation(id);
149
- if (!loaded) return;
150
- conversation.value = loaded.conversation;
151
- usageTurns.value = loaded.usageTurns;
152
- feedbackItemIds.value = new Set(loaded.feedbackItemIds);
153
- activeItem.value = null;
154
- activeConversationId.value = id;
155
- send({ type: "restore_conversation", state: loaded.serverState });
156
- showConversationList.value = false;
157
- }
158
- async function refreshConversationList() {
159
- if (!adapter.agentConversations) {
160
- conversationList.value = [];
161
- return;
162
- }
163
- try {
164
- const list = await adapter.agentConversations.list();
165
- conversationList.value = list.sort(
166
- (a, b) => b.updatedAt.localeCompare(a.updatedAt)
167
- );
168
- } catch (e) {
169
- console.warn("[blokkli agent] Failed to list conversations:", e);
170
- conversationList.value = [];
171
- }
172
- }
173
- watch(isProcessing, (processing) => {
174
- if (processing) {
175
- ui.setTransform(agentName);
176
- } else {
177
- ui.setTransform(null);
178
- }
179
- });
180
- function send(message) {
181
- if (ws?.readyState === WebSocket.OPEN) {
182
- ws.send(JSON.stringify(message));
183
- }
184
- }
185
- function onWebSocketOpen() {
186
- isConnected.value = true;
187
- reconnectAttempts = 0;
188
- pingInterval = window.setInterval(() => {
189
- send({ type: "ping" });
190
- }, 3e4);
191
- onConnect();
192
- }
193
- function onWebSocketClose() {
194
- if (pingInterval) {
195
- window.clearInterval(pingInterval);
196
- pingInterval = null;
197
- }
198
- isConnected.value = false;
199
- isReady.value = false;
200
- isProcessing.value = false;
201
- reconnectAttempts++;
202
- if (reconnectAttempts > MAX_RECONNECT_ATTEMPTS) {
203
- conversation.value.push({
204
- type: "error",
205
- id: generateId(),
206
- errorType: "connection",
207
- timestamp: Date.now()
208
- });
209
- return;
210
- }
211
- reconnectTimeout = window.setTimeout(() => {
212
- if (!isConnected.value) {
213
- connect();
214
- }
215
- }, 3e3);
216
- }
217
- function onWebSocketError(error) {
218
- console.error("WebSocket error:", error);
219
- }
220
- function onWebSocketMessage(event) {
221
- try {
222
- const data = JSON.parse(event.data);
223
- handleServerMessage(data);
224
- } catch (error) {
225
- console.error("Failed to parse WebSocket message:", error);
226
- }
227
- }
228
- function connect() {
229
- if (ws && (ws.readyState === WebSocket.OPEN || ws.readyState === WebSocket.CONNECTING)) {
230
- return;
231
- }
232
- if (ws) {
233
- ws.removeEventListener("open", onWebSocketOpen);
234
- ws.removeEventListener("close", onWebSocketClose);
235
- ws.removeEventListener("error", onWebSocketError);
236
- ws.removeEventListener("message", onWebSocketMessage);
237
- ws = null;
238
- }
239
- const protocol = window.location.protocol === "https:" ? "wss:" : "ws:";
240
- const url = `${protocol}//${window.location.host}${routeAgent}`;
241
- ws = new WebSocket(url);
242
- ws.addEventListener("open", onWebSocketOpen);
243
- ws.addEventListener("close", onWebSocketClose);
244
- ws.addEventListener("error", onWebSocketError);
245
- ws.addEventListener("message", onWebSocketMessage);
246
- }
247
- function disconnect() {
248
- if (pingInterval) {
249
- window.clearInterval(pingInterval);
250
- pingInterval = null;
251
- }
252
- if (reconnectTimeout) {
253
- window.clearTimeout(reconnectTimeout);
254
- reconnectTimeout = null;
255
- }
256
- if (ws) {
257
- ws.removeEventListener("open", onWebSocketOpen);
258
- ws.removeEventListener("close", onWebSocketClose);
259
- ws.removeEventListener("error", onWebSocketError);
260
- ws.removeEventListener("message", onWebSocketMessage);
261
- ws.close();
262
- ws = null;
263
- }
264
- isConnected.value = false;
265
- isReady.value = false;
266
- ui.setTransform(null);
267
- if (pendingMutation.value) {
268
- if (pendingMutation.value.action.revert) {
269
- pendingMutation.value.action.revert();
270
- }
271
- pendingMutation.value.resolve(false);
272
- pendingMutation.value = null;
273
- }
274
- if (pendingToolCallResolve) {
275
- pendingToolCallResolve({ cancelled: true });
276
- pendingToolCallResolve = null;
277
- }
278
- pendingToolCall.value = null;
279
- }
280
- async function onConnect() {
281
- const ctx = createToolContext();
282
- const resolved = await resolveTools(mcpTools, ctx);
283
- toolMap = createToolMap(resolved);
284
- const toolNames = await getToolInfoForServer(
285
- resolved,
286
- state.editMode.value,
287
- app,
288
- adapter
289
- );
290
- let contentSearchTabs;
291
- if (adapter.getContentSearchTabs) {
292
- try {
293
- contentSearchTabs = await adapter.getContentSearchTabs();
294
- } catch (e) {
295
- console.warn("[blokkli agent] Failed to fetch content search tabs:", e);
296
- }
297
- }
298
- if (adapter.getAgentAuthToken) {
299
- try {
300
- const authToken = await adapter.getAgentAuthToken();
301
- if (!authToken) {
302
- conversation.value.push({
303
- type: "error",
304
- id: generateId(),
305
- errorType: "unauthorized",
306
- timestamp: Date.now()
307
- });
308
- disconnect();
309
- return;
310
- }
311
- const pageContext2 = await buildPageContext(contentSearchTabs);
312
- storedPageContext.value = pageContext2;
313
- pendingInit = { toolNames, pageContext: pageContext2 };
314
- send({ type: "authenticate", authToken });
315
- return;
316
- } catch (e) {
317
- console.error("Failed to obtain agent auth token:", e);
318
- }
319
- }
320
- const pageContext = await buildPageContext(contentSearchTabs);
321
- storedPageContext.value = pageContext;
322
- sendInit(toolNames, pageContext);
323
- }
324
- async function sendInit(toolNames, pageContext) {
325
- sentToolNames = toolNames;
326
- send({ type: "init", toolNames, pageContext });
327
- isReady.value = true;
328
- hasBeenReady.value = true;
329
- if (adapter.agentConversations && !pendingPrompt) {
330
- try {
331
- const latest = await adapter.agentConversations.loadLatest();
332
- if (latest) {
333
- const parsed = parseConversationData(latest);
334
- if (parsed) {
335
- activeConversationId.value = latest.uuid;
336
- conversation.value = parsed.conversation;
337
- usageTurns.value = parsed.usageTurns;
338
- feedbackItemIds.value = new Set(parsed.feedbackItemIds);
339
- send({ type: "restore_conversation", state: parsed.serverState });
340
- }
341
- }
342
- } catch (e) {
343
- console.warn("[blokkli agent] Failed to load latest conversation:", e);
344
- }
345
- }
346
- if (pendingPrompt) {
347
- const {
348
- prompt,
349
- displayPrompt,
350
- selectedUuids,
351
- attachments,
352
- autoLoadTools,
353
- autoLoadSkills,
354
- preSeededResults,
355
- autoExecuteTools
356
- } = pendingPrompt;
357
- pendingPrompt = null;
358
- sendPrompt(
359
- prompt,
360
- displayPrompt,
361
- selectedUuids,
362
- attachments,
363
- autoLoadTools,
364
- autoLoadSkills,
365
- preSeededResults,
366
- autoExecuteTools
367
- );
368
- }
369
- }
370
- async function buildPageContext(contentSearchTabs) {
371
- const { types, definitions } = app;
372
- const bundles = [];
373
- for (const bundle of types.generallyAvailableBundles) {
374
- const contentFields = [
375
- ...types.editableFieldConfig.forEntityTypeAndBundle(itemEntityType, bundle.id).filter((f) => f.type !== "table").map((f) => ({
376
- name: f.name,
377
- label: f.label,
378
- type: f.type === "frame" || f.type === "markup" ? "markup" : "plain"
379
- })),
380
- ...types.droppableFieldConfig.forEntityTypeAndBundle(itemEntityType, bundle.id).map((f) => ({
381
- name: f.name,
382
- label: f.label,
383
- type: f.type,
384
- allowed: f.allowed
385
- }))
386
- ];
387
- const paragraphFields = types.fieldConfig.forEntityTypeAndBundle(itemEntityType, bundle.id).map((f) => ({
388
- name: f.name,
389
- label: f.label,
390
- allowedBundles: f.allowedBundles,
391
- cardinality: f.cardinality
392
- }));
393
- bundles.push({
394
- id: bundle.id,
395
- label: bundle.label,
396
- description: bundle.description,
397
- contentFields,
398
- paragraphFields
399
- });
400
- }
401
- const fragments = definitions.fragmentDefinitions.value.map((f) => ({
402
- name: f.name,
403
- label: f.label,
404
- description: f.description
405
- }));
406
- const entityContentFields = [
407
- ...types.editableFieldConfig.forEntityTypeAndBundle(
408
- context.value.entityType,
409
- context.value.entityBundle
410
- ).filter((f) => f.type !== "table").map((f) => ({
411
- name: f.name,
412
- label: f.label,
413
- type: f.type === "frame" || f.type === "markup" ? "markup" : "plain"
414
- })),
415
- ...types.droppableFieldConfig.forEntityTypeAndBundle(
416
- context.value.entityType,
417
- context.value.entityBundle
418
- ).map((f) => ({
419
- name: f.name,
420
- label: f.label,
421
- type: f.type,
422
- allowed: f.allowed
423
- }))
424
- ];
425
- await app.analyze.ensureInitialized();
426
- const analyzersList = app.analyze.analyzers.value.filter((a) => !a.requireRawPage).map((a) => ({
427
- id: a.id,
428
- type: a.type,
429
- label: typeof a.label === "function" ? a.label(context.value.language, $t) : a.label,
430
- description: typeof a.description === "function" ? a.description(context.value.language, $t) : a.description
431
- }));
432
- const pageContext = {
433
- title: state.entity.value.label || "",
434
- entityType: context.value.entityType,
435
- entityUuid: context.value.entityUuid,
436
- entityBundle: context.value.entityBundle,
437
- bundleLabel: state.entity.value.bundleLabel || "",
438
- itemEntityType,
439
- bundles,
440
- interfaceLanguage: ui.interfaceLanguage.value,
441
- entityLanguage: context.value.language,
442
- isPublished: state.entity.value.status ?? null,
443
- editMode: state.editMode.value,
444
- fragments,
445
- entityContentFields,
446
- ...contentSearchTabs?.length ? { contentSearchTabs } : {},
447
- ...analyzersList.length ? { analyzers: analyzersList } : {}
448
- };
449
- return pageContext;
450
- }
451
- function generateId() {
452
- return `${Date.now()}-${Math.random().toString(36).substring(2, 9)}`;
453
- }
454
- function finalizeActiveItem() {
455
- const item = activeItem.value;
456
- if (!item) return;
457
- if (item.type === "assistant" && item.content) {
458
- conversation.value.push({
459
- type: "assistant",
460
- id: item.id,
461
- content: item.content,
462
- timestamp: item.timestamp
463
- });
464
- }
465
- activeItem.value = null;
466
- }
467
- function handleTextContent(content) {
468
- if (activeItem.value?.type === "assistant") {
469
- activeItem.value = {
470
- ...activeItem.value,
471
- content: activeItem.value.content + content
472
- };
473
- } else {
474
- activeItem.value = {
475
- type: "assistant",
476
- id: generateId(),
477
- content,
478
- timestamp: Date.now()
479
- };
480
- }
481
- }
482
- function handleServerMessage(data) {
483
- switch (data.type) {
484
- case "authenticated":
485
- if (pendingInit) {
486
- sendInit(pendingInit.toolNames, pendingInit.pageContext);
487
- pendingInit = null;
488
- }
489
- break;
490
- case "thinking":
491
- activeItem.value = null;
492
- isThinking.value = true;
493
- break;
494
- case "text":
495
- isThinking.value = false;
496
- handleTextContent(data.content);
497
- break;
498
- case "text_delta":
499
- isThinking.value = false;
500
- handleTextContent(data.content);
501
- break;
502
- case "tool_call":
503
- finalizeActiveItem();
504
- handleToolCall(data.callId, data.tool, data.params);
505
- break;
506
- case "usage":
507
- usageTurns.value = [...usageTurns.value, data.usage];
508
- break;
509
- case "done":
510
- finalizeActiveItem();
511
- isThinking.value = false;
512
- isProcessing.value = false;
513
- if (data.message) {
514
- const lastItem = conversation.value[conversation.value.length - 1];
515
- const isDuplicate = lastItem?.type === "assistant" && lastItem.content === data.message;
516
- if (!isDuplicate) {
517
- conversation.value.push({
518
- type: "assistant",
519
- id: generateId(),
520
- content: data.message,
521
- timestamp: Date.now()
522
- });
523
- }
524
- }
525
- break;
526
- case "error":
527
- finalizeActiveItem();
528
- isThinking.value = false;
529
- isProcessing.value = false;
530
- if (data.detail) {
531
- console.warn(`[blokkli agent] ${data.errorType} error:`, data.detail);
532
- }
533
- conversation.value.push({
534
- type: "error",
535
- id: generateId(),
536
- errorType: data.errorType,
537
- timestamp: Date.now(),
538
- ...data.retryable ? { retryable: true } : {}
539
- });
540
- break;
541
- case "server_tool_result":
542
- finalizeActiveItem();
543
- conversation.value.push({
544
- type: "server_tool",
545
- id: generateId(),
546
- tool: data.tool,
547
- label: data.label,
548
- timestamp: Date.now()
549
- });
550
- break;
551
- case "plan_update":
552
- if (data.plan && data.plan.steps.length > 0 && data.plan.steps.every((s) => s.status === "completed")) {
553
- conversation.value.push({
554
- type: "server_tool",
555
- id: generateId(),
556
- tool: "plan_completed",
557
- label: data.plan.title,
558
- timestamp: Date.now()
559
- });
560
- plan.value = null;
561
- } else {
562
- plan.value = data.plan;
563
- }
564
- break;
565
- case "transcript":
566
- transcriptContent.value = data.transcript;
567
- showTranscript.value = true;
568
- break;
569
- case "conversation_state":
570
- saveCurrentConversation(data.state);
571
- break;
572
- case "conversation_restored":
573
- break;
574
- case "conversation_restore_failed":
575
- console.warn(
576
- "[blokkli agent] Conversation restore failed:",
577
- data.reason
578
- );
579
- conversation.value = [];
580
- if (activeConversationId.value) {
581
- const failedId = activeConversationId.value;
582
- activeConversationId.value = null;
583
- if (adapter.agentConversations) {
584
- adapter.agentConversations.delete(failedId).catch(() => {
585
- });
586
- }
587
- }
588
- break;
589
- }
590
- }
591
- async function handleToolCall(callId, tool, params) {
592
- const toolDef = getToolDefinition(toolMap, tool);
593
- const initialLabel = toolDef.label($t);
594
- const toolId = generateId();
595
- const timestamp = Date.now();
596
- let currentLabel = initialLabel;
597
- activeItem.value = {
598
- type: "tool",
599
- id: toolId,
600
- callId,
601
- tool,
602
- label: initialLabel,
603
- status: "active",
604
- timestamp
605
- };
606
- try {
607
- const result = await executeToolLocally(tool, params, (label) => {
608
- currentLabel = label;
609
- if (activeItem.value?.type === "tool" && activeItem.value.callId === callId) {
610
- activeItem.value = { ...activeItem.value, label };
611
- }
612
- });
613
- if (isToolError(result)) {
614
- conversation.value.push({
615
- type: "tool",
616
- id: toolId,
617
- callId,
618
- tool,
619
- label: currentLabel,
620
- status: "error",
621
- timestamp
622
- });
623
- activeItem.value = null;
624
- send({
625
- type: "tool_result",
626
- callId,
627
- result: null,
628
- error: result.error
629
- });
630
- } else {
631
- conversation.value.push({
632
- type: "tool",
633
- id: toolId,
634
- callId,
635
- tool,
636
- label: currentLabel,
637
- status: "success",
638
- timestamp
639
- });
640
- activeItem.value = null;
641
- if (toolDef.buildDetails) {
642
- try {
643
- const detailsSource = typeof result === "object" && result !== null && "_details" in result ? result._details : result;
644
- const details = toolDef.buildDetails(detailsSource);
645
- if (details != null) {
646
- toolDetails.set(callId, details);
647
- }
648
- } catch {
649
- }
650
- }
651
- let resultForServer = result;
652
- let skipLlmResponse;
653
- if (typeof result === "object" && result !== null) {
654
- const rec = result;
655
- if ("_usage" in rec && rec._usage) {
656
- usageTurns.value = [...usageTurns.value, rec._usage];
657
- }
658
- if (rec._skipLlmResponse === true) {
659
- skipLlmResponse = true;
660
- }
661
- if ("_details" in rec || "_usage" in rec || "_skipLlmResponse" in rec) {
662
- const {
663
- _details: _,
664
- _usage: __,
665
- _skipLlmResponse: ___,
666
- ...rest
667
- } = rec;
668
- resultForServer = rest;
669
- }
670
- }
671
- if (toolDef.prunedSummary && typeof resultForServer === "object" && resultForServer !== null) {
672
- try {
673
- const summary = toolDef.prunedSummary(resultForServer);
674
- if (summary) {
675
- resultForServer = {
676
- ...resultForServer,
677
- _summary: summary
678
- };
679
- }
680
- } catch {
681
- }
682
- }
683
- send({
684
- type: "tool_result",
685
- callId,
686
- result: resultForServer,
687
- skipLlmResponse
688
- });
689
- }
690
- } catch (error) {
691
- conversation.value.push({
692
- type: "tool",
693
- id: toolId,
694
- callId,
695
- tool,
696
- label: currentLabel,
697
- status: "error",
698
- timestamp
699
- });
700
- activeItem.value = null;
701
- send({
702
- type: "tool_result",
703
- callId,
704
- result: null,
705
- error: error.message
706
- });
707
- }
708
- }
709
- function createToolContext() {
710
- return {
711
- app,
712
- itemEntityType,
713
- adapter,
714
- pageContext: storedPageContext.value
715
- };
716
- }
717
- async function runToolForPrompt(toolName, params) {
718
- const ctx = createToolContext();
719
- if (!Object.keys(toolMap).length) {
720
- const resolved = await resolveTools(mcpTools, ctx);
721
- toolMap = createToolMap(resolved);
722
- }
723
- const toolDef = getToolDefinition(toolMap, toolName);
724
- const rawResult = await executeTool(toolMap, toolName, ctx, params);
725
- const label = isQueryResult(rawResult) ? rawResult.label : toolDef.label($t);
726
- const result = isQueryResult(rawResult) ? rawResult.result : rawResult;
727
- return { toolName, params, result, label };
728
- }
729
- function waitForToolComponent(toolName, params) {
730
- return new Promise((resolve) => {
731
- pendingToolCall.value = { toolName, params };
732
- pendingToolCallResolve = resolve;
733
- });
734
- }
735
- async function executeToolLocally(toolName, params, setLabel) {
736
- const toolContext = createToolContext();
737
- const toolDef = getToolDefinition(toolMap, toolName);
738
- if (toolDef.component) {
739
- const preparedParams = await executeTool(
740
- toolMap,
741
- toolName,
742
- toolContext,
743
- params
744
- );
745
- if (typeof preparedParams === "object" && preparedParams !== null && "error" in preparedParams) {
746
- return preparedParams;
747
- }
748
- const result2 = await waitForToolComponent(
749
- toolDef.name,
750
- preparedParams
751
- );
752
- if (setLabel && typeof result2 === "object" && result2 !== null && "label" in result2) {
753
- setLabel(result2.label);
754
- }
755
- return result2;
756
- }
757
- const category = getToolCategory(toolMap, toolName);
758
- const result = await executeTool(toolMap, toolName, toolContext, params);
759
- if (category === "query") {
760
- if (isQueryResult(result)) {
761
- if (setLabel) {
762
- setLabel(result.label);
763
- }
764
- if (result.affectedUuids?.length) {
765
- app.eventBus.emit("select", result.affectedUuids);
766
- app.eventBus.emit("scrollSelectionIntoView", {});
767
- }
768
- return result.result;
769
- }
770
- return result;
771
- }
772
- if (typeof result === "object" && result !== null && "error" in result) {
773
- return result;
774
- }
775
- if (!isMutationAction(result)) {
776
- return { error: "Invalid mutation tool result" };
777
- }
778
- const action = result;
779
- if (setLabel) {
780
- setLabel(action.label);
781
- }
782
- async function applyMutation() {
783
- const uuidsBefore = state.getAllUuids();
784
- await state.mutateWithLoadingState(() => action.apply(adapter));
785
- const newUuids = state.getAllUuids().filter((uuid) => !uuidsBefore.includes(uuid));
786
- const selectUuids = newUuids.length ? newUuids : action.affectedUuids || [];
787
- if (selectUuids.length) {
788
- app.eventBus.emit("select", selectUuids);
789
- app.eventBus.emit("scrollSelectionIntoView", {});
790
- }
791
- return newUuids;
792
- }
793
- function buildMutationResult(newUuids) {
794
- const newParagraphs = action.type === "add" && newUuids.length ? newUuids.map((uuid) => {
795
- const block = app.blocks.getBlock(uuid);
796
- if (!block) return null;
797
- const blockFieldNames = app.types.fieldConfig.forEntityTypeAndBundle(itemEntityType, block.bundle).map((f) => f.name);
798
- return {
799
- uuid,
800
- bundle: block.bundle,
801
- ...blockFieldNames.length ? { paragraphFields: blockFieldNames } : {}
802
- };
803
- }).filter(
804
- (b) => b !== null
805
- ) : void 0;
806
- return {
807
- success: true,
808
- historyIndex: state.currentMutationIndex.value,
809
- newParagraphs: newParagraphs?.length ? newParagraphs : void 0,
810
- ...action.result
811
- };
812
- }
813
- if (autoApprove.value || !toolDef.requiresApproval) {
814
- const newUuids = await applyMutation();
815
- return buildMutationResult(newUuids);
816
- }
817
- const approved = await waitForApproval(action);
818
- if (approved) {
819
- const newUuids = await applyMutation();
820
- return buildMutationResult(newUuids);
821
- } else {
822
- if (action.revert) {
823
- action.revert();
824
- }
825
- return { success: false, rejected: true };
826
- }
827
- }
828
- function waitForApproval(action) {
829
- return new Promise((resolve) => {
830
- pendingMutation.value = { action, resolve };
831
- });
832
- }
833
- async function sendPrompt(prompt, displayPrompt, selectedUuids, attachments, autoLoadTools, autoLoadSkills, preSeededResults, autoExecuteTools) {
834
- if (!prompt.trim() || isProcessing.value) return;
835
- if (!isReady.value) {
836
- pendingPrompt = {
837
- prompt,
838
- displayPrompt,
839
- selectedUuids,
840
- attachments,
841
- autoLoadTools,
842
- autoLoadSkills,
843
- preSeededResults,
844
- autoExecuteTools
845
- };
846
- return;
847
- }
848
- isProcessing.value = true;
849
- if (!activeConversationId.value) {
850
- activeConversationId.value = generateUUID();
851
- }
852
- conversation.value.push({
853
- type: "user",
854
- id: generateId(),
855
- content: displayPrompt ?? prompt,
856
- timestamp: Date.now(),
857
- attachments: attachments?.length ? attachments : void 0
858
- });
859
- isThinking.value = true;
860
- const isFirstMessage = conversation.value.filter((v) => v.type === "user").length === 1;
861
- const hasClientDirectives = !!(autoLoadTools?.length || autoLoadSkills?.length);
862
- let resolvedAutoLoadTools = autoLoadTools;
863
- let resolvedAutoLoadSkills = autoLoadSkills;
864
- if (isFirstMessage && !hasClientDirectives && storedPageContext.value) {
865
- try {
866
- const routingResult = await fetch(routeRoute, {
867
- method: "POST",
868
- headers: { "Content-Type": "application/json" },
869
- body: JSON.stringify({
870
- prompt,
871
- toolNames: sentToolNames,
872
- pageContext: storedPageContext.value
873
- })
874
- }).then(
875
- (r) => r.json()
876
- );
877
- if (routingResult.usage) {
878
- usageTurns.value = [...usageTurns.value, routingResult.usage];
879
- }
880
- if (routingResult.tools?.length) {
881
- resolvedAutoLoadTools = [
882
- ...resolvedAutoLoadTools || [],
883
- ...routingResult.tools
884
- ];
885
- }
886
- if (routingResult.skills?.length) {
887
- resolvedAutoLoadSkills = [
888
- ...resolvedAutoLoadSkills || [],
889
- ...routingResult.skills
890
- ];
891
- }
892
- } catch {
893
- }
894
- }
895
- const serverPreSeeded = preSeededResults?.length ? preSeededResults.map(({ toolName, params, result }) => ({
896
- toolName,
897
- params,
898
- result
899
- })) : void 0;
900
- send({
901
- type: "start",
902
- prompt,
903
- selectedUuids: selectedUuids?.length ? selectedUuids : void 0,
904
- autoLoadTools: resolvedAutoLoadTools?.length ? resolvedAutoLoadTools : void 0,
905
- autoLoadSkills: resolvedAutoLoadSkills?.length ? resolvedAutoLoadSkills : void 0,
906
- preSeededResults: serverPreSeeded,
907
- autoExecuteTools: autoExecuteTools?.length ? autoExecuteTools : void 0
908
- });
909
- }
910
- function retry() {
911
- if (isProcessing.value || !isReady.value) return;
912
- const lastUserItem = [...conversation.value].reverse().find((item) => item.type === "user");
913
- if (!lastUserItem || lastUserItem.type !== "user") return;
914
- conversation.value = conversation.value.filter(
915
- (item) => !(item.type === "error" && "retryable" in item && item.retryable)
916
- );
917
- isProcessing.value = true;
918
- send({
919
- type: "start",
920
- prompt: lastUserItem.content
921
- });
922
- }
923
- function approve() {
924
- if (pendingMutation.value) {
925
- pendingMutation.value.resolve(true);
926
- pendingMutation.value = null;
927
- }
928
- }
929
- function reject() {
930
- if (pendingMutation.value) {
931
- pendingMutation.value.resolve(false);
932
- pendingMutation.value = null;
933
- }
934
- }
935
- function setAutoApprove(value) {
936
- autoApprove.value = value;
937
- if (value) {
938
- approve();
939
- }
940
- }
941
- function cancel() {
942
- if (pendingMutation.value) {
943
- pendingMutation.value.resolve(false);
944
- pendingMutation.value = null;
945
- }
946
- if (pendingToolCallResolve) {
947
- pendingToolCallResolve({ cancelled: true });
948
- pendingToolCallResolve = null;
949
- }
950
- pendingToolCall.value = null;
951
- activeItem.value = null;
952
- send({ type: "cancel" });
953
- isProcessing.value = false;
954
- conversation.value.push({
955
- type: "assistant",
956
- id: generateId(),
957
- content: $t("aiAgentCancelled", "Cancelled"),
958
- timestamp: Date.now()
959
- });
960
- }
961
- function approvePlan() {
962
- send({ type: "plan_approve" });
963
- }
964
- function rejectPlan() {
965
- send({ type: "plan_reject" });
966
- }
967
- function newConversation() {
968
- if (pendingMutation.value) {
969
- pendingMutation.value.resolve(false);
970
- pendingMutation.value = null;
971
- }
972
- if (pendingToolCallResolve) {
973
- pendingToolCallResolve({ cancelled: true });
974
- pendingToolCallResolve = null;
975
- }
976
- pendingToolCall.value = null;
977
- conversation.value = [];
978
- activeItem.value = null;
979
- isProcessing.value = false;
980
- isThinking.value = false;
981
- activeConversationId.value = null;
982
- plan.value = null;
983
- usageTurns.value = [];
984
- feedbackItemIds.value = /* @__PURE__ */ new Set();
985
- toolDetails.clear();
986
- send({ type: "new_conversation" });
987
- }
988
- function getTranscript() {
989
- send({ type: "get_transcript" });
990
- }
991
- function onToolComponentDone(result) {
992
- if (pendingToolCallResolve) {
993
- pendingToolCallResolve(result);
994
- pendingToolCallResolve = null;
995
- }
996
- pendingToolCall.value = null;
997
- }
998
- return {
999
- // Connection state
1000
- isConnected: readonly(isConnected),
1001
- isReady: readonly(isReady),
1002
- hasBeenReady: readonly(hasBeenReady),
1003
- connect,
1004
- disconnect,
1005
- // Conversation state
1006
- conversation,
1007
- activeItem,
1008
- isProcessing,
1009
- isThinking,
1010
- // Mutation/tool approval state
1011
- autoApprove,
1012
- pendingMutation,
1013
- pendingToolCall,
1014
- // Plan state
1015
- plan,
1016
- approvePlan,
1017
- rejectPlan,
1018
- // Token usage
1019
- usageTurns,
1020
- // Actions
1021
- sendPrompt,
1022
- runToolForPrompt,
1023
- retry,
1024
- approve,
1025
- reject,
1026
- setAutoApprove,
1027
- cancel,
1028
- newConversation,
1029
- getTranscript,
1030
- onToolComponentDone,
1031
- // Tool details (ephemeral)
1032
- toolDetails,
1033
- // Transcript dialog state
1034
- transcriptContent,
1035
- showTranscript,
1036
- // Conversation list
1037
- conversationList,
1038
- showConversationList,
1039
- activeConversationId: readonly(activeConversationId),
1040
- switchConversation,
1041
- deleteConversation,
1042
- refreshConversationList,
1043
- // Feedback
1044
- feedbackItemIds,
1045
- // Page context (built once during connection)
1046
- pageContext: storedPageContext
1047
- };
1048
- }