@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.
- package/README.md +4 -1
- package/dist/AgentRunner.d.ts +43 -5
- package/dist/AgentRunner.d.ts.map +1 -1
- package/dist/AgentRunner.js +92 -32
- package/dist/AgentRunner.js.map +1 -1
- package/dist/ClientToolRequestManager.d.ts +62 -0
- package/dist/ClientToolRequestManager.d.ts.map +1 -0
- package/dist/ClientToolRequestManager.js +107 -0
- package/dist/ClientToolRequestManager.js.map +1 -0
- package/dist/KnowledgeAgent.d.ts +102 -0
- package/dist/KnowledgeAgent.d.ts.map +1 -0
- package/dist/KnowledgeAgent.js +338 -0
- package/dist/KnowledgeAgent.js.map +1 -0
- package/dist/agent-types/loop-agent-prompt-params.d.ts +29 -10
- package/dist/agent-types/loop-agent-prompt-params.d.ts.map +1 -1
- package/dist/agent-types/loop-agent-prompt-params.js +16 -6
- package/dist/agent-types/loop-agent-prompt-params.js.map +1 -1
- package/dist/agent-types/loop-agent-response-type.d.ts +16 -2
- package/dist/agent-types/loop-agent-response-type.d.ts.map +1 -1
- package/dist/agent-types/loop-agent-response-type.js.map +1 -1
- package/dist/agent-types/loop-agent-type.d.ts.map +1 -1
- package/dist/agent-types/loop-agent-type.js +28 -3
- package/dist/agent-types/loop-agent-type.js.map +1 -1
- package/dist/base-agent.d.ts +37 -0
- package/dist/base-agent.d.ts.map +1 -1
- package/dist/base-agent.js +291 -14
- package/dist/base-agent.js.map +1 -1
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -1
- package/package.json +13 -12
package/dist/base-agent.js
CHANGED
|
@@ -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
|
-
|
|
2605
|
+
isToolResult: this.IsToolResultMessage(msg)
|
|
2590
2606
|
}))
|
|
2591
|
-
.filter(c => c.
|
|
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
|
-
|
|
2658
|
+
isToolResult: this.IsToolResultMessage(msg),
|
|
2643
2659
|
alreadyCompacted: msg.metadata?.wasCompacted === true
|
|
2644
2660
|
}))
|
|
2645
|
-
.filter(c => c.
|
|
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
|
-
|
|
2720
|
+
isToolResult: this.IsToolResultMessage(msg),
|
|
2705
2721
|
alreadyCompacted: msg.metadata?.wasCompacted === true
|
|
2706
2722
|
}))
|
|
2707
|
-
.filter(c => c.
|
|
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
|
-
|
|
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.
|
|
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.
|
|
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.
|
|
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
|
|
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
|