Skip to content

sync: upstream multi-provider PR #2277 + 21 fixes#76

Closed
aaditagrawal wants to merge 34 commits intomainfrom
aaditagrawal/sync-multiprovider
Closed

sync: upstream multi-provider PR #2277 + 21 fixes#76
aaditagrawal wants to merge 34 commits intomainfrom
aaditagrawal/sync-multiprovider

Conversation

@aaditagrawal
Copy link
Copy Markdown
Owner

@aaditagrawal aaditagrawal commented May 2, 2026

Summary

Merge of upstream's multi-provider refactor (PR pingdotgg#2277) plus 21 supporting fixes, with the fork's four extra drivers (Amp / Copilot / GeminiCli / Kilo) fully ported onto upstream's per-instance ProviderDriver SPI instead of left as stubs.

Architecture migration (upstream PR pingdotgg#2277)

Upstream introduced a per-instance driver model:

  • ProviderDriver is a plain factory whose create() returns one ProviderInstance (snapshot + adapter + textGeneration closures).
  • ProviderInstanceRegistry (singleton Effect service) holds Map<InstanceId, ProviderInstance> and rebuilds entries on settings changes.
  • ProviderInstanceRegistryHydration auto-seeds default instances from the legacy ServerSettings.providers.<kind> blob.

Fork driver port

Each fork driver is a real ProviderDriver value with configSchema (GenericProviderSettings), defaultConfig, and a create() returning a captured-closure ProviderInstance:

File Approach Status
apps/server/src/provider/Drivers/AmpDriver.ts Inlines make<X>Adapter closure capture, ports cleanly. Ported
apps/server/src/provider/Drivers/GeminiCliDriver.ts Same pattern as Amp. Ported
apps/server/src/provider/Drivers/KiloDriver.ts Same pattern; threads kilo: { binaryPath } through to KiloServerManager.startSession. Ported
apps/server/src/provider/Drivers/CopilotDriver.ts Acceptable shortcut: 1.7k-line Layers/CopilotAdapter.ts body kept intact, only entry point reworked into makeCopilotAdapterImpl(config, options). TODO(sync) flags the indirection. Ported (transitional shim)

Adapter factories at apps/server/src/provider/Layers/{Amp,GeminiCli,Kilo,Copilot}Adapter.ts capture GenericProviderSettings in closures and return the ProviderAdapterShape directly — no Layer.effect/Context.Service indirection. Two driver instances spin up two independent XServerManager processes with their own binaryPath, configDir, and runtime-event queues.

Restored helper modules:

  • apps/server/src/ampServerManager.ts (Node EventEmitter, JSONL stream parsing)
  • apps/server/src/geminiCliServerManager.ts
  • apps/server/src/kiloServerManager.ts + apps/server/src/kilo/{errors,eventHandlers,index,serverLifecycle,types,utils}.ts
  • apps/server/src/provider/Layers/{copilotCliPath,copilotTurnTracking,ProviderAdapterUtils}.ts
  • apps/server/src/provider/Services/CopilotAdapter.ts (vestigial Service tag — kept so the adapter body still compiles; TODO(sync) to delete)
  • apps/server/src/provider/copilot-sdk.d.ts

Branding/contract fixes for the upstream merge:

  • PROVIDER constants now use ProviderDriverKind.make() so they satisfy the branded ProviderSession.provider type (ampServerManager.ts, geminiCliServerManager.ts, kilo/types.ts, CopilotAdapter.ts).
  • CopilotAdapter's input.modelSelection?.provider === "copilot" check becomes input.modelSelection?.optionsModelSelection no longer carries a provider field after upstream's refactor (instanceId is the routing key).
  • Local CodexReasoningEffort literal alias replaces the contracts export that died in the merge; TODO(sync) tracks the proper migration onto ModelSelection.options.

Text generation is intentionally unsupported — the four CLIs don't expose a suitable surface — so each driver's textGeneration returns TextGenerationError for every method. Routing falls back to other providers transparently.

Snapshot probes are placeholder: installed: true / status: enabled ? ready : error. The real <binary> --version probe is marked TODO(sync) inside each driver.

UI metadata

apps/web/src/components/settings/providerDriverMeta.ts already has the four fork drivers wired up with their icons, binaryPath/configDir fields, and a Pending sync badge. TODO(sync): drop the Pending sync badge once a follow-up PR confirms end-to-end smoke testing on a real install of each CLI; the driver code is wired through.

apps/web/src/appSettings.ts is still the minimal shim from the previous sync commit. Restoring the rich per-provider custom-models UI is deferred — the original module pulled in 477 lines of helper modules (customModels.ts, gitTextGeneration.ts, appearance.ts) that referenced legacy ProviderKind contracts. TODO(sync) at the top of appSettings.ts flags the follow-up.

Migrations

Fork's IDs 23 (NormalizeLegacyProviderKinds) + 24 (RepairProjectionThread...) stay put. Upstream's filenames 023..028 register at fork IDs 25..30. Added fork ID 31 (BackfillForkProviderInstanceIds) to backfill provider_instance_id = '<driverKind>' on existing rows that name one of the fork drivers, matching defaultInstanceIdForDriver(driverKind).

Tests

Latest follow-up commits (this push) take the previously-flagged 5 failures to 0:

Failure Root cause Fix Status
CodexAdapter.test.tsflushes managed native logs when the adapter layer shuts down Pre-existing typo on upstream too: regex /NTIVE: .../ (substring NTIVE is not in the actual NATIVE: prefix written by EventNdjsonLogger). s/NTIVE:/NATIVE:/ in the assertion. Green
ProviderRegistry.test.tskeeps cursor disabled and skips probing when the provider setting is disabled Caused by this PR: upstream's expected list (["claudeAgent","codex","cursor","opencode"]) doesn't include the fork's amp/copilot/geminiCli/kilo. Add the four fork drivers to the expected sorted list. Green
021_RepairProjectionThreadProposedPlanImplementationColumns.test.ts Caused by the renumbering: test fakes migration history through ID 23 then calls runMigrations() unbounded. After renumbering, the runner now also executes IDs 25-31; ID 28 (CanonicalizeModelSelectionOptions) reads model_selection_json which was created by the (faked) ID 16. Bound the second runMigrations to toMigrationInclusive: 24 (the migration this test actually validates). Green
026_CanonicalizeModelSelectionOptions.test.ts Caused by the renumbering: hardcoded toMigrationInclusive: 25 then 26 from when canonicalize was migration 26 upstream. After fork renumber it sits at 28. Bump to 27 / 28. Green
@t3tools/marketing typecheck — Vite "Not implemented" Pre-existing on the merge ancestor (a63852e) and on upstream — Astro 6.2.1's astro:dev-toolbar plugin registers an esbuild-shaped optimizeDeps.esbuildOptions plugin whose onEnd callback reads result.metafile; Vite 8.0.10's Rolldown esbuild compatibility shim returns a Proxy that throws "Not implemented" on any property access. Workaround: set vite.optimizeDeps.disabled: true in apps/marketing/astro.config.mjs. The marketing site is static and has no client deps that benefit from prebundling. Vite warns disabled is deprecated in favor of noDiscovery: true + empty include, but astro force-injects entries into optimizeDeps.include, so noDiscovery alone doesn't suffice. Remove once Astro migrates the dev-toolbar plugin to rolldownOptions (or vite/rolldown shims metafile). Green

Other state of the tree:

  • bun typecheck passes for all 10 packages.
  • bun lint clean — 0 errors.
  • apps/web vitest: 925 / 925 pass.
  • packages/contracts vitest: 136 / 136 pass.
  • packages/shared vitest: 97 / 97 pass.
  • apps/server vitest: the 4 originally-flagged unit tests are now green. The 11 failures still showing in integration/orchestrationEngine.integration.test.ts are pre-existing 60s timeouts under apps/server/integration/ (tests wait on heavy claudeAgent fixtures that don't terminate in this CI sandbox). Not introduced by this PR; not in the original scope of this fix-up.
  • New / restored adapter coverage: AmpAdapter.test.ts, GeminiCliAdapter.test.ts, KiloAdapter.test.ts, copilotCliPath.test.ts, copilotTurnTracking.test.ts, kilo/eventHandlers.test.ts — 23 / 23 pass.
  • Targeted ProviderInstanceRegistryLive.test.ts — 3 / 3 pass.

Manual review checklist

  1. Existing fork users' Amp/Copilot/GeminiCli/Kilo settings should now produce functional instances (not "unavailable" cards) after this PR. The CLI binaries themselves still need to be installed locally.
  2. 031_BackfillForkProviderInstanceIds should populate provider_instance_id on existing rows naming any of those four drivers.
  3. Adding a second instance of any fork driver (e.g. amp_personal and amp_work with different binaryPath) should yield two independent <X>ServerManager processes — neither sharing the other's session map.
  4. Drop the Pending sync badge in providerDriverMeta.ts once the four CLIs are end-to-end smoke-tested.

Risks

  • DB migration is irreversible (column add + index create + UPDATE for fork drivers). The 029 migration is idempotent (NULL-guarded) but rolls forward only.
  • Snapshot probe is optimistic — until the per-driver <binary> --version probe is ported, configured instances always render as "ready when enabled". Failures surface at session start instead.

Follow-up TODO(sync) markers

  • Inline Layers/CopilotAdapter.ts directly into Drivers/CopilotDriver.ts and delete the vestigial Services/CopilotAdapter.ts Service tag.
  • Port the real <binary> --version probes for the four fork drivers and rebuild buildSnapshot against them.
  • Migrate the local CodexReasoningEffort literal alias in CopilotAdapter.ts to read from ModelSelection.options directly.
  • Restore the rich per-provider custom-models UI (apps/web/src/appSettings.ts is still a shim).
  • Drop the marketing vite.optimizeDeps.disabled: true workaround once Astro migrates its dev-toolbar plugin to rolldownOptions.
  • Investigate the integration-test timeouts under apps/server/integration/ (pre-existing, observed in this CI environment regardless of branch).

juliusmarminge and others added 29 commits April 27, 2026 02:30
…#2364)

Co-authored-by: codex <codex@users.noreply.github.com>
OpenCode can leave the long-lived serve child in the spawned process group after the wrapper exits. Provider refresh owns only a scoped inventory probe, so cleanup targets that local process group when the refresh scope closes.

Constraint: Keep active OpenCode thread sessions scoped to their own session lifecycle.
Rejected: Disable provider refresh | would hide model and auth state changes.
Confidence: high
Scope-risk: narrow
Tested: bun run test src/provider/Layers/OpenCodeProvider.test.ts src/provider/Layers/OpenCodeAdapter.test.ts
Tested: bun fmt
Tested: bun lint
Tested: bun typecheck
Tested: bun run build:desktop
Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
Co-authored-by: codex <codex@users.noreply.github.com>
Co-authored-by: codex <codex@users.noreply.github.com>
Co-authored-by: codex <codex@users.noreply.github.com>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Co-authored-by: codex <codex@users.noreply.github.com>
Co-authored-by: Julius Marminge <julius0216@outlook.com>
…gdotgg#1293)

Co-authored-by: Murat Aslan <murataslan1@users.noreply.github.com>
Co-authored-by: Julius Marminge <julius0216@outlook.com>
…2419)

Co-authored-by: Claude Haiku 4.5 <noreply@anthropic.com>
Co-authored-by: Julius Marminge <julius0216@outlook.com>
…tgg#2423)

Co-authored-by: Claude Sonnet 4.7 <noreply@anthropic.com>
Co-authored-by: Julius Marminge <julius0216@outlook.com>
…layout fixes (pingdotgg#2392)

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-authored-by: Julius Marminge <julius0216@outlook.com>
)

Co-authored-by: Claude Haiku 4.5 <noreply@anthropic.com>
Co-authored-by: Julius Marminge <julius0216@outlook.com>
Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: Julius Marminge <juliusmarminge@users.noreply.github.com>
Co-authored-by: Julius Marminge <julius@macmini.local>
…gdotgg#2431)

Co-authored-by: Julius Marminge <julius@macmini.local>
Co-authored-by: Julius Marminge <julius@macmini.local>
Merge of 25 upstream commits including the multi-provider refactor
(PR pingdotgg#2277) that introduced ProviderDriverKind/ProviderInstanceId split
plus 21 fixes (mobile, Codex, OpenCode, Discord release notifications,
electron 40.9.3).

Resolution policy:
- Adopt upstream architecture wholesale for provider Layers, Drivers,
  Services, contracts, and shared model schemas.
- Preserve fork README and release.yml (publish_cli stripped).
- Renumber upstream migrations 023..028 to fork IDs 25..30 so fork's
  IDs 23/24 (NormalizeLegacyProviderKinds, RepairProjectionThread...)
  stay put.
- Add fork migration 31 (BackfillForkProviderInstanceIds) to backfill
  amp/copilot/geminiCli/kilo session rows.

Fork driver port (Amp/Copilot/GeminiCli/Kilo) and remaining typecheck
fixes will follow in subsequent commits.
Fix typecheck regressions from the upstream merge by:

- Removing fork-only adapter files (Amp/Copilot/GeminiCli/Kilo) — these
  were tied to the old singleton-Service architecture and need to be
  reimplemented as ProviderDriver factories. Tracked as TODO(sync).
- Removing fork's claude-agent-sdk.d.ts override now that the upstream
  bump to ^0.2.111 ships proper types.
- Adopting upstream's TextGeneration / OrchestrationEventStore /
  CursorAcpSupport / CursorAdapter / OpenCodeAdapter wholesale.
- Adding fork-only providers (amp/copilot/geminiCli/kilo) back into
  ServerSettings.providers + the patch shape with GenericProviderSettings.
- Replacing fork's appSettings module with a minimal AppSettings shim
  that satisfies surviving consumers.
- Restoring the upstream 026 migration that the merge dropped.
- Wrapping bare provider strings in tests with ProviderDriverKind.make().

Server, web, contracts, shared, desktop typecheck: green.
Marketing typecheck: still red (unrelated Vite/Astro issue, see comment).
Each fork driver is registered in BUILT_IN_DRIVERS as a ProviderDriver
whose create() fails with ProviderDriverError. The registry catches
that failure and surfaces the configured instances as 'unavailable'
shadow snapshots, so the UI shows the driver kind exists but can't run
yet. This preserves visibility for fork users mid-upgrade and gives a
single TODO(sync) marker per driver for the re-port.

Aligned the backfill migration's default instance ids with
defaultInstanceIdForDriver(driverKind) (the bare driver slug, not
'<kind>_default').

Renumbered the upstream-provided 027_028_ProviderInstanceIdColumns
test against the fork's 29/30 ids while keeping the upstream filename
prefix for review legibility.
Surfaces the Amp/Copilot/GeminiCli/Kilo drivers in the
AddProviderInstance dialog and per-instance settings card with their
proper icons and the binaryPath/configDir editable fields. The drivers
themselves are still stubs (registered by Drivers/{Name}Driver.ts) so
each option carries a 'Pending sync' badge to flag the fact that
configured instances will surface as 'unavailable' until the adapters
are reimplemented.
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 2, 2026

Important

Review skipped

Draft detected.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: dc237b98-d36e-44c8-ad66-474f948a84dc

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch aaditagrawal/sync-multiprovider

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@github-actions github-actions Bot added vouch:trusted PR author is trusted by repo permissions or the VOUCHED list. size:XXL 1,000+ effective changed lines (test files excluded in mixed PRs). labels May 2, 2026
…oviderDriver factories

The previous sync commit replaced the fork's 4 working adapters with stubs that
fail their create() with ProviderDriverError. This change recovers the deleted
adapter code and properly wires each one through the new per-instance
driver/factory architecture introduced upstream in PR pingdotgg#2277.

For each driver:
  - Layers/<X>Adapter.ts exposes a make<X>Adapter(config, options) factory
    that captures GenericProviderSettings in closures and returns a
    ProviderAdapterShape value (no Layer/Service.tag indirection).
  - Drivers/<X>Driver.ts is a real ProviderDriver value: configSchema,
    defaultConfig, and a create() that builds snapshot + adapter +
    textGeneration closures over the typed config.
  - Two driver instances run independent XServerManager processes with
    their own binary paths, config dirs, and runtime-event queues.

Shortcut for Copilot: the 1.7k-line CopilotAdapter body stays intact; only
the entry point is reworked into makeCopilotAdapterImpl(config, options),
called from CopilotDriver.create(). The transitional indirection is marked
with TODO(sync) for a future cleanup pass.

Restored to support the adapters:
  - apps/server/src/ampServerManager.ts (Node EventEmitter)
  - apps/server/src/geminiCliServerManager.ts
  - apps/server/src/kiloServerManager.ts + kilo/* helpers
  - provider/Layers/copilotCliPath.ts, copilotTurnTracking.ts (+ tests)
  - provider/Layers/ProviderAdapterUtils.ts (toRequestError helpers)
  - provider/Services/CopilotAdapter.ts (vestigial Service tag)
  - provider/copilot-sdk.d.ts

Branding fixes for upstream's contract changes:
  - PROVIDER constants now use ProviderDriverKind.make() so they satisfy
    the branded ProviderSession.provider type.
  - CopilotAdapter's input.modelSelection?.provider === 'copilot' check
    becomes input.modelSelection?.options (ModelSelection no longer carries
    a 'provider' field — instanceId is the routing key).
  - Local CodexReasoningEffort literal alias replaces the contracts export
    that died in the merge; TODO(sync) tracks the proper migration.

Text generation is not wired for any of the four — Amp/Copilot/GeminiCli/Kilo
CLIs don't expose a suitable surface — so each driver's textGeneration
methods return TextGenerationError. Routing falls back to other providers.

The fork's snapshot probe (X --version) is not yet ported; drivers report a
'ready' status when enabled and 'error' otherwise. Marked TODO(sync) inside
each driver file.
The fork renumbered upstream's migrations 023-028 to 25-30 to slot in
after the fork-only auth migrations 23/24. Two tests were unaware of the
new layout:

- 021_RepairProjectionThreadProposedPlanImplementationColumns.test.ts
  faked migration history through ID 23 then called runMigrations() with
  no upper bound. After renumbering, that runs through 31 and migration
  28_CanonicalizeModelSelectionOptions tries to read the
  model_selection_json column added by the (faked) migration 16. Bound
  the second runMigrations call to ID 24, which is the migration this
  test actually validates.

- 026_CanonicalizeModelSelectionOptions.test.ts pre-seeded data with
  toMigrationInclusive: 25 (everything upstream needed before the
  canonicalize migration), then re-ran with 26. After renumbering, the
  canonicalize migration is ID 28; bump to 27 and 28.
The NDJSON managed-native-log assertion regex used /NTIVE: .../ which
never matches the actual 'NATIVE: ' prefix written by EventNdjsonLogger
(the substring 'NTIVE' does not appear in 'NATIVE'). Fix to /NATIVE: /.
The cursor-disabled probe test asserted that getProviders returned only
upstream's four providers ('claudeAgent', 'codex', 'cursor', 'opencode').
The fork's ProviderRegistry also surfaces amp, copilot, geminiCli, and
kilo, so include them in the expected sorted list.
Astro 6.2.1's astro:dev-toolbar plugin registers an esbuild-shaped
plugin via optimizeDeps.esbuildOptions whose onEnd callback reads
result.metafile. Vite 8.0.10's Rolldown-based esbuild compatibility
shim returns a Proxy that throws 'Not implemented' on any property
access, crashing dependency prebundling and 'astro check'.

Set vite.optimizeDeps.disabled: true in the marketing astro.config.mjs
to skip the prebundle entirely. The marketing site is static and has no
client-side dependencies that benefit from the optimizer. Vite warns
this option is deprecated in favor of noDiscovery: true + empty
include, but astro's dev-toolbar plugin force-injects entries into
optimizeDeps.include, so noDiscovery alone is not sufficient.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:XXL 1,000+ effective changed lines (test files excluded in mixed PRs). vouch:trusted PR author is trusted by repo permissions or the VOUCHED list.

Projects

None yet

Development

Successfully merging this pull request may close these issues.