WP Manifestindependent plugin directory
manifest / ai / ai-provider-for-microsoft-copilot

AI Provider for Microsoft Copilot

Microsoft 365 Copilot provider for the WordPress AI Client.

by Alan Smodic · github.com/alansmodic/ai-provider-for-microsoft-copilot · website

0stars
0forks

Install

No release zip yet. The repository archive installs, but the folder name will carry the branch suffix and updates will not flow:

wp plugin install https://github.com/alansmodic/ai-provider-for-microsoft-copilot/archive/refs/heads/main.zip

Microsoft 365 Copilot provider for the WordPress PHP AI Client.

$result = AiClient::prompt('Summarise our Q3 planning notes.')
    ->usingProvider('microsoft-copilot')
    ->generateTextResult();

Requirements

WordPress 6.9 or later
PHP 7.4 or later, with the sodium extension
PHP AI Client Bundled with WordPress 7.0 and later; a separate plugin on 6.9
Microsoft An Entra ID app registration, and a Microsoft 365 Copilot add-on license per user

There is deliberately no Requires Plugins header. On WordPress 7.0 and later the AI Client is part of core rather than a plugin, so declaring the dependency that way would block activation on exactly the versions that satisfy it. The plugin checks for AiClient at runtime instead and shows an admin notice when it is missing.

How it differs from the other AI Client providers

Anthropic, OpenAI and Gemini providers take one site-wide API key and talk to a completions endpoint. The Microsoft 365 Copilot Chat API does neither, and this provider is shaped around those two facts.

Delegated authentication only. Microsoft's permissions reference lists Application and Delegated (personal Microsoft account) as "Not supported" for the Chat API. Only Delegated (work or school account) works. There is no key the site can hold on its own behalf, so this plugin ships an Entra ID OAuth flow and stores a refresh token per WordPress user. CopilotProviderAvailability::isConfigured() is therefore per-user: it is false for a visitor, false for an admin who has not signed in to Microsoft, and false inside cron.

The PHP AI Client models API key authentication only — RequestAuthenticationMethod has a single API_KEY case — so the provider overrides getRequestAuthentication() on both the model and the metadata directory and substitutes CopilotBearerRequestAuthentication. The declared authentication method in ProviderMetadata is nominal; no API key field is ever read.

A conversation API, not a completions API. Copilot keeps conversation state server side. Generating text means POST /copilot/conversations to open a conversation, then POST /copilot/conversations/{id}/chat to send one turn. CopilotTextGenerationModel opens a fresh conversation per call and replays any prior prompt messages, plus the system instruction, as additionalContext — the documented channel for extra grounding text. Copilot treats that history as background material rather than as turns of its own, which is the closest available mapping.

Timeouts are set here, not by the SDK. The AI Client resolves its HTTP client through Psr18ClientDiscovery and applies no timeout of its own, and Microsoft documents the Chat API as prone to gateway timeouts. Without a limit a slow turn occupies a PHP worker until the web server kills it, so the model sets its own: 60 seconds for a chat turn, 15 for opening a conversation, 10 to connect. A caller that supplies its own RequestOptions is left alone, including when it deliberately sets none.

One pseudo-model. Graph publishes no model catalogue for Copilot and callers cannot choose the underlying model, so CopilotModelMetadataDirectory returns a single fixed microsoft-365-copilot entry instead of issuing a list-models request.

Advertised options

Only options the endpoint honours are declared, so the AI Client routes prompts needing anything else to a different provider rather than sending a request that is silently ignored:

Option Status
systemInstruction Supported, delivered as grounding context
webSearch Supported. Web grounding is switched off unless a prompt asks for it, and the toggle is sent every turn because Microsoft treats it as single-turn
customOptions Supported. A contextualResources key is forwarded to Graph verbatim, which means it can also override the web grounding decision above — treat it as a privileged passthrough
temperature, topP, topK Not supported by the API
maxTokens, stopSequences Not supported by the API
functionDeclarations Not supported by the API
outputSchema, outputMimeType Not supported by the API

Token usage is reported as zero because the Chat API returns no token counts. Citations, when present, are passed through on the result's additional data as attributions.

Configuration

Settings live under Settings → Microsoft Copilot AI. On production sites, prefer defining the credentials as constants so the secret stays out of the database:

define('MICROSOFT_COPILOT_TENANT_ID', '...');
define('MICROSOFT_COPILOT_CLIENT_ID', '...');
define('MICROSOFT_COPILOT_CLIENT_SECRET', '...');

The Entra ID app registration needs the redirect URI shown on the settings screen and these seven delegated Microsoft Graph permissions, all of which Microsoft requires for a single Chat API call to succeed: Sites.Read.All, Mail.Read, People.Read.All, OnlineMeetingTranscript.Read.All, Chat.Read, ChannelMessage.Read.All, ExternalItem.Read.All — plus offline_access to receive a refresh token.

Development

composer install
composer qa          # php -l, PHPCS and PHPStan
composer phpcbf      # auto-fix what PHPCS can

PHPStan runs at level 8 with szepeviktor/phpstan-wordpress for the WordPress stubs. PHPCS enforces PSR-12 alongside the WordPress security and i18n rules — PSR-12 rather than the full WordPress standard because these provider packages are PSR-4 Composer libraries, matching how wordpress/ai-provider-for-anthropic is built. Two rules are excluded deliberately, each documented in phpcs.xml.dist: PSR-1's side-effects rule for plugin.php, which a plugin bootstrap cannot satisfy, and EscapeOutput.ExceptionNotEscaped, which misreads library exception messages as screen output.

CI runs all three on every push and pull request, with php -l additionally on PHP 7.4 so syntax newer than the declared floor cannot pass unnoticed.

Storage and privacy

Each connected user's tokens live in user meta, encrypted with sodium_crypto_secretbox keyed from wp_salt('auth'). If libsodium or the salt is unavailable the plugin refuses to store anything rather than falling back to plaintext — a refresh token here carries Mail.Read, Sites.Read.All and Chat.Read against the owner's account.

The client secret is stored with autoloading disabled, so it is not read into memory on every page load. Better still, define it as a constant and keep it out of the database entirely.

uninstall.php removes the settings and every user's tokens. It cannot revoke anything at Microsoft; consent is withdrawn at myaccount.microsoft.com.

Note for multisite: user meta is network-global while the app registration is a per-site option, so a user connected on one site of a network is connected on all of them, even where a different Entra ID application is configured. Treat the network as one trust boundary.

Caveats worth repeating

The Chat API is on Graph /beta, which Microsoft marks as unsupported for production and subject to change. Every user needs a Microsoft 365 Copilot add-on license. Background generation without a signed-in user is not possible.

This plugin has not been run against a live tenant. It passes php -l, PHPStan level 8 and PHPCS, and static analysis has already caught one bug that would have made every request fail, but no part of the OAuth flow, the token refresh or the Graph calls has been exercised for real. There is no test suite yet either. Treat the runtime behaviour as unverified until you have completed a connection yourself.

License

GPL-2.0-or-later.