Skip to content

feat: add per-model extraBody support#149

Merged
mcowger merged 3 commits intomcowger:mainfrom
zicochaos:feat/per-model-extrabody
Apr 5, 2026
Merged

feat: add per-model extraBody support#149
mcowger merged 3 commits intomcowger:mainfrom
zicochaos:feat/per-model-extrabody

Conversation

@zicochaos
Copy link
Copy Markdown
Contributor

Summary

Adds the ability to configure extraBody parameters at the individual model level within a provider, not just at the provider level. When both provider-level and model-level extraBody are set, model-level overrides provider-level.

Motivation

Currently, extraBody is only configurable at the provider level. This means all models under a provider share the same extra parameters. In practice, different models on the same provider often need different configurations.

Example use case: On OpenRouter, you may want to disable reasoning/thinking for Qwen models ("reasoning": {"effort": "none"}) while keeping it enabled for other models on the same provider. Without per-model extraBody, this requires creating separate providers pointing to the same endpoint — a workaround that clutters the configuration.

Changes

Backend:

  • Schema (drizzle/schema/sqlite/provider-models.ts, drizzle/schema/postgres/provider-models.ts): Added extra_body column to provider_models table
  • Config (config.ts): Added extraBody: z.record(z.any()).optional() to ModelProviderConfigSchema
  • Repository (config-repository.ts): Save/load extraBody when persisting/reading model configs from DB
  • Dispatcher (dispatcher.ts): Merge model-level extraBody after provider-level extraBody (model overrides provider)

Frontend:

  • UI (Providers.tsx): Per-model "Extra Body Fields" collapsible section with add/edit/remove key-value pairs, matching the existing provider-level Extra Body Fields UI pattern

Migration

  • SQLite: ALTER TABLE provider_models ADD COLUMN extra_body TEXT;
  • PostgreSQL: ALTER TABLE provider_models ADD COLUMN extra_body JSONB;

The existing drizzle migration system should handle this automatically on startup.

Testing

Tested with:

  • OpenRouter + Qwen model: {"reasoning": {"effort": "none"}} → 0 reasoning tokens (vs 1400+ without)
  • Local vLLM + Qwen model: {"chat_template_kwargs": {"enable_thinking": false}} → thinking correctly overridden

7 files changed, 137 insertions(+), 8 deletions(-)

Adds the ability to configure extraBody parameters at the model level
within a provider, not just at the provider level. Model-level extraBody
overrides provider-level extraBody when both are set.

Changes:
- Schema: added extra_body column to provider_models table (SQLite + Postgres)
- Config: added extraBody to ModelProviderConfigSchema (z.record)
- Repository: save/load extraBody when persisting/reading model configs
- Dispatcher: merge model-level extraBody after provider-level extraBody
- Frontend: per-model Extra Body Fields UI with add/edit/remove key-value pairs

Use case: configure different parameters per model within the same provider.
For example, disable thinking for Qwen models on OpenRouter with
{"reasoning": {"effort": "none"}} without affecting other models on
the same provider.
@mcowger
Copy link
Copy Markdown
Owner

mcowger commented Apr 3, 2026

Very nice, and I just needed this myself the other day!

I'm on vacation fo another day, but will try and review tomorrow.

@mcowger
Copy link
Copy Markdown
Owner

mcowger commented Apr 3, 2026

Thanks so much for this — it's a genuinely useful addition and the implementation is clean and well thought out. The merge precedence logic in the dispatcher is exactly right, and the motivation (per-model reasoning control on OpenRouter) is a use case I've bumped into myself.

One small request before merge: in config-repository.ts, the new extraBody field uses JSON.stringify(cfg.extraBody) directly when building the insert row, while all the other JSON fields in that same block (e.g. accessVia, pricingConfig) go through the project's toJson() helper. Could you swap that to toJson(cfg.extraBody) (with the same null guard pattern as the others) to keep things consistent? Just want to make sure any special handling in toJson — null safety, error catching, etc. — applies uniformly.

Everything else looks great. Happy to merge once that's tidied up!

Swap JSON.stringify(cfg.extraBody) to toJson(cfg.extraBody) in both
insert paths for consistency with other JSON fields (accessVia,
pricingConfig, etc.)

Per review feedback from @mcowger on PR mcowger#149.
@zicochaos
Copy link
Copy Markdown
Contributor Author

Thanks for the review! Good catch — swapped both occurrences of JSON.stringify(cfg.extraBody) to toJson(cfg.extraBody) (lines 217 and 276 in config-repository.ts). Should be consistent with the other JSON fields now.

@mcowger mcowger merged commit dbb7fe9 into mcowger:main Apr 5, 2026
github-actions bot pushed a commit that referenced this pull request Apr 6, 2026
* feat: add per-model extraBody support

Adds the ability to configure extraBody parameters at the model level
within a provider, not just at the provider level. Model-level extraBody
overrides provider-level extraBody when both are set.

Changes:
- Schema: added extra_body column to provider_models table (SQLite + Postgres)
- Config: added extraBody to ModelProviderConfigSchema (z.record)
- Repository: save/load extraBody when persisting/reading model configs
- Dispatcher: merge model-level extraBody after provider-level extraBody
- Frontend: per-model Extra Body Fields UI with add/edit/remove key-value pairs

Use case: configure different parameters per model within the same provider.
For example, disable thinking for Qwen models on OpenRouter with
{"reasoning": {"effort": "none"}} without affecting other models on
the same provider.

* fix: use toJson() helper for extraBody serialization

Swap JSON.stringify(cfg.extraBody) to toJson(cfg.extraBody) in both
insert paths for consistency with other JSON fields (accessVia,
pricingConfig, etc.)

Per review feedback from @mcowger on PR #149.

* fix: remove unrelated .gitignore change and fix indentation nits

---------

Co-authored-by: sbochna <sbochna@openclaw-mac.local>
Co-authored-by: Matt Cowger <matt@cowger.us>
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.

2 participants