fix(coding-agent,ai): finalize provider unregister lifecycle and dependency security updates fixes #1669

This commit is contained in:
Mario Zechner 2026-02-27 21:00:25 +01:00
parent 975de88eb1
commit 2f55890452
12 changed files with 904 additions and 832 deletions

View file

@ -7,8 +7,9 @@ import * as os from "node:os";
import * as path from "node:path";
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
import { AuthStorage } from "../src/core/auth-storage.js";
import { discoverAndLoadExtensions } from "../src/core/extensions/loader.js";
import { createExtensionRuntime, discoverAndLoadExtensions } from "../src/core/extensions/loader.js";
import { ExtensionRunner } from "../src/core/extensions/runner.js";
import type { ExtensionActions, ExtensionContextActions, ProviderConfig } from "../src/core/extensions/types.js";
import { DEFAULT_KEYBINDINGS, type KeyId } from "../src/core/keybindings.js";
import { ModelRegistry } from "../src/core/model-registry.js";
import { SessionManager } from "../src/core/session-manager.js";
@ -32,6 +33,50 @@ describe("ExtensionRunner", () => {
fs.rmSync(tempDir, { recursive: true, force: true });
});
const providerModelConfig: ProviderConfig = {
baseUrl: "https://provider.test/v1",
apiKey: "PROVIDER_TEST_KEY",
api: "openai-completions",
models: [
{
id: "instant-model",
name: "Instant Model",
reasoning: false,
input: ["text"],
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
contextWindow: 128000,
maxTokens: 4096,
},
],
};
const extensionActions: ExtensionActions = {
sendMessage: () => {},
sendUserMessage: () => {},
appendEntry: () => {},
setSessionName: () => {},
getSessionName: () => undefined,
setLabel: () => {},
getActiveTools: () => [],
getAllTools: () => [],
setActiveTools: () => {},
getCommands: () => [],
setModel: async () => false,
getThinkingLevel: () => "off",
setThinkingLevel: () => {},
};
const extensionContextActions: ExtensionContextActions = {
getModel: () => undefined,
isIdle: () => true,
abort: () => {},
hasPendingMessages: () => false,
shutdown: () => {},
getContextUsage: () => undefined,
compact: () => {},
getSystemPrompt: () => "",
};
describe("shortcut conflicts", () => {
it("warns when extension shortcut conflicts with built-in", async () => {
const extCode = `
@ -491,6 +536,47 @@ describe("ExtensionRunner", () => {
});
});
describe("provider registration", () => {
it("pre-bind unregister removes all queued registrations for a provider", () => {
const runtime = createExtensionRuntime();
runtime.registerProvider("queued-provider", providerModelConfig);
runtime.registerProvider("queued-provider", {
...providerModelConfig,
models: [
{
id: "instant-model-2",
name: "Instant Model 2",
reasoning: false,
input: ["text"],
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
contextWindow: 128000,
maxTokens: 4096,
},
],
});
expect(runtime.pendingProviderRegistrations).toHaveLength(2);
runtime.unregisterProvider("queued-provider");
expect(runtime.pendingProviderRegistrations).toHaveLength(0);
});
it("post-bind register and unregister take effect immediately", () => {
const runtime = createExtensionRuntime();
const runner = new ExtensionRunner([], runtime, tempDir, sessionManager, modelRegistry);
runner.bindCore(extensionActions, extensionContextActions);
expect(runtime.pendingProviderRegistrations).toHaveLength(0);
runtime.registerProvider("instant-provider", providerModelConfig);
expect(runtime.pendingProviderRegistrations).toHaveLength(0);
expect(modelRegistry.find("instant-provider", "instant-model")).toBeDefined();
runtime.unregisterProvider("instant-provider");
expect(modelRegistry.find("instant-provider", "instant-model")).toBeUndefined();
});
});
describe("hasHandlers", () => {
it("returns true when handlers exist for event type", async () => {
const extCode = `