Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/agents/dev.agent.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
---
description: Acts as a development stakeholder, being able to break down features into technical tasks and manage project guidelines and standards.
tools: ['runCommands', 'runTasks', 'edit', 'runNotebooks', 'search', 'new', 'context7/*', 'deepwiki/*', 'microsoft.docs.mcp/*', 'Azure MCP/azd', 'Azure MCP/cloudarchitect', 'Azure MCP/documentation', 'Azure MCP/extension_azqr', 'Azure MCP/extension_cli_generate', 'Azure MCP/extension_cli_install', 'Azure MCP/get_bestpractices', 'Azure MCP/search', 'copilotkit/*', 'extensions', 'usages', 'vscodeAPI', 'problems', 'changes', 'testFailure', 'openSimpleBrowser', 'fetch', 'githubRepo', 'ms-azuretools.vscode-azure-github-copilot/azure_recommend_custom_modes', 'ms-azuretools.vscode-azure-github-copilot/azure_query_azure_resource_graph', 'ms-azuretools.vscode-azure-github-copilot/azure_get_auth_context', 'ms-azuretools.vscode-azure-github-copilot/azure_set_auth_context', 'ms-azuretools.vscode-azure-github-copilot/azure_get_dotnet_template_tags', 'ms-azuretools.vscode-azure-github-copilot/azure_get_dotnet_templates_for_tag', 'ms-windows-ai-studio.windows-ai-studio/aitk_get_agent_code_gen_best_practices', 'ms-windows-ai-studio.windows-ai-studio/aitk_get_ai_model_guidance', 'ms-windows-ai-studio.windows-ai-studio/aitk_get_agent_model_code_sample', 'ms-windows-ai-studio.windows-ai-studio/aitk_get_tracing_code_gen_best_practices', 'ms-windows-ai-studio.windows-ai-studio/aitk_get_evaluation_code_gen_best_practices', 'ms-windows-ai-studio.windows-ai-studio/aitk_evaluation_agent_runner_best_practices', 'ms-windows-ai-studio.windows-ai-studio/aitk_evaluation_planner', 'todos', 'runSubagent', 'runTests']
tools: ['runCommands', 'runTasks', 'context7/*', 'deepwiki/*', 'github/*', 'microsoft.docs.mcp/*', 'Azure MCP/azd', 'Azure MCP/cloudarchitect', 'Azure MCP/documentation', 'Azure MCP/extension_azqr', 'Azure MCP/extension_cli_generate', 'Azure MCP/extension_cli_install', 'Azure MCP/get_bestpractices', 'edit', 'runNotebooks', 'search', 'new', 'extensions', 'ms-azuretools.vscode-azure-github-copilot/azure_recommend_custom_modes', 'ms-azuretools.vscode-azure-github-copilot/azure_query_azure_resource_graph', 'ms-azuretools.vscode-azure-github-copilot/azure_get_auth_context', 'ms-azuretools.vscode-azure-github-copilot/azure_set_auth_context', 'ms-azuretools.vscode-azure-github-copilot/azure_get_dotnet_template_tags', 'ms-azuretools.vscode-azure-github-copilot/azure_get_dotnet_templates_for_tag', 'ms-windows-ai-studio.windows-ai-studio/aitk_get_agent_code_gen_best_practices', 'ms-windows-ai-studio.windows-ai-studio/aitk_get_ai_model_guidance', 'ms-windows-ai-studio.windows-ai-studio/aitk_get_agent_model_code_sample', 'ms-windows-ai-studio.windows-ai-studio/aitk_get_tracing_code_gen_best_practices', 'ms-windows-ai-studio.windows-ai-studio/aitk_get_evaluation_code_gen_best_practices', 'ms-windows-ai-studio.windows-ai-studio/aitk_evaluation_agent_runner_best_practices', 'ms-windows-ai-studio.windows-ai-studio/aitk_evaluation_planner', 'ms-windows-ai-studio.windows-ai-studio/aitk_open_tracing_page', 'todos', 'runTests', 'runSubagent', 'usages', 'vscodeAPI', 'problems', 'changes', 'testFailure', 'openSimpleBrowser', 'fetch', 'githubRepo']
model: Claude Sonnet 4.5 (copilot)
handoffs:
- label: Implement Code for technical tasks (/implement)
Expand Down
578 changes: 578 additions & 0 deletions .github/agents/spec2cloud.agent.md

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions .github/prompts/adr.prompt.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
```prompt
---
agent: architect
---
Expand Down
146 changes: 146 additions & 0 deletions .github/skills/human-in-the-loop/SKILL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
---
name: human-in-the-loop
description: Implement human-in-the-loop approval patterns where agents request user approval before proceeding. Use when adding approval workflows, user confirmations, or HITL features.
---

# Implementing Human-in-the-Loop Approval

**HITL** allows agents to request user approval before proceeding. Requires backend-frontend coordination.

## Backend: Create Approval Requests

### 1. Use ApprovalRequestHelper

Located at `src/agentic-api/ApprovalRequestHelper.cs`:

```csharp
public static class ApprovalRequestHelper
{
public static FunctionApprovalRequestContent CreateApprovalRequest(
string functionName,
Dictionary<string, object?> arguments)
{
return new FunctionApprovalRequestContent(
Guid.NewGuid().ToString(),
new FunctionCallContent(functionName, functionName, arguments: arguments)
);
}
}
```

### 2. Return Approval Requests from Executors

Instead of returning a direct response, return an approval request:

```csharp
public override async ValueTask<AIContent> HandleAsync(UserInputEvent input, IWorkflowContext context, CancellationToken ct)
{
var responseText = await GenerateContent(input);

// Return approval request instead of direct response
return ApprovalRequestHelper.CreateApprovalRequest(
functionName: "approve_copyright_command", // Must match frontend hook name
arguments: new Dictionary<string, object?> { { "copyright", responseText } }
);
}
```

### 3. Handle Approval Responses in Input Executor

```csharp
private async ValueTask<UserInputEvent> HandleChatMessagesAsync(List<ChatMessage> messages, IWorkflowContext context, CancellationToken ct)
{
// Check for approval response (comes back as ChatRole.Tool with FunctionResultContent)
var approvalMessage = messages.LastOrDefault(m => m.Role == ChatRole.Tool);
var functionResult = approvalMessage?.Contents.OfType<FunctionResultContent>().FirstOrDefault();

var textApproved = functionResult?.Result?.ToString()?.Contains("text-approved");
var textRejected = functionResult?.Result?.ToString()?.Contains("text-rejected");

// Route based on approval status
if (textApproved == true) return new UserInputEvent { NextStep = WorkflowSteps.GenerateImage };
if (textRejected == true) return new UserInputEvent { NextStep = WorkflowSteps.RegenerateText };
return new UserInputEvent { NextStep = WorkflowSteps.GenerateText }; // Start workflow
}
```

**Note**: `AGUIWorkflowAgent` automatically converts `FunctionApprovalRequestContent` to `FunctionCallContent` for the frontend (auto-registered via `.AddAsAIAgent()`).

## Frontend: Add useHumanInTheLoop Hook

```typescript
import { useHumanInTheLoop } from "@copilotkit/react-core";

