@memberjunction/ai-agents 5.34.0 → 5.35.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.
Files changed (53) hide show
  1. package/dist/AgentRunner.d.ts +1 -21
  2. package/dist/AgentRunner.d.ts.map +1 -1
  3. package/dist/AgentRunner.js +69 -99
  4. package/dist/AgentRunner.js.map +1 -1
  5. package/dist/ArtifactToolManager.d.ts +57 -46
  6. package/dist/ArtifactToolManager.d.ts.map +1 -1
  7. package/dist/ArtifactToolManager.js +161 -132
  8. package/dist/ArtifactToolManager.js.map +1 -1
  9. package/dist/artifact-tools/CSVToolLibrary.d.ts +8 -0
  10. package/dist/artifact-tools/CSVToolLibrary.d.ts.map +1 -0
  11. package/dist/artifact-tools/CSVToolLibrary.js +161 -0
  12. package/dist/artifact-tools/CSVToolLibrary.js.map +1 -0
  13. package/dist/artifact-tools/DataSnapshotToolLibrary.d.ts +2 -3
  14. package/dist/artifact-tools/DataSnapshotToolLibrary.d.ts.map +1 -1
  15. package/dist/artifact-tools/DataSnapshotToolLibrary.js +4 -29
  16. package/dist/artifact-tools/DataSnapshotToolLibrary.js.map +1 -1
  17. package/dist/artifact-tools/DocxToolLibrary.d.ts +2 -2
  18. package/dist/artifact-tools/DocxToolLibrary.d.ts.map +1 -1
  19. package/dist/artifact-tools/DocxToolLibrary.js +2 -2
  20. package/dist/artifact-tools/DocxToolLibrary.js.map +1 -1
  21. package/dist/artifact-tools/ExcelToolLibrary.d.ts +2 -2
  22. package/dist/artifact-tools/ExcelToolLibrary.d.ts.map +1 -1
  23. package/dist/artifact-tools/ExcelToolLibrary.js +4 -4
  24. package/dist/artifact-tools/ExcelToolLibrary.js.map +1 -1
  25. package/dist/artifact-tools/GenericBinaryToolLibrary.d.ts +6 -0
  26. package/dist/artifact-tools/GenericBinaryToolLibrary.d.ts.map +1 -0
  27. package/dist/artifact-tools/GenericBinaryToolLibrary.js +49 -0
  28. package/dist/artifact-tools/GenericBinaryToolLibrary.js.map +1 -0
  29. package/dist/artifact-tools/JSONToolLibrary.d.ts +2 -2
  30. package/dist/artifact-tools/JSONToolLibrary.d.ts.map +1 -1
  31. package/dist/artifact-tools/JSONToolLibrary.js +2 -2
  32. package/dist/artifact-tools/JSONToolLibrary.js.map +1 -1
  33. package/dist/artifact-tools/PDFToolLibrary.d.ts +6 -3
  34. package/dist/artifact-tools/PDFToolLibrary.d.ts.map +1 -1
  35. package/dist/artifact-tools/PDFToolLibrary.js +41 -20
  36. package/dist/artifact-tools/PDFToolLibrary.js.map +1 -1
  37. package/dist/artifact-tools/SearchResultSetToolLibrary.d.ts +2 -2
  38. package/dist/artifact-tools/SearchResultSetToolLibrary.d.ts.map +1 -1
  39. package/dist/artifact-tools/SearchResultSetToolLibrary.js +2 -2
  40. package/dist/artifact-tools/SearchResultSetToolLibrary.js.map +1 -1
  41. package/dist/artifact-tools/TextToolLibrary.d.ts +2 -2
  42. package/dist/artifact-tools/TextToolLibrary.d.ts.map +1 -1
  43. package/dist/artifact-tools/TextToolLibrary.js +2 -2
  44. package/dist/artifact-tools/TextToolLibrary.js.map +1 -1
  45. package/dist/base-agent.d.ts +27 -12
  46. package/dist/base-agent.d.ts.map +1 -1
  47. package/dist/base-agent.js +153 -70
  48. package/dist/base-agent.js.map +1 -1
  49. package/dist/index.d.ts +2 -0
  50. package/dist/index.d.ts.map +1 -1
  51. package/dist/index.js +2 -0
  52. package/dist/index.js.map +1 -1
  53. package/package.json +17 -17
