Skip to content

feat: add mode-specific provider and model configuration#640

Open
akramcodez wants to merge 3 commits into
Nano-Collective:mainfrom
akramcodez:feature/mode-specific-provider-config
Open

feat: add mode-specific provider and model configuration#640
akramcodez wants to merge 3 commits into
Nano-Collective:mainfrom
akramcodez:feature/mode-specific-provider-config

Conversation

@akramcodez

@akramcodez akramcodez commented Jul 7, 2026

Copy link
Copy Markdown
Collaborator

Closes #277

Description

This PR adds support for mode-specific provider and model configuration via a new modeProviders configuration in agents.config.json.

Previously, Nanocoder used a single global provider/model across all development modes (normal, plan, yolo, auto-accept, headless). With this change, users can configure different providers and models for individual modes, allowing workflows such as using a powerful cloud model for planning while using a fast local model for implementation.

Highlights

  • Introduces a new modeProviders configuration schema.
  • Validates configured providers and models during configuration loading.
  • Automatically switches to the configured provider/model when the development mode changes.
  • Restores the user's default provider/model when leaving a mode without a dedicated configuration.
  • Reuses the existing model-switch workflow, preserving current provider-switch behavior while keeping the client factory independent of development modes.
  • Gracefully falls back to the default configuration when invalid mode-specific entries are encountered.

Type of Change

  • Bug fix
  • New feature
  • Breaking change
  • Documentation update

Testing

Automated Tests

  • New features include passing tests in .spec.ts/tsx files
  • All existing tests pass (pnpm test:all completes successfully)
  • Tests cover both success and error scenarios

Manual Testing

  • Tested with Ollama
  • Tested with OpenRouter
  • Tested with OpenAI-compatible API
  • Tested MCP integration (if applicable)

Checklist

  • Code follows project style guidelines
  • Self-review completed
  • Documentation updated (if needed)
  • No breaking changes (or clearly documented)
  • Appropriate logging added using structured logging (see CONTRIBUTING.md#logging)

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Adds mode-specific provider/model selection via a new modeProviders config, allowing Nanocoder to automatically switch providers/models when entering specific development modes while preserving the user’s default provider/model when leaving those modes.

Changes:

  • Introduces modeProviders config typing and integrates it into AppConfig.
  • Loads and validates modeProviders entries during config load (provider/model existence).
  • Switches provider/model on mode toggle and at startup, while avoiding preference writes for programmatic switches; adds/updates tests around this behavior.

Reviewed changes

Copilot reviewed 7 out of 7 changed files in this pull request and generated 6 comments.

Show a summary per file
File Description
source/types/config.ts Adds ModeProviderConfig and modeProviders to the app configuration type.
source/config/index.ts Implements modeProviders config loading/validation and includes it in loadAppConfig().
source/hooks/useAppInitialization.tsx Applies mode-specific provider/model selection during initialization and supports programmatic switching.
source/hooks/useModeHandlers.tsx Adds a programmatic flag to suppress preference updates and success messages during automatic switches.
source/hooks/useAppHandlers.tsx Switches provider/model when toggling modes and restores defaults when no mode-specific config exists.
source/hooks/useAppHandlers.spec.tsx Updates handler surface/tests to account for mode toggle behavior and model switching.
source/config/index.spec.ts Adds coverage for modeProviders config parsing/validation scenarios.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread source/hooks/useAppInitialization.tsx
Comment thread source/config/index.ts
Comment thread source/config/index.ts
Comment thread source/config/index.spec.ts
Comment thread source/config/index.spec.ts
Comment thread source/hooks/useAppHandlers.spec.tsx Outdated
@will-lamerton

Copy link
Copy Markdown
Member

Hey @akramcodez - Thanks for this- the config layer is the strong part. loadModeProvidersConfig is nicely defensive: it lowercases the provider match, treats an empty models: [] as "any model allowed", logs a specific logError per invalid mode, and returns undefined when nothing valid remains. The isProgrammatic threading through useAppInitialization and useModeHandlers to suppress the "Model changed to: X" toast and avoid polluting preferences.lastProvider/lastModel is clean and well-commented, and backward compatibility genuinely holds since every consumer uses optional chaining. Requesting changes on a few blockers before it lands.

Blockers

  • Wizard AC is unmet. Issue [Feature] Enable mode-specific model and provider configuration for optimized performance #277 AC If you press ESC whilst waiting for the model to answer, it quits the CLI. #4 ("Setup wizard guides users through mode-specific configuration") isn't addressed - the diff touches no wizard files. Note the issue points at source/wizard/config-wizard.tsx, which doesn't exist; the real dir is source/wizards/ (base-config-wizard.tsx, provider-wizard.tsx, mcp-wizard.tsx), so any wizard step would go there. Either implement it or, with maintainer agreement, explicitly narrow AC If you press ESC whilst waiting for the model to answer, it quits the CLI. #4 to a follow-up and open a tracking issue linked from the PR.

  • Shift+Tab fires an async handleModelSelect on every toggle. In useAppHandlers.tsx, the void (async () => {...})() runs unconditionally, including for modes with no modeConfig. In that fallback the resolved target is the user's current default, so useModeHandlers.handleModelSelect hits the same-provider/same-model branch and calls exitMode() before returning true - so every toggle now also triggers that state side effect, not just a wasted dispatch. currentProvider and currentModel are already available as props (declared at useAppHandlers.tsx:53,55 and used throughout the file), so guard the call: skip handleModelSelect when the resolved target equals the current selection. That fixes both the wasted dispatch and the spurious exitMode().

  • Spec reformatting will fail CI, so this is a blocker, not just churn. source/hooks/useAppHandlers.spec.tsx reformats {foo} to { foo } across the whole file, but biome.json sets bracketSpacing: false, so project style is {foo}. That reformatting violates Biome and will fail pnpm run test:format, which breaks AC Feature Request: Add /export command to export chat history to markdown #6. Please revert the formatting-only lines so the file matches project style (and the feature diff stays reviewable).

  • The new test doesn't exercise the modeProviders path, and is environment-dependent. handleToggleDevelopmentMode calls handleModelSelect on mode switch uses developmentMode: 'normal' with no modeProviders, so it takes the fallback branch, not the modeConfig branch it's meant to cover. Worse: the fallback only calls handleModelSelect when targetModel resolves truthy, and getAppConfig()/loadPreferences() aren't mocked here, so whether the assertion even holds depends on the machine's real preferences. Please inject an AppConfig with modeProviders['auto-accept'] = { provider, model } and assert the call args, or split into two tests (one for the modeConfig branch, one for fallback).

Smaller items

  • The detached void (async () => {...})() has no .catch. handleModelSelect can reject if the new provider fails to instantiate. Add a .catch that logs so a rejection doesn't surface as an unhandled promise. Its own error handling in useModeHandlers covers most cases, but the detached IIFE is a lint smell.

  • ModeProviderConfig omits temperature, which appears in the issue's example config. If deferring is intentional, a one-line note in the type or PR description avoids confusion.

  • With isProgrammatic=true the model-change toast is suppressed, so a user toggling into a mode with an override gets no feedback that the model changed. A small [plan mode -> glm-4.7] status line would help. Not blocking.

Once the wizard question is settled, the perf guard is in, the spec formatting is reverted, and the test hits the real branch, this should be good to go.

Also, are you able to address the co-pilot review as well? :)

