Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

.Net: Add Calendar write for Copilot Agent Plugin Calendar #10622

Open
wants to merge 15 commits into
base: main
Choose a base branch
from
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
13 changes: 13 additions & 0 deletions .vscode/launch.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,19 @@
{
"version": "0.2.0",
"configurations": [
{
"name": "CAPs - Demo Sample",
"type": "coreclr",
"request": "launch",
"preLaunchTask": "build (CopilotAgentPluginsDemoSample)",
"program": "${workspaceFolder}/dotnet/samples/Demos/CopilotAgentPlugins/CopilotAgentPluginsDemoSample/bin/Debug/net8.0/CopilotAgentPluginsDemoSample.exe",
"args": [
"demo"
],
"cwd": "${workspaceFolder}/dotnet/samples/Demos/CopilotAgentPlugins/CopilotAgentPluginsDemoSample",
"stopAtEntry": false,
"console": "integratedTerminal"
},
{
// Use IntelliSense to find out which attributes exist for C# debugging
// Use hover for the description of the existing attributes
Expand Down
9 changes: 9 additions & 0 deletions .vscode/tasks.json
Original file line number Diff line number Diff line change
Expand Up @@ -222,6 +222,15 @@
"cwd": "${workspaceFolder}/dotnet/src/IntegrationTests/"
}
},
{
"label": "build (CopilotAgentPluginsDemoSample)",
"command": "dotnet",
"type": "process",
"args": [
"build",
"${workspaceFolder}/dotnet/samples/Demos/CopilotAgentPlugins/CopilotAgentPluginsDemoSample/CopilotAgentPluginsDemoSample.csproj"
]
},
// ****************
// Samples (dotnet)
// ****************
Expand Down
29 changes: 27 additions & 2 deletions dotnet/samples/Concepts/Plugins/CopilotAgentBasedPlugins.cs
Original file line number Diff line number Diff line change
Expand Up @@ -102,31 +102,55 @@ private void WriteSampleHeadingToConsole(string pluginToTest, string functionToT
[
"@odata.type",
"attachments",
"allowNewTimeProposals",
"bccRecipients",
"bodyPreview",
"calendar",
"categories",
"ccRecipients",
"changeKey",
"conversationId",
"coordinates",
"conversationIndex",
"createdDateTime",
"discriminator",
"lastModifiedDateTime",
"locations",
"extensions",
"flag",
"from",
"hasAttachments",
"iCalUId",
"id",
"inferenceClassification",
"internetMessageHeaders",
"instances",
"isCancelled",
"isDeliveryReceiptRequested",
"isDraft",
"isOrganizer",
"isRead",
"isReadReceiptRequested",
"multiValueExtendedProperties",
"onlineMeeting",
"onlineMeetingProvider",
"onlineMeetingUrl",
"organizer",
"originalStart",
"parentFolderId",
"range",
"receivedDateTime",
"recurrence",
"replyTo",
"sender",
"sentDateTime",
"seriesMasterId",
"singleValueExtendedProperties",
"transactionId",
"time",
"uniqueBody",
"uniqueId",
"uniqueIdType",
"webLink",
],
StringComparer.OrdinalIgnoreCase
Expand Down Expand Up @@ -185,8 +209,9 @@ private static void TrimPropertiesFromJsonNode(JsonNode jsonNode)
}
private static readonly RestApiParameterFilter s_restApiParameterFilter = (RestApiParameterFilterContext context) =>
{
if ("me_sendMail".Equals(context.Operation.Id, StringComparison.OrdinalIgnoreCase) &&
"payload".Equals(context.Parameter.Name, StringComparison.OrdinalIgnoreCase))
if (("me_sendMail".Equals(context.Operation.Id, StringComparison.OrdinalIgnoreCase) ||
("me_calendar_CreateEvents".Equals(context.Operation.Id, StringComparison.OrdinalIgnoreCase)) &&
"payload".Equals(context.Parameter.Name, StringComparison.OrdinalIgnoreCase)))
{
context.Parameter.Schema = TrimPropertiesFromRequestBody(context.Parameter.Schema);
return context.Parameter;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,17 @@
"conversation_starters": [
{
"text": "List events"
},
{
"text": "Create new navigation property to events for me"
}
]
},
"functions": [
{
"name": "me_calendar_CreateEvents",
"description": "Create new navigation property to events for me"
},
{
"name": "me_calendar_ListEvents",
"description": "Retrieve a list of events in a calendar. The calendar can be one for a user, or the default calendar of a Microsoft 365 group. The list of events contains single instance meetings and series masters. To get expanded event instances, you can get the calendar view, or\nget the instances of an event."
Expand All @@ -29,7 +36,8 @@
"url": "calendar-openapi.yml"
},
"run_for_functions": [
"me_calendar_ListEvents"
"me_calendar_ListEvents",
"me_calendar_CreateEvents"
]
}
]
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
openapi: 3.0.1
openapi: 3.0.4
info:
title: OData Service for namespace microsoft.graph - Subset
description: This OData service is located at https://graph.microsoft.com/v1.0
Expand Down Expand Up @@ -56,23 +56,27 @@ paths:
nextLinkName: '@odata.nextLink'
operationName: listMore
itemName: value
post:
tags:
- me.calendar
summary: Create new navigation property to events for me
operationId: me_calendar_CreateEvents
requestBody:
description: New navigation property
content:
application/json:
schema:
$ref: '#/components/schemas/microsoft.graph.event'
required: true
responses:
2XX:
description: Created navigation property.
content:
application/json:
schema:
$ref: '#/components/schemas/microsoft.graph.event'
components:
schemas:
microsoft.graph.eventCollectionResponse:
title: Base collection pagination and count responses
type: object
properties:
'@odata.count':
type: integer
format: int64
nullable: true
'@odata.nextLink':
type: string
nullable: true
value:
type: array
items:
$ref: '#/components/schemas/microsoft.graph.event'
microsoft.graph.event:
title: event
required:
Expand Down Expand Up @@ -547,6 +551,21 @@ components:
type: string
description: A property value.
nullable: true
microsoft.graph.eventCollectionResponse:
title: Base collection pagination and count responses
type: object
properties:
'@odata.count':
type: integer
format: int64
nullable: true
'@odata.nextLink':
type: string
nullable: true
value:
type: array
items:
$ref: '#/components/schemas/microsoft.graph.event'
microsoft.graph.emailAddress:
title: emailAddress
required:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -390,31 +390,55 @@ await kernel.ImportPluginFromCopilotAgentPluginAsync(
[
"@odata.type",
"attachments",
"allowNewTimeProposals",
"bccRecipients",
"bodyPreview",
"calendar",
"categories",
"ccRecipients",
"changeKey",
"conversationId",
"coordinates",
"conversationIndex",
"createdDateTime",
"discriminator",
"lastModifiedDateTime",
"locations",
"extensions",
"flag",
"from",
"hasAttachments",
"iCalUId",
"id",
"inferenceClassification",
"internetMessageHeaders",
"instances",
"isCancelled",
"isDeliveryReceiptRequested",
"isDraft",
"isOrganizer",
"isRead",
"isReadReceiptRequested",
"multiValueExtendedProperties",
"onlineMeeting",
"onlineMeetingProvider",
"onlineMeetingUrl",
"organizer",
"originalStart",
"parentFolderId",
"range",
"receivedDateTime",
"recurrence",
"replyTo",
"sender",
"sentDateTime",
"seriesMasterId",
"singleValueExtendedProperties",
"transactionId",
"time",
"uniqueBody",
"uniqueId",
"uniqueIdType",
"webLink",
],
StringComparer.OrdinalIgnoreCase
Expand Down Expand Up @@ -475,8 +499,9 @@ private static void TrimPropertiesFromJsonNode(JsonNode jsonNode)
private static readonly RestApiParameterFilter s_restApiParameterFilter = (RestApiParameterFilterContext context) =>
{
#pragma warning restore SKEXP0040
if ("me_sendMail".Equals(context.Operation.Id, StringComparison.OrdinalIgnoreCase) &&
"payload".Equals(context.Parameter.Name, StringComparison.OrdinalIgnoreCase))
if (("me_sendMail".Equals(context.Operation.Id, StringComparison.OrdinalIgnoreCase) ||
("me_calendar_CreateEvents".Equals(context.Operation.Id, StringComparison.OrdinalIgnoreCase)) &&
"payload".Equals(context.Parameter.Name, StringComparison.OrdinalIgnoreCase)))
{
context.Parameter.Schema = TrimPropertiesFromRequestBody(context.Parameter.Schema);
return context.Parameter;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
using System.IO;
using System.Linq;
using System.Net.Http;
using System.Runtime.InteropServices;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Extensions.DependencyInjection;
Expand Down Expand Up @@ -159,13 +160,44 @@ await DocumentLoader.LoadDocumentFromUriAsStreamAsync(parsedDescriptionUrl,
#pragma warning disable CA2000 // Dispose objects before losing scope. No need to dispose the Http client here. It can either be an internal client using NonDisposableHttpClientHandler or an external client managed by the calling code, which should handle its disposal.
var operationRunnerHttpClient = HttpClientProvider.GetHttpClient(openApiFunctionExecutionParameters?.HttpClient ?? kernel.Services.GetService<HttpClient>());
#pragma warning restore CA2000
static IDictionary<string, string>? CopilotAgentPluginHeadersFactory(RestApiOperation operation, IDictionary<string, object?> arguments, RestApiOperationRunOptions? options)
{
var graphAllowedHosts = new[]
{
"graph.microsoft.com",
"graph.microsoft.us",
"dod-graph.microsoft.us",
"graph.microsoft.de",
"microsoftgraph.chinacloudapi.cn",
"canary.graph.microsoft.com",
"graph.microsoft-ppe.com"
};
if (options?.ApiHostUrl?.Host is not { } hostString || !graphAllowedHosts.Contains(hostString))
{
return null;
}
string frameworkDescription = RuntimeInformation.FrameworkDescription;
string osDescription = RuntimeInformation.OSDescription;
string copilotAgentPluginVersion = HttpHeaderConstant.Values.GetAssemblyVersion(typeof(CopilotAgentPluginKernelExtensions));
var defaultHeaders = new Dictionary<string, string>
{
// TODO: version and format updates
["SdkVersion"] = $"copilot-agent-plugins/{copilotAgentPluginVersion}, (runtimeEnvironment={frameworkDescription}; hostOS={osDescription})",
["client-request-id"] = Guid.NewGuid().ToString()
};

var currentHeaders = operation.BuildHeaders(arguments);
var finalHeaders = defaultHeaders.Concat(currentHeaders).ToDictionary(k => k.Key, v => v.Value);
return finalHeaders;
}

var runner = new RestApiOperationRunner(
operationRunnerHttpClient,
openApiFunctionExecutionParameters?.AuthCallback,
openApiFunctionExecutionParameters?.UserAgent,
openApiFunctionExecutionParameters?.EnableDynamicPayload ?? false,
openApiFunctionExecutionParameters?.EnablePayloadNamespacing ?? true);
openApiFunctionExecutionParameters?.EnablePayloadNamespacing ?? true,
headersFactory: CopilotAgentPluginHeadersFactory);

var info = OpenApiDocumentParser.ExtractRestApiInfo(filteredOpenApiDocument);
var security = OpenApiDocumentParser.CreateRestApiOperationSecurityRequirements(filteredOpenApiDocument.SecurityRequirements);
Expand Down
Loading