Fix AI model registry staleness on self-hosted instances (#19427)
## Summary Fixes #19422. Self-hosted users hitting "No AI models are available" after configuring API keys via the admin panel were victims of a stale `AiModelRegistryService` cache. Only the four `addAiProvider` / `removeAiProvider` / `addModelToProvider` / `removeModelFromProvider` mutations called `refreshRegistry()` — setting an API key through `set/update/deleteDatabaseConfigVariable` left the registry pointing at the pre-mutation provider state. Rather than patch each mutation site (and re-introduce the same class of bug on the next one), the registry now invalidates lazily based on the LLM config-group hash, mirroring the pattern `WebSearchDriverFactory` already uses via `DriverFactoryBase`. Any mutation to an LLM-tagged config variable is picked up automatically on the next read — callers never have to remember to refresh. - Extracted `getConfigGroupHash` into a shared util reused by both `DriverFactoryBase` and `AiModelRegistryService` (and switched the hash to `JSON.stringify` so object-typed config vars like `AI_PROVIDERS` actually contribute meaningfully). - `AiModelRegistryService` gates all internal `Map` access behind private getters that call `ensureFresh()`, so future read paths can't accidentally observe stale state. Build path uses underscored backing fields directly to avoid recursing. - Dropped the now-redundant `refreshRegistry()` public method and its four call sites in `admin-panel.resolver.ts`. ## Test plan - [x] `nx typecheck twenty-server` passes - [x] Existing admin-panel + ai-models specs pass (79 tests) - [ ] Manual: on a self-hosted instance, set `OPENAI_API_KEY` (or another provider key) via the admin panel `setDatabaseConfigVariable` mutation and confirm AI features work without a server restart - [ ] Manual: existing flows (`addAiProvider`, `addModelToProvider`, etc.) still pick up new providers on the next read 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -8,15 +8,17 @@ import { CaptchaDriverType } from 'src/engine/core-modules/captcha/interfaces';
|
||||
import { SecureHttpClientService } from 'src/engine/core-modules/secure-http-client/secure-http-client.service';
|
||||
import { DriverFactoryBase } from 'src/engine/core-modules/twenty-config/dynamic-factory.base';
|
||||
import { ConfigVariablesGroup } from 'src/engine/core-modules/twenty-config/enums/config-variables-group.enum';
|
||||
import { ConfigGroupHashService } from 'src/engine/core-modules/twenty-config/services/config-group-hash.service';
|
||||
import { TwentyConfigService } from 'src/engine/core-modules/twenty-config/twenty-config.service';
|
||||
|
||||
@Injectable()
|
||||
export class CaptchaDriverFactory extends DriverFactoryBase<CaptchaDriver | null> {
|
||||
constructor(
|
||||
twentyConfigService: TwentyConfigService,
|
||||
configGroupHashService: ConfigGroupHashService,
|
||||
private readonly secureHttpClientService: SecureHttpClientService,
|
||||
) {
|
||||
super(twentyConfigService);
|
||||
super(twentyConfigService, configGroupHashService);
|
||||
}
|
||||
|
||||
protected buildConfigKey(): string {
|
||||
@@ -26,7 +28,7 @@ export class CaptchaDriverFactory extends DriverFactoryBase<CaptchaDriver | null
|
||||
return 'disabled';
|
||||
}
|
||||
|
||||
return `${driver}|${this.getConfigGroupHash(ConfigVariablesGroup.CAPTCHA_CONFIG)}`;
|
||||
return `${driver}|${this.configGroupHashService.computeHash(ConfigVariablesGroup.CAPTCHA_CONFIG)}`;
|
||||
}
|
||||
|
||||
protected createDriver(): CaptchaDriver | null {
|
||||
|
||||
Reference in New Issue
Block a user