export default function Page() {
const [approvedContent, setApprovedContent] = useState<string | null>(null);

useHumanInTheLoop({
name: "approve_copyright_command", // Must match backend functionName
description: "Ask the user to approve the generated text content",
parameters: [
{ name: "copyright", type: "string", description: "The text to approve", required: true },
],
render: ({ args, respond }) => {
if (!respond) return <></>;
return (
<div className="approval-container">
<pre>{args.copyright}</pre>
<button onClick={() => { setApprovedContent(args.copyright); respond("text-approved"); }}>Approve</button>
<button onClick={() => { respond("text-rejected"); }}>Reject</button>
</div>
);
},
});
// ...
}
```

## Key Matching Requirements

| Frontend | Backend | Must Match |
|----------|---------|------------|
| `name` | `functionName` | Exact string match |
| `parameters[].name` | `arguments` dictionary keys | Key names |
| `respond()` value | `FunctionResultContent.Result` check | Response strings |

## Workflow with Conditional Routing

```csharp
public Workflow BuildWorkflow(string name)
{
var chatInput = new DummyChatInputExecutor(_inputLogger);
var textGenerator = new TextGeneratorExecutor(_logger, _chatClient);
var imageGenerator = new ImageGeneratorExecutor(_logger, _chatClient, _imageGenerator);

return new WorkflowBuilder(chatInput)
.WithName(name)
.AddSwitch(chatInput, switchBuilder =>
switchBuilder
.AddCase(input => input?.NextStep == WorkflowSteps.GenerateText, textGenerator)
.AddCase(input => input?.NextStep == WorkflowSteps.GenerateImage, imageGenerator)
.WithDefault(textGenerator))
.WithOutputFrom(textGenerator)
.WithOutputFrom(imageGenerator)
.Build();
}
```

## Execution Flow

```
User → InputExecutor (checks approvals) → Executor → Returns FunctionApprovalRequestContent
→ Frontend shows approval UI → User responds → InputExecutor routes to next step
```

## Troubleshooting

| Issue | Solution |
|-------|----------|
| Approval UI not appearing | Check `functionName` matches exactly, executor registered with `.WithOutputFrom()` |
| Response not reaching backend | Ensure `respond()` called with string, check `ChatRole.Tool` messages in input executor |

## Best Practices

- Use descriptive names: `approve_copyright_command`, `validate_data_command`
- Use clear response values: `"text-approved"`, `"image-rejected"`
- Always provide both approve and reject options
88 changes: 88 additions & 0 deletions .github/skills/image-generation/SKILL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
---
name: image-generation
description: Add text-to-image generation using IImageGenerator and Azure AI Foundry models (Flux, GPT-Image, DALL-E). Use when implementing image generation features in workflows.
---

# Using IImageGenerator for Text-to-Image Generation

**IImageGenerator** provides text-to-image generation using Azure AI Foundry models (Flux, GPT-Image, DALL-E, etc.).

## 1. Register in Program.cs

The deployment name is auto-populated by `azd provision`:

```csharp
string imageDeploymentName = builder.Configuration["AZURE_IMAGE_MODEL_DEPLOYMENT_NAME"]
?? throw new InvalidOperationException("AZURE_IMAGE_MODEL_DEPLOYMENT_NAME is not set.");

