Skip to content
Original file line number Diff line number Diff line change
Expand Up @@ -269,6 +269,31 @@ codeunit 4303 "Agent Task"
exit(AgentTaskImpl.GetDetailsForAgentTaskLogEntry(AgentTaskLogEntry));
end;

/// <summary>
/// Gets the number of steps done for the specified agent task.
/// </summary>
/// <param name="AgentTaskID">The ID of the agent task.</param>
/// <returns>The number of steps done.</returns>
procedure GetStepsDoneCount(AgentTaskID: BigInteger): Integer
var
AgentTaskImpl: Codeunit "Agent Task Impl.";
begin
FeatureAccessManagement.AgentManagementAllowed(true);
exit(AgentTaskImpl.GetStepsDoneCount(AgentTaskID));
end;

/// <summary>
/// Opens the agent task log entries page for the specified agent task.
/// </summary>
/// <param name="AgentTaskID">The ID of the agent task to show log entries for.</param>
procedure OpenAgentTaskLogEntries(AgentTaskID: BigInteger)
var
AgentTaskImpl: Codeunit "Agent Task Impl.";
begin
FeatureAccessManagement.AgentManagementAllowed(true);
AgentTaskImpl.ShowTaskLogEntries(AgentTaskID);
end;

/// <summary>
/// Archives the agent task.
/// </summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,14 @@ codeunit 4300 "Agent Task Impl."
InherentEntitlements = X;
InherentPermissions = X;

procedure GetStepsDoneCount(AgentTaskID: BigInteger): Integer
var
AgentTask: Record "Agent Task";
begin
AgentTask.Get(AgentTaskID);
Comment thread
qutreson marked this conversation as resolved.
exit(GetStepsDoneCount(AgentTask));
end;

procedure GetStepsDoneCount(var AgentTask: Record "Agent Task"): Integer
var
AgentTaskLogEntry: Record "Agent Task Log Entry";
Expand All @@ -35,6 +43,14 @@ codeunit 4300 "Agent Task Impl."
exit(ContentText);
end;

procedure ShowTaskLogEntries(AgentTaskID: BigInteger)
var
AgentTask: Record "Agent Task";
begin
AgentTask.Get(AgentTaskID);
Comment thread
qutreson marked this conversation as resolved.
ShowTaskLogEntries(AgentTask);
end;

procedure ShowTaskLogEntries(var AgentTask: Record "Agent Task")
var
AgentTaskLogEntry: Record "Agent Task Log Entry";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,5 +43,7 @@ permissionset 149031 "AI Test Toolkit - Obj"
page "AIT Test Suite" = X,
page "AIT Test Suite List" = X,
page "AIT Test Suite Language Lookup" = X,
page "AIT Log Entry Outcome Part" = X,
page "AIT Agent Log Entry Part" = X,
page "AIT Run History" = X;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
// ------------------------------------------------------------------------------------------------
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License. See License.txt in the project root for license information.
// ------------------------------------------------------------------------------------------------

namespace System.TestTools.AITestToolkit;

using System.Agents;

page 149050 "AIT Agent Log Entry Part"
{
Caption = 'Agent Details';
PageType = ListPart;
Editable = false;
SourceTable = "Agent Task Log";

layout
{
area(Content)
{
repeater(Tasks)
{
field("Agent Task ID"; Rec."Agent Task ID")
{
Caption = 'Task ID';
ApplicationArea = All;
ToolTip = 'Specifies the Agent Task ID.';

trigger OnDrillDown()
begin
AgentTestContextImpl.OpenAgentTaskList(Format(Rec."Agent Task ID"));
end;
}
field(NumberOfStepsDone; NumberOfStepsDone)
{
Caption = 'Steps Done';
ApplicationArea = All;
ToolTip = 'Specifies the number of steps that have been done for the specific task.';

trigger OnDrillDown()
begin
AgentTask.OpenAgentTaskLogEntries(Rec."Agent Task ID");
end;
}
field("Copilot Credits"; CopilotCredits)
{
ApplicationArea = All;
AutoFormatType = 0;
Caption = 'Copilot Credits';
ToolTip = 'Specifies the Copilot Credits consumed by this Agent Task.';

trigger OnDrillDown()
begin
AgentConsumptionOverview.OpenAgentTaskConsumptionOverview(Rec."Agent Task ID");
end;
}
}
}
}

var
AgentConsumptionOverview: Codeunit "Agent Consumption Overview";
AgentTask: Codeunit "Agent Task";
AgentTestContextImpl: Codeunit "Agent Test Context Impl.";
CopilotCredits: Decimal;
NumberOfStepsDone: Integer;
Comment thread
qutreson marked this conversation as resolved.

trigger OnAfterGetRecord()
begin
CopilotCredits := AgentConsumptionOverview.GetCopilotCreditsConsumed(Rec."Agent Task ID");
Comment thread
qutreson marked this conversation as resolved.
NumberOfStepsDone := AgentTask.GetStepsDoneCount(Rec."Agent Task ID");
end;
}
11 changes: 11 additions & 0 deletions src/Tools/AI Test Toolkit/src/Agent/AgentLogEntries.PageExt.al
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,17 @@ pageextension 149030 "Agent Log Entries" extends "AIT Log Entries"
end;
}
}
addafter(TestOutcome)
{
part(AgentDetails; "AIT Agent Log Entry Part")
{
ApplicationArea = All;
SubPageLink =
"Test Log Entry ID" = field("Entry No."),
"Test Suite Code" = field("Test Suite Code");
Visible = AgentTaskIDs <> '';
}
}
}

trigger OnAfterGetRecord()
Expand Down
8 changes: 8 additions & 0 deletions src/Tools/AI Test Toolkit/src/Logs/AITLogEntries.Page.al
Original file line number Diff line number Diff line change
Expand Up @@ -186,6 +186,14 @@ page 149033 "AIT Log Entries"
}
}
}
area(FactBoxes)
{
part(TestOutcome; "AIT Log Entry Outcome Part")
{
ApplicationArea = All;
SubPageLink = "Entry No." = field("Entry No.");
}
}
}
actions
{
Expand Down
112 changes: 112 additions & 0 deletions src/Tools/AI Test Toolkit/src/Logs/AITLogEntryOutcomePart.Page.al
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
// ------------------------------------------------------------------------------------------------
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License. See License.txt in the project root for license information.
// ------------------------------------------------------------------------------------------------

namespace System.TestTools.AITestToolkit;

page 149049 "AIT Log Entry Outcome Part"
{
ApplicationArea = All;
Caption = 'Test Outcome';
PageType = CardPart;
Editable = false;
SourceTable = "AIT Log Entry";
Extensible = true;

layout
{
area(Content)
{
field(Status; Rec.Status)
Comment thread
qutreson marked this conversation as resolved.
Comment thread
qutreson marked this conversation as resolved.
{
StyleExpr = StatusStyleExpr;
}
field(Accuracy; Rec."Test Method Line Accuracy")
{
Caption = 'Evaluation Result';
ToolTip = 'Specifies the accuracy of the eval line.';
AutoFormatType = 0;
}
field(TurnsText; TurnsText)
{
Caption = 'No. of Turns Passed';
ToolTip = 'Specifies the number of turns that passed out of the total number of turns.';
StyleExpr = TurnsStyleExpr;
}
group(ErrorMessageGroup)
{
Caption = 'Error Message';
Visible = ErrorMessage <> '';

field(ErrorMessage; ErrorMessage)
{
ShowCaption = false;
ToolTip = 'Specifies the error message from the eval.';
Style = Unfavorable;
Multiline = true;

trigger OnDrillDown()
begin
Message(ErrorMessage);
end;
}
}
field(Duration; Rec."Duration (ms)")
{
Caption = 'Duration (ms)';
ToolTip = 'Specifies the duration of the test execution in milliseconds.';
AutoFormatType = 0;
}
}
}

var
TurnsText: Text;
ErrorMessage: Text;
StatusStyleExpr: Text;
TurnsStyleExpr: Text;

trigger OnAfterGetRecord()
var
AITTestSuiteMgt: Codeunit "AIT Test Suite Mgt.";
begin
TurnsText := AITTestSuiteMgt.GetTurnsAsText(Rec);
SetStatusStyleExpr();
SetTurnsStyleExpr();
SetErrorMessage();
end;

local procedure SetStatusStyleExpr()
begin
case Rec.Status of
Rec.Status::Success:
StatusStyleExpr := Format(PageStyle::Favorable);
Rec.Status::Error:
StatusStyleExpr := Format(PageStyle::Unfavorable);
Rec.Status::Skipped:
StatusStyleExpr := Format(PageStyle::Ambiguous);
else
StatusStyleExpr := '';
end;
end;

Comment thread
qutreson marked this conversation as resolved.
local procedure SetTurnsStyleExpr()
begin
case Rec."No. of Turns Passed" of
Rec."No. of Turns":
TurnsStyleExpr := Format(PageStyle::Favorable);
0:
TurnsStyleExpr := Format(PageStyle::Unfavorable);
else
TurnsStyleExpr := Format(PageStyle::Ambiguous);
end;
end;

local procedure SetErrorMessage()
begin
ErrorMessage := '';
if Rec.Status = Rec.Status::Error then
ErrorMessage := Rec.GetMessage();
end;
}
Original file line number Diff line number Diff line change
Expand Up @@ -161,18 +161,24 @@ query:
title: <task title>
message: <task message body>
attachments:
- file: <relative path inside .resources>
- file: <another path>
- file: <relative path inside .resources> # static file
- file: # OR: dynamically generated
action_type: <generator name>
action_data:
<key>: <value> # arbitrary data for the generator
```

The `file` key supports two forms: a **scalar** value (static file path) or an **object** with `action_type` / `action_data` (dynamically generated file).

How keys flow into library calls:

| YAML key | Flows into |
|---|---|
| `query.title` | `AgentTaskBuilder.Initialize(AgentUserSecurityId, title)` — required, asserted via `Library Assert`. |
| `query.from` | `AgentTaskMessageBuilder.Initialize(from, ...)`. If `from` is missing, no message is added (only the task title). |
| `query.message` | `AgentTaskMessageBuilder.Initialize(..., message)`. Optional. |
| `query.attachments[].file` | `IAgentTestResourceProvider.GetResource(file, ...)` → `AgentTaskMessageBuilder.AddAttachment(...)`. Use the `RunTurnAndWait` overload that accepts a provider when YAML uses attachments. |
| `query.attachments[].file` (scalar) | `IAgentTestResourceProvider.GetResource(file, ...)` → `AgentTaskMessageBuilder.AddAttachment(...)`. Use the `RunTurnAndWait` overload that accepts a provider when YAML uses attachments. |
| `query.attachments[].file` (object) | `IAgentTestResourceProvider.GenerateResource(action_type, action_data, ...)` → `AgentTaskMessageBuilder.AddAttachment(...)`. The `action_data` sub-object is extracted and passed as a `Test Input Json` codeunit; `action_type` is passed separately. |

### 7.3 Intervention continuation

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@

namespace System.TestLibraries.Agents;

using System.TestTools.TestRunner;

/// <summary>
/// Interface for resolving test resource files from the consuming test app.
/// Implement this in your test app to provide resource file access to the agent test library.
Expand All @@ -19,4 +21,20 @@ interface "IAgentTestResourceProvider"
/// <param name="FileName">Returns the file name extracted from the path.</param>
/// <param name="MIMEType">Returns the MIME type of the file.</param>
procedure GetResource(ResourcePath: Text; var ResourceInStream: InStream; var FileName: Text[250]; var MIMEType: Text[100])

/// <summary>
/// Generates a resource dynamically from YAML-declared data.
/// Override this to support 'filegenerator' entries in YAML attachments.
/// The GeneratorData parameter contains the full filegenerator object from the YAML,
/// including a 'name' key for dispatch and any additional data keys the generator needs.
/// </summary>
/// <param name="GeneratorName">The generator to invoke, from the 'action_type' key in the YAML attachment object.</param>
/// <param name="GeneratorData">The 'action_data' sub-object from the YAML attachment, accessible via Test Input Json.</param>
/// <param name="GeneratorData">The filegenerator object from the YAML, accessible via Test Input Json.</param>
/// <param name="ResourceInStream">Returns the generated file content as an InStream.</param>
/// <param name="FileName">Returns the generated file name.</param>
/// <param name="MIMEType">Returns the MIME type of the generated file.</param>
procedure GenerateResource(GeneratorName: Text; GeneratorData: Codeunit "Test Input Json"; var ResourceInStream: InStream; var FileName: Text[250]; var MIMEType: Text[100])
Comment thread
qutreson marked this conversation as resolved.
Comment thread
qutreson marked this conversation as resolved.
Comment thread
qutreson marked this conversation as resolved.
begin
Comment thread
qutreson marked this conversation as resolved.
end;
}
Loading
Loading