Disclaimer: This bug report was created with AI assistance, but was verified manually.
- Extension version: 0.4.1
- TYPO3: 14.3
- symfony/ai-platform: 0.13.0
PR #25 (fixing #21) switched bridge discovery from the symfony/ai-*-platform name pattern to the symfony-ai-platform Composer package type, so that third-party bridges are found too. Discovery itself works. But the identifier and display name are still derived on the old naming assumption, so the very packages #25 set out to support end up with a broken identifier.
The derivation still strips a hardcoded symfony/ai- prefix
Classes/DependencyInjection/SymfonyAiCompilerPass.php:387 (deriveIdentifier()) and :404 (deriveName()) both start with:
|
$slug = preg_replace('/^symfony\/ai-/', '', $packageName); |
|
$slug = preg_replace('/-platform$/', '', $slug); |
For any vendor other than symfony/, that first pattern does not match, so the vendor prefix — including the slash — survives:
| package |
identifier |
display name |
symfony/ai-open-ai-platform |
openai |
Symfony AI: OpenAI |
symfony/ai-anthropic-platform |
anthropic |
Symfony AI: Anthropic |
mittwald/symfony-ai-platform |
mittwald/symfonyai |
Symfony AI: Mittwald/symfony Ai |
acme/ai-cool-platform |
acme/aicool |
Symfony AI: Acme/ai Cool |
Why the identifier matters beyond cosmetics
It is not only shown in the backend, it is used as a key in two places:
- DI service id —
'aim.symfony_ai.' . $bridge['identifier'] (SymfonyAiCompilerPass.php:107), giving aim.symfony_ai.mittwald/symfonyai.
- Persisted TCA value —
'value' => $identifier in Classes/Tca/ItemsProcFunc/AiProvidersItemsProcFunc.php::getAiProviders(), so the slash is written into tx_aim_provider rows and is what every later AiProviderRegistry::hasProvider() / getProvider() lookup keys on.
The display name is a smaller problem but visible to every editor: the provider select in the backend reads "Symfony AI: Mittwald/symfony Ai".
Reproduction
TYPO3 14.3 + b13/aim: ^0.4.1 + any bridge from a non-symfony/ vendor declaring "type": "symfony-ai-platform". Running AiM's own compiler pass against the container:
$c = new ContainerBuilder();
$c->setDefinition(AiProviderRegistry::class, new Definition(AiProviderRegistry::class));
(new SymfonyAiCompilerPass())->process($c);
$calls = $c->findDefinition(AiProviderRegistry::class)->getMethodCalls();
foreach ($calls as $call) {
echo ' - ' . $call[1][0]->getArgument(0) . ' (' . $call[1][0]->getArgument(1) . ")\n";
}
With mittwald/symfony-ai-platform installed:
- mittwald/symfonyai (Symfony AI: Mittwald/symfony Ai)
(That bridge additionally had to be fixed on our side first — it shipped the pre-0.8 PlatformFactory::create() rather than Factory::createProvider(), so it never reached this point: mittwald/symfony-ai-platform#18. The output above is from the fixed version.)
Suggested change
The bridge already states its canonical name, and the Symfony AI contract is the place it does so: ProviderInterface::getName(): non-empty-string. Symfony's own bridges declare it right in the factory signature — Factory::createProvider(..., string $name = 'openai') — and a bridge that hardcodes it does so in the new Provider(...) call. It is a routing key inside Symfony AI itself, not prose, and no bridge author has to know AiM exists to have set it.
It can be read at container-compile time, with no API key and no network:
$provider = $factoryClass::createProvider('');
$identifier = $provider->getName();
Against mittwald/symfony-ai-platform that returns 'mittwald' in ~18 ms; against symfony/ai-open-ai-platform it returns 'openai'. Instantiating at compile time inside a try/catch is already the pattern this pass uses for the model catalog a few lines down (SymfonyAiCompilerPass.php:231-241), so it is not a new kind of risk — and a factory that validates its API key eagerly just falls into the catch, where today's derived value can stay as a last-resort fallback for the service id.
That also disposes of the display name. getName() gives openai / anthropic / mittwald — the same names these providers carry in the Symfony AI docs. The existing $nameMap in deriveName() can stay as pure prettification, keyed on that stable slug instead of on the package name, so an unmapped bridge degrades to mittwald rather than to Mittwald/symfony Ai.
Disclaimer: This bug report was created with AI assistance, but was verified manually.
PR #25 (fixing #21) switched bridge discovery from the
symfony/ai-*-platformname pattern to thesymfony-ai-platformComposer package type, so that third-party bridges are found too. Discovery itself works. But the identifier and display name are still derived on the old naming assumption, so the very packages #25 set out to support end up with a broken identifier.The derivation still strips a hardcoded
symfony/ai-prefixClasses/DependencyInjection/SymfonyAiCompilerPass.php:387(deriveIdentifier()) and:404(deriveName()) both start with:aim/Classes/DependencyInjection/SymfonyAiCompilerPass.php
Lines 390 to 391 in c7fc4be
For any vendor other than
symfony/, that first pattern does not match, so the vendor prefix — including the slash — survives:symfony/ai-open-ai-platformopenaisymfony/ai-anthropic-platformanthropicmittwald/symfony-ai-platformmittwald/symfonyaiacme/ai-cool-platformacme/aicoolWhy the identifier matters beyond cosmetics
It is not only shown in the backend, it is used as a key in two places:
'aim.symfony_ai.' . $bridge['identifier'](SymfonyAiCompilerPass.php:107), givingaim.symfony_ai.mittwald/symfonyai.'value' => $identifierinClasses/Tca/ItemsProcFunc/AiProvidersItemsProcFunc.php::getAiProviders(), so the slash is written intotx_aim_providerrows and is what every laterAiProviderRegistry::hasProvider()/getProvider()lookup keys on.The display name is a smaller problem but visible to every editor: the provider select in the backend reads "Symfony AI: Mittwald/symfony Ai".
Reproduction
TYPO3 14.3 +
b13/aim: ^0.4.1+ any bridge from a non-symfony/vendor declaring"type": "symfony-ai-platform". Running AiM's own compiler pass against the container:With
mittwald/symfony-ai-platforminstalled:(That bridge additionally had to be fixed on our side first — it shipped the pre-0.8
PlatformFactory::create()rather thanFactory::createProvider(), so it never reached this point: mittwald/symfony-ai-platform#18. The output above is from the fixed version.)Suggested change
The bridge already states its canonical name, and the Symfony AI contract is the place it does so:
ProviderInterface::getName(): non-empty-string. Symfony's own bridges declare it right in the factory signature —Factory::createProvider(..., string $name = 'openai')— and a bridge that hardcodes it does so in thenew Provider(...)call. It is a routing key inside Symfony AI itself, not prose, and no bridge author has to know AiM exists to have set it.It can be read at container-compile time, with no API key and no network:
Against
mittwald/symfony-ai-platformthat returns'mittwald'in ~18 ms; againstsymfony/ai-open-ai-platformit returns'openai'. Instantiating at compile time inside atry/catchis already the pattern this pass uses for the model catalog a few lines down (SymfonyAiCompilerPass.php:231-241), so it is not a new kind of risk — and a factory that validates its API key eagerly just falls into thecatch, where today's derived value can stay as a last-resort fallback for the service id.That also disposes of the display name.
getName()givesopenai/anthropic/mittwald— the same names these providers carry in the Symfony AI docs. The existing$nameMapinderiveName()can stay as pure prettification, keyed on that stable slug instead of on the package name, so an unmapped bridge degrades tomittwaldrather than toMittwald/symfony Ai.