plugin-agent-orchestrator 1.0.6 → 1.0.13

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.
@@ -13,6 +13,128 @@ import type { ToolsEntry } from '@nocobase/ai';
13
13
  */
14
14
  const ORCHESTRATOR_DEPTH_KEY = '__orchestratorDepth';
15
15
 
16
+ type TraceEvent = {
17
+ type: string;
18
+ at: string;
19
+ title: string;
20
+ content?: string;
21
+ toolName?: string;
22
+ args?: any;
23
+ status?: string;
24
+ };
25
+
26
+ type AgentExecutionResult = {
27
+ content: string;
28
+ messages: any[];
29
+ };
30
+
31
+ function sanitizeToolPart(value: string) {
32
+ return (value || '').replace(/[^a-zA-Z0-9_-]/g, '_');
33
+ }
34
+
35
+ function buildDelegateToolName(leaderUsername: string, subAgentUsername: string) {
36
+ return `delegate_${sanitizeToolPart(leaderUsername)}_to_${sanitizeToolPart(subAgentUsername)}`;
37
+ }
38
+
39
+ function isDelegateToolName(toolName: string) {
40
+ return toolName.startsWith('delegate_to_') || (toolName.startsWith('delegate_') && toolName.includes('_to_'));
41
+ }
42
+
43
+ function createDelegateToolOptions(
44
+ plugin: any,
45
+ options: {
46
+ leaderUsername: string;
47
+ subAgentUsername: string;
48
+ subAgentEmployee: any;
49
+ maxDepth?: number;
50
+ timeout?: number;
51
+ toolName: string;
52
+ legacyAlias?: boolean;
53
+ llmService?: string;
54
+ model?: string;
55
+ },
56
+ ) {
57
+ const { leaderUsername, subAgentUsername, subAgentEmployee, maxDepth, timeout, toolName, legacyAlias, llmService, model } = options;
58
+ const toolDescription = [
59
+ `Delegate a task from "${leaderUsername}" to the AI Employee "${subAgentEmployee.nickname || subAgentUsername}".`,
60
+ legacyAlias ? 'This is a backward-compatible alias for existing skill assignments.' : '',
61
+ subAgentEmployee.about ? `Specialist profile: ${subAgentEmployee.about.substring(0, 200)}` : '',
62
+ 'The sub-agent will execute the task independently and return its final answer.',
63
+ ].filter(Boolean).join(' ');
64
+
65
+ return {
66
+ scope: 'CUSTOM',
67
+ execution: 'backend',
68
+ defaultPermission: 'ALLOW',
69
+ silence: false,
70
+ introduction: {
71
+ title: `[${leaderUsername}] ${subAgentEmployee.nickname || subAgentUsername}${legacyAlias ? ' (legacy)' : ''}`,
72
+ about: toolDescription,
73
+ },
74
+ definition: {
75
+ name: toolName,
76
+ description: toolDescription,
77
+ schema: z.object({
78
+ task: z.string().describe('The detailed task description for the sub-agent to execute.'),
79
+ context: z.string().optional().describe('Optional additional context to help the sub-agent understand the task better.'),
80
+ }),
81
+ },
82
+ invoke: async (ctx: Context, args: { task: string; context?: string }, id: string) => {
83
+ const callingEmployee = resolveCallingEmployee(ctx);
84
+ if (callingEmployee && callingEmployee !== leaderUsername) {
85
+ await logDelegation(ctx, plugin, {
86
+ leaderUsername,
87
+ subAgentUsername,
88
+ toolName,
89
+ task: args.task,
90
+ context: args.context,
91
+ result: '',
92
+ status: 'error',
93
+ depth: (ctx as any)[ORCHESTRATOR_DEPTH_KEY] ?? 0,
94
+ durationMs: 0,
95
+ error: `Employee "${callingEmployee}" is not authorized to use this delegation rule.`,
96
+ });
97
+ return {
98
+ status: 'error' as const,
99
+ content: `Employee "${callingEmployee}" is not authorized to delegate to "${subAgentUsername}". Configure an orchestration rule first.`,
100
+ };
101
+ }
102
+
103
+ return invokeDelegateTask(ctx, plugin, {
104
+ leaderUsername,
105
+ subAgentUsername,
106
+ subAgentEmployee,
107
+ task: args.task,
108
+ context: args.context,
109
+ maxDepth: maxDepth ?? 1,
110
+ timeout: timeout ?? 120000,
111
+ toolCallId: id,
112
+ toolName,
113
+ llmService,
114
+ model,
115
+ });
116
+ },
117
+ };
118
+ }
119
+
120
+ function resolveCallingEmployee(ctx: Context) {
121
+ const raw =
122
+ (ctx as any)._currentAIEmployee ||
123
+ (ctx as any).state?.currentAIEmployee ||
124
+ (ctx as any).runtime?.context?.currentAIEmployee;
125
+ if (!raw) return null;
126
+ return typeof raw === 'string' ? raw : raw.username;
127
+ }
128
+
129
+ function truncateText(value: any, maxLen: number) {
130
+ const text = typeof value === 'string' ? value : value == null ? '' : JSON.stringify(value);
131
+ return text.length > maxLen ? `${text.slice(0, maxLen)}\n...[truncated]` : text;
132
+ }
133
+
134
+ function nowIso() {
135
+ return new Date().toISOString();
136
+ }
137
+
16
138
  /**
17
139
  * Creates one dynamic tool per configured sub-agent for a given leader.
18
140
  * Uses Strategy B (Per-SubAgent Tool): each sub-agent becomes a separate tool
@@ -38,86 +160,67 @@ export function createDelegateToolsProvider(plugin: any) {
38
160
  });
39
161
  if (!configs?.length) return;
40
162
 
41
- // Build a lookup: which leaders are allowed to use each delegate tool
42
- // Multiple leaders may share the same sub-agent, but each leader must
43
- // have an explicit rule.
44
- const leadersByTool = new Map<string, Set<string>>();
163
+ const employeeCache = new Map<string, any>();
164
+ const tools = [];
165
+ const configsBySubAgent = new Map<string, any[]>();
45
166
  for (const config of configs) {
46
- const toolName = `delegate_to_${config.subAgentUsername.replace(/[^a-zA-Z0-9_-]/g, '_')}`;
47
- if (!leadersByTool.has(toolName)) {
48
- leadersByTool.set(toolName, new Set());
49
- }
50
- leadersByTool.get(toolName)!.add(config.leaderUsername);
167
+ const items = configsBySubAgent.get(config.subAgentUsername) || [];
168
+ items.push(config);
169
+ configsBySubAgent.set(config.subAgentUsername, items);
51
170
  }
52
171
 
53
- // De-duplicate: register one tool per sub-agent (not per config row)
54
- const seenSubAgents = new Set<string>();
55
-
56
172
  for (const config of configs) {
57
173
  const { leaderUsername, subAgentUsername, maxDepth, timeout } = config;
58
174
 
59
- // Skip if we already registered this sub-agent's tool
60
- if (seenSubAgents.has(subAgentUsername)) continue;
61
- seenSubAgents.add(subAgentUsername);
62
-
63
175
  // Fetch the sub-agent employee model for its description and LLM config
64
- const subAgentEmployee = await plugin.db.getRepository('aiEmployees').findOne({
65
- filter: { username: subAgentUsername },
66
- });
176
+ let subAgentEmployee = employeeCache.get(subAgentUsername);
177
+ if (!subAgentEmployee) {
178
+ subAgentEmployee = await plugin.db.getRepository('aiEmployees').findOne({
179
+ filter: { username: subAgentUsername },
180
+ });
181
+ if (subAgentEmployee) {
182
+ employeeCache.set(subAgentUsername, subAgentEmployee);
183
+ }
184
+ }
67
185
  if (!subAgentEmployee) continue;
68
186
 
69
- const toolName = `delegate_to_${subAgentUsername.replace(/[^a-zA-Z0-9_-]/g, '_')}`;
70
- const toolDescription = [
71
- `Delegate a task to the AI Employee "${subAgentEmployee.nickname || subAgentUsername}".`,
72
- subAgentEmployee.about ? `Specialist profile: ${subAgentEmployee.about.substring(0, 200)}` : '',
73
- 'The sub-agent will execute the task independently and return its final answer.',
74
- ].filter(Boolean).join(' ');
75
-
76
- // Capture the allowed leaders for this tool (for invoke-time scoping)
77
- const allowedLeaders = leadersByTool.get(toolName)!;
78
-
79
- register.registerTools({
80
- scope: 'CUSTOM',
81
- execution: 'backend',
82
- defaultPermission: 'ALLOW',
83
- silence: false,
84
- introduction: {
85
- title: `[Sub-Agent] ${subAgentEmployee.nickname || subAgentUsername}`,
86
- about: toolDescription,
87
- },
88
- definition: {
89
- name: toolName,
90
- description: toolDescription,
91
- schema: z.object({
92
- task: z.string().describe('The detailed task description for the sub-agent to execute.'),
93
- context: z.string().optional().describe('Optional additional context to help the sub-agent understand the task better.'),
94
- }),
95
- },
96
- invoke: async (ctx: Context, args: { task: string; context?: string }, id: string) => {
97
- // --- P2 FIX: Per-leader scoping at invoke time ---
98
- // Core ToolsOptions doesn't support leaderUsername field, so
99
- // we enforce scoping here by checking the calling employee.
100
- const callingEmployee = (ctx as any)._currentAIEmployee?.username
101
- || (ctx as any).state?.currentAIEmployee;
102
- if (callingEmployee && !allowedLeaders.has(callingEmployee)) {
103
- return {
104
- status: 'error' as const,
105
- content: `Employee "${callingEmployee}" is not authorized to delegate to "${subAgentUsername}". Configure an orchestration rule first.`,
106
- };
107
- }
187
+ const toolName = buildDelegateToolName(leaderUsername, subAgentUsername);
188
+ tools.push(createDelegateToolOptions(plugin, {
189
+ leaderUsername,
190
+ subAgentUsername,
191
+ subAgentEmployee,
192
+ maxDepth,
193
+ timeout,
194
+ toolName,
195
+ llmService: config.llmService,
196
+ model: config.model,
197
+ }));
198
+ }
108
199
 
109
- return invokeDelegateTask(ctx, plugin, {
110
- leaderUsername: callingEmployee || Array.from(allowedLeaders)[0] || '',
111
- subAgentUsername,
112
- subAgentEmployee,
113
- task: args.task,
114
- context: args.context,
115
- maxDepth: maxDepth ?? 1,
116
- timeout: timeout ?? 120000,
117
- toolCallId: id,
118
- });
119
- },
120
- });
200
+ // Compatibility for existing single-parent setups that already assigned
201
+ // delegate_to_<sub> to the parent employee's skills.
202
+ for (const [subAgentUsername, items] of configsBySubAgent.entries()) {
203
+ if (items.length !== 1) continue;
204
+ const config = items[0];
205
+ const subAgentEmployee = employeeCache.get(subAgentUsername);
206
+ if (!subAgentEmployee) continue;
207
+ const legacyToolName = `delegate_to_${sanitizeToolPart(subAgentUsername)}`;
208
+ if (tools.some((tool: any) => tool.definition.name === legacyToolName)) continue;
209
+ tools.push(createDelegateToolOptions(plugin, {
210
+ leaderUsername: config.leaderUsername,
211
+ subAgentUsername,
212
+ subAgentEmployee,
213
+ maxDepth: config.maxDepth,
214
+ timeout: config.timeout,
215
+ toolName: legacyToolName,
216
+ legacyAlias: true,
217
+ llmService: config.llmService,
218
+ model: config.model,
219
+ }));
220
+ }
221
+
222
+ if (tools.length) {
223
+ register.registerTools(tools);
121
224
  }
122
225
  } catch (e) {
123
226
  plugin.app.log.error('[AgentOrchestrator] Failed to register delegate tools', e);
@@ -153,13 +256,28 @@ async function invokeDelegateTask(
153
256
  maxDepth: number;
154
257
  timeout: number;
155
258
  toolCallId: string;
259
+ toolName: string;
260
+ llmService?: string;
261
+ model?: string;
156
262
  },
157
263
  ) {
158
- const { leaderUsername, subAgentUsername, subAgentEmployee, task, context, maxDepth, timeout, toolCallId } = options;
264
+ const { leaderUsername, subAgentUsername, subAgentEmployee, task, context, maxDepth, timeout, toolCallId, toolName, llmService, model } = options;
159
265
 
160
266
  // --- P1: Depth enforcement ---
161
267
  const currentDepth: number = (ctx as any)[ORCHESTRATOR_DEPTH_KEY] ?? 0;
162
268
  if (currentDepth >= maxDepth) {
269
+ await logDelegation(ctx, plugin, {
270
+ leaderUsername,
271
+ subAgentUsername,
272
+ toolName,
273
+ task,
274
+ context,
275
+ result: '',
276
+ status: 'error',
277
+ depth: currentDepth,
278
+ durationMs: 0,
279
+ error: `Delegation depth limit reached (${currentDepth}/${maxDepth}).`,
280
+ });
163
281
  return {
164
282
  status: 'error' as const,
165
283
  content: `Delegation depth limit reached (${currentDepth}/${maxDepth}). Sub-agent "${subAgentUsername}" cannot delegate further.`,
@@ -167,6 +285,26 @@ async function invokeDelegateTask(
167
285
  }
168
286
 
169
287
  const startTime = Date.now();
288
+ const trace: TraceEvent[] = [
289
+ {
290
+ type: 'start',
291
+ at: nowIso(),
292
+ title: `Delegation started: ${leaderUsername} -> ${subAgentUsername}`,
293
+ content: task,
294
+ },
295
+ ];
296
+ const logRecord = await logDelegation(ctx, plugin, {
297
+ leaderUsername,
298
+ subAgentUsername,
299
+ toolName,
300
+ task,
301
+ context,
302
+ result: '',
303
+ status: 'running',
304
+ depth: currentDepth,
305
+ durationMs: 0,
306
+ trace,
307
+ });
170
308
 
171
309
  try {
172
310
  const aiPlugin = ctx.app.pm.get('ai') as PluginAIServer;
@@ -175,9 +313,27 @@ async function invokeDelegateTask(
175
313
  }
176
314
 
177
315
  // --- Step 1: Resolve LLM model from sub-agent's employee config ---
178
- const modelSettings = subAgentEmployee.modelSettings;
316
+ let modelSettings = subAgentEmployee.modelSettings;
317
+
318
+ // Override with orchestrator config if provided
319
+ if (llmService && model) {
320
+ modelSettings = { llmService, model };
321
+ }
322
+
179
323
  if (!modelSettings?.llmService || !modelSettings?.model) {
180
- throw new Error(`Sub-agent "${subAgentUsername}" has no LLM model configured. Please configure a model in the AI Employee settings.`);
324
+ // Fallback to leader's LLM model if sub-agent doesn't have one
325
+ const leaderEmployee = await plugin.db.getRepository('aiEmployees').findOne({
326
+ filter: { username: leaderUsername },
327
+ });
328
+
329
+ // The leader's model might be empty in the DB if it relies on the dynamic system default.
330
+ // In that case, we extract the dynamic `model` passed from the frontend request.
331
+ const dynamicModel = ctx.action?.params?.values?.model;
332
+ modelSettings = leaderEmployee?.modelSettings || dynamicModel;
333
+
334
+ if (!modelSettings?.llmService || !modelSettings?.model) {
335
+ throw new Error(`Sub-agent "${subAgentUsername}" has no LLM model configured (and leader fallback failed). Please configure a model in the Orchestrator Config or AI Employee settings.`);
336
+ }
181
337
  }
182
338
 
183
339
  const { provider } = await aiPlugin.aiManager.getLLMService({
@@ -207,7 +363,7 @@ async function invokeDelegateTask(
207
363
  // Only include tools that the sub-agent employee is configured to use.
208
364
  // Also skip our own delegate_to_* tools to prevent circular delegation
209
365
  // (belt-and-suspenders with the depth check above).
210
- if (!employeeSkills.includes(toolName) || toolName.startsWith('delegate_to_')) {
366
+ if (!employeeSkills.includes(toolName) || isDelegateToolName(toolName)) {
211
367
  continue;
212
368
  }
213
369
 
@@ -221,11 +377,39 @@ async function invokeDelegateTask(
221
377
  const invokeCtx = Object.create(ctx);
222
378
  (invokeCtx as any)[ORCHESTRATOR_DEPTH_KEY] = currentDepth + 1;
223
379
 
224
- const res = await toolEntry.invoke(invokeCtx, toolArgs, `orch-${toolCallId}`);
225
- if (res?.status === 'error') {
226
- throw new Error(`Tool <${toolName}> failed: ${res.content}`);
380
+ trace.push({
381
+ type: 'tool_call',
382
+ at: nowIso(),
383
+ title: `Calling tool: ${toolEntry.definition.name}`,
384
+ toolName: toolEntry.definition.name,
385
+ args: toolArgs,
386
+ });
387
+
388
+ try {
389
+ const res = await toolEntry.invoke(invokeCtx, toolArgs, `orch-${toolCallId}`);
390
+ trace.push({
391
+ type: 'tool_result',
392
+ at: nowIso(),
393
+ title: `Tool finished: ${toolEntry.definition.name}`,
394
+ toolName: toolEntry.definition.name,
395
+ status: res?.status || 'success',
396
+ content: truncateText(res?.content ?? res, 2000),
397
+ });
398
+ if (res?.status === 'error') {
399
+ throw new Error(`Tool <${toolEntry.definition.name}> failed: ${res.content}`);
400
+ }
401
+ return typeof res?.content === 'string' ? res.content : JSON.stringify(res);
402
+ } catch (e: any) {
403
+ trace.push({
404
+ type: 'tool_error',
405
+ at: nowIso(),
406
+ title: `Tool failed: ${toolEntry.definition.name}`,
407
+ toolName: toolEntry.definition.name,
408
+ status: 'error',
409
+ content: e.message,
410
+ });
411
+ throw e;
227
412
  }
228
- return typeof res?.content === 'string' ? res.content : JSON.stringify(res);
229
413
  },
230
414
  }),
231
415
  );
@@ -255,19 +439,31 @@ async function invokeDelegateTask(
255
439
  const result = await Promise.race([
256
440
  invokePromise,
257
441
  createTimeout(timeout, subAgentUsername, abortController),
258
- ]);
442
+ ]) as AgentExecutionResult;
259
443
 
260
- const content = (result as string) || 'Sub-agent completed the task but produced no output.';
444
+ const content = result.content || 'Sub-agent completed the task but produced no output.';
445
+ trace.push({
446
+ type: 'finish',
447
+ at: nowIso(),
448
+ title: `Delegation finished: ${subAgentUsername}`,
449
+ status: 'success',
450
+ content: truncateText(content, 2000),
451
+ });
261
452
 
262
453
  // Log successful execution for tracing
263
454
  await logDelegation(ctx, plugin, {
455
+ id: logRecord?.id,
264
456
  leaderUsername,
265
457
  subAgentUsername,
458
+ toolName,
266
459
  task,
460
+ context,
267
461
  result: content,
268
462
  status: 'success',
269
463
  depth: currentDepth,
270
464
  durationMs: Date.now() - startTime,
465
+ trace,
466
+ messages: result.messages,
271
467
  });
272
468
 
273
469
  return {
@@ -279,15 +475,30 @@ async function invokeDelegateTask(
279
475
 
280
476
  // Log failed execution for tracing
281
477
  await logDelegation(ctx, plugin, {
478
+ id: logRecord?.id,
282
479
  leaderUsername,
283
480
  subAgentUsername,
481
+ toolName,
284
482
  task,
483
+ context,
285
484
  result: '',
286
485
  status: 'error',
287
486
  depth: currentDepth,
288
487
  durationMs: Date.now() - startTime,
289
488
  error: e.message,
290
- }).catch(() => {}); // Don't let logging errors mask the real error
489
+ trace: [
490
+ ...trace,
491
+ {
492
+ type: 'error',
493
+ at: nowIso(),
494
+ title: `Delegation failed: ${subAgentUsername}`,
495
+ status: 'error',
496
+ content: e.message,
497
+ },
498
+ ],
499
+ }).catch((logErr) => {
500
+ plugin.app.log.warn('[AgentOrchestrator] Failed to save error log for delegation', logErr);
501
+ });
291
502
 
292
503
  return {
293
504
  status: 'error' as const,
@@ -303,34 +514,68 @@ async function logDelegation(
303
514
  ctx: Context,
304
515
  plugin: any,
305
516
  data: {
517
+ id?: number | string;
306
518
  leaderUsername: string;
307
519
  subAgentUsername: string;
520
+ toolName: string;
308
521
  task: string;
522
+ context?: string;
309
523
  result: string;
310
524
  status: string;
311
525
  depth: number;
312
526
  durationMs: number;
313
527
  error?: string;
528
+ trace?: TraceEvent[];
529
+ messages?: any[];
314
530
  },
315
531
  ) {
316
532
  try {
317
533
  const logsRepo = plugin.db.getRepository('orchestratorLogs');
318
- if (!logsRepo) return;
534
+ if (!logsRepo) {
535
+ plugin.app.log.warn('[AgentOrchestrator] orchestratorLogs repository not found — skipping log');
536
+ return;
537
+ }
538
+
539
+ // Safely resolve userId — ctx may be stale after long-running agent execution
540
+ let userId: number | undefined;
541
+ try {
542
+ userId = ctx.auth?.user?.id || ctx.state?.currentUser?.id;
543
+ } catch {
544
+ // ctx lifecycle ended — proceed without userId
545
+ }
546
+
547
+ const values = {
548
+ leaderUsername: data.leaderUsername,
549
+ subAgentUsername: data.subAgentUsername,
550
+ toolName: data.toolName,
551
+ task: truncateText(data.task, 10000),
552
+ context: truncateText(data.context || '', 10000),
553
+ result: truncateText(data.result || '', 50000),
554
+ status: data.status,
555
+ depth: data.depth,
556
+ durationMs: data.durationMs,
557
+ error: truncateText(data.error || '', 10000),
558
+ trace: data.trace || [],
559
+ messages: data.messages || [],
560
+ userId,
561
+ updatedAt: new Date(),
562
+ };
319
563
 
320
- await logsRepo.create({
564
+ if (data.id) {
565
+ await logsRepo.update({
566
+ filterByTk: data.id,
567
+ values,
568
+ });
569
+ return { id: data.id };
570
+ }
571
+
572
+ const record = await logsRepo.create({
321
573
  values: {
322
- leaderUsername: data.leaderUsername,
323
- subAgentUsername: data.subAgentUsername,
324
- toolName: `delegate_to_${data.subAgentUsername}`,
325
- task: (data.task || '').substring(0, 2000),
326
- result: (data.result || '').substring(0, 5000),
327
- status: data.status,
328
- depth: data.depth,
329
- durationMs: data.durationMs,
330
- error: (data.error || '').substring(0, 2000),
331
- userId: ctx.auth?.user?.id || ctx.state?.currentUser?.id,
574
+ ...values,
575
+ createdAt: new Date(),
332
576
  },
333
577
  });
578
+ return record?.toJSON?.() || record;
334
579
  } catch (e) {
335
580
  plugin.app.log.warn('[AgentOrchestrator] Failed to log delegation event', e);
336
581
  }
@@ -346,7 +591,7 @@ async function executeAgent(
346
591
  systemPrompt: string,
347
592
  task: string,
348
593
  signal?: AbortSignal,
349
- ): Promise<string> {
594
+ ): Promise<AgentExecutionResult> {
350
595
  const config: any = { recursionLimit: 50 };
351
596
  if (signal) {
352
597
  config.signal = signal;
@@ -366,20 +611,37 @@ async function executeAgent(
366
611
  const lastAIMessage = [...messages].reverse().find(m => m.getType() === 'ai');
367
612
 
368
613
  if (!lastAIMessage || !lastAIMessage.content) {
369
- return '';
614
+ return { content: '', messages: serializeMessages(messages) };
370
615
  }
371
616
 
617
+ let content = '';
372
618
  if (typeof lastAIMessage.content === 'string') {
373
- return lastAIMessage.content;
374
- }
375
-
376
- if (Array.isArray(lastAIMessage.content)) {
377
- return lastAIMessage.content
619
+ content = lastAIMessage.content;
620
+ } else if (Array.isArray(lastAIMessage.content)) {
621
+ content = lastAIMessage.content
378
622
  .map((c: any) => c.text || JSON.stringify(c))
379
623
  .join('\n');
624
+ } else {
625
+ content = String(lastAIMessage.content);
380
626
  }
381
627
 
382
- return String(lastAIMessage.content);
628
+ return { content, messages: serializeMessages(messages) };
629
+ }
630
+
631
+ function serializeMessages(messages: any[]) {
632
+ return (messages || []).map((message, index) => {
633
+ const type = typeof message.getType === 'function' ? message.getType() : message.type;
634
+ return {
635
+ index,
636
+ type,
637
+ name: message.name,
638
+ content: truncateText(message.content, 10000),
639
+ toolCalls: message.tool_calls || message.toolCalls || [],
640
+ toolCallId: message.tool_call_id,
641
+ additionalKwargs: message.additional_kwargs,
642
+ responseMetadata: message.response_metadata,
643
+ };
644
+ });
383
645
  }
384
646
 
385
647
  /**