feat(coding-agent): flush registerProvider immediately after bindCore, add unregisterProvider

This commit is contained in:
Aliou Diallo 2026-02-18 10:59:51 +01:00
parent 4ba3e5be22
commit 975de88eb1
6 changed files with 90 additions and 3 deletions

View file

@ -109,7 +109,7 @@ export function createExtensionRuntime(): ExtensionRuntime {
throw new Error("Extension runtime not initialized. Action methods cannot be called during extension loading.");
};
return {
const runtime: ExtensionRuntime = {
sendMessage: notInitialized,
sendUserMessage: notInitialized,
appendEntry: notInitialized,
@ -125,7 +125,18 @@ export function createExtensionRuntime(): ExtensionRuntime {
setThinkingLevel: notInitialized,
flagValues: new Map(),
pendingProviderRegistrations: [],
// Pre-bind: queue registrations so bindCore() can flush them once the
// model registry is available. bindCore() replaces both with direct calls.
registerProvider: (name, config) => {
runtime.pendingProviderRegistrations.push({ name, config });
},
unregisterProvider: (name) => {
const idx = runtime.pendingProviderRegistrations.findIndex((r) => r.name === name);
if (idx !== -1) runtime.pendingProviderRegistrations.splice(idx, 1);
},
};
return runtime;
}
/**
@ -246,7 +257,11 @@ function createExtensionAPI(
},
registerProvider(name: string, config: ProviderConfig) {
runtime.pendingProviderRegistrations.push({ name, config });
runtime.registerProvider(name, config);
},
unregisterProvider(name: string) {
runtime.unregisterProvider(name);
},
events: eventBus,

View file

@ -259,11 +259,16 @@ export class ExtensionRunner {
this.compactFn = contextActions.compact;
this.getSystemPromptFn = contextActions.getSystemPrompt;
// Process provider registrations queued during extension loading
// Flush provider registrations queued during extension loading
for (const { name, config } of this.runtime.pendingProviderRegistrations) {
this.modelRegistry.registerProvider(name, config);
}
this.runtime.pendingProviderRegistrations = [];
// From this point on, provider registration/unregistration takes effect immediately
// without requiring a /reload.
this.runtime.registerProvider = (name, config) => this.modelRegistry.registerProvider(name, config);
this.runtime.unregisterProvider = (name) => this.modelRegistry.unregisterProvider(name);
}
bindCommandContext(actions?: ExtensionCommandContextActions): void {

View file

@ -1071,6 +1071,11 @@ export interface ExtensionAPI {
* If `oauth` is provided: registers OAuth provider for /login support.
* If `streamSimple` is provided: registers a custom API stream handler.
*
* During initial extension load this call is queued and applied once the
* runner has bound its context. After that it takes effect immediately, so
* it is safe to call from command handlers or event callbacks without
* requiring a `/reload`.
*
* @example
* // Register a new provider with custom models
* pi.registerProvider("my-proxy", {
@ -1112,6 +1117,21 @@ export interface ExtensionAPI {
*/
registerProvider(name: string, config: ProviderConfig): void;
/**
* Unregister a previously registered provider.
*
* Removes all models belonging to the named provider and restores any
* built-in models that were overridden by it. Has no effect if the provider
* was not registered by this extension API.
*
* Like `registerProvider`, this takes effect immediately when called after
* the initial load phase.
*
* @example
* pi.unregisterProvider("my-proxy");
*/
unregisterProvider(name: string): void;
/** Shared event bus for extension communication. */
events: EventBus;
}
@ -1247,6 +1267,14 @@ export interface ExtensionRuntimeState {
flagValues: Map<string, boolean | string>;
/** Provider registrations queued during extension loading, processed when runner binds */
pendingProviderRegistrations: Array<{ name: string; config: ProviderConfig }>;
/**
* Register or unregister a provider.
*
* Before bindCore(): queues registrations / removes from queue.
* After bindCore(): calls ModelRegistry directly for immediate effect.
*/
registerProvider: (name: string, config: ProviderConfig) => void;
unregisterProvider: (name: string) => void;
}
/**

View file

@ -540,6 +540,20 @@ export class ModelRegistry {
this.applyProviderConfig(providerName, config);
}
/**
* Unregister a previously registered provider.
*
* Removes the provider from the registry and reloads models from disk so that
* built-in models overridden by this provider are restored to their original state.
* Has no effect if the provider was never registered.
*/
unregisterProvider(providerName: string): void {
if (!this.registeredProviders.has(providerName)) return;
this.registeredProviders.delete(providerName);
this.customProviderApiKeys.delete(providerName);
this.refresh();
}
private applyProviderConfig(providerName: string, config: ProviderConfigInput): void {
// Register OAuth provider if provided
if (config.oauth) {