Install
$ agentstack add skill-louage-frw-agentic-coding-skill-copilot ✓ scanned · ✓ verified — works with Claude Code, Cursor, and more.
Security review
✓ PassedNo issues found. Passed automated security review. · v0.1.0 How review works →
- ✓ Prompt-injection patterns
- ✓ Secret / credential exfiltration
- ✓ Dangerous shell & filesystem operations
- ✓ Untrusted network calls
- ✓ Known-malicious package signatures
What it can access
- ✓ Network access No
- ✓ Filesystem access No
- ✓ Shell / process execution No
- ✓ Environment & secrets No
- ✓ Dynamic code execution No
From automated source analysis of v0.1.0. “Used” means the capability is present in the source — more access means more to trust, not that it’s unsafe.
About
Skill: AL Copilot Development (Full Lifecycle)
Purpose
Build AI-powered Copilot experiences in Business Central end-to-end: capability registration, PromptDialog page design, Azure OpenAI generation codeunit, and testing with AI Test Toolkit.
When to Load
This skill should be loaded when:
- A new Copilot/AI feature is being designed or implemented
- A PromptDialog page needs to be created or modified
- Azure OpenAI integration is required (chat completions, JSON mode)
- AI Test Toolkit tests need to be created for a Copilot feature
- Prompt engineering guidance is needed (system/user prompt design)
- A capability needs to be registered in BC's Copilot admin page
Phase 1: Capability Registration
Objects Required
- Enum Extension, extend
"Copilot Capability"to register your feature - Install Codeunit, register the capability on app install
- Isolated Storage Wrapper, manage Azure OpenAI secrets securely
Pattern: Enum Extension
namespace Contoso.CopilotFeatures;
using System.AI;
enumextension 50100 "Contoso Copilot Capabilities" extends "Copilot Capability"
{
value(50100; "Sales Forecasting")
{
Caption = 'Sales Forecasting with Copilot';
}
}
Pattern: Install Codeunit
namespace Contoso.CopilotFeatures;
using System.AI;
codeunit 50100 "Contoso Copilot Setup"
{
Subtype = Install;
InherentEntitlements = X;
InherentPermissions = X;
Access = Internal;
trigger OnInstallAppPerDatabase()
begin
RegisterCapability();
end;
local procedure RegisterCapability()
var
CopilotCapability: Codeunit "Copilot Capability";
LearnMoreUrlTxt: Label 'https://learn.microsoft.com/dynamics365/business-central/', Locked = true;
begin
if not CopilotCapability.IsCapabilityRegistered(
Enum::"Copilot Capability"::"Sales Forecasting") then
CopilotCapability.RegisterCapability(
Enum::"Copilot Capability"::"Sales Forecasting",
Enum::"Copilot Availability"::Preview,
LearnMoreUrlTxt);
end;
}
Availability options: Preview (opt-in), GA (general availability).
After publishing, verify the capability appears in BC: search "Copilot & AI Capabilities" page.
Pattern: Isolated Storage Wrapper (Secrets)
codeunit 50101 "Contoso Isolated Storage"
{
Access = Internal;
procedure GetSecretKey(): SecretText
var
Secret: Text;
begin
if IsolatedStorage.Get('AzureOpenAIKey', DataScope::Module, Secret) then
exit(Secret);
Error('Azure OpenAI key not configured.');
end;
procedure SetSecretKey(NewKey: SecretText)
begin
IsolatedStorage.Set('AzureOpenAIKey', NewKey, DataScope::Module);
end;
procedure GetEndpoint(): Text
var
Endpoint: Text;
begin
if IsolatedStorage.Get('AzureOpenAIEndpoint', DataScope::Module, Endpoint) then
exit(Endpoint);
Error('Azure OpenAI endpoint not configured.');
end;
procedure SetEndpoint(NewEndpoint: Text)
begin
IsolatedStorage.Set('AzureOpenAIEndpoint', NewEndpoint, DataScope::Module);
end;
procedure GetDeployment(): Text
var
Deployment: Text;
begin
if IsolatedStorage.Get('AzureOpenAIDeployment', DataScope::Module, Deployment) then
exit(Deployment);
exit('gpt-4o'); // default model
end;
procedure SetDeployment(NewDeployment: Text)
begin
IsolatedStorage.Set('AzureOpenAIDeployment', NewDeployment, DataScope::Module);
end;
}
Production vs Development:
- Development: configure own Azure OpenAI credentials via
SetSecretKey/SetEndpoint/SetDeployment - Production: use
SetManagedResourceAuthorization(Microsoft-managed, no secrets needed)
Phase 2: PromptDialog Page
Page Areas
| Area | Purpose | Contains | |---|---|---| | PromptOptions | User settings/filters | Option/Enum fields only | | Prompt | User text input | Free-text field with InstructionalText | | Content | AI output display | Text field or part subpage with results | | PromptGuide (actions) | Example prompts | Actions that pre-fill the Prompt field | | SystemActions (actions) | Generate / OK / Cancel | systemaction(Generate), systemaction(OK), etc. |
PromptMode Options
| Mode | Behavior | Use when | |---|---|---| | Prompt | Shows input first, user clicks Generate | User needs to provide context | | Generate | Auto-runs generation when page opens | Context is pre-filled from calling page | | Content | Shows content only, no generation | Displaying previously generated results |
Pattern: Complete PromptDialog Page
namespace Contoso.CopilotFeatures;
using System.AI;
page 50110 "Contoso Sales Forecast Copilot"
{
PageType = PromptDialog;
Extensible = false;
IsPreview = true;
Caption = 'Sales Forecast with Copilot';
PromptMode = Prompt;
layout
{
area(PromptOptions)
{
field(ForecastPeriod; SelectedPeriod)
{
ApplicationArea = All;
Caption = 'Forecast Period';
ToolTip = 'Select the forecast time horizon.';
}
}
area(Prompt)
{
field(UserInput; UserPromptText)
{
ShowCaption = false;
MultiLine = true;
ApplicationArea = All;
InstructionalText = 'Describe what you want to forecast (e.g., "top 10 items for next quarter")';
trigger OnValidate()
begin
CurrPage.Update();
end;
}
}
area(Content)
{
// Option A: simple text response
field(AIResponse; AIResponseText)
{
ApplicationArea = All;
Caption = 'Copilot Suggestion';
MultiLine = true;
Editable = false;
}
// Option B: structured results via subpage
// part(Proposals; "Contoso Forecast Proposal Sub")
// {
// ApplicationArea = All;
// }
}
}
actions
{
area(PromptGuide)
{
action(ExampleTopItems)
{
ApplicationArea = All;
Caption = 'Top selling items next quarter';
ToolTip = 'Predict the best-selling items for the next quarter.';
trigger OnAction()
begin
UserPromptText := 'What will be the top 10 selling items next quarter based on historical sales?';
CurrPage.Update(false);
end;
}
action(ExampleSlowMovers)
{
ApplicationArea = All;
Caption = 'Slow-moving inventory';
ToolTip = 'Identify items with declining sales trends.';
trigger OnAction()
begin
UserPromptText := 'Which items show declining sales over the last 6 months?';
CurrPage.Update(false);
end;
}
}
area(SystemActions)
{
systemaction(Generate)
{
Caption = 'Generate';
ToolTip = 'Generate AI forecast suggestions.';
trigger OnAction()
begin
RunGeneration();
end;
}
systemaction(Regenerate)
{
Caption = 'Regenerate';
ToolTip = 'Generate different suggestions.';
trigger OnAction()
begin
RunGeneration();
end;
}
systemaction(OK)
{
Caption = 'Keep it';
ToolTip = 'Accept and apply the forecast.';
}
systemaction(Cancel)
{
Caption = 'Discard';
ToolTip = 'Discard suggestions.';
}
}
}
trigger OnQueryClosePage(CloseAction: Action): Boolean
begin
if CloseAction = CloseAction::OK then
ApplySuggestions();
end;
local procedure RunGeneration()
var
GenerationCU: Codeunit "Contoso Forecast Generation";
begin
AIResponseText := '';
GenerationCU.SetUserPrompt(UserPromptText);
GenerationCU.SetPeriod(SelectedPeriod);
if GenerationCU.Run() then
AIResponseText := GenerationCU.GetCompletionResult()
else
Error('Generation failed: %1', GetLastErrorText());
CurrPage.Update(false);
end;
local procedure ApplySuggestions()
begin
// Apply user-approved results to BC data
end;
/// Call from external page to set context before opening
procedure SetItemFilter(ItemCategoryCode: Code[20])
begin
ContextItemCategory := ItemCategoryCode;
end;
var
UserPromptText: Text;
AIResponseText: Text;
SelectedPeriod: Option "Next Month","Next Quarter","Next Year";
ContextItemCategory: Code[20];
}
Pattern: Temporary Table for Structured Output
When AI returns a list of proposals (not just text), use a temporary table:
table 50110 "Contoso Forecast Proposal"
{
TableType = Temporary;
Caption = 'Forecast Proposal';
fields
{
field(1; "Entry No."; Integer) { AutoIncrement = true; }
field(10; "Item No."; Code[20]) { Caption = 'Item No.'; }
field(20; Description; Text[100]) { Caption = 'Description'; }
field(30; "Forecast Qty"; Decimal) { Caption = 'Forecast Quantity'; }
field(40; Explanation; Text[250]) { Caption = 'AI Explanation'; }
field(50; "Confidence Score"; Decimal) { Caption = 'Confidence'; MinValue = 0; MaxValue = 1; }
}
keys
{
key(PK; "Entry No.") { Clustered = true; }
}
}
Display via a ListPart subpage linked by part() in the Content area.
Phase 3: AI Generation Codeunit
Builds the Azure OpenAI chat-completion codeunit and the structured-output handling (temporary tables).
When implementing the generation codeunit, load references/copilot-ai-generation.md.
Phase 4: Testing with AI Test Toolkit
Validates the Copilot experience with the AI Test Toolkit: dependency setup, the test codeunit pattern, and the test workflow.
When writing Copilot tests, load references/copilot-testing.md.
Workflow
Step 1: Design Copilot Experience
Define before coding:
- User problem, what task does this Copilot help with?
- PromptMode, Prompt (user types) vs Generate (auto-run)?
- Input, free text, options, context from calling page?
- Output, simple text or structured proposals (temp table + subpage)?
- AI model, Temperature (deterministic vs creative), MaxTokens
Step 2: Implement (Phase 1 → Phase 3)
- Register capability (Phase 1: enum + install codeunit)
- Create PromptDialog page (Phase 2: areas, system actions, prompt guide)
- Create generation codeunit (Phase 3: Azure OpenAI + JSON parsing)
- Build:
al_build
Step 3: Test (Phase 4)
- Add AI Test Toolkit dependency to Test app
- Create test codeunit with happy path, edge cases, consistency, performance
- Create AI Test Suite dataset in BC for systematic prompt evaluation
- Run tests and iterate on prompts
Step 4: Responsible AI Review
Before shipping:
- User transparency, users know they're interacting with AI
- Content filtering, no raw AI output without validation
- Data privacy, no sensitive data in prompts without sanitization
- Feedback, users can accept/reject AI suggestions (OK/Cancel)
- Error handling, graceful handling of all Azure OpenAI error codes
References
- Build Copilot Capability in AL
- Build Copilot User Experience
- PromptDialog Page Type
- Azure OpenAI Module in AL
- AI Test Toolkit
- BCTech Samples, Copilot
- Responsible AI, Microsoft
Constraints
- Do NOT expose raw AI responses without validation, always parse and verify structure
- Do NOT include sensitive customer data in prompts without sanitization
- Do NOT deploy Copilot features without Responsible AI compliance review
- Do NOT skip AI Test Toolkit testing, every Copilot feature MUST have test coverage
- Do NOT use deprecated
SetAuthorizationin production, useSetManagedResourceAuthorization - Do NOT hardcode Azure OpenAI credentials, always use
IsolatedStorage - Permission set generation →
skill-permissions.md - Debugging AI integration issues →
skill-debug.md - Test strategy design →
skill-testing.md
Source & license
This open-source skill is cataloged on AgentStack and links to its original source — we do not rehost the code.
- Author: Louage
- Source: Louage/frw-agentic-coding
- License: MIT
Install and usage instructions live in the source repository linked above.
Reviews
No reviews yet — be the first.
Write a review
Versions
- v0.1.0 Imported from the upstream source.