feat: introduce pluggable service-provider contract, migrate 9router (#7333)#7730
Merged
diegosouzapw merged 2 commits intoJul 19, 2026
Merged
Conversation
Contributor
|
Warning You have reached your daily quota limit. Please wait up to 24 hours and I will start processing your requests again! |
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
Refs #7333 — Phase 1 of the broader "formalize service providers as pluggable provider backends" architecture proposal.
Introduces a small
ServiceProviderPlugincontract (src/lib/services/providerPlugins/) that packages what today is spread across two unrelated files per embedded-service backend:bootstrap.ts's inlineSERVICES[]object literal (port, health-check config, spawn args)serviceBackends.ts'sSERVICE_BACKEND_MANIFEST_TEMPLATEentry (manifest fields)Migrates exactly one service —
9router— through the new contract end to end, as living proof the abstraction is sound.cliproxy,mux, andbifrostare completely untouched, still on their pre-existing inlineSERVICES[]branches.What changed
src/lib/services/providerPlugins/types.ts(new) —ServiceProviderPlugininterface.src/lib/services/providerPlugins/registry.ts(new) —SERVICE_PROVIDER_PLUGINS(deliberately typedRecord<"9router", ...>, not the fullServiceBackendPluginIdunion, so future backend migrations are explicit reviewable additions) +getServiceProviderPlugin().src/lib/services/bootstrap.ts— 9router'sSERVICES[]entry now derives fromgetServiceProviderPlugin("9router")instead of an inline literal; throws a clear startup error if the plugin is ever missing.cliproxy/mux/bifrostentries andbuildSpawnArgsFactory()are unmodified.open-sse/config/providerPluginManifest.ts— additivecreateServiceBackendManifestEntry(pluginId, template)export. Not wired intogenerateProviderPluginManifestFromRegistry()or any live request path in this PR.docs/frameworks/EMBEDDED-SERVICES.md— new subsection documenting the contract's current (9router-only) scope and linking to feat(providers): formalize service providers as pluggable provider backends #7333 for the roadmap.changelog.d/features/7333-pluggable-service-providers.md.How validated (Hard Rule #18 — TDD)
Two new top-level test files under
tests/unit/:tests/unit/service-provider-plugin-registry.test.tstests/unit/service-provider-plugin-manifest.test.tsRed→green proof:
src/lib/services/providerPlugins/does not exist onorigin/release/v3.8.49(git show origin/release/v3.8.49:src/lib/services/providerPlugins/registry.ts→fatal: path ... exists on disk, but not in 'origin/release/v3.8.49'), so running these exact test files against the base branch fails at import resolution. After this PR:Coverage includes:
20130, health path, intervals,needsApiKey).getServiceProviderPlugin("cliproxy"/"cliproxyapi"/"mux"/"bifrost")all returnundefined— proves the narrow typing and that nothing else was silently migrated.spawnArgsvia the plugin deep-equals a direct call tonineRouterSpawnArgs()— same function, new indirection.createServiceBackendManifestEntry()preserves the template verbatim withmodels: [].generateProviderPluginManifestFromRegistry()output foropenai/anthropiccontains no9router/cliproxyapientries — proves Step 4 did not leak into the static-registry manifest path./v1/providers/[provider]/models/route.tssource still dispatches embedded-service backends viaisServiceBackendPluginId()/getServiceModels(), and does not reference the new manifest helper — proves this PR left the one real behavior seam untouched.isLocalOnlyPath("/api/services/9router/status")andisLocalOnlyPath("/api/services/cliproxy/status")still returntrue.Gates run (all green)
node scripts/check/check-test-discovery.mjsnpm run typecheck:corenpm run typecheck:noimplicit:core(pre-existing unrelated errors inopen-sse/services/combo.ts,open-sse/utils/usageTracking.ts,src/shared/services/cliRuntime.ts— confirmed zero diff against base on those files)npx eslint --suppressions-location config/quality/eslint-suppressions.json <changed files>npm run check:complexity-ratchets(cyclomatic 2058 ≤ baseline 2059, cognitive 890 = baseline 890)node scripts/check/check-file-size.mjs(no violations on touched files)npm run check:cyclesnpm run check:docs-syncnode scripts/check/check-fabricated-docs.mjs --strict(3 pre-existing base-red failures, all unrelated to this PR's files —/api/versionindocs/INCIDENT_RESPONSE.md/docs/PERF_BUDGETS.mdand a migration filename indocs/routing/REASONING_ROUTING.md)npm run test:coverageintentionally not run locally (heaviest gate, shared devbox) — CI runs it authoritatively.Explicitly deferred (out of scope for this PR, tracked under #7333)
cliproxyapithrough the same plugin registry.mux/bifrostinto theServiceBackendPluginIdunion.open-sse/executors/index.ts,open-sse/handlers/chatCore/executorProxy.ts,mode === "cliproxyapi"checks) into the plugin contract.createServiceBackendManifestEntry()into any live/v1/modelsor/v1/providers/[provider]/modelscode path./api/services/9router/*//api/services/cliproxy/*endpoints or their UI cards.Live check (not required for this PR, informational)
This PR does not require a VPS live check under Hard Rule #18 — the behavior seam (9router bootstrap spawn args, health-check config, manifest template) is fully covered by the deep-equal regression tests above, and no live request path was touched. If a maintainer wants an end-to-end confirmation anyway: install/enable 9router locally (
npm run dev) and confirm it still spawns, passes health checks, and its models still surface at/api/services/9router/modelsand/v1/providers/9router/modelsexactly as before.