#pragma warning disable MEAI001
builder.Services.AddSingleton(_ =>
new AzureOpenAIClient(new Uri(endpoint), new DefaultAzureCredential())
.GetImageClient(imageDeploymentName)
.AsIImageGenerator());
#pragma warning restore MEAI001
```

## 2. Inject and Use in Executors

```csharp
public class MyExecutor(ILogger<MyExecutor> logger, IChatClient chatClient, IImageGenerator imageGenerator)
: Executor<InputEvent, OutputEvent>("MyExecutor")
{
public override async ValueTask<OutputEvent> HandleAsync(InputEvent input, IWorkflowContext context, CancellationToken ct)
{
var options = new ImageGenerationOptions
{
MediaType = "image/png",
ResponseFormat = ImageGenerationResponseFormat.Hosted, // or .Base64
Size = "1024x1024", // Model-dependent
Quality = "standard", // or "hd"
Style = "natural" // or "vivid"
};

var response = await imageGenerator.GenerateImagesAsync("A futuristic city at sunset", options, ct);
var dataContent = response.Contents.OfType<DataContent>().First();

return new OutputEvent { Text = "Image generated!", ImageUrl = dataContent.Uri?.ToString() };
}
}
```

## 3. Send to UI

Use `YieldOutputAsync` or return with `ImageUrl` property:

```csharp
await context.YieldOutputAsync(new AgentMessage { Text = "Here's your image:", ImageUrl = dataContent.Uri.ToString() });
```

## 4. Frontend Handling

In `CustomMessageRenderer.tsx`:

```typescript
export function CustomMessageRenderer({ message }: { message: { text?: string; imageUrl?: string } }) {
return (
<div className="agent-message">
{message.text && <p>{message.text}</p>}
{message.imageUrl && <img src={message.imageUrl} alt="Generated by AI" className="max-w-full rounded-lg mt-2" />}
</div>
);
}
```

## Best Practices

- Wrap in try-catch for error handling
- Log prompts and URLs for debugging
- Use `Hosted` response format for UI display
- Image generation typically takes 10-30 seconds

## Image Generation Options

| Option | Values | Notes |
|--------|--------|-------|
| `ResponseFormat` | `Hosted`, `Base64` | Use `Hosted` for UI |
| `Size` | `1024x1024`, `512x512`, etc. | Model-dependent |
| `Quality` | `standard`, `hd` | HD takes longer |
| `Style` | `natural`, `vivid` | Artistic style |
127 changes: 127 additions & 0 deletions .github/skills/new-workflow/SKILL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
---
name: new-workflow
description: Create a new agent workflow with executors, registration, and AGUI protocol integration. Use when adding new AI agent capabilities, creating executors, or building workflow graphs.
---

# Adding a New Agent Workflow

Follow these steps to create a new workflow in the agentic-shell-dotnet project.

## 1. Create Workflow File

Create a new file at `src/agentic-api/Workflows/MyWorkflow.cs`:

```csharp
using Microsoft.Agents.AI.Workflows;
using Microsoft.Extensions.AI;
using Microsoft.Extensions.Logging;

namespace agentic_api.Workflows;

// Input Executor: Receives chat input, converts to internal event
public class MyChatInputExecutor(ILogger<MyChatInputExecutor> logger)
: ExecutorBase<IConversationUpdate, UserInputEvent>(logger)
{
protected override ValueTask ExecuteAsync(IConversationUpdate input, CancellationToken ct)
{
var userMessage = input switch
{
ChatMessage msg => msg.Text,
TurnToken token => token.Text,
_ => "Hello"
};
return ValueTask.FromResult(new ExecutionResult<UserInputEvent>(new UserInputEvent { Input = userMessage }));
}
}

// Processing Executor: Handles business logic, calls AI models
public class MyProcessingExecutor(ILogger<MyProcessingExecutor> logger, IChatClient chatClient)
: ExecutorBase<UserInputEvent, WorkflowOutputEvent>(logger)
{
protected override async ValueTask ExecuteAsync(UserInputEvent input, CancellationToken ct)
{
var response = await chatClient.CompleteAsync($"User message: {input.Input}", cancellationToken: ct);
return new ExecutionResult<WorkflowOutputEvent>(new WorkflowOutputEvent(response.Message.Text ?? "Hello!"));
}
}

// Factory: Builds the workflow graph
public class MyWorkflowFactory(
ILogger<MyChatInputExecutor> inputLogger,
ILogger<MyProcessingExecutor> processingLogger,
IChatClient chatClient)
{
public Workflow BuildWorkflow(string name)
{
var inputExecutor = new MyChatInputExecutor(inputLogger);
var processingExecutor = new MyProcessingExecutor(processingLogger, chatClient);

return new WorkflowBuilder(inputExecutor)
.WithName(name)
.AddEdge(inputExecutor, processingExecutor)
.WithOutputFrom(processingExecutor) // Required for streaming to UI
.Build();
}
}
```

## 2. Register in Program.cs

Add the workflow registration in `src/agentic-api/Program.cs`:

```csharp
builder.Services.AddSingleton<MyWorkflowFactory>();
builder.AddWorkflow("MyWorkflow", (sp, name) =>
sp.GetRequiredService<MyWorkflowFactory>().BuildWorkflow(name))
.AddAsAIAgent(); // Wraps with AGUIWorkflowAgent for AGUI protocol compatibility
```

## What `.AddAsAIAgent()` Does

- Wraps workflow with `AGUIWorkflowAgent` for AGUI protocol
- Registers agent with AGUI endpoint (via `app.MapAGUI()`)
- Makes workflow accessible via `/api/copilotkit`

## Workflow Patterns

### Basic Linear Workflow
```
User Input → InputExecutor → ProcessingExecutor → Response
```

### Conditional Routing with Switch
```csharp
return new WorkflowBuilder(chatInput)
.WithName(name)
.AddSwitch(chatInput, switchBuilder =>
switchBuilder
.AddCase(input => input?.NextStep == WorkflowSteps.StepA, executorA)
.AddCase(input => input?.NextStep == WorkflowSteps.StepB, executorB)
.WithDefault(executorA))
.WithOutputFrom(executorA)
.WithOutputFrom(executorB)
.Build();
```

## Streaming Messages to UI

Use `YieldOutputAsync` to stream intermediate results:

```csharp
await context.YieldOutputAsync(new AgentMessage { Text = "Processing..." });
```

**Critical**: Every executor calling `YieldOutputAsync` must be registered with `.WithOutputFrom()`:

```csharp
var workflow = new WorkflowBuilder(inputExecutor)
.AddEdge(inputExecutor, processingExecutor)
.WithOutputFrom(processingExecutor) // Required!
.Build();
```

## Key Files Reference

- `src/agentic-api/Program.cs` - Backend config and workflow registration
- `src/agentic-api/Workflows/DummyWorkflow.cs` - Example workflow
- `src/agentic-api/AGUIWorkflowAgent.cs` - AGUI adapter
Loading