AI Tools
The optional Shiny.Calendar.Extensions.AI package exposes ICalendarStore as
Microsoft.Extensions.AI tool functions (AIFunctions) for
LLM agents. You opt-in per operation — read, create, update, delete — and optionally per
calendar, an allow-list you control on behalf of the agent (this is not an OS permission prompt;
the platform calendar permission must already be granted). AOT-compatible: hand-built schemas and
JsonNode results, no reflection.
using Shiny.Calendar;using Shiny.Calendar.Extensions.AI;
builder.Services.AddCalendarStore(); // registers ICalendarStorebuilder.Services.AddCalendarAITools(tools => tools .AddCalendar(CalendarAICapabilities.Read | CalendarAICapabilities.Create));Resolve the bundle and hand the tools to any IChatClient:
var tools = sp.GetRequiredService<CalendarAITools>().Tools;var response = await chatClient.GetResponseAsync( messages, new ChatOptions { Tools = [.. tools] });Capabilities
Section titled “Capabilities”CalendarAICapabilities is a [Flags] enum, so each operation toggles independently:
| Flag | Tools exposed |
|---|---|
Read |
list_calendars, search_events, get_event |
Create |
create_event |
Update |
update_event |
Delete |
delete_event |
Write |
Create | Update | Delete |
All |
Read | Write |
// read + create only — the agent can add events but not modify or delete them.AddCalendar(CalendarAICapabilities.Read | CalendarAICapabilities.Create)
// full access.AddCalendar(CalendarAICapabilities.All)Scoping to specific calendars
Section titled “Scoping to specific calendars”AddCalendar(capabilities) applies to every calendar on the device.
AddCalendar(calendarId, capabilities) applies to one calendar and replaces the global set for
it — so a per-calendar entry can widen or narrow access. That covers the three shapes you normally
want:
// 1. global — every calendar, same rulesbuilder.Services.AddCalendarAITools(tools => tools .AddCalendar(CalendarAICapabilities.Read));
// 2. mixed — read everything, but full read/write on the work calendar, and hide the private onebuilder.Services.AddCalendarAITools(tools => tools .AddCalendar(CalendarAICapabilities.Read) .AddCalendar(workCalendarId, CalendarAICapabilities.All) .AddCalendar(privateCalendarId, CalendarAICapabilities.None));
// 3. strict allow-list — no global grant, so ONLY these calendars exist to the agentbuilder.Services.AddCalendarAITools(tools => tools .AddCalendar(workCalendarId, CalendarAICapabilities.All) .AddCalendars([teamCalendarId, holidayCalendarId], CalendarAICapabilities.Read));The tools enforce this on every call — the model cannot talk its way around the filter:
list_calendarsreturns only calendars with at least one capability, each with anallowedOperationsarray (read/create/update/delete) so the model knows where it may write.search_eventssearches only readable calendars; passing acalendarIdoutside the filter is an error.get_event,update_event, anddelete_eventresolve the event’s calendar first and refuse when it isn’t allowed.create_eventrefuses a disallowedcalendarId. WhenCreateisn’t granted globally, omittingcalendarIdis an error too — the device default calendar can’t be vetted up front, so the model is told to pick a calendar fromlist_calendars.
A tool is generated when any calendar grants that capability, so AddCalendar(workId, All) on its
own still produces create_event / update_event / delete_event — scoped to that calendar.
Filters chosen at runtime
Section titled “Filters chosen at runtime”Calendar ids are platform-assigned, so the ids you want are usually picked by the user and persisted.
Use the service-provider overload — the callback runs when CalendarAITools is first resolved, so it
can read your own services:
builder.Services.AddCalendarAITools((sp, tools) =>{ var settings = sp.GetRequiredService<AppSettings>(); // the ids the user picked tools.AddCalendars(settings.AgentCalendarIds, CalendarAICapabilities.All);});Generated Tools
Section titled “Generated Tools”| Tool | Capability | Description |
|---|---|---|
list_calendars |
Read | Lists the allowed calendars (id, name, colour, read-only, allowedOperations). |
search_events |
Read | Events in a date window across the readable calendars, optional calendar id + free-text query. |
get_event |
Read | Full detail for one event (attendees, reminders, recurrence). |
create_event |
Create | Creates an event (title, start, end required; ISO-8601 dates). |
update_event |
Update | Updates supplied fields on an existing event. |
delete_event |
Delete | Deletes an event by id. Optional deleteSeries (default false) chooses one occurrence vs. this and all future ones. |
Key Types
Section titled “Key Types”AddCalendarAITools(Action<ICalendarAIToolBuilder>)— DI extension; throws if nothing is added.AddCalendarAITools(Action<IServiceProvider, ICalendarAIToolBuilder>)— same, but the callback runs on first resolve so the filter can come from your own services.ICalendarAIToolBuilder—AddCalendar(CalendarAICapabilities)(global),AddCalendar(string calendarId, CalendarAICapabilities),AddCalendars(IEnumerable<string> calendarIds, CalendarAICapabilities).CalendarAICapabilities[Flags]—None,Read,Create,Update,Delete,Write,All.CalendarAITools— resolve from DI;.ToolsisIReadOnlyList<AITool>.
Combine with your app’s own functions
Section titled “Combine with your app’s own functions”The functions your app exposes to Siri and Gemini can join these tools in the same chat. Shiny.AppFunctions.Extensions.AI turns them into AIFunctions with the same *AITools bundle shape, so you concatenate the .Tools lists into one ChatOptions.Tools.


