Initial commit, add base structure, vue frontend#1
Conversation
Add forced-provider support for cloud environment
Support JSON completion requests across providers, expose decoded JSON responses, prevent duplicate provider registrations, and hide AI provider settings in managed environments.
Add AIProvider exception hierarchy, properly implemented test connection function, fixed custom provider model implementation, dropped rawReponse key from the AIProviderResponse
Add converse() methods for plugins that need conversations with tool calling. Added canonical message shapes
| "DefaultProviderHelp": "Connect providers and pick a default for Matomo to use. Individual features may override this and choose their preferred connected provider.", | ||
| "DefaultsTitle": "Change provider settings and default values", | ||
| "Disconnect": "Disconnect", | ||
| "Disconnecting": "Disconnecting...", |
There was a problem hiding this comment.
| "Disconnecting": "Disconnecting...", | |
| "Disconnecting": "Disconnecting…", |
Same for TestingConnection, having a single character ellipsis usually looks nicer that separate dots.
| tests/System/processed/*xml | ||
| /vue/dist/demo.html | ||
| /vue/dist/*.common.js | ||
| /vue/dist/*.map |
There was a problem hiding this comment.
Some of the ignored files have already been committed and should be removed to prevent them from not being updated.
| { | ||
| "name": "AIProviders", | ||
| "description": "Configure AI provider connections and default model settings used by Matomo AI features.", | ||
| "version": "0.1.0", |
There was a problem hiding this comment.
| "version": "0.1.0", | |
| "version": "5.0.0", |
As this is intended to be integrated into Core anyway this is more nit-picky than relevant. Plugins usually share the major version with the support core major, but after a full integration it will bump itself to the core version anyway.
|
|
||
| private function getConfiguration(): Configuration | ||
| { | ||
| return StaticContainer::get(Configuration::class); |
There was a problem hiding this comment.
This could be done using DI in the constructor. Same for the Controller and Menu.
I would only keep the one in the AIProvidersList unless that class is also going to be handled with a container lookup.
| * | ||
| * Provider IDs are unique and cannot be overwritten: the first registration | ||
| * for an ID wins (see {@link AIProvidersList::addProvider()}). This protects | ||
| * the built-in providers, and a provider that is centrally forced in a |
There was a problem hiding this comment.
Yes-ish. Event registration supports before and after for grouping, but otherwise should be treated as random. The actual order is generated by the plugin loading (PluginList#107) but I would not rely on that.
So the strong guarantee is that a duplicated registration will raise a warning. Everthing else depends on plugin sorting and how the individual events are tagged.
| {{ translate('General_Cancel') }} | ||
| </button> | ||
| <SaveButton | ||
| :disabled="!hasUnsavedChanges" |
There was a problem hiding this comment.
This is just me doing intentionally weird things, but if you have a fully unconfigured plugin, and then only switch the capability level, you can save.
But the then still empty defaultProviderId only generates an error because Matomo does not like the combination of a required parameter and an empty string:
Please specify a value for 'defaultProviderId'.
#0 /core/API/Proxy.php(217): Piwik\\API\\Proxy->getSanitizedRequestParametersArray(Array, Array)
#1 /core/Context.php(29): Piwik\\API\\Proxy->Piwik\\API\\{closure}()
#2 /core/API/Proxy.php(386): Piwik\\Context::executeWithQueryParameters(Array, Object(Closure))
| method: 'AIProviders.saveSettings', | ||
| }, | ||
| { | ||
| defaultProviderId: defaultProviderId.value, |
There was a problem hiding this comment.
Weird/interesting edge case:
If I have no model configured, the missing defaultProviderId exception prevents me from saving invalid API keys. After connecting at least one provider I can then save any invalid API key I like.
Saving invalid API keys is imho not a bad thing, because it might still let you save the the settings of one provider if any other provider's key has been revoked. But maybe the current behaviour is also intentional, configuring the first provider without a valid key does not feel like a regular use case.
| openaiEndpointUrl = "..." ; or env MATOMO_AIPROVIDERS_OPENAI_ENDPOINT_URL | ||
| ``` | ||
|
|
||
| The config key is the provider ID verbatim plus the field suffix; the environment variable upper-cases the ID and replaces `-` with `_`. Credentials supplied this way never appear in the UI as secret values and cannot be edited or removed there. |
There was a problem hiding this comment.
Don't know how known that structure is, but the config file can change per tenant in a multi-tenant setup. Doing that with environment variables would be possible but tricky.
Not worth to be pointed out in the README until someone complains, just wanted to mention the multi-tenant config file :)
| $responseBody, | ||
| &$capturedUrl, | ||
| &$capturedBody | ||
| ): void { |
There was a problem hiding this comment.
| ): void { | |
| ): void { |
Just in case you want to set up a CI configuration that would catch this.
| $payload = [ | ||
| 'model' => $model, | ||
| 'messages' => $messages, | ||
| 'max_tokens' => $request->getMaxTokens(), |
There was a problem hiding this comment.
There is a commit in the history that changed this to max_completion_tokens. A the following commit has changed that value right back to max_tokens.
The max_completion_tokens change would have nicely broken a lot of tests. But there is nothing that would run those tests.
There will be automated testing, right? Including a happy-path UI test to ensure it works?
…t, add fail reason handling
…e DI instead of StaticContainer in some places, fix ellipsis
…bump plugin version, update README.md, rebuild files
Description:
Adds the AIProviders plugin, which gives Matomo a shared server-side integration layer for AI features. It supports
Claude,OpenAI,Gemini, and OpenAI-compatiblecustom endpoints, with an admin UI for self-managed instances to configure credentials, default provider, model settings, and connection tests.Other plugins should not call completion endpoints through
API.php; those methods are only for the administration UI. Runtime use goes throughAIProviderService:complete(new AIRequest($prompt, $callerPlugin))for prompt-in/text-out or JSON responses.converse(new AIConversationRequest($messages, $callerPlugin))for multi-turn conversations and tool-calling flows.Requests use immutable request objects for:
Conversation messages use a provider-agnostic canonical shape, and each provider translates that shape to its own wire format. Responses expose text/content, provider/model metadata, token usage where available, stop reason, and timing.
Self-managed instances can configure providers in the UI or via server-side configuration. In a managed environment, provider configuration is locked server-side, the admin screen is hidden, and normal plugin requests are routed through the managed default provider. Provider/model selection is only honored for selected trusted server-side callers.
A custom provider can either be configured through the built-in Custom Provider option in the UI, using an OpenAI-compatible base URL plus an optional API key, or registered by another plugin via the
AIProviders.addAIProvidersevent with a customAIProviderclass.Includes unit and integration coverage for configuration resolution, managed-environment behavior, provider registration, completion requests, JSON responses, conversation support, and provider-specific request/response translation.
Review