@minded-ai/mindedjs 1.0.139 → 1.0.141

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 (54) hide show
  1. package/dist/agent.d.ts.map +1 -1
  2. package/dist/agent.js +88 -57
  3. package/dist/agent.js.map +1 -1
  4. package/dist/edges/createLogicalRouter.d.ts.map +1 -1
  5. package/dist/edges/createLogicalRouter.js +30 -4
  6. package/dist/edges/createLogicalRouter.js.map +1 -1
  7. package/dist/events/AgentEvents.d.ts +23 -17
  8. package/dist/events/AgentEvents.d.ts.map +1 -1
  9. package/dist/events/AgentEvents.js.map +1 -1
  10. package/dist/index.d.ts +1 -1
  11. package/dist/index.d.ts.map +1 -1
  12. package/dist/index.js.map +1 -1
  13. package/dist/interrupts/BaseInterruptSessionManager.d.ts +3 -6
  14. package/dist/interrupts/BaseInterruptSessionManager.d.ts.map +1 -1
  15. package/dist/interrupts/BaseInterruptSessionManager.js.map +1 -1
  16. package/dist/nodes/addPromptNode.d.ts.map +1 -1
  17. package/dist/nodes/addPromptNode.js +8 -2
  18. package/dist/nodes/addPromptNode.js.map +1 -1
  19. package/dist/platform/config.js +6 -6
  20. package/dist/platform/config.js.map +1 -1
  21. package/dist/platform/mindedConnectionTypes.d.ts +9 -41
  22. package/dist/platform/mindedConnectionTypes.d.ts.map +1 -1
  23. package/dist/types/Agent.types.d.ts +9 -7
  24. package/dist/types/Agent.types.d.ts.map +1 -1
  25. package/dist/types/Agent.types.js.map +1 -1
  26. package/dist/types/LangGraph.types.d.ts +0 -2
  27. package/dist/types/LangGraph.types.d.ts.map +1 -1
  28. package/dist/types/LangGraph.types.js +0 -4
  29. package/dist/types/LangGraph.types.js.map +1 -1
  30. package/dist/utils/logger.d.ts.map +1 -1
  31. package/dist/utils/logger.js +1 -1
  32. package/dist/utils/logger.js.map +1 -1
  33. package/dist/voice/voiceSession.js +1 -1
  34. package/dist/voice/voiceSession.js.map +1 -1
  35. package/docs/SUMMARY.md +2 -0
  36. package/docs/getting-started/environment-configuration.md +45 -0
  37. package/docs/low-code-editor/tools.md +29 -1
  38. package/docs/low-code-editor/triggers.md +26 -0
  39. package/docs/platform/sso.md +27 -0
  40. package/docs/sdk/agent-api.md +3 -3
  41. package/docs/sdk/events.md +67 -15
  42. package/package.json +2 -2
  43. package/src/agent.ts +96 -70
  44. package/src/edges/createLogicalRouter.ts +36 -4
  45. package/src/events/AgentEvents.ts +23 -17
  46. package/src/index.ts +1 -0
  47. package/src/interrupts/BaseInterruptSessionManager.ts +4 -6
  48. package/src/nodes/addPromptNode.ts +8 -2
  49. package/src/platform/config.ts +6 -6
  50. package/src/platform/mindedConnectionTypes.ts +9 -41
  51. package/src/types/Agent.types.ts +11 -7
  52. package/src/types/LangGraph.types.ts +0 -4
  53. package/src/utils/logger.ts +14 -11
  54. package/src/voice/voiceSession.ts +6 -6
@@ -0,0 +1,27 @@
1
+ # SSO (Single Sign-On)
2
+
3
+ ## Overview
4
+
5
+ The Minded platform supports SAML-based Single Sign-On (SSO) as an **enterprise feature** for authentication. This allows organizations to integrate their existing identity provider (IdP) with the Minded platform for seamless and secure user authentication.
6
+
7
+ ## Configuration
8
+
9
+ Setting up SSO involves exchanging configuration details between Minded (as the Service Provider) and your organization's Identity Provider.
10
+
11
+ ### From Minded (Service Provider → IdP)
12
+
13
+ When initiating SSO setup, Minded will provide the following configuration details:
14
+
15
+ - **Identifier (Entity ID)**: A unique identifier for the Minded platform as a service provider
16
+ - **Reply URL**: The endpoint where your IdP will send SAML responses after authentication
17
+
18
+ ### From Customer (IdP → Service Provider)
19
+
20
+ Once SSO is configured on your organization's side, you will need to provide Minded with:
21
+
22
+ - **Identity Provider (IdP) metadata**: XML metadata file containing your IdP's configuration
23
+ - **SAML signing certificates**: Certificates used to verify the authenticity of SAML assertions
24
+
25
+ ## Support
26
+
27
+ To setup SSO configuration please contact the Minded support team.
@@ -49,7 +49,7 @@ async updateState({
49
49
  - `state` (Partial<State>): An object containing the state properties to update. Can include:
50
50
  - `messages`: Array of messages to add or update
51
51
  - `memory`: Partial memory object to merge with existing memory
52
- - `overrideStartFromNodeId`: Node ID to override the next execution point
52
+ - `goto`: Node ID to override the next execution point
53
53
  - Any other state properties defined in your agent
54
54
 
55
55
  #### Usage Examples
@@ -156,7 +156,7 @@ await agent.updateState({
156
156
  sessionId: voiceSessionId,
157
157
  state: {
158
158
  messages: [correctedMessage],
159
- overrideStartFromNodeId: currentNodeId,
159
+ goto: currentNodeId,
160
160
  },
161
161
  });
162
162
  ```
@@ -214,7 +214,7 @@ const graphState = await agent.compiledGraph.getState(agent.getLangraphConfig(se
214
214
  // Update state with LangGraph API
215
215
  await agent.compiledGraph.updateState(agent.getLangraphConfig(sessionId), {
216
216
  messages: [newMessage],
217
- overrideStartFromNodeId: 'specific-node',
217
+ goto: 'specific-node',
218
218
  });
219
219
  ```
220
220
 
@@ -207,6 +207,9 @@ The three common patterns are:
207
207
  memory?: Memory, // Optional initial memory state
208
208
  history?: HistoryStep[], // Optional history steps to include
209
209
  sessionId?: string, // Optional session continuity identifier
210
+ state?: {
211
+ goto?: string, // Optional: jump to a specific node ID
212
+ }
210
213
  }
211
214
  ```
212
215
 
@@ -292,6 +295,27 @@ agent.on(events.TRIGGER_EVENT, async ({ triggerName, triggerBody }) => {
292
295
  });
293
296
  ```
294
297
 
298
+ #### Dynamic Flow Control with goto
299
+
300
+ ```typescript
301
+ agent.on(events.TRIGGER_EVENT, async ({ triggerName, triggerBody, state }) => {
302
+ // Check condition and jump to specific node
303
+ if (triggerBody.priority === 'high') {
304
+ return {
305
+ isQualified: true,
306
+ state: {
307
+ ...state,
308
+ goto: 'high-priority-handler', // Jump to specific node
309
+ },
310
+ };
311
+ }
312
+
313
+ return { isQualified: true };
314
+ });
315
+ ```
316
+
317
+ > **Note:** The jump occurs only when the current invocation completes and the next one begins. The agent will finish executing the current node before jumping to the specified node.
318
+
295
319
  ### Common Use Cases
296
320
 
297
321
  - **Input Validation**: Ensure trigger data meets requirements before processing
@@ -302,7 +326,7 @@ agent.on(events.TRIGGER_EVENT, async ({ triggerName, triggerBody }) => {
302
326
 
303
327
  ## ERROR
304
328
 
305
- The `ERROR` event is emitted when an error occurs during agent execution. This event allows you to handle errors gracefully, log them for debugging, or implement custom error recovery logic.
329
+ The `ERROR` event is emitted when an error occurs during agent execution. This event allows you to handle errors gracefully, log them for debugging, implement custom error recovery logic, or control whether the error should be thrown onwards.
306
330
 
307
331
  ### Input Structure
308
332
 
@@ -320,9 +344,15 @@ The `ERROR` event is emitted when an error occurs during agent execution. This e
320
344
 
321
345
  ### Handler Return Value
322
346
 
323
- - **Return type**: `void`
324
- - **Purpose**: Handlers are used for side effects like error logging, notifications, or recovery actions
325
- - **Note**: Return values are ignored
347
+ ```typescript
348
+ {
349
+ throwError: boolean; // Whether to throw the error onwards and stop the agent run
350
+ state?: Partial<State<Memory>>; // Optional state updates to apply
351
+ }
352
+ ```
353
+
354
+ - **throwError**: When set to `true`, the error will be thrown onwards and the agent run will stop. If `false` or not returned, the error is caught and the agent continues.
355
+ - **state**: Optional partial state updates to apply before continuing or throwing
326
356
 
327
357
  ### Usage Example
328
358
 
@@ -335,13 +365,12 @@ const agent = new Agent({
335
365
  tools,
336
366
  });
337
367
 
338
- // Listen to errors
368
+ // Listen to errors and control error flow
339
369
  agent.on(events.ERROR, async ({ error, state }) => {
340
370
  console.error('Agent error occurred:', error.message);
341
371
  console.error('Error stack:', error.stack);
342
372
  console.log('Session ID:', state.sessionId);
343
373
  console.log('Current memory:', state.memory);
344
- console.log('Messages at error:', state.messages.length);
345
374
 
346
375
  // Log to external service
347
376
  await logger.error({
@@ -352,26 +381,49 @@ agent.on(events.ERROR, async ({ error, state }) => {
352
381
  memory: state.memory,
353
382
  });
354
383
 
355
- // Notify monitoring system
356
- await notificationService.alert({
357
- type: 'agent_error',
358
- error: error.message,
359
- sessionId: state.sessionId,
360
- });
384
+ // Handle recoverable vs non-recoverable errors
385
+ if (error.message.includes('rate limit')) {
386
+ // Recoverable error - update state and continue
387
+ return {
388
+ throwError: false, // Don't stop the agent
389
+ state: {
390
+ memory: {
391
+ ...state.memory,
392
+ errorRecovered: true,
393
+ lastError: error.message,
394
+ },
395
+ },
396
+ };
397
+ }
398
+
399
+ // Critical error - stop the agent
400
+ if (error.message.includes('authentication failed')) {
401
+ await notificationService.alert({
402
+ type: 'critical_error',
403
+ error: error.message,
404
+ sessionId: state.sessionId,
405
+ });
406
+
407
+ return {
408
+ throwError: true, // Stop the agent run
409
+ };
410
+ }
361
411
 
362
- // Send error response to user (if needed)
363
- await sendErrorMessageToUser('Something went wrong. Please try again.', state.sessionId);
412
+ // Default: let the error be handled normally
413
+ return { throwError: false };
364
414
  });
365
415
  ```
366
416
 
367
417
  ### Common Use Cases
368
418
 
369
419
  - **Error Logging**: Record errors for debugging and monitoring with full context
420
+ - **Error Recovery**: Implement custom recovery logic by returning `throwError: false` and updating state
421
+ - **Critical Error Handling**: Stop agent execution for unrecoverable errors with `throwError: true`
370
422
  - **User Notifications**: Provide graceful error messages to users
371
423
  - **Monitoring & Alerting**: Integrate with monitoring systems for error tracking
372
- - **Error Recovery**: Implement custom recovery logic or fallback behaviors
373
424
  - **Session Management**: Clean up or reset sessions that encountered errors
374
425
  - **Analytics**: Track error patterns and frequencies for system improvement
426
+ - **Graceful Degradation**: Continue execution with fallback behavior for non-critical errors
375
427
 
376
428
  ## ON_LOGICAL_CONDITION
377
429
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@minded-ai/mindedjs",
3
- "version": "1.0.139",
3
+ "version": "1.0.141",
4
4
  "description": "MindedJS is a TypeScript library for building agents.",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -67,4 +67,4 @@
67
67
  "zod": "^3.25.74",
68
68
  "zod-to-json-schema": "^3.24.6"
69
69
  }
70
- }
70
+ }
package/src/agent.ts CHANGED
@@ -26,9 +26,9 @@ import { AgentInvokeParams, MindedSDKConfig, SessionType } from './types/Agent.t
26
26
  import { createLlmInstance } from './llm/createLlmInstance';
27
27
  import { createCheckpointSaver } from './checkpointer/checkpointSaverFactory';
28
28
  import { getConfig } from './platform/config';
29
- import { InterruptSessionManager, InterruptType } from './interrupts/BaseInterruptSessionManager';
29
+ import { InterruptPayload, InterruptSessionManager, InterruptType } from './interrupts/BaseInterruptSessionManager';
30
30
  import { createInterruptSessionManager } from './interrupts/interruptSessionManagerFactory';
31
- import { BaseMessage, HumanMessage } from '@langchain/core/messages';
31
+ import { HumanMessage } from '@langchain/core/messages';
32
32
  import triggerTypeToDefaultMessage from './triggers/triggerTypeToDefaultMessage';
33
33
  import appActionRunnerToolCreator from './internalTools/appActionRunnerTool';
34
34
  import { VoiceSession } from './voice/voiceSession';
@@ -417,96 +417,105 @@ export class Agent {
417
417
  return;
418
418
  }
419
419
  // Session lock acquired, proceed with processing
420
-
421
- let messages: Array<BaseMessage> = [];
422
- let memoryUpdate = {};
423
- let sessionType: SessionType = SessionType.TEXT;
424
-
425
420
  const langraphConfig = this.getLangraphConfig(sessionId);
421
+ const state = await this.compiledGraph.getState(langraphConfig);
426
422
 
427
- let state = await this.compiledGraph.getState(langraphConfig);
423
+ // Initialize state update with defaults
424
+ let stateUpdate: Partial<State<z.infer<typeof this.memorySchema>>> = {
425
+ sessionId,
426
+ sessionType: SessionType.TEXT,
427
+ };
428
428
 
429
+ // Process special trigger types (dashboard/voice messages)
429
430
  if (triggerName === KnownTriggerNames.DASHBOARD_MESSAGE || triggerName === KnownTriggerNames.VOICE_MESSAGE) {
430
- // Parse attachments if present
431
+ // Parse attachments and create message
431
432
  const attachmentsString = parseAttachments(triggerBody);
432
433
  const finalContent = combineContentWithAttachments(triggerBody.content || '', attachmentsString);
433
434
  if (finalContent) {
434
- messages = [new HumanMessage({ content: finalContent, id: uuidv4() })];
435
+ stateUpdate.messages = [new HumanMessage({ content: finalContent, id: uuidv4() })];
435
436
  }
436
437
  triggerBody.content = finalContent;
437
- sessionType = triggerName === KnownTriggerNames.VOICE_MESSAGE ? SessionType.VOICE : SessionType.TEXT;
438
+ stateUpdate.sessionType = triggerName === KnownTriggerNames.VOICE_MESSAGE ? SessionType.VOICE : SessionType.TEXT;
438
439
  }
439
440
 
440
- // Trigger event
441
+ // Merge current state with any updates we've made so far
442
+ const currentState = { ...state.values, ...stateUpdate };
443
+
444
+ // Emit trigger event and let handlers modify the state
441
445
  const results = await this.emit(AgentEvents.TRIGGER_EVENT, {
442
446
  triggerName,
443
447
  triggerBody,
444
- state: state.values,
448
+ state: currentState,
445
449
  });
446
- if (results.length === 0) {
447
- if (appName) {
448
- messages =
449
- messages.length > 0
450
- ? messages
451
- : triggerTypeToDefaultMessage[appName]?.[triggerName]?.(triggerBody) ?? [
452
- new HumanMessage({ content: JSON.stringify(triggerBody), id: uuidv4() }),
453
- ];
454
- } else {
455
- messages = messages.length > 0 ? messages : [new HumanMessage({ content: JSON.stringify(triggerBody), id: uuidv4() })];
450
+
451
+ // Process handler results
452
+ const handlerResult = results.find((r) => r !== undefined);
453
+ if (handlerResult) {
454
+ if (!handlerResult.isQualified) {
455
+ logger.debug({ message: '[Trigger] Disqualified', triggerName, triggerBody, sessionId });
456
+ await this.interruptSessionManager.release(sessionId);
457
+ return;
456
458
  }
457
- } else {
458
- const handlerResult = results.find((r) => r !== undefined);
459
- if (handlerResult) {
460
- if (!handlerResult.isQualified) {
461
- logger.trace({ message: '[Trigger] Disqualified', triggerName, sessionId });
462
- await this.interruptSessionManager.release(sessionId);
463
- return;
464
- }
465
- memoryUpdate = handlerResult.memory || {};
466
- messages = handlerResult.messages ?? (messages.length > 0 ? messages : []);
459
+ // Merge handler's state updates
460
+ if (handlerResult.state) {
461
+ stateUpdate = { ...stateUpdate, ...handlerResult.state };
462
+ }
463
+ }
464
+
465
+ // If no messages were set by handler or initial processing, create default message
466
+ if (!stateUpdate.messages || stateUpdate.messages.length === 0) {
467
+ if (appName && triggerTypeToDefaultMessage[appName]?.[triggerName]) {
468
+ stateUpdate.messages = triggerTypeToDefaultMessage[appName][triggerName](triggerBody);
469
+ } else {
470
+ stateUpdate.messages = [new HumanMessage({ content: JSON.stringify(triggerBody), id: uuidv4() })];
467
471
  }
468
472
  }
469
473
 
470
- logger.info({ msg: '[Trigger] Received', triggerName, sessionId });
471
- state = await this.compiledGraph.getState(langraphConfig);
474
+ logger.info({ msg: '[Trigger] Received', triggerName, triggerBody, sessionId });
472
475
 
473
476
  // 2) Decide how to invoke: resume/update/goto/normal
474
- let nodeToBeInvoked = state.next?.length > 0 ? state.values.overrideStartFromNodeId ?? state.next[0] : this.startingNodeId;
477
+ let nodeToBeInvoked = state.next.length > 0 ? (state.values.goto ? state.values.goto : state.next[0]) : this.startingNodeId;
475
478
  if (!nodeToBeInvoked) throw new Error('No node to be invoked');
476
479
 
477
480
  const suffixes = Object.values(internalNodesSuffix);
478
481
  nodeToBeInvoked = nodeToBeInvoked.replace(new RegExp(suffixes.join('|'), 'g'), '');
479
482
 
480
- const historyStep = createTriggerHistoryStep(
481
- state.values.history,
482
- nodeToBeInvoked,
483
- messages.map((m) => m.id!),
484
- triggerName,
485
- triggerBody,
486
- appName,
487
- );
483
+ // Create a history step if not provided by the handler
484
+ if (!stateUpdate.history) {
485
+ const messageIds = stateUpdate.messages?.map((m) => m.id!) || [];
486
+ const historyStep = createTriggerHistoryStep(state.values.history, nodeToBeInvoked, messageIds, triggerName, triggerBody, appName);
487
+ // History reducer expects an array or single item
488
+ stateUpdate.history = [historyStep];
489
+ }
488
490
 
489
491
  // 3) Handle interrupts/goto, else normal invoke
490
492
  let res;
491
493
  const lastTask = state.tasks[state.tasks.length - 1];
492
494
  const hasInterrupt = lastTask?.interrupts?.length > 0;
493
495
  if (hasInterrupt) {
494
- const interruptValue = (lastTask!.interrupts[0] as any).value;
495
- if (interruptValue?.type === InterruptType.HUMAN_IN_THE_LOOP) {
496
- // For HUMAN_IN_THE_LOOP, use resume with the full object
496
+ const interruptValue = lastTask!.interrupts[0].value as InterruptPayload;
497
+ if (interruptValue.type === InterruptType.HUMAN_IN_THE_LOOP) {
498
+ // For HUMAN_IN_THE_LOOP, use resume with the state update
497
499
  res = await this.compiledGraph.invoke(
498
500
  new Command({
499
- resume: { memory: memoryUpdate, messages, history: historyStep, sessionId, sessionType, overrideStartFromNodeId: null },
501
+ resume: { ...stateUpdate, goto: null },
500
502
  }),
501
503
  langraphConfig,
502
504
  );
503
505
  } else if (interruptValue?.type === InterruptType.NEW_TRIGGERS) {
504
- // For NEW_TRIGGERS, check if there's an updateStateObject to apply first
505
- const finalState = { memory: memoryUpdate, messages, history: [historyStep], sessionId, sessionType };
506
- if (interruptValue.updateStateObject) {
507
- finalState.messages = [...interruptValue.updateStateObject.messages, ...messages];
508
- finalState.history = [...(interruptValue.updateStateObject.history || []), historyStep];
509
- //add handlers for other state fields as needed
506
+ // For NEW_TRIGGERS, merge with updateStateObject if present
507
+ const finalState: any = { ...stateUpdate };
508
+ const updatedInterruptState = interruptValue.updateStateObject;
509
+ if (updatedInterruptState) {
510
+ // Merge messages and history using the reducers' logic
511
+ finalState.messages = [...(updatedInterruptState.messages || []), ...(stateUpdate.messages || [])];
512
+ finalState.history = [...(updatedInterruptState.history || []), ...(stateUpdate.history || [])];
513
+ // Merge other fields from updateStateObject
514
+ Object.keys(updatedInterruptState).forEach((key) => {
515
+ if (key !== 'messages' && key !== 'history') {
516
+ finalState[key] = updatedInterruptState[key as keyof typeof updatedInterruptState];
517
+ }
518
+ });
510
519
  }
511
520
 
512
521
  // Then use update with the full object and empty resume
@@ -518,26 +527,19 @@ export class Agent {
518
527
  langraphConfig,
519
528
  );
520
529
  }
521
- } else if (state.values.overrideStartFromNodeId) {
530
+ } else if (state.values.goto) {
522
531
  res = await this.compiledGraph.invoke(
523
532
  new Command({
524
533
  update: {
525
- overrideStartFromNodeId: null, //reset the overrideStartFromNodeId
526
- messages,
527
- memory: memoryUpdate,
528
- history: historyStep,
529
- sessionId,
530
- sessionType,
534
+ ...stateUpdate,
535
+ goto: null, //reset the goto
531
536
  },
532
- goto: state.values.overrideStartFromNodeId,
537
+ goto: state.values.goto,
533
538
  }),
534
539
  langraphConfig,
535
540
  );
536
541
  } else {
537
- res = await this.compiledGraph.invoke(
538
- { messages, memory: memoryUpdate, history: historyStep, sessionId, sessionType },
539
- langraphConfig,
540
- );
542
+ res = await this.compiledGraph.invoke(stateUpdate, langraphConfig);
541
543
  }
542
544
  const nextMessage = await this.interruptSessionManager.dequeue(sessionId);
543
545
  if (nextMessage) {
@@ -566,8 +568,24 @@ export class Agent {
566
568
  await this.interruptSessionManager.release(sessionId);
567
569
 
568
570
  const state = await this.compiledGraph.getState(this.getLangraphConfig(sessionId));
569
- this.emit(AgentEvents.ERROR, { error: err instanceof Error ? err : new Error(JSON.stringify(err)), state: state.values });
570
- throw err;
571
+ const results = await this.emit(AgentEvents.ERROR, {
572
+ error: err instanceof Error ? err : new Error(JSON.stringify(err)),
573
+ state: state.values,
574
+ });
575
+ const handlerResult = results.find((r) => r !== undefined);
576
+ let throwError = handlerResult === undefined;
577
+ if (handlerResult) {
578
+ // Merge handler's state updates
579
+ if (handlerResult.state) {
580
+ handlerResult.state = { ...state.values, ...handlerResult.state };
581
+ await this.compiledGraph.updateState(this.getLangraphConfig(sessionId), handlerResult.state);
582
+ }
583
+ // If one handler returns throwError, we throw the error
584
+ throwError = throwError || handlerResult.throwError;
585
+ }
586
+ if (throwError) {
587
+ throw err;
588
+ }
571
589
  }
572
590
  }
573
591
 
@@ -801,12 +819,19 @@ export class Agent {
801
819
 
802
820
  this.voiceSessions.set(params.sessionId, voiceSession);
803
821
 
822
+ const state = await this.compiledGraph.getState(this.getLangraphConfig(params.sessionId));
823
+
804
824
  // Emit voice session start event
805
- await this.emit(AgentEvents.VOICE_SESSION_START, {
806
- sessionId: params.sessionId,
825
+ const results = await this.emit(AgentEvents.VOICE_SESSION_START, {
807
826
  metadata: params.metadata,
827
+ state: state.values as State<z.infer<typeof this.memorySchema>>,
808
828
  });
809
829
 
830
+ const handlerResult = results.find((r) => r !== undefined);
831
+ if (handlerResult && handlerResult.state) {
832
+ await this.compiledGraph.updateState(this.getLangraphConfig(params.sessionId), handlerResult.state);
833
+ }
834
+
810
835
  return voiceSession;
811
836
  }
812
837
 
@@ -852,6 +877,7 @@ export class Agent {
852
877
  return { error: 'Failed to execute tool' };
853
878
  }
854
879
  }
880
+
855
881
  public async getCurrentNode(sessionId: string) {
856
882
  const langConfig = this.getLangraphConfig(sessionId);
857
883
  const currentNodeId = (await this.compiledGraph.getState(langConfig))?.tasks?.[0]?.name;
@@ -13,7 +13,7 @@ export const createLogicalRouter = ({ edges, agent }: { edges: LogicalConditionE
13
13
  logger.debug({ msg: `[Router] Evaluating logical conditions for ${edges.length} edges` });
14
14
 
15
15
  if (state.goto) {
16
- console.log('Jumping to node', state.goto);
16
+ logger.debug({ msg: `[Router] Jumping to node`, node: state.goto });
17
17
  return state.goto;
18
18
  }
19
19
 
@@ -30,12 +30,17 @@ export const createLogicalRouter = ({ edges, agent }: { edges: LogicalConditionE
30
30
  logger.debug({ msg: '[LogicalCondition] Evaluating condition', edge });
31
31
 
32
32
  // Emit ON_LOGICAL_CONDITION event
33
- await agent.emit(AgentEvents.ON_LOGICAL_CONDITION, {
33
+ const onLogicalConditionRes = await agent.emit(AgentEvents.ON_LOGICAL_CONDITION, {
34
34
  edge,
35
35
  state,
36
36
  condition: edge.condition,
37
37
  });
38
38
 
39
+ const handlerResult = onLogicalConditionRes.find((r) => r !== undefined);
40
+ if (handlerResult && handlerResult.state) {
41
+ state = { ...state, ...handlerResult.state };
42
+ }
43
+
39
44
  // Customer is responsible for providing valid JavaScript syntax
40
45
  // We execute their condition in a sandboxed VM with timeout protection
41
46
  const conditionCode = `
@@ -90,7 +95,7 @@ export const createLogicalRouter = ({ edges, agent }: { edges: LogicalConditionE
90
95
  });
91
96
 
92
97
  // Emit ON_LOGICAL_CONDITION_RESULT event
93
- await agent.emit(AgentEvents.ON_LOGICAL_CONDITION_RESULT, {
98
+ const onLogicalConditionResultRes = await agent.emit(AgentEvents.ON_LOGICAL_CONDITION_RESULT, {
94
99
  edge,
95
100
  state,
96
101
  condition: edge.condition,
@@ -98,6 +103,21 @@ export const createLogicalRouter = ({ edges, agent }: { edges: LogicalConditionE
98
103
  executionTimeMs: executionTime,
99
104
  });
100
105
 
106
+ let goto = null;
107
+ const onLogicalConditionResultHandlerResult = onLogicalConditionResultRes.find((r) => r !== undefined);
108
+ if (onLogicalConditionResultHandlerResult && onLogicalConditionResultHandlerResult.state) {
109
+ state = { ...state, ...onLogicalConditionResultHandlerResult.state };
110
+ goto = state.goto;
111
+ state.goto = null;
112
+ }
113
+
114
+ await agent.updateState({ sessionId: state.sessionId, state });
115
+
116
+ if (goto) {
117
+ logger.debug({ msg: `[Router] Jumping to node`, node: goto });
118
+ return goto;
119
+ }
120
+
101
121
  if (booleanResult) {
102
122
  if (edge.source == edge.target) {
103
123
  logger.info({ msg: `[Router] Stay at node ${edge.source}`, node: edge.source, condition: edge.condition });
@@ -128,7 +148,7 @@ export const createLogicalRouter = ({ edges, agent }: { edges: LogicalConditionE
128
148
  });
129
149
 
130
150
  // Emit ON_LOGICAL_CONDITION_RESULT event with error
131
- await agent.emit(AgentEvents.ON_LOGICAL_CONDITION_RESULT, {
151
+ const onLogicalConditionResultRes = await agent.emit(AgentEvents.ON_LOGICAL_CONDITION_RESULT, {
132
152
  edge,
133
153
  state,
134
154
  condition: edge.condition,
@@ -137,8 +157,20 @@ export const createLogicalRouter = ({ edges, agent }: { edges: LogicalConditionE
137
157
  error: errorObj,
138
158
  });
139
159
 
160
+ const onLogicalConditionResultHandlerResult = onLogicalConditionResultRes.find((r) => r !== undefined);
161
+ let throwError = false;
162
+ if (onLogicalConditionResultHandlerResult && onLogicalConditionResultHandlerResult.state) {
163
+ state = { ...state, ...onLogicalConditionResultHandlerResult.state };
164
+ }
165
+ throwError = onLogicalConditionResultHandlerResult?.throwError == true;
166
+
167
+ await agent.updateState({ sessionId: state.sessionId, state });
168
+
140
169
  // Continue to next edge instead of failing completely
141
170
  // This allows other conditions to be evaluated even if one fails
171
+ if (throwError) {
172
+ throw errorObj;
173
+ }
142
174
  continue;
143
175
  }
144
176
  }
@@ -1,7 +1,6 @@
1
- import { BaseMessage } from '@langchain/core/messages';
2
- import { HistoryStep } from '../types/Agent.types';
3
1
  import { State } from '../types/LangGraph.types';
4
2
  import { LogicalConditionEdge } from '../types/Flows.types';
3
+ import { TriggerEvent } from '../types/Agent.types';
5
4
 
6
5
  export enum AgentEvents {
7
6
  AI_MESSAGE = 'AI_MESSAGE',
@@ -17,18 +16,16 @@ export type AgentEventRequestPayloads<Memory> = {
17
16
  message: string;
18
17
  state: State<Memory>;
19
18
  };
20
- [AgentEvents.TRIGGER_EVENT]: {
21
- triggerName: string;
22
- triggerBody: any;
19
+ [AgentEvents.TRIGGER_EVENT]: TriggerEvent & {
23
20
  state: State<Memory>;
24
21
  };
25
22
  [AgentEvents.VOICE_SESSION_START]: {
26
- sessionId: string;
27
23
  metadata?: Record<string, any>;
24
+ state: State<Memory>;
28
25
  };
29
26
  [AgentEvents.ERROR]: {
30
27
  error: Error;
31
- state?: State<Memory>;
28
+ state: State<Memory>;
32
29
  };
33
30
  [AgentEvents.ON_LOGICAL_CONDITION]: {
34
31
  edge: LogicalConditionEdge;
@@ -46,16 +43,25 @@ export type AgentEventRequestPayloads<Memory> = {
46
43
  };
47
44
 
48
45
  export type AgentEventResponsePayloads<Memory> = {
49
- [AgentEvents.AI_MESSAGE]: void;
46
+ [AgentEvents.AI_MESSAGE]: {
47
+ state?: Partial<State<Memory>>;
48
+ };
50
49
  [AgentEvents.TRIGGER_EVENT]: {
51
50
  isQualified: boolean;
52
- messages?: BaseMessage[];
53
- memory?: Memory;
54
- history?: HistoryStep[];
55
- sessionId?: string;
56
- };
57
- [AgentEvents.VOICE_SESSION_START]: void;
58
- [AgentEvents.ERROR]: void;
59
- [AgentEvents.ON_LOGICAL_CONDITION]: void;
60
- [AgentEvents.ON_LOGICAL_CONDITION_RESULT]: void;
51
+ state?: Partial<State<Memory>>;
52
+ };
53
+ [AgentEvents.VOICE_SESSION_START]: {
54
+ state?: Partial<State<Memory>>;
55
+ };
56
+ [AgentEvents.ERROR]: {
57
+ throwError: boolean;
58
+ state?: Partial<State<Memory>>;
59
+ };
60
+ [AgentEvents.ON_LOGICAL_CONDITION]: {
61
+ state?: Partial<State<Memory>>;
62
+ };
63
+ [AgentEvents.ON_LOGICAL_CONDITION_RESULT]: {
64
+ state?: Partial<State<Memory>>;
65
+ throwError?: boolean;
66
+ };
61
67
  };
package/src/index.ts CHANGED
@@ -75,6 +75,7 @@ export {
75
75
  AppTriggerHistoryStep,
76
76
  CustomActionInvocationHistoryStep,
77
77
  SessionType,
78
+ TriggerEvent,
78
79
  } from './types/Agent.types';
79
80
  export type { AgentInvokeParams, MindedSDKConfig } from './types/Agent.types';
80
81
  export type { Environment } from './types/Platform.types';
@@ -1,6 +1,7 @@
1
1
  import { State } from '../types/LangGraph.types';
2
2
  import { GraphInterrupt } from '@langchain/langgraph';
3
3
  import { logger } from '../utils/logger';
4
+ import { TriggerEvent } from '../types/Agent.types';
4
5
 
5
6
  export enum InterruptType {
6
7
  NEW_TRIGGERS = 'NEW_TRIGGERS',
@@ -9,13 +10,10 @@ export enum InterruptType {
9
10
 
10
11
  export const QUEUE_INTERRUPT_DETECTED_BEFORE_SPEECH = 'QUEUE_INTERRUPT_DETECTED_BEFORE_SPEECH';
11
12
 
12
- export interface QueuedMessage {
13
- triggerBody: any;
14
- triggerName: string;
15
- appName?: string;
16
- }
13
+ export type QueuedMessage = TriggerEvent;
14
+
17
15
  export interface InterruptPayload {
18
- type: InterruptType.NEW_TRIGGERS;
16
+ type: InterruptType;
19
17
  updateStateObject?: Partial<State>;
20
18
  id?: string;
21
19
  }