Skip to content

Make LLM providers pluggable via auto-configuration - #69

Merged
arefbehboudi merged 3 commits into
ClawRunr:mainfrom
MarcosLM11:feat/pluggable-providers
Jul 28, 2026
Merged

Make LLM providers pluggable via auto-configuration#69
arefbehboudi merged 3 commits into
ClawRunr:mainfrom
MarcosLM11:feat/pluggable-providers

Conversation

@MarcosLM11

Copy link
Copy Markdown
Contributor

Closes #17

What

Make the providers/* modules self-register from the classpath the same way
the plugins/* modules do, instead of depending on the app's component scan.

Each provider now contributes its AgentOnboardingProvider through a Spring
Boot @AutoConfiguration class listed in the module's
META-INF/spring/…AutoConfiguration.imports. The ChatModel side was already
auto-configured by the Spring AI starters, so no change was needed there.

Changes

  • anthropic: AnthropticClaudeCodeConfiguration @Configuration
    @AutoConfiguration; onboarding provider moved to a @Bean in a new
    AnthropicAgentAutoConfiguration (keeps the Claude Code token condition).
  • openai / ollama / google: drop @Component, add a small per-module
    @AutoConfiguration + .imports.
  • Remove dead AgentProvider: it was never injected or called; its
    isConfigured stub returned false and getDefaultChatModel() would have
    thrown on an empty list. Active-model selection is handled centrally by
    spring.ai.model.chat.
  • Tests: one test per provider asserting the bean registers via the
    auto-config and that the .imports file names it — currently the only guard
    for provider discovery, since OnboardingControllerTest mocks
    AgentOnboardingProviders.
  • Docs: update AGENTS.md wording.

Notes

  • No behavior change: providers are discovered exactly as before, just wired via
    auto-configuration. spring.ai.model.chat still selects the active model.
  • Ollama needs no api-key gating — like the others, its ChatModel only loads
    when spring.ai.model.chat=ollama.
  • Scope is classpath pluggability (parity with plugins). Runtime provider jars
    are intentionally left out.

Testing

./gradlew compileJava test — all modules green.

Register each provider's AgentOnboardingProvider through a Spring Boot
@autoConfiguration class listed in the module's AutoConfiguration.imports,
mirroring the plugin modules. Providers now self-register from the classpath
independently of the app's component scan, instead of relying on @component
being reachable under the shared ai.javaclaw package.

- anthropic: AnthropticClaudeCodeConfiguration @configuration -> @autoConfiguration;
  onboarding provider moved to a @bean in AnthropicAgentAutoConfiguration
- openai/ollama/google: drop @component, add per-module @autoConfiguration + imports
- Remove dead AgentProvider (unused; isConfigured stub returned false and
  getDefaultChatModel would throw). Model selection is handled centrally by
  spring.ai.model.chat.
- Add per-provider tests asserting the bean registers and the imports file
  names the auto-config (the only guard for provider discovery, since
  OnboardingControllerTest mocks AgentOnboardingProviders)
- Update AGENTS.md wording
@arefbehboudi

arefbehboudi commented Jul 11, 2026

Copy link
Copy Markdown
Collaborator

Great work.

I checked out the branch and verified: full build + all tests green, removing a provider from app/build.gradle still compiles and passes tests (true drop-in pluggability), all four providers register at runtime, and the Claude Code path still produces exactly one AnthropicChatModel.

One change requested before merge (auto-config ordering, see inline), the rest are minor.

Follow-up idea: switch the provider deps in app/build.gradle from implementation to runtimeOnly, so the compiler enforces that app never references provider classes directly.

- Make Anthropic's ChatModel auto-config ordering explicit against
  AnthropicChatAutoConfiguration instead of relying on alphabetical
  package sort.
- Add @ConditionalOnMissingBean, typed to the concrete provider class,
  to each provider's onboarding auto-config so user overrides win.
- Fix the imports-file test helper to read each module's own
  .imports resource instead of scanning the whole classpath, so it
  can't pass on another module's entry.
@MarcosLM11
MarcosLM11 requested a review from arefbehboudi July 13, 2026 10:07
@rdehuyss

Copy link
Copy Markdown
Contributor

Really nice work!

Sorry for the delay, I was OOO. One last change and goot to merge!

Thanks for your contribution!

Each provider test class duplicated the same block for reading its
module's AutoConfiguration.imports file. Extract it into a shared
AutoConfigurationImportsTestSupport test fixture in base (using
Files.readAllLines instead of manual stream reading) and have all
four provider tests use it.
@MarcosLM11

Copy link
Copy Markdown
Contributor Author

@arefbehboudi @rdehuyss All requested changes are addressed and pushed (commit a281139):

  • Explicit auto-config ordering (@AutoConfiguration(before = AnthropicChatAutoConfiguration.class))
  • @ConditionalOnMissingBean typed to the concrete provider class in all four modules
  • Fixed the getResources()getResource() test bug so each test only reads its own module's .imports file
  • Deduplicated the imports-reading helper into a shared AutoConfigurationImportsTestSupport test fixture in base, used by all four provider tests

All review threads are resolved and ./gradlew compileJava test is green. Ready for another pass whenever you have a moment 🙏

@arefbehboudi arefbehboudi left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you @MarcosLM11!

@arefbehboudi
arefbehboudi merged commit 4761d7a into ClawRunr:main Jul 28, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Feature - refactor so providers are pluggable

3 participants