fix(extensions): use safe legacy multimodel capability fallback

This commit is contained in:
777genius 2026-04-17 14:46:43 +03:00
parent 5007f3eebb
commit 8423656b97
4 changed files with 125 additions and 4 deletions

View file

@ -1,7 +1,10 @@
import { execCli } from '@main/utils/childProcess';
import { resolveInteractiveShellEnv } from '@main/utils/shellEnv';
import { createLogger } from '@shared/utils/logger';
import { createDefaultCliExtensionCapabilities } from '@shared/utils/providerExtensionCapabilities';
import {
createDefaultCliExtensionCapabilities,
createLegacyRuntimeFallbackCliExtensionCapabilities,
} from '@shared/utils/providerExtensionCapabilities';
import { resolveGeminiRuntimeAuth } from './geminiRuntimeAuth';
import { buildProviderAwareCliEnv } from './providerAwareCliEnv';
@ -145,7 +148,7 @@ function createDefaultProviderStatus(providerId: CliProviderId): CliProviderStat
capabilities: {
teamLaunch: false,
oneShot: false,
extensions: createDefaultCliExtensionCapabilities(),
extensions: createLegacyRuntimeFallbackCliExtensionCapabilities(),
},
selectedBackendId: null,
resolvedBackendId: null,
@ -159,7 +162,9 @@ function createDefaultProviderStatus(providerId: CliProviderId): CliProviderStat
function mapRuntimeExtensionCapabilities(
capabilities?: RuntimeExtensionCapabilitiesResponse
): CliProviderStatus['capabilities']['extensions'] {
const defaults = createDefaultCliExtensionCapabilities();
const defaults = capabilities
? createDefaultCliExtensionCapabilities()
: createLegacyRuntimeFallbackCliExtensionCapabilities();
return {
plugins: {

View file

@ -10,6 +10,27 @@ const SUPPORTED_SHARED_CAPABILITY: CliExtensionCapability = {
reason: null,
};
const LEGACY_MULTIMODEL_FALLBACK_CAPABILITIES: CliExtensionCapabilities = {
plugins: {
status: 'unsupported',
ownership: 'shared',
reason:
'This runtime does not declare plugin capability support. Upgrade the runtime to manage plugins here.',
},
mcp: {
status: 'read-only',
ownership: 'shared',
reason:
'This runtime does not declare MCP management support. Upgrade the runtime to install or remove MCP servers here.',
},
skills: {
...SUPPORTED_SHARED_CAPABILITY,
},
apiKeys: {
...SUPPORTED_SHARED_CAPABILITY,
},
};
export function createDefaultCliExtensionCapabilities(
overrides?: Partial<CliExtensionCapabilities>
): CliExtensionCapabilities {
@ -22,10 +43,22 @@ export function createDefaultCliExtensionCapabilities(
};
}
export function createLegacyRuntimeFallbackCliExtensionCapabilities(
overrides?: Partial<CliExtensionCapabilities>
): CliExtensionCapabilities {
return {
plugins: { ...LEGACY_MULTIMODEL_FALLBACK_CAPABILITIES.plugins },
mcp: { ...LEGACY_MULTIMODEL_FALLBACK_CAPABILITIES.mcp },
skills: { ...LEGACY_MULTIMODEL_FALLBACK_CAPABILITIES.skills },
apiKeys: { ...LEGACY_MULTIMODEL_FALLBACK_CAPABILITIES.apiKeys },
...overrides,
};
}
export function getCliProviderExtensionCapabilities(
provider: Pick<CliProviderStatus, 'capabilities'>
): CliExtensionCapabilities {
return provider.capabilities.extensions ?? createDefaultCliExtensionCapabilities();
return provider.capabilities.extensions ?? createLegacyRuntimeFallbackCliExtensionCapabilities();
}
export function getCliProviderExtensionCapability(

View file

@ -254,4 +254,43 @@ describe('ClaudeMultimodelBridgeService', () => {
});
expect(provider.statusMessage).toContain('ANTHROPIC_API_KEY');
});
it('falls back conservatively when the runtime omits extension capability metadata', async () => {
execCliMock.mockResolvedValue({
stdout: JSON.stringify({
providers: {
codex: {
supported: true,
authenticated: true,
verificationState: 'verified',
canLoginFromUi: true,
capabilities: {
teamLaunch: true,
oneShot: true,
},
},
},
}),
stderr: '',
exitCode: 0,
});
const { ClaudeMultimodelBridgeService } =
await import('@main/services/runtime/ClaudeMultimodelBridgeService');
const service = new ClaudeMultimodelBridgeService();
const provider = await service.getProviderStatus('/mock/agent_teams_orchestrator', 'codex');
expect(provider).toMatchObject({
providerId: 'codex',
capabilities: {
extensions: {
plugins: { status: 'unsupported' },
mcp: { status: 'read-only' },
skills: { status: 'supported' },
apiKeys: { status: 'supported' },
},
},
});
});
});

View file

@ -0,0 +1,44 @@
import { describe, expect, it } from 'vitest';
import {
createLegacyRuntimeFallbackCliExtensionCapabilities,
getCliProviderExtensionCapabilities,
} from '@shared/utils/providerExtensionCapabilities';
import type { CliProviderStatus } from '@shared/types';
function makeProvider(
overrides?: Partial<CliProviderStatus>
): Pick<CliProviderStatus, 'capabilities'> {
return {
capabilities: {
teamLaunch: false,
oneShot: false,
...(overrides?.capabilities ?? {}),
} as CliProviderStatus['capabilities'],
};
}
describe('providerExtensionCapabilities', () => {
it('returns conservative fallback capabilities when runtime omits extension metadata', () => {
const capabilities = getCliProviderExtensionCapabilities(
makeProvider({
capabilities: {
teamLaunch: true,
oneShot: true,
} as CliProviderStatus['capabilities'],
})
);
expect(capabilities).toEqual(createLegacyRuntimeFallbackCliExtensionCapabilities());
});
it('keeps plugins unsupported and mcp read-only in the legacy multimodel fallback', () => {
const capabilities = createLegacyRuntimeFallbackCliExtensionCapabilities();
expect(capabilities.plugins.status).toBe('unsupported');
expect(capabilities.mcp.status).toBe('read-only');
expect(capabilities.skills.status).toBe('supported');
expect(capabilities.apiKeys.status).toBe('supported');
});
});