Thanks!

@will-lamerton

Copy link
Copy Markdown
Member

Hey @akramcodez!

Thanks for the thorough turnaround. I re-ran the checks locally, and most of the blockers are resolved. One thing remain - the temperature field the amends added, which dead-ends.

Still a blocker: per-mode temperature is collected but never applied

  • The wizard collects temperature, the type carries temperature?: number, and the loader persists it, but nothing reads it at runtime. The mode switch calls handleModelSelect(provider, model, true), which carries only provider and model.
  • Temperature does have a working channel in this codebase, and it is a different one. It lives in ModelParameters and flows through tune: source/config/tune.ts layers appConfig.tune / providerConfig.tune / preferences.tune, then conversation-loop.tsx:251 reads tune?.enabled ? tune.modelParameters : undefined and chat-handler.ts:216 applies it to the actual call. The new modeProviders[mode].temperature never joins that pipeline.
  • So this is worse than a no-op field: a user who sets temperature in the mode wizard and also has a /tune temperature will get two values that silently disagree, and only the tune one takes effect.
  • Recommendation for this PR: drop temperature from the wizard step, the ModeProviderConfig type, and the loader, and track "mode-aware temperature" as a follow-up. Wiring it correctly means feeding the mode's temperature into the tune/modelParameters path on switch and deciding precedence against an active /tune setting, which is more than this PR should take on. Better to not expose the input than to expose one that does nothing.

Once the spec formatting is reverted and temperature is either removed or wired through tune, I'd be happy to approve. The core feature (mode-specific provider/model switching, validation, fallback, wizard) is in good shape.

@akramcodez akramcodez force-pushed the feature/mode-specific-provider-config branch from 6dd9d3a to 59f02b4 Compare July 10, 2026 09:20
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] Enable mode-specific model and provider configuration for optimized performance

3 participants