feat: unify provider discovery, model metadata, routing, and agent controls - #6302
Open
wangfei010313 wants to merge 42 commits into
Open
feat: unify provider discovery, model metadata, routing, and agent controls#6302wangfei010313 wants to merge 42 commits into
wangfei010313 wants to merge 42 commits into
Conversation
wangfei010313
requested a deployment
to
ai-review-approved
July 21, 2026 08:10 — with
GitHub Actions
Waiting
|
Hi @wangfei010313, this is your 29th Pull Request. 🙌 Join Developer CommunityThanks so much for your contribution! We'd love to invite you to join the official QwenPaw developer group! You can find the Discord and DingTalk group links under the "Developer Community" section on our docs page: We truly appreciate your enthusiasm—and look forward to your future contributions! 😊 We'll review your PR soon. |
wangfei010313
requested a deployment
to
maintainer-approved
July 21, 2026 08:10 — with
GitHub Actions
Waiting
wangfei010313
requested a deployment
to
ai-review-approved
July 22, 2026 06:22 — with
GitHub Actions
Waiting
wangfei010313
requested a deployment
to
maintainer-approved
July 22, 2026 06:22 — with
GitHub Actions
Waiting
wangfei010313
requested a deployment
to
ai-review-approved
July 22, 2026 07:30 — with
GitHub Actions
Waiting
wangfei010313
requested a deployment
to
maintainer-approved
July 22, 2026 07:30 — with
GitHub Actions
Waiting
wangfei010313
requested a deployment
to
ai-review-approved
July 22, 2026 08:13 — with
GitHub Actions
Waiting
wangfei010313
requested a deployment
to
maintainer-approved
July 22, 2026 08:13 — with
GitHub Actions
Waiting
wangfei010313
requested a deployment
to
ai-review-approved
July 22, 2026 12:05 — with
GitHub Actions
Waiting
wangfei010313
force-pushed
the
fix/provider-model-discovery
branch
from
July 22, 2026 12:29
29b31f7 to
acf030d
Compare
wangfei010313
requested a deployment
to
ai-review-approved
July 22, 2026 12:29 — with
GitHub Actions
Waiting
wangfei010313
requested a deployment
to
maintainer-approved
July 22, 2026 12:29 — with
GitHub Actions
Waiting
wangfei010313
requested a deployment
to
ai-review-approved
July 23, 2026 02:26 — with
GitHub Actions
Waiting
wangfei010313
requested a deployment
to
maintainer-approved
July 23, 2026 02:27 — with
GitHub Actions
Waiting
wangfei010313
requested a deployment
to
ai-review-approved
July 23, 2026 02:47 — with
GitHub Actions
Waiting
wangfei010313
requested a deployment
to
maintainer-approved
July 23, 2026 02:47 — with
GitHub Actions
Waiting
wangfei010313
requested a deployment
to
ai-review-approved
July 23, 2026 06:07 — with
GitHub Actions
Waiting
wangfei010313
requested a deployment
to
maintainer-approved
July 23, 2026 06:07 — with
GitHub Actions
Waiting
wangfei010313
requested a deployment
to
ai-review-approved
July 23, 2026 07:57 — with
GitHub Actions
Waiting
wangfei010313
requested a deployment
to
maintainer-approved
July 23, 2026 07:58 — with
GitHub Actions
Waiting
wangfei010313
requested a deployment
to
ai-review-approved
July 23, 2026 09:55 — with
GitHub Actions
Waiting
wangfei010313
requested a deployment
to
maintainer-approved
July 23, 2026 09:55 — with
GitHub Actions
Waiting
wangfei010313
requested a deployment
to
ai-review-approved
July 24, 2026 02:00 — with
GitHub Actions
Waiting
wangfei010313
requested a deployment
to
maintainer-approved
July 24, 2026 02:00 — with
GitHub Actions
Waiting
wangfei010313
force-pushed
the
fix/provider-model-discovery
branch
from
July 27, 2026 06:48
7baefd1 to
b57353c
Compare
This was referenced Aug 11, 2026
- Restrict custom agent workspace_dir to home or WORKING_DIR (py/path-injection, high severity) - Escape user-controlled values (agent/provider/model/plugin ids, session ids, skill names) with sanitize_log_value before logging across routers, providers, channels, and runtime (py/log-injection) - Restore _evaluate_video_response delegation to the shared multimodal_prober evaluator lost in a merge-conflict resolution - Re-align test_agents_ordering with the mutate_config transaction API and add the new supports_agent_thinking field to llamacpp expected metadata - Drop dead assignment, unreachable guards, and duplicate config transaction path accumulated across review iterations
- Remove the superseded first dark-mode block and declarations fully overridden by the token restyle layer in ModelSelector styles; this also fixes stale orange/indigo colors leaking into dark mode - Delete API type fields, interfaces, and store surface added during review iterations but never read (availability_*/discovery_* mirror fields, AgentSummary routing copies, EligibleProvider.chatModel, turnUsageStore.reset, unused triggerRef)
- Drop unreferenced sync methods superseded by their async counterparts (_merge_and_save_provider_snapshot, save_provider_config, clear_active_model, _save_plugin_provider, register_plugin_provider, PluginProviderRegistry.register) - Deduplicate _replace_with_retry onto provider_persistence and drop the write_provider_snapshot replace_operation indirection - Delete production code only reachable from tests (ComparisonSummary, generate_summary, _register hook, _sanitize_connection_message, identity _local_source_to_base64, ProbeSource enum, unused ModelConnectionResult re-export) - Collapse duplicated discovery snapshot writers and the async identity wrapper in list_provider_info
- Build the default agent workspace path through safe_join so the caller-supplied custom id cannot influence the final path (py/path-injection, high) - Sanitize agent ids and exception text in workspace, service manager, and multi-agent manager log entries (py/log-injection, 26 pre-existing alerts swept into this PR's scan scope) - Align _resolve_custom_workspace_dir with black formatting
- Drop the now-unused background_tasks parameter from _should_auto_discover - Move sanitize_log_value imports after standard-library imports in service_manager and multimodal_prober
- Bridge fallback transparency data past agentscope's event conversion (which drops ChatResponse.metadata): FallbackChatModel publishes each hop into a request-scoped sink and QwenPawAgent._reasoning re-attaches it to outgoing events and the final assistant message, so Console and channel notices now fire - Stop a broken mid-chain candidate (revoked key, deleted model) from masking healthy fallbacks behind it - Skip fallback slots that fail to instantiate instead of breaking the primary model at agent build time - Drop the provider_manager roundabout imports in the persistence and discovery mixins (SECRET_DIR and capability catalog are now imported from their source modules; tests patch those directly) - Persist /model switch and reset off the event loop via update_agent_config_async; guard catalog temp-file cleanup on Windows so it cannot mask the original install error - Cover the model-to-event chain and mid-chain skips with tests
The legacy providers.json migration read this module's SECRET_DIR import, which the test-isolation fixtures (global conftest, kimi/mimo tests) do not patch -- on a machine with a real legacy file, tests would load real API keys and delete the user's file. Derive the path from self.root_path.parent instead so instance paths keep flowing from the single patched provider_manager SECRET_DIR, and drop the second patch point added to the provider-manager test fixture.
FallbackChatModel set its active-model ContextVar per attempt but never reset it, so after a fallback the between-requests window (compaction budget via context_size, capability learning via model_key) kept seeing the last-served candidate instead of the primary the next request tries first. Requests now snapshot the pre-request state and restore it when the response settles, including the streaming wrapper's finally. Remote media downloads raised on HTTP/network errors, which the model error policy misclassified (404 -> model_not_found) and burned the whole fallback chain on every turn while the dead URL stayed in history. Failures now degrade to the same placeholder path as unreadable local files, sibling downloads are shielded with return_exceptions, and oversize chunked downloads no longer fabricate an exact byte size.
The discovery and persistence mixins reference dozens of private members defined in the other two files of the ProviderManager trio with nothing declaring that surface, so a rename in one file breaks the others silently. ProviderManagerHost now declares every cross-file attribute and helper; both mixins inherit it, putting the stubs last in the MRO so real implementations always win while static checkers can verify each mixin in isolation. A contract test regenerates the external-member set from the AST and fails when a mixin starts using an undeclared member. Also repairs UTF-8 text mangled by a GBK round-trip in provider docstrings, a persistence log message, and a test docstring.
The backend-settings PATCH treated absent fields as explicit clears
(model_dump defaults), so sending only {model} silently erased a
stored reasoning_effort; it now applies model_fields_set semantics
like its sibling endpoint. The OAuth confirm modal gained an unmount
cleanup (polling used to outlive the component and could navigate
from a dead modal) and its five-minute timeout now reports an error
and closes instead of spinning forever. Adds the missing
models.discoveryOrigin*/availability*/modelAlreadyConfigured locale
keys and the new oauthTimeout message across en/zh/ja/ru/pt-BR/id.
An async provider update that lost the revision race left its stale snapshot on disk, where it would swallow the concurrent winning update on the next restart; the stale exit now rewrites the snapshot from the live provider via _restore_latest_snapshot, shared with the discovery path that already used this compensation inline. The Windows fallback-kill reap replaced its 5s bound with an unbounded proc.wait(), so a child stuck in uninterruptible kernel I/O could park a worker thread forever; the wait is bounded again (leaked handle over hung thread). Startup sync failures were logged through an f-string, interpolating the provider id and exception text directly (CodeQL log injection); switched to lazy %s formatting with sanitized values.
Narrow the fallback stream token before handing it to the consumer (mypy arg-type), let black settle the host-contract test formatting, mark the mixins' intentionally-inherited host stubs for pylint's abstract-method check, and clean the unused import and protected-access noise in the new tests.
HarnessModelSelector was the only consumer of the backend-settings PATCH and relied on the old clear-on-omit behavior; JSON.stringify drops undefined, so under field-presence semantics its clears became silent no-ops that diverged from the optimistic UI state. It now sends explicit nulls, restoring the intended clears. The OAuth modal guard reset on every open change, so parents that keep the modal mounted lost the close protection: a poll response in flight at close time could still fire onSuccess from a dismissed modal. The guard now tracks liveness (!open), Continue ignores double-clicks, bails when the modal closed during startOAuth, and clears any previous timers before installing new ones. FallbackChatModel._end_request now enforces the between-requests invariant directly after the token reset: CPython silently restores an outdated token's snapshot, so a stream abandoned in request 1 and closed mid-request-2 would have reinstated the leaked fallback model. _restore_latest_snapshot deletes the freshly written snapshot when the provider was removed mid-flight instead of leaving a file that would resurrect the provider on the next startup scan.
This was referenced Aug 16, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.

Summary
This PR unifies provider discovery, model metadata, model routing, and agent model controls.
It introduces a catalog-driven provider model system, runtime model discovery, capability-aware routing, fallback support, and a redesigned model selection experience. The implementation also hardens asynchronous I/O, persistence transactions, media formatting, and provider lifecycle handling.
The branch has been synchronized with the latest upstream
main, and all merge conflicts in the provider/media implementation and related tests have been resolved.What changed
Provider discovery and model metadata
thinking parameters, output limits, and model families.
Provider manager and persistence
registry responsibilities into focused modules.
fails.
Model routing and fallback
testing.
Agent model controls
subagent models, and thinking level.
nullfor non-null routing fields while preservingsubagent_model: nullas a valid clear operation.Media handling and formatters
ContextVar.Anthropic, Gemini, and DashScope.
disable_thinkinghandling for Responses models.Console and model selector
Packaging and runtime
Windows, macOS, and Linux.
Validation
Focused backend regression suites:
Result:
259 passed1 skippedStatic checks for the changed conflict files:
10.00/10git diff --check: passedReview follow-up
The branch has been rebased through a merge with the latest upstream
main.The following review concerns were explicitly addressed: