Skip to content

refactor(ai): provider interface + registry, remove unreachable session-failed state - #381

Open
dobrinyonkov wants to merge 3 commits into
masterfrom
split/pr1-provider-refactor
Open

refactor(ai): provider interface + registry, remove unreachable session-failed state#381
dobrinyonkov wants to merge 3 commits into
masterfrom
split/pr1-provider-refactor

Conversation

@dobrinyonkov

@dobrinyonkov dobrinyonkov commented Aug 31, 2026

Copy link
Copy Markdown
Contributor

What this does

Refactors the AI assistant to sit behind a small Provider interface. This is the foundation for multi-provider support (the OpenAI provider follows in a stacked PR).

Three commits:

  1. Rename PromptClientGeminiNanoProvider behind a Provider interface. The class implements an explicit contract: checkAvailability, sendMessage, destroy, plus optional downloadModel and getUsageInfo.
  2. Add a provider registry. A static PROVIDERS map and a createProvider(name, config) factory. Gemini Nano is the only provider registered here.
  3. Remove the unreachable session-failed capability state (see below).

One deliberate behavior change: session recovery moves into the provider

This is not a pure no-op, and I want to be clear about the one thing that changed.

On master, the controller owned session recovery. It tracked _pendingReseed and checked hasActiveSession() before every send, so a Send after Chrome killed the idle background worker would reseed first. A failed reseed surfaced as a distinct session-failed state.

After this refactor, the provider owns recovery. GeminiNanoProvider caches its seed prefix and rebuilds the session whenever the prefix diverges. When the worker dies, the port disconnect nulls the cached prefix, so the next sendMessage recreates the session and answers transparently. Same observable recovery, one mechanism, one place.

Because nothing calls the old reseed path anymore, no code emits session-failed. The state became unreachable. Commit 3 deletes the dead handling it left behind:

  • the AIChat capability-config entry and its comment
  • the .status-session-failed LESS rule and the recompiled theme CSS
  • two AIChat.spec tests for the state and a stale entry in the canonical-states list

Any failure that does surface now reports as streaming-failed.

Known limitations (pre-existing, not introduced here)

  • The two race guards the old _pendingReseed provided (a Send racing clearConversation or setUrl) are gone. In practice a real user cannot hit the microtask window, since a confirm dialog and page navigation separate the actions. The controller-level tests for these races were removed with _pendingReseed. Worth a follow-up test at the provider level.
  • destroy() clearing handlers before port.disconnect() can leave an in-flight _createSession promise unsettled. This exists identically on master. Not a regression.

Testing

npx grunt karma:CI — 581 tests pass. eslint and jshint clean.

Review path

Read GeminiNanoProvider.js and providers/index.js. Everything else is the rename propagating through call sites and tests, plus the session-failed deletion.

… interface

Introduces the Provider abstraction (three required methods: checkAvailability,
sendMessage, destroy; two optional: downloadModel, getUsageInfo) and swaps
PromptClient out for GeminiNanoProvider — a single file that speaks the
background port protocol internally.

- PromptBuilder gains buildMessages({appInfo, history, userMessage,
  inspectionContext, consoleErrors}) returning the full [system, ...history,
  wrappedUser] array on every send. buildSeedMessages is removed.
- GeminiNanoProvider caches the messages the underlying Prompt API session was
  seeded with. sendMessage compares messages.slice(0, -1) to the cache; if they
  differ (URL change, clearConversation, port disconnect from an idle-killed
  service worker), it recreates the session before streaming.
- AssistantController takes a provider instead of a promptClient. Loses the
  session-lifecycle vocabulary: no _pendingReseed, no _trackReseed, no explicit
  createSession calls, no hasActiveSession checks. sendUserMessage builds the
  messages array once and hands it to provider.sendMessage(messages, {onChunk}).
- PromptClient.js and PromptClient.spec.js are deleted. Coverage moves to
  GeminiNanoProvider.spec.js (same fake-port pattern, plus prefix-cache and
  cancellation scenarios).
- AssistantController.spec.js is rewritten around a fake Provider.

First slice of the multi-provider-support work. Registry and OpenAIProvider
land in follow-up slices.

Refs: .scratch/multi-provider-support/issues/01
AssistantController now constructs its provider through a static registry
instead of taking one directly. The registry lives at modules/ai/providers/
index.js and holds a single entry so far — 'gemini-nano' — with the shape
{displayName, ProviderClass, configSchema} the settings modal will render
against in a later slice.

- providers/index.js exports the PROVIDERS map and a createProvider(name,
  config) factory. Unknown names throw.
- AssistantController takes {providerName, providerConfig, createProvider}
  instead of {provider}. createProvider defaults to the registry factory and
  doubles as a test seam so specs can swap in a fake without registering it.
- AIChat passes 'gemini-nano' and an empty config through to the controller.
- AssistantController.spec.js uses the createProvider seam to inject its
  fake Provider. No new registry-standalone test — it's a trivial static
  object, covered transitively by the controller specs.

No user-visible change. Validated end-to-end against the mock harness
(port init → send → clear) with all existing behaviours intact.

Refs: .scratch/multi-provider-support/issues/02
Moving session recovery into the provider (previous commit) left the
controller with no code path that emits `session-failed`. The provider
rebuilds its session when its cached prefix diverges, so a killed
background worker recovers transparently on the next send. Any failure
that does surface now reports as `streaming-failed`.

This deletes the now-dead handling the state left behind:
- AIChat capability-config entry and its clear-button comment
- the .status-session-failed LESS rule (and recompiled theme CSS)
- the two AIChat.spec tests and the stale canonical-states entry

No reachable behavior changes — the removed state could not fire.
@dobrinyonkov dobrinyonkov changed the title refactor(ai): provider interface + registry (no behavior change) refactor(ai): provider interface + registry, remove unreachable session-failed state Sep 2, 2026
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.

1 participant