@memberjunction/ai-agents 5.21.0 → 5.23.0

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.
@@ -24,6 +24,7 @@ import { AgentRunner } from './AgentRunner.js';
24
24
  import { PayloadManager } from './PayloadManager.js';
25
25
  import { ScratchpadManager } from './ScratchpadManager.js';
26
26
  import { AgentDataPreloader } from './AgentDataPreloader.js';
27
+ import { ClientToolRequestManager } from './ClientToolRequestManager.js';
27
28
  import { ConversationMessageResolver } from './utils/ConversationMessageResolver.js';
28
29
  import _ from 'lodash';
29
30
  /**
@@ -81,6 +82,13 @@ export class BaseAgent {
81
82
  * @private
82
83
  */
83
84
  this._metadata = new Metadata();
85
+ /**
86
+ * Active per-request metadata provider, set at the start of Execute().
87
+ * Defaults to the global Metadata.Provider; overridden when a per-request
88
+ * provider is passed through ExecuteAgentParams.provider for server isolation.
89
+ * @private
90
+ */
91
+ this._activeProvider = Metadata.Provider;
84
92
  /**
85
93
  * This is state information that is specific to the agent type. BaseAgent doesn't know what
86
94
  * this contains or care, it is just responsible for keeping this, giving the Agent Type the
@@ -819,6 +827,9 @@ export class BaseAgent {
819
827
  * ```
820
828
  */
821
829
  async Execute(params) {
830
+ // Capture per-request provider for the duration of this execution so all entity
831
+ // saves go through the isolated provider, never the global singleton's transaction.
832
+ this._activeProvider = params.provider || Metadata.Provider;
822
833
  try {
823
834
  this.logStatus(`🤖 Starting execution of agent '${params.agent.Name}'`, true, params);
824
835
  // Check permissions - user must have run permission or be the owner
@@ -1597,6 +1608,8 @@ export class BaseAgent {
1597
1608
  promptParams.apiKeys = params.apiKeys;
1598
1609
  this.logStatus(`🔑 Using ${params.apiKeys.length} API key(s) provided at runtime`, true, params);
1599
1610
  }
1611
+ // Thread the per-request provider so prompt run records are saved through the isolated provider
1612
+ promptParams.provider = params.provider || this._activeProvider;
1600
1613
  return promptParams;
1601
1614
  }
1602
1615
  /**
@@ -1661,6 +1674,9 @@ export class BaseAgent {
1661
1674
  case 'While':
1662
1675
  // While loops are valid - no additional validation needed
1663
1676
  return nextStep;
1677
+ case 'ClientTools':
1678
+ // Client tools are valid - execution handled by executeClientToolsStep
1679
+ return nextStep;
1664
1680
  default:
1665
1681
  // if we get here, the next step is not recognized, we can return a retry step
1666
1682
  this.logError(`Invalid next step '${nextStep.step}' for agent '${params.agent.Name}'`, {
@@ -2586,9 +2602,9 @@ export class BaseAgent {
2586
2602
  ? currentStepCount - msg.metadata.turnAdded
2587
2603
  : 0,
2588
2604
  tokens: this.estimateTokens(msg.content),
2589
- isActionResult: msg.metadata?.messageType === 'action-result'
2605
+ isToolResult: this.IsToolResultMessage(msg)
2590
2606
  }))
2591
- .filter(c => c.isActionResult && c.age >= minAge)
2607
+ .filter(c => c.isToolResult && c.age >= minAge)
2592
2608
  .sort((a, b) => b.age - a.age); // Oldest first
2593
2609
  // Remove messages until we've saved enough
2594
2610
  for (const candidate of candidates) {
@@ -2639,10 +2655,10 @@ export class BaseAgent {
2639
2655
  ? currentStepCount - msg.metadata.turnAdded
2640
2656
  : 0,
2641
2657
  tokens: this.estimateTokens(msg.content),
2642
- isActionResult: msg.metadata?.messageType === 'action-result',
2658
+ isToolResult: this.IsToolResultMessage(msg),
2643
2659
  alreadyCompacted: msg.metadata?.wasCompacted === true
2644
2660
  }))
2645
- .filter(c => c.isActionResult && c.age >= minAge && !c.alreadyCompacted)
2661
+ .filter(c => c.isToolResult && c.age >= minAge && !c.alreadyCompacted)
2646
2662
  .sort((a, b) => b.age - a.age); // Oldest first
2647
2663
  for (const candidate of candidates) {
2648
2664
  if (tokensSaved >= tokensToSave)
@@ -2701,10 +2717,10 @@ export class BaseAgent {
2701
2717
  message: msg,
2702
2718
  index: index,
2703
2719
  tokens: this.estimateTokens(msg.content),
2704
- isActionResult: msg.metadata?.messageType === 'action-result',
2720
+ isToolResult: this.IsToolResultMessage(msg),
2705
2721
  alreadyCompacted: msg.metadata?.wasCompacted === true
2706
2722
  }))
2707
- .filter(c => c.isActionResult && !c.alreadyCompacted && c.tokens > 200)
2723
+ .filter(c => c.isToolResult && !c.alreadyCompacted && c.tokens > 200)
2708
2724
  .sort((a, b) => b.tokens - a.tokens); // Largest first
2709
2725
  for (const candidate of candidates) {
2710
2726
  if (tokensSaved >= tokensToSave)
@@ -2992,6 +3008,10 @@ The context is now within limits. Please retry your request with the recovered c
2992
3008
  const agentType = engine.AgentTypes.find(at => UUIDsEqual(at.ID, agent.TypeID));
2993
3009
  const runtimePromptParamOverrides = extraData?.__agentTypePromptParams;
2994
3010
  const agentTypePromptParams = this.buildAgentTypePromptParams(agentType, agent, runtimePromptParamOverrides);
3011
+ // Build client tool details for the prompt
3012
+ const clientToolDetails = this.buildClientToolPromptSection(agent, extraData);
3013
+ // Build app context section if provided in extraData
3014
+ const appContext = this.buildAppContextSection(extraData);
2995
3015
  const contextData = {
2996
3016
  agentName: agent.Name,
2997
3017
  agentDescription: agent.Description,
@@ -3000,6 +3020,8 @@ The context is now within limits. Please retry your request with the recovered c
3000
3020
  subAgentDetails: this.formatSubAgentDetails(uniqueActiveSubAgents),
3001
3021
  actionCount: activeActions.length,
3002
3022
  actionDetails: this.formatActionDetails(activeActions),
3023
+ clientToolDetails: clientToolDetails,
3024
+ appContext: appContext,
3003
3025
  };
3004
3026
  // Build the final result with __agentTypePromptParams injected
3005
3027
  // Note: extraData can override contextData properties, but __agentTypePromptParams
@@ -3011,7 +3033,11 @@ The context is now within limits. Please retry your request with the recovered c
3011
3033
  if (extraData) {
3012
3034
  // Spread extraData but don't let it override __agentTypePromptParams
3013
3035
  // (which was already built with runtime overrides included)
3014
- const { __agentTypePromptParams: _ignored, ...restExtraData } = extraData;
3036
+ // Spread extraData into the result but exclude properties that were already
3037
+ // processed into formatted prompt sections above. Without this exclusion,
3038
+ // the raw objects from extraData would overwrite the formatted markdown strings
3039
+ // in contextData (e.g., appContext object would replace the markdown string).
3040
+ const { __agentTypePromptParams: _ignored, appContext: _ignoredAppContext, ...restExtraData } = extraData;
3015
3041
  return {
3016
3042
  ...result,
3017
3043
  ...restExtraData
@@ -3327,8 +3353,8 @@ The context is now within limits. Please retry your request with the recovered c
3327
3353
  async ExecuteSubAgent(params, subAgentRequest, subAgent, stepEntity, payload, contextMessage, stepCount = 0) {
3328
3354
  try {
3329
3355
  this.logStatus(`🤖 Executing sub-agent '${subAgentRequest.name}'`, true, params);
3330
- // Create a new AgentRunner instance
3331
- const runner = new AgentRunner();
3356
+ // Create a new AgentRunner instance with the same isolated provider
3357
+ const runner = new AgentRunner(params.provider || this._activeProvider);
3332
3358
  // Prepare messages for sub-agent using database-configured message mode
3333
3359
  const subAgentMessages = this.prepareSubAgentMessages(params, subAgentRequest, subAgent, contextMessage);
3334
3360
  // Set parent run ID in the sub-agent's execution
@@ -3489,6 +3515,111 @@ The context is now within limits. Please retry your request with the recovered c
3489
3515
  return lines.join('\n');
3490
3516
  }).join('\n\n');
3491
3517
  }
3518
+ /**
3519
+ * Build the client tool prompt section for system prompt injection.
3520
+ *
3521
+ * Tool sources (checked in order, all merged — first registration wins):
3522
+ * 1. Metadata tools from AI Agent Client Tools junction table
3523
+ * 2. Session-level enriched tools from ClientToolRequestManager (set by client SDK)
3524
+ * 3. Tools provided directly in extraData.clientTools (runtime override)
3525
+ */
3526
+ buildClientToolPromptSection(agent, extraData) {
3527
+ const toolMap = new Map();
3528
+ // 1. Metadata tools from junction table (authoritative source)
3529
+ const engine = AIEngine.Instance;
3530
+ const metadataTools = engine.GetClientToolsForAgent(agent.ID);
3531
+ for (const tool of metadataTools) {
3532
+ toolMap.set(tool.Name, {
3533
+ Name: tool.Name,
3534
+ Description: tool.Description,
3535
+ InputSchema: tool.InputSchemaJSON ? JSON.parse(tool.InputSchemaJSON) : {},
3536
+ OutputSchema: tool.OutputSchemaJSON ? JSON.parse(tool.OutputSchemaJSON) : undefined,
3537
+ Category: tool.Category || undefined,
3538
+ DefaultTimeoutMs: tool.DefaultTimeoutMs || undefined
3539
+ });
3540
+ }
3541
+ // 2. Session-level enriched tools (client SDK decorated tools)
3542
+ const sessionID = extraData?.sessionID;
3543
+ if (sessionID) {
3544
+ for (const tool of ClientToolRequestManager.Instance.GetSessionTools(sessionID)) {
3545
+ if (!toolMap.has(tool.Name)) {
3546
+ toolMap.set(tool.Name, tool);
3547
+ }
3548
+ }
3549
+ }
3550
+ // 3. Runtime extraData override
3551
+ if (extraData?.clientTools) {
3552
+ for (const tool of extraData.clientTools) {
3553
+ if (!toolMap.has(tool.Name)) {
3554
+ toolMap.set(tool.Name, tool);
3555
+ }
3556
+ }
3557
+ }
3558
+ const tools = Array.from(toolMap.values());
3559
+ if (tools.length === 0) {
3560
+ return ''; // No client tools available
3561
+ }
3562
+ const lines = [];
3563
+ lines.push('### Client Tools (execute in the user\'s browser)');
3564
+ lines.push('Client tools run in the user\'s browser and interact with the UI. Use these when you need');
3565
+ lines.push('to navigate the user somewhere, display a specific view, switch dashboard tabs, or show');
3566
+ lines.push('records. When you choose client tools, set nextStep.type to "ClientTools".');
3567
+ lines.push('');
3568
+ lines.push('NOTE: Do NOT use client tools for asking the user questions or collecting input.');
3569
+ lines.push('Use the "Chat" step for that. Client tools are for programmatic UI interaction only.');
3570
+ lines.push('');
3571
+ for (const tool of tools) {
3572
+ const categoryTag = tool.Category ? ` [${tool.Category}]` : '';
3573
+ lines.push(`- **${tool.Name}**${categoryTag}: ${tool.Description}`);
3574
+ // Show input parameters from InputSchema
3575
+ const props = tool.InputSchema?.properties;
3576
+ const required = tool.InputSchema?.required;
3577
+ if (props) {
3578
+ const paramParts = Object.entries(props).map(([name, schema]) => {
3579
+ const req = required?.includes(name) ? '\\*' : '';
3580
+ const desc = schema.description ? ` — ${schema.description}` : '';
3581
+ return `\`${name}\`${req}${desc}`;
3582
+ });
3583
+ lines.push(` Inputs: ${paramParts.join(', ')}`);
3584
+ }
3585
+ }
3586
+ return lines.join('\n');
3587
+ }
3588
+ /**
3589
+ * Build the app context section for system prompt injection.
3590
+ * Reads the AppContextSnapshot from extraData.appContext and formats
3591
+ * it as a concise markdown section the LLM can reference.
3592
+ */
3593
+ buildAppContextSection(extraData) {
3594
+ const ctx = extraData?.appContext;
3595
+ if (!ctx)
3596
+ return '';
3597
+ const app = ctx['App'];
3598
+ const activeNav = ctx['ActiveNavItem'];
3599
+ const otherNavs = ctx['OtherNavItems'];
3600
+ const user = ctx['User'];
3601
+ if (!app?.Name)
3602
+ return '';
3603
+ const lines = [];
3604
+ lines.push('### Current Application Context');
3605
+ lines.push(`The user is currently in the **${app.Name}** application${app.Description ? ` — ${app.Description}` : ''}.`);
3606
+ if (activeNav?.Name) {
3607
+ lines.push('');
3608
+ lines.push(`**Active view:** ${activeNav.Name}${activeNav.Description ? ` — ${activeNav.Description}` : ''}${activeNav.ResourceType ? ` (${activeNav.ResourceType})` : ''}`);
3609
+ }
3610
+ if (otherNavs && otherNavs.length > 0) {
3611
+ lines.push('');
3612
+ lines.push('**Other views available in this app:**');
3613
+ for (const nav of otherNavs) {
3614
+ lines.push(`- ${nav.Name}${nav.Description ? ` — ${nav.Description}` : ''}`);
3615
+ }
3616
+ }
3617
+ if (user?.Name) {
3618
+ lines.push('');
3619
+ lines.push(`**User:** ${user.Name}${user.Roles?.length ? ` (Roles: ${user.Roles.join(', ')})` : ''}`);
3620
+ }
3621
+ return lines.join('\n');
3622
+ }
3492
3623
  /**
3493
3624
  * Formats a single action parameter as a compact inline string.
3494
3625
  * Uses \* suffix for required, (array) when IsArray, and only shows
@@ -3834,7 +3965,7 @@ The context is now within limits. Please retry your request with the recovered c
3834
3965
  // Reset prompt turn counter for this execution
3835
3966
  this._promptTurnCount = 0;
3836
3967
  // Create MJAIAgentRunEntity
3837
- this._agentRun = await this._metadata.GetEntityObject('MJ: AI Agent Runs', params.contextUser);
3968
+ this._agentRun = await (params.provider || this._activeProvider).GetEntityObject('MJ: AI Agent Runs', params.contextUser);
3838
3969
  this._agentRun.AgentID = params.agent.ID;
3839
3970
  if (params.conversationDetailId) {
3840
3971
  this._agentRun.ConversationDetailID = params.conversationDetailId;
@@ -3987,7 +4118,7 @@ The context is now within limits. Please retry your request with the recovered c
3987
4118
  * @returns {Promise<MJAIAgentRunStepEntityExtended>} - The created step entity
3988
4119
  */
3989
4120
  async createStepEntity(params) {
3990
- const stepEntity = await this._metadata.GetEntityObject('MJ: AI Agent Run Steps', params.contextUser);
4121
+ const stepEntity = await this._activeProvider.GetEntityObject('MJ: AI Agent Run Steps', params.contextUser);
3991
4122
  stepEntity.AgentRunID = this._agentRun.ID;
3992
4123
  // Step number is based on current count of steps + 1
3993
4124
  stepEntity.StepNumber = (this._agentRun.Steps?.length || 0) + 1;
@@ -4235,6 +4366,10 @@ The context is now within limits. Please retry your request with the recovered c
4235
4366
  return await this.processSubAgentStep(params, previousDecision, undefined, undefined, stepCount);
4236
4367
  case 'Actions':
4237
4368
  return await this.executeActionsStep(params, previousDecision, undefined, true, stepCount);
4369
+ // Type assertion required because 'ClientTools' is not yet in the DB StepType value list.
4370
+ // The LoopAgentType.DetermineNextStep() emits this value when the LLM chooses client tools.
4371
+ case 'ClientTools':
4372
+ return await this.executeClientToolsStep(params, config, previousDecision, stepCount);
4238
4373
  case 'Chat':
4239
4374
  return await this.executeChatStep(params, previousDecision);
4240
4375
  case 'Success':
@@ -4608,6 +4743,17 @@ The context is now within limits. Please retry your request with the recovered c
4608
4743
  else if (updatedNextStep.step === 'Success' || updatedNextStep.step === 'Failed') {
4609
4744
  return { ...updatedNextStep, terminate: true };
4610
4745
  }
4746
+ else if (updatedNextStep.step === 'ClientTools') {
4747
+ // ClientTools must return terminate: false so the main loop continues
4748
+ // to the next iteration where executeClientToolsStep actually dispatches
4749
+ // and awaits the tool. The LLM's original terminate intent is preserved in
4750
+ // terminateAfterExecution so executeClientToolsStep can honor it post-execution.
4751
+ return {
4752
+ ...updatedNextStep,
4753
+ terminate: false,
4754
+ terminateAfterExecution: updatedNextStep.terminate
4755
+ };
4756
+ }
4611
4757
  else {
4612
4758
  return { ...updatedNextStep, terminate: false };
4613
4759
  }
@@ -5809,6 +5955,130 @@ The context is now within limits. Please retry your request with the recovered c
5809
5955
  *
5810
5956
  * @private
5811
5957
  */
5958
+ // ================================================================
5959
+ // Client Tools Step Execution
5960
+ // ================================================================
5961
+ /**
5962
+ * Execute client-side tools requested by the agent.
5963
+ * Sends each tool invocation via PubSub, awaits the client's response
5964
+ * (or timeout), then adds results to the conversation and continues.
5965
+ */
5966
+ async executeClientToolsStep(params, config, previousDecision, stepCount = 0) {
5967
+ const clientTools = previousDecision.clientTools ?? [];
5968
+ if (clientTools.length === 0) {
5969
+ // No tools to execute — continue with next prompt
5970
+ return await this.executePromptStep(params, config, previousDecision, stepCount);
5971
+ }
5972
+ if (!params.sessionID) {
5973
+ // No session ID — can't communicate with client
5974
+ const errorMsg = 'Cannot execute client tools: no sessionID provided in ExecuteAgentParams';
5975
+ LogError(errorMsg);
5976
+ params.conversationMessages.push({
5977
+ role: 'user',
5978
+ content: `Client tool execution skipped: ${errorMsg}`
5979
+ });
5980
+ return await this.executePromptStep(params, config, previousDecision, stepCount);
5981
+ }
5982
+ const currentPayload = previousDecision?.newPayload || previousDecision?.previousPayload || params.payload;
5983
+ // Build assistant message describing the tool invocations
5984
+ const toolMessage = clientTools.length === 1
5985
+ ? `I'm invoking the **${clientTools[0].Name}** client tool${clientTools[0].Description ? ` — ${clientTools[0].Description}` : ''}.`
5986
+ : `I'm invoking **${clientTools.length} client tools**:\n\n` +
5987
+ clientTools.map((t, i) => `${i + 1}. **${t.Name}**${t.Description ? ` — ${t.Description}` : ''}`).join('\n');
5988
+ params.conversationMessages.push({
5989
+ role: 'assistant',
5990
+ content: toolMessage
5991
+ });
5992
+ // Report progress
5993
+ params.onProgress?.({
5994
+ step: 'action_execution', // Reuse action_execution step type for progress reporting
5995
+ message: this.formatHierarchicalMessage(toolMessage),
5996
+ metadata: {
5997
+ toolCount: clientTools.length,
5998
+ toolNames: clientTools.map(t => t.Name),
5999
+ stepCount: stepCount + 1,
6000
+ hierarchicalStep: this.buildHierarchicalStep(stepCount + 1, this._parentStepCounts)
6001
+ },
6002
+ displayMode: 'live'
6003
+ });
6004
+ // Resolve default timeout: per-tool > params > agent config > 30s
6005
+ const defaultTimeout = params.clientToolTimeoutMs ?? 30_000;
6006
+ const results = [];
6007
+ const agentRunID = this._agentRun?.ID ?? 'unknown';
6008
+ // Execute tools sequentially (client may not support parallel UI operations)
6009
+ for (const tool of clientTools) {
6010
+ const stepEntity = await this.createStepEntity({
6011
+ // INTENTIONAL: We use 'Actions' as the DB step type because the MJ: AI Agent Run Steps
6012
+ // entity's StepType value list does not yet include 'ClientTools'. A future database
6013
+ // migration will add 'ClientTools' to the allowed values in the StepType CHECK constraint
6014
+ // and CodeGen will regenerate the types. Until then, client tool steps are recorded under
6015
+ // 'Actions' in the run history. The step name ("Client Tool: {name}") distinguishes them.
6016
+ stepType: 'Actions',
6017
+ stepName: `Client Tool: ${tool.Name}`,
6018
+ inputData: { toolName: tool.Name, params: tool.Params },
6019
+ contextUser: params.contextUser,
6020
+ payloadAtStart: currentPayload,
6021
+ payloadAtEnd: currentPayload
6022
+ });
6023
+ const timeoutMs = tool.TimeoutMs ?? defaultTimeout;
6024
+ const response = await ClientToolRequestManager.Instance.RequestClientTool(`ct_${Date.now()}_${Math.random().toString(36).substring(2, 9)}`, tool.Name, tool.Params, params.sessionID, agentRunID, timeoutMs, tool.Description);
6025
+ await this.finalizeStepEntity(stepEntity, response.Success, response.ErrorMessage, { result: response.Result });
6026
+ results.push({
6027
+ ToolName: tool.Name,
6028
+ Success: response.Success,
6029
+ Result: response.Result,
6030
+ ErrorMessage: response.ErrorMessage
6031
+ });
6032
+ }
6033
+ // Format results as conversation message
6034
+ const resultsMarkdown = this.formatClientToolResultsAsMarkdown(results);
6035
+ params.conversationMessages.push({
6036
+ role: 'user',
6037
+ content: resultsMarkdown,
6038
+ metadata: {
6039
+ turnAdded: this._promptTurnCount,
6040
+ messageType: 'client-tool-result'
6041
+ }
6042
+ });
6043
+ // If the LLM already declared taskComplete=true alongside the client tools,
6044
+ // honor that intent now that tools have executed — no need for another LLM call.
6045
+ if (previousDecision.terminateAfterExecution) {
6046
+ return {
6047
+ step: 'Success',
6048
+ terminate: true,
6049
+ message: previousDecision.message || 'Client tools executed successfully.',
6050
+ payloadChangeRequest: previousDecision.payloadChangeRequest,
6051
+ scratchpad: previousDecision.scratchpad,
6052
+ responseForm: previousDecision.responseForm,
6053
+ actionableCommands: previousDecision.actionableCommands,
6054
+ automaticCommands: previousDecision.automaticCommands
6055
+ };
6056
+ }
6057
+ return await this.executePromptStep(params, config, previousDecision, stepCount);
6058
+ }
6059
+ /**
6060
+ * Format client tool results as a compact markdown summary for the conversation.
6061
+ */
6062
+ formatClientToolResultsAsMarkdown(results) {
6063
+ const failedCount = results.filter(r => !r.Success).length;
6064
+ const header = failedCount > 0
6065
+ ? `${failedCount} of ${results.length} client tool(s) failed:`
6066
+ : 'Client tool results:';
6067
+ const lines = results.map(r => {
6068
+ const icon = r.Success ? '✓' : '✗';
6069
+ let line = `${icon} **${r.ToolName}**: ${r.Success ? 'succeeded' : 'failed'}`;
6070
+ if (r.ErrorMessage)
6071
+ line += ` — ${r.ErrorMessage}`;
6072
+ if (r.Success && r.Result != null) {
6073
+ const resultStr = typeof r.Result === 'string' ? r.Result : JSON.stringify(r.Result);
6074
+ if (resultStr.length <= 500) {
6075
+ line += `\n Result: ${resultStr}`;
6076
+ }
6077
+ }
6078
+ return line;
6079
+ });
6080
+ return `${header}\n${lines.join('\n')}`;
6081
+ }
5812
6082
  async executeChatStep(params, previousDecision) {
5813
6083
  const stepEntity = await this.createStepEntity({ stepType: 'Chat', stepName: 'User Interaction', contextUser: params.contextUser });
5814
6084
  // Chat steps are successful - they indicate a need for user interaction
@@ -5850,7 +6120,7 @@ The context is now within limits. Please retry your request with the recovered c
5850
6120
  const requestForUserId = this.resolveUserFromStrategy(resolvedStrategy, params);
5851
6121
  const priority = resolvedStrategy?.priority ?? 50;
5852
6122
  const expirationMinutes = resolvedStrategy?.expirationMinutes;
5853
- const request = await this._metadata.GetEntityObject('MJ: AI Agent Requests', params.contextUser);
6123
+ const request = await (params.provider || this._activeProvider).GetEntityObject('MJ: AI Agent Requests', params.contextUser);
5854
6124
  request.NewRecord();
5855
6125
  request.AgentID = params.agent.ID;
5856
6126
  request.RequestedAt = new Date();
@@ -7016,8 +7286,7 @@ The context is now within limits. Please retry your request with the recovered c
7016
7286
  if (!this._agentRun) {
7017
7287
  throw new Error('Cannot create compaction step: agent run not initialized');
7018
7288
  }
7019
- const md = new Metadata();
7020
- const step = await md.GetEntityObject('MJ: AI Agent Run Steps', params.contextUser);
7289
+ const step = await (params.provider || this._activeProvider).GetEntityObject('MJ: AI Agent Run Steps', params.contextUser);
7021
7290
  step.NewRecord();
7022
7291
  step.AgentRunID = this._agentRun.ID;
7023
7292
  step.StepType = 'Prompt';
@@ -7154,6 +7423,14 @@ The context is now within limits. Please retry your request with the recovered c
7154
7423
  * @param modelName - Optional model name for accurate tokenization
7155
7424
  * @protected
7156
7425
  */
7426
+ /**
7427
+ * Returns true if the message is a tool result (action or client tool).
7428
+ * Used by recovery strategies to identify compactable result messages.
7429
+ */
7430
+ IsToolResultMessage(msg) {
7431
+ const messageType = msg.metadata?.messageType;
7432
+ return messageType === 'action-result' || messageType === 'client-tool-result';
7433
+ }
7157
7434
  estimateTokens(content, modelName) {
7158
7435
  const text = typeof content === 'string'
7159
7436
  ? content