feat(coding-agent): accept native extension providers
Allow extensions to register complete pi-ai Provider objects while preserving models.json composition and resolved provider auth access. Refs #4823 and #4824.
This commit is contained in:
@@ -8,6 +8,7 @@ import { createRequire } from "node:module";
|
||||
import * as path from "node:path";
|
||||
import { fileURLToPath } from "node:url";
|
||||
import * as _bundledPiAgentCore from "@earendil-works/pi-agent-core";
|
||||
import type { Provider } from "@earendil-works/pi-ai";
|
||||
import * as _bundledPiAiCompat from "@earendil-works/pi-ai/compat";
|
||||
import * as _bundledPiAiOauth from "@earendil-works/pi-ai/oauth";
|
||||
import * as _bundledPiAiProviders from "@earendil-works/pi-ai/providers/all";
|
||||
@@ -195,6 +196,7 @@ export function createExtensionRuntime(): ExtensionRuntime {
|
||||
setThinkingLevel: notInitialized,
|
||||
flagValues: new Map(),
|
||||
pendingProviderRegistrations: [],
|
||||
pendingNativeProviderRegistrations: [],
|
||||
assertActive,
|
||||
invalidate: (message) => {
|
||||
state.staleMessage ??=
|
||||
@@ -206,8 +208,14 @@ export function createExtensionRuntime(): ExtensionRuntime {
|
||||
registerProvider: (name, config, extensionPath = "<unknown>") => {
|
||||
runtime.pendingProviderRegistrations.push({ name, config, extensionPath });
|
||||
},
|
||||
registerNativeProvider: (provider, extensionPath = "<unknown>") => {
|
||||
runtime.pendingNativeProviderRegistrations.push({ provider, extensionPath });
|
||||
},
|
||||
unregisterProvider: (name) => {
|
||||
runtime.pendingProviderRegistrations = runtime.pendingProviderRegistrations.filter((r) => r.name !== name);
|
||||
runtime.pendingNativeProviderRegistrations = runtime.pendingNativeProviderRegistrations.filter(
|
||||
(r) => r.provider.id !== name,
|
||||
);
|
||||
},
|
||||
};
|
||||
|
||||
@@ -363,9 +371,14 @@ function createExtensionAPI(
|
||||
runtime.setThinkingLevel(level);
|
||||
},
|
||||
|
||||
registerProvider(name: string, config: ProviderConfig) {
|
||||
registerProvider(providerOrName: Provider | string, config?: ProviderConfig) {
|
||||
runtime.assertActive();
|
||||
runtime.registerProvider(name, config, extension.path);
|
||||
if (typeof providerOrName === "string") {
|
||||
if (!config) throw new Error("Provider config is required when registering by name");
|
||||
runtime.registerProvider(providerOrName, config, extension.path);
|
||||
return;
|
||||
}
|
||||
runtime.registerNativeProvider(providerOrName, extension.path);
|
||||
},
|
||||
|
||||
unregisterProvider(name: string) {
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
*/
|
||||
|
||||
import type { AgentMessage } from "@earendil-works/pi-agent-core";
|
||||
import type { ImageContent, Model, ProviderHeaders } from "@earendil-works/pi-ai";
|
||||
import type { ImageContent, Model, Provider, ProviderHeaders } from "@earendil-works/pi-ai";
|
||||
import type { KeyId } from "@earendil-works/pi-tui";
|
||||
import { type Theme, theme } from "../../modes/interactive/theme/theme.ts";
|
||||
import type { ResourceDiagnostic } from "../diagnostics.ts";
|
||||
@@ -313,6 +313,7 @@ export class ExtensionRunner {
|
||||
contextActions: ExtensionContextActions,
|
||||
providerActions?: {
|
||||
registerProvider?: (name: string, config: ProviderConfig) => void;
|
||||
registerNativeProvider?: (provider: Provider) => void;
|
||||
unregisterProvider?: (name: string) => void;
|
||||
},
|
||||
): void {
|
||||
@@ -363,6 +364,23 @@ export class ExtensionRunner {
|
||||
}
|
||||
}
|
||||
this.runtime.pendingProviderRegistrations = [];
|
||||
for (const { provider, extensionPath } of this.runtime.pendingNativeProviderRegistrations) {
|
||||
try {
|
||||
if (providerActions?.registerNativeProvider) {
|
||||
providerActions.registerNativeProvider(provider);
|
||||
} else {
|
||||
this.modelRegistry.registerProvider(provider);
|
||||
}
|
||||
} catch (err) {
|
||||
this.emitError({
|
||||
extensionPath,
|
||||
event: "register_provider",
|
||||
error: err instanceof Error ? err.message : String(err),
|
||||
stack: err instanceof Error ? err.stack : undefined,
|
||||
});
|
||||
}
|
||||
}
|
||||
this.runtime.pendingNativeProviderRegistrations = [];
|
||||
|
||||
// From this point on, provider registration/unregistration takes effect immediately
|
||||
// without requiring a /reload.
|
||||
@@ -373,6 +391,13 @@ export class ExtensionRunner {
|
||||
}
|
||||
this.modelRegistry.registerProvider(name, config);
|
||||
};
|
||||
this.runtime.registerNativeProvider = (provider) => {
|
||||
if (providerActions?.registerNativeProvider) {
|
||||
providerActions.registerNativeProvider(provider);
|
||||
return;
|
||||
}
|
||||
this.modelRegistry.registerProvider(provider);
|
||||
};
|
||||
this.runtime.unregisterProvider = (name) => {
|
||||
if (providerActions?.unregisterProvider) {
|
||||
providerActions.unregisterProvider(name);
|
||||
|
||||
@@ -24,6 +24,7 @@ import type {
|
||||
Model,
|
||||
OAuthCredentials,
|
||||
OAuthLoginCallbacks,
|
||||
Provider,
|
||||
ProviderHeaders,
|
||||
RefreshModelsContext,
|
||||
SimpleStreamOptions,
|
||||
@@ -1378,6 +1379,7 @@ export interface ExtensionAPI {
|
||||
* }
|
||||
* });
|
||||
*/
|
||||
registerProvider(provider: Provider): void;
|
||||
registerProvider(name: string, config: ProviderConfig): void;
|
||||
|
||||
/**
|
||||
@@ -1553,8 +1555,10 @@ export type SetLabelHandler = (entryId: string, label: string | undefined) => vo
|
||||
*/
|
||||
export interface ExtensionRuntimeState {
|
||||
flagValues: Map<string, boolean | string>;
|
||||
/** Provider registrations queued during extension loading, processed when runner binds */
|
||||
/** Legacy provider-config registrations queued during extension loading, processed when runner binds. */
|
||||
pendingProviderRegistrations: Array<{ name: string; config: ProviderConfig; extensionPath: string }>;
|
||||
/** Native pi-ai provider registrations queued during extension loading, processed when runner binds. */
|
||||
pendingNativeProviderRegistrations: Array<{ provider: Provider; extensionPath: string }>;
|
||||
/** Throws when this extension instance is stale after runtime replacement. */
|
||||
assertActive: () => void;
|
||||
/** Marks this extension instance as stale after runtime replacement or reload. */
|
||||
@@ -1566,6 +1570,7 @@ export interface ExtensionRuntimeState {
|
||||
* After bindCore(): calls ModelRegistry directly for immediate effect.
|
||||
*/
|
||||
registerProvider: (name: string, config: ProviderConfig, extensionPath?: string) => void;
|
||||
registerNativeProvider: (provider: Provider, extensionPath?: string) => void;
|
||||
unregisterProvider: (name: string, extensionPath?: string) => void;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user