@@ -792,15 +792,28 @@ export class BaseAgent {
792
792
  destinations.push(`payload(${Object.keys(preloadedResult.payload).length})`);
793
793
  }
794
794
  this.logStatus(`📊 Preloaded ${totalSources} data source(s) for agent '${params.agent.Name}': ${destinations.join(', ')}`, true, params);
795
- // Merge with existing data/context/payload (caller values take precedence)
795
+ // Merge with existing data/context/payload (caller values take precedence).
796
+ // IMPORTANT: Do NOT spread params.context — it may be a class instance
797
+ // whose getters/methods would be destroyed by spreading into a plain object.
798
+ // Instead, copy preloaded properties onto the existing context object.
796
799
  params.data = {
797
800
  ...preloadedResult.data,
798
801
  ...params.data
799
802
  };
800
- params.context = {
801
- ...preloadedResult.context,
802
- ...params.context
803
- };
803
+ if (preloadedResult.context && typeof preloadedResult.context === 'object') {
804
+ if (!params.context || typeof params.context !== 'object') {
805
+ params.context = preloadedResult.context;
806
+ }
807
+ else {
808
+ // Copy preloaded properties onto the existing context without
809
+ // replacing it, so class identity (prototype, getters) is preserved.
810
+ for (const key of Object.keys(preloadedResult.context)) {
811
+ if (!(key in params.context)) {
812
+ params.context[key] = preloadedResult.context[key];
813
+ }
814
+ }
815
+ }
816
+ }
804
817
  params.payload = {
805
818
  ...preloadedResult.payload,
806
819
  ...params.payload
@@ -978,8 +991,12 @@ export class BaseAgent {
978
991
  // Reset scratchpad and artifact tools for each new execution (ephemeral per run)
979
992
  this._scratchpadManager.Clear();
980
993
  this._artifactToolManager.Clear();
981
- // Initialize artifact tools with any input artifacts from the conversation
982
- const inputArtifacts = wrappedParams.data?.__inputArtifacts;
994
+ // Initialize artifact tools with any input artifacts attached to the run.
995
+ // Artifacts arrive as a typed first-class field on ExecuteAgentParams —
996
+ // they are NOT routed through `data` because prompt-template rendering
997
+ // would otherwise serialize artifact bodies into the LLM payload. Only
998
+ // the manifest (injected via _ARTIFACT_MANIFEST below) reaches the LLM.
999
+ const inputArtifacts = wrappedParams.inputArtifacts;
983
1000
  if (inputArtifacts?.length) {
984
1001
  this._artifactToolManager.Initialize(inputArtifacts);
985
1002
  this.logStatus(`[ArtifactTools] Initialized with ${inputArtifacts.length} artifact(s): ${inputArtifacts.map(a => `${a.typeName}:"${a.name}"`).join(', ')}`, true, params);
@@ -1335,8 +1352,7 @@ export class BaseAgent {
1335
1352
  }
1336
1353
  const injector = new AgentContextInjector();
1337
1354
  // Parse reranker configuration if present
1338
- // Access dynamically since field may not exist until CodeGen runs after migration
1339
- const rerankerConfigJson = agent.Get('RerankerConfiguration');
1355
+ const rerankerConfigJson = agent.RerankerConfiguration;
1340
1356
  const rerankerConfig = RerankerService.Instance.parseConfiguration(rerankerConfigJson);
1341
1357
  // Get notes if injection enabled
1342
1358
  const notes = agent.InjectNotes
@@ -1790,12 +1806,15 @@ export class BaseAgent {
1790
1806
  promptParams.data['_SCRATCHPAD_TASKS'] = this._scratchpadManager.ToPromptString();
1791
1807
  promptParams.data['_SCRATCHPAD_TASK_SUMMARY'] = this._scratchpadManager.GetTaskSummary();
1792
1808
  }
1793
- // Inject artifact tools template variables if enabled and artifacts are present
1809
+ // Inject artifact tools template variables if enabled and artifacts are present.
1810
+ // Note: prior tool results are NO LONGER injected via a per-turn template var.
1811
+ // They are pushed into conversationMessages as a one-shot 'tool-result'
1812
+ // message at execution time (see injectArtifactToolResultsMessage) and decay
1813
+ // via pruneAndCompactExpiredMessages — same lifecycle as action results.
1794
1814
  const artifactToolsEnabled = agentTypePromptParams?.includeArtifactToolsDocs !== false;
1795
1815
  if (artifactToolsEnabled && this._artifactToolManager.HasArtifacts()) {
1796
1816
  promptParams.data['_ARTIFACT_MANIFEST'] = this._artifactToolManager.ToManifestString();
1797
1817
  promptParams.data['_ARTIFACT_TOOLS'] = this._artifactToolManager.GetToolDocumentation();
1798
- promptParams.data['_ARTIFACT_TOOL_RESULTS'] = this._artifactToolManager.GetPendingResults();
1799
1818
  promptParams.data['_ARTIFACT_TOOL_SUMMARY'] = this._artifactToolManager.GetSummary();
1800
1819
  this.logStatus(`[ArtifactTools] Injected manifest into prompt: ${this._artifactToolManager.GetSummary()}`, true, params);
1801
1820
  }
@@ -2863,7 +2882,7 @@ export class BaseAgent {
2863
2882
  return payload ? JSON.stringify(payload) : null;
2864
2883
  }
2865
2884
  /**
2866
- * Recovery Strategy 1: Remove oldest action-result messages.
2885
+ * Recovery Strategy 1: Remove oldest tool-result messages.
2867
2886
  * Targets messages older than minAge turns for removal.
2868
2887
  *
2869
2888
  * @param params - Agent execution parameters
@@ -2873,10 +2892,10 @@ export class BaseAgent {
2873
2892
  * @returns Result with tokens saved and strategy description
2874
2893
  * @protected
2875
2894
  */
2876
- recoveryStrategy_RemoveOldestActionResults(params, tokensToSave, currentStepCount, minAge = 5) {
2895
+ recoveryStrategy_RemoveOldestToolResults(params, tokensToSave, currentStepCount, minAge = 5) {
2877
2896
  let tokensSaved = 0;
2878
2897
  const removedIndices = [];
2879
- // Find action-result messages older than minAge turns
2898
+ // Find tool-result messages older than minAge turns
2880
2899
  const candidates = params.conversationMessages
2881
2900
  .map((msg, index) => ({
2882
2901
  message: msg,
@@ -2895,7 +2914,7 @@ export class BaseAgent {
2895
2914
  break;
2896
2915
  removedIndices.push(candidate.index);
2897
2916
  tokensSaved += candidate.tokens;
2898
- this.logStatus(`Removing action-result from ${candidate.age} turns ago (${candidate.tokens} tokens)`, true, params);
2917
+ this.logStatus(`Removing tool-result from ${candidate.age} turns ago (${candidate.tokens} tokens)`, true, params);
2899
2918
  }
2900
2919
  // Remove in reverse order to maintain indices
2901
2920
  removedIndices.sort((a, b) => b - a).forEach(index => {
@@ -2906,17 +2925,17 @@ export class BaseAgent {
2906
2925
  turn: currentStepCount,
2907
2926
  messageIndex: index,
2908
2927
  message: removed,
2909
- reason: 'Context recovery - oldest action results',
2928
+ reason: 'Context recovery - oldest tool results',
2910
2929
  tokensSaved: this.estimateTokens(removed.content)
2911
2930
  });
2912
2931
  });
2913
2932
  return {
2914
2933
  tokensSaved,
2915
- strategyName: `Removed ${removedIndices.length} old action-results (${minAge}+ turns)`
2934
+ strategyName: `Removed ${removedIndices.length} old tool-results (${minAge}+ turns)`
2916
2935
  };
2917
2936
  }
2918
2937
  /**
2919
- * Recovery Strategy 2: Compact old action-result messages.
2938
+ * Recovery Strategy 2: Compact old tool-result messages.
2920
2939
  * Uses smart trimming to reduce size while preserving some content.
2921
2940
  *
2922
2941
  * @param params - Agent execution parameters
@@ -2926,10 +2945,10 @@ export class BaseAgent {
2926
2945
  * @returns Result with tokens saved and strategy description
2927
2946
  * @protected
2928
2947
  */
2929
- async recoveryStrategy_CompactOldActionResults(params, tokensToSave, currentStepCount, minAge = 3) {
2948
+ async recoveryStrategy_CompactOldToolResults(params, tokensToSave, currentStepCount, minAge = 3) {
2930
2949
  let tokensSaved = 0;
2931
2950
  let compactedCount = 0;
2932
- // Find action-result messages to compact
2951
+ // Find tool-result messages to compact
2933
2952
  const candidates = params.conversationMessages
2934
2953
  .map((msg, index) => ({
2935
2954
  message: msg,
@@ -2974,16 +2993,16 @@ export class BaseAgent {
2974
2993
  };
2975
2994
  tokensSaved += saved;
2976
2995
  compactedCount++;
2977
- this.logStatus(`Compacted action-result from ${candidate.age} turns ago (saved ${saved} tokens)`, true, params);
2996
+ this.logStatus(`Compacted tool-result from ${candidate.age} turns ago (saved ${saved} tokens)`, true, params);
2978
2997
  }
2979
2998
  }
2980
2999
  return {
2981
3000
  tokensSaved,
2982
- strategyName: `Compacted ${compactedCount} old action-results (${minAge}+ turns)`
3001
+ strategyName: `Compacted ${compactedCount} old tool-results (${minAge}+ turns)`
2983
3002
  };
2984
3003
  }
2985
3004
  /**
2986
- * Recovery Strategy 3: Aggressively compact ALL action-result messages.
3005
+ * Recovery Strategy 3: Aggressively compact ALL tool-result messages.
2987
3006
  * Used when gentler strategies haven't freed enough space.
2988
3007
  *
2989
3008
  * @param params - Agent execution parameters
@@ -2991,10 +3010,10 @@ export class BaseAgent {
2991
3010
  * @returns Result with tokens saved and strategy description
2992
3011
  * @protected
2993
3012
  */
2994
- async recoveryStrategy_CompactAllActionResults(params, tokensToSave) {
3013
+ async recoveryStrategy_CompactAllToolResults(params, tokensToSave) {
2995
3014
  let tokensSaved = 0;
2996
3015
  let compactedCount = 0;
2997
- // Find ALL action-result messages that aren't already compacted
3016
+ // Find ALL tool-result messages that aren't already compacted
2998
3017
  const candidates = params.conversationMessages
2999
3018
  .map((msg, index) => ({
3000
3019
  message: msg,
@@ -3040,7 +3059,7 @@ export class BaseAgent {
3040
3059
  }
3041
3060
  return {
3042
3061
  tokensSaved,
3043
- strategyName: `Aggressively compacted ${compactedCount} action-results`
3062
+ strategyName: `Aggressively compacted ${compactedCount} tool-results`
3044
3063
  };
3045
3064
  }
3046
3065
  /**
@@ -3099,7 +3118,7 @@ export class BaseAgent {
3099
3118
  /**
3100
3119
  * Attempts to recover from a context length exceeded error using multiple strategies.
3101
3120
  * Uses escalating strategies: remove old results → compact old results → compact all → trim user message.
3102
- * This approach preserves the user's original request while removing stale action results.
3121
+ * This approach preserves the user's original request while removing stale tool results.
3103
3122
  *
3104
3123
  * @param params - Agent execution parameters (conversationMessages will be modified)
3105
3124
  * @param payload - Current payload to carry forward
@@ -3131,10 +3150,10 @@ export class BaseAgent {
3131
3150
  const currentPromptTurn = this._promptTurnCount;
3132
3151
  // Try multiple recovery strategies in order
3133
3152
  const strategies = [
3134
- () => this.recoveryStrategy_RemoveOldestActionResults(params, tokensToSave, currentPromptTurn, 5),
3135
- () => this.recoveryStrategy_CompactOldActionResults(params, tokensToSave, currentPromptTurn, 3),
3136
- () => this.recoveryStrategy_RemoveOldestActionResults(params, tokensToSave, currentPromptTurn, 2),
3137
- () => this.recoveryStrategy_CompactAllActionResults(params, tokensToSave),
3153
+ () => this.recoveryStrategy_RemoveOldestToolResults(params, tokensToSave, currentPromptTurn, 5),
3154
+ () => this.recoveryStrategy_CompactOldToolResults(params, tokensToSave, currentPromptTurn, 3),
3155
+ () => this.recoveryStrategy_RemoveOldestToolResults(params, tokensToSave, currentPromptTurn, 2),
3156
+ () => this.recoveryStrategy_CompactAllToolResults(params, tokensToSave),
3138
3157
  () => Promise.resolve(this.recoveryStrategy_TrimLastUserMessage(params, tokensToSave))
3139
3158
  ];
3140
3159
  let tokensSaved = 0;
@@ -3203,30 +3222,88 @@ The context is now within limits. Please retry your request with the recovered c
3203
3222
  return guardrailCheckedStep;
3204
3223
  }
3205
3224
  /**
3206
- * Creates a chat message containing action execution results.
3225
+ * Executes a batch of artifact tool calls, recording each as its own
3226
+ * `Tool` AIAgentRunStep (a sibling of the Prompt step that requested them)
3227
+ * with full inputs/outputs captured in InputData/OutputData. Returns the
3228
+ * stored results so the caller can render them into a single recall-friendly
3229
+ * message for the next prompt turn.
3230
+ *
3231
+ * Step naming convention: `Artifact Tool: {toolName}` for log/UI clarity.
3207
3232
  *
3208
- * @param {AgentAction[]} actions - The actions that were executed
3209
- * @param {any[]} results - The results from action execution
3210
- * @returns {ChatMessage} A formatted message with action results
3211
3233
  * @protected
3212
3234
  */
3213
- createActionResultMessage(actions, results) {
3214
- const actionSummaries = actions.map((action, index) => {
3215
- const result = results[index];
3216
- const outputParams = result.Params?.filter(p => p.Type === 'Output' || p.Type === 'Both') || [];
3217
- return {
3218
- actionName: action.name,
3219
- success: result.Success,
3220
- params: outputParams,
3221
- resultCode: result.Result?.ResultCode || 'N/A',
3222
- message: result.Message || '(no message)',
3223
- aiDirectives: result.AIDirectives,
3224
- };
3225
- });
3226
- return {
3235
+ async executeArtifactToolCallsAsSteps(calls, params) {
3236
+ // No parentId: artifact tool steps are siblings of the prompt step that
3237
+ // requested them, matching how action steps render. ParentID is reserved
3238
+ // for genuine control-flow nesting (ForEach/While loops, sub-agents), which
3239
+ // artifact tools are never dispatched from.
3240
+ const results = await Promise.all(calls.map(async (call) => {
3241
+ const toolStep = await this.createStepEntity({
3242
+ stepType: 'Tool',
3243
+ stepName: `Artifact Tool: ${call.tool}`,
3244
+ contextUser: params.contextUser,
3245
+ inputData: {
3246
+ artifactId: call.artifactId,
3247
+ tool: call.tool,
3248
+ input: call.input,
3249
+ },
3250
+ });
3251
+ const stored = await this._artifactToolManager.ExecuteSingleToolCall(call);
3252
+ await this.finalizeStepEntity(toolStep, stored.result.success, stored.result.success ? undefined : stored.result.errorMessage, {
3253
+ artifactId: stored.artifactId,
3254
+ tool: stored.tool,
3255
+ input: stored.input,
3256
+ result: stored.result,
3257
+ durationMs: stored.durationMs,
3258
+ });
3259
+ return stored;
3260
+ }));
3261
+ return results;
3262
+ }
3263
+ /**
3264
+ * Pushes a single user-role message containing rendered artifact-tool
3265
+ * results into the conversation. This mirrors the action-result
3266
+ * "inject once, then expire" pattern — the LLM sees the results on its
3267
+ * next turn, and older messages are pruned/compacted by
3268
+ * `pruneAndCompactExpiredMessages` instead of being re-rendered into
3269
+ * every system-prompt turn.
3270
+ *
3271
+ * @protected
3272
+ */
3273
+ injectArtifactToolResultsMessage(params, toolResults) {
3274
+ if (toolResults.length === 0)
3275
+ return;
3276
+ const header = toolResults.length === 1
3277
+ ? 'Artifact tool result:'
3278
+ : `Artifact tool results (${toolResults.length} calls):`;
3279
+ const body = toolResults.map((r, i) => {
3280
+ const heading = `### ${i + 1}. ${r.artifactId}.${r.tool}(${JSON.stringify(r.input)})`;
3281
+ if (r.result.success) {
3282
+ const data = typeof r.result.data === 'string'
3283
+ ? r.result.data
3284
+ : JSON.stringify(r.result.data, null, 2);
3285
+ return `${heading}\n\`\`\`json\n${data}\n\`\`\``;
3286
+ }
3287
+ return `${heading}\n**Error:** ${r.result.errorMessage}`;
3288
+ }).join('\n\n');
3289
+ const message = {
3227
3290
  role: 'user',
3228
- content: `Action results:\n${this.formatActionResultsAsMarkdown(actionSummaries)}`
3291
+ content: `${header}\n${body}`,
3292
+ metadata: {
3293
+ turnAdded: this._promptTurnCount,
3294
+ messageType: 'tool-result',
3295
+ // Default: keep results visible for a few turns then compact to a
3296
+ // first-N-chars preview. The LLM is taught (via the loop-agent
3297
+ // system prompt) that older tool results are summarised and that
3298
+ // it can re-call the tool if it needs the full result back.
3299
+ expirationTurns: 3,
3300
+ expirationMode: 'Compact',
3301
+ compactMode: 'First N Chars',
3302
+ compactLength: 500,
3303
+ compactPromptId: '',
3304
+ },
3229
3305
  };
3306
+ params.conversationMessages.push(message);
3230
3307
  }
3231
3308
  /**
3232
3309
  * Creates a chat message containing sub-agent execution results.
@@ -3491,16 +3568,16 @@ The context is now within limits. Please retry your request with the recovered c
3491
3568
  Value: value,
3492
3569
  Type: 'Input'
3493
3570
  }));
3494
- // Build action context: preserve the agent's context, stamp the
3495
- // calling agent's identity so scope-aware actions (Scoped Search,
3496
- // future agent-aware tools) can attribute the call without the LLM
3497
- // needing to pass it explicitly, and inject resolved storage account ID.
3498
- const baseContext = typeof params.context === 'object' && params.context ? params.context : {};
3499
- const actionContext = {
3500
- ...baseContext,
3501
- AgentID: params.agent.ID,
3502
- ...(this._resolvedStorageAccountId ? { __resolvedStorageAccountId: this._resolvedStorageAccountId } : {}),
3503
- };
3571
+ // Build action context: preserve the agent's context by reference
3572
+ // (do NOT spread — spreading destroys class instances, losing
3573
+ // getters/methods on typed contexts like SkipAgentContext).
3574
+ // Stamp the calling agent's identity and resolved storage account ID
3575
+ // directly onto the original context object.
3576
+ const actionContext = typeof params.context === 'object' && params.context ? params.context : {};
3577
+ actionContext.AgentID = params.agent.ID;
3578
+ if (this._resolvedStorageAccountId) {
3579
+ actionContext.__resolvedStorageAccountId = this._resolvedStorageAccountId;
3580
+ }
3504
3581
  // Execute the action and return the full ActionResult
3505
3582
  const result = await actionEngine.RunAction({
3506
3583
  Action: actionEntity,
@@ -4673,8 +4750,8 @@ The context is now within limits. Please retry your request with the recovered c
4673
4750
  return await this.processSubAgentStep(params, previousDecision, undefined, undefined, stepCount);
4674
4751
  case 'Actions':
4675
4752
  return await this.executeActionsStep(params, previousDecision, undefined, true, stepCount);
4676
- // Type assertion required because 'ClientTools' is not yet in the DB StepType value list.
4677
- // The LoopAgentType.DetermineNextStep() emits this value when the LLM chooses client tools.
4753
+ // Type assertion required because 'ClientTools' is not part of the BaseAgentNextStep
4754
+ // step union — LoopAgentType.DetermineNextStep() emits it when the LLM chooses client tools.
4678
4755
  case 'ClientTools':
4679
4756
  return await this.executeClientToolsStep(params, config, previousDecision, stepCount);
4680
4757
  case 'Chat':
@@ -4989,7 +5066,16 @@ The context is now within limits. Please retry your request with the recovered c
4989
5066
  const artifactToolsExecutedThisTurn = !!(artifactToolCalls?.length);
4990
5067
  if (artifactToolsExecutedThisTurn) {
4991
5068
  this.logStatus(`[ArtifactTools] LLM requested ${artifactToolCalls.length} tool call(s): ${artifactToolCalls.map(c => `${c.artifactId}.${c.tool}`).join(', ')}`, true, params);
4992
- await this._artifactToolManager.ExecuteToolCalls(artifactToolCalls);
5069
+ // Per-call observability: wrap each invocation in its own AIAgentRunStep
5070
+ // (StepType='Tool') so the run tree shows the calls + their inputs/outputs
5071
+ // at full fidelity instead of a summary buried inside the parent Prompt
5072
+ // step's OutputData. Step naming convention is "Artifact Tool: {tool}".
5073
+ // The result is also surfaced to the agent on the NEXT turn via a
5074
+ // one-shot ChatMessage push (see injectArtifactToolResultsMessage below),
5075
+ // mirroring the action-result inject-once-then-expire pattern instead of
5076
+ // the previous re-render-every-turn `_ARTIFACT_TOOL_RESULTS` template var.
5077
+ const toolResults = await this.executeArtifactToolCallsAsSteps(artifactToolCalls, params);
5078
+ this.injectArtifactToolResultsMessage(params, toolResults);
4993
5079
  }
4994
5080
  else if (this._artifactToolManager.HasArtifacts()) {
4995
5081
  this.logStatus(`[ArtifactTools] LLM did not use artifact tools this turn (artifacts available but not accessed)`, true, params);
@@ -6350,12 +6436,7 @@ The context is now within limits. Please retry your request with the recovered c
6350
6436
  // Execute tools sequentially (client may not support parallel UI operations)
6351
6437
  for (const tool of clientTools) {
6352
6438
  const stepEntity = await this.createStepEntity({
6353
- // INTENTIONAL: We use 'Actions' as the DB step type because the MJ: AI Agent Run Steps
6354
- // entity's StepType value list does not yet include 'ClientTools'. A future database
6355
- // migration will add 'ClientTools' to the allowed values in the StepType CHECK constraint
6356
- // and CodeGen will regenerate the types. Until then, client tool steps are recorded under
6357
- // 'Actions' in the run history. The step name ("Client Tool: {name}") distinguishes them.
6358
- stepType: 'Actions',
6439
+ stepType: 'Tool',
6359
6440
  stepName: `Client Tool: ${tool.Name}`,
6360
6441
  inputData: { toolName: tool.Name, params: tool.Params },
6361
6442
  contextUser: params.contextUser,
@@ -7848,7 +7929,9 @@ The context is now within limits. Please retry your request with the recovered c
7848
7929
  */
7849
7930
  IsToolResultMessage(msg) {
7850
7931
  const messageType = msg.metadata?.messageType;
7851
- return messageType === 'action-result' || messageType === 'client-tool-result';
7932
+ return messageType === 'action-result'
7933
+ || messageType === 'client-tool-result'
7934
+ || messageType === 'tool-result';
7852
7935
  }
7853
7936
  estimateTokens(content, modelName) {
7854
7937
  const text = typeof content === 'string'