mirror of
https://github.com/QwenLM/qwen-code.git
synced 2025-12-19 09:33:53 +00:00
Merge pull request #977 from QwenLM/refactor-about
refactor: Unifying the system information display between `/about` and `/bug` commands
This commit is contained in:
@@ -8,38 +8,22 @@ import { describe, it, expect, beforeEach, vi, afterEach } from 'vitest';
|
|||||||
import { aboutCommand } from './aboutCommand.js';
|
import { aboutCommand } from './aboutCommand.js';
|
||||||
import { type CommandContext } from './types.js';
|
import { type CommandContext } from './types.js';
|
||||||
import { createMockCommandContext } from '../../test-utils/mockCommandContext.js';
|
import { createMockCommandContext } from '../../test-utils/mockCommandContext.js';
|
||||||
import * as versionUtils from '../../utils/version.js';
|
|
||||||
import { MessageType } from '../types.js';
|
import { MessageType } from '../types.js';
|
||||||
import { IdeClient } from '@qwen-code/qwen-code-core';
|
import * as systemInfoUtils from '../../utils/systemInfo.js';
|
||||||
|
|
||||||
vi.mock('@qwen-code/qwen-code-core', async (importOriginal) => {
|
vi.mock('../../utils/systemInfo.js');
|
||||||
const actual =
|
|
||||||
await importOriginal<typeof import('@qwen-code/qwen-code-core')>();
|
|
||||||
return {
|
|
||||||
...actual,
|
|
||||||
IdeClient: {
|
|
||||||
getInstance: vi.fn().mockResolvedValue({
|
|
||||||
getDetectedIdeDisplayName: vi.fn().mockReturnValue('test-ide'),
|
|
||||||
}),
|
|
||||||
},
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
vi.mock('../../utils/version.js', () => ({
|
|
||||||
getCliVersion: vi.fn(),
|
|
||||||
}));
|
|
||||||
|
|
||||||
describe('aboutCommand', () => {
|
describe('aboutCommand', () => {
|
||||||
let mockContext: CommandContext;
|
let mockContext: CommandContext;
|
||||||
const originalPlatform = process.platform;
|
|
||||||
const originalEnv = { ...process.env };
|
const originalEnv = { ...process.env };
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
mockContext = createMockCommandContext({
|
mockContext = createMockCommandContext({
|
||||||
services: {
|
services: {
|
||||||
config: {
|
config: {
|
||||||
getModel: vi.fn(),
|
getModel: vi.fn().mockReturnValue('test-model'),
|
||||||
getIdeMode: vi.fn().mockReturnValue(true),
|
getIdeMode: vi.fn().mockReturnValue(true),
|
||||||
|
getSessionId: vi.fn().mockReturnValue('test-session-id'),
|
||||||
},
|
},
|
||||||
settings: {
|
settings: {
|
||||||
merged: {
|
merged: {
|
||||||
@@ -56,21 +40,25 @@ describe('aboutCommand', () => {
|
|||||||
},
|
},
|
||||||
} as unknown as CommandContext);
|
} as unknown as CommandContext);
|
||||||
|
|
||||||
vi.mocked(versionUtils.getCliVersion).mockResolvedValue('test-version');
|
vi.mocked(systemInfoUtils.getExtendedSystemInfo).mockResolvedValue({
|
||||||
vi.spyOn(mockContext.services.config!, 'getModel').mockReturnValue(
|
cliVersion: 'test-version',
|
||||||
'test-model',
|
osPlatform: 'test-os',
|
||||||
);
|
osArch: 'x64',
|
||||||
process.env['GOOGLE_CLOUD_PROJECT'] = 'test-gcp-project';
|
osRelease: '22.0.0',
|
||||||
Object.defineProperty(process, 'platform', {
|
nodeVersion: 'v20.0.0',
|
||||||
value: 'test-os',
|
npmVersion: '10.0.0',
|
||||||
|
sandboxEnv: 'no sandbox',
|
||||||
|
modelVersion: 'test-model',
|
||||||
|
selectedAuthType: 'test-auth',
|
||||||
|
ideClient: 'test-ide',
|
||||||
|
sessionId: 'test-session-id',
|
||||||
|
memoryUsage: '100 MB',
|
||||||
|
baseUrl: undefined,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
afterEach(() => {
|
afterEach(() => {
|
||||||
vi.unstubAllEnvs();
|
vi.unstubAllEnvs();
|
||||||
Object.defineProperty(process, 'platform', {
|
|
||||||
value: originalPlatform,
|
|
||||||
});
|
|
||||||
process.env = originalEnv;
|
process.env = originalEnv;
|
||||||
vi.clearAllMocks();
|
vi.clearAllMocks();
|
||||||
});
|
});
|
||||||
@@ -81,30 +69,55 @@ describe('aboutCommand', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('should call addItem with all version info', async () => {
|
it('should call addItem with all version info', async () => {
|
||||||
process.env['SANDBOX'] = '';
|
|
||||||
if (!aboutCommand.action) {
|
if (!aboutCommand.action) {
|
||||||
throw new Error('The about command must have an action.');
|
throw new Error('The about command must have an action.');
|
||||||
}
|
}
|
||||||
|
|
||||||
await aboutCommand.action(mockContext, '');
|
await aboutCommand.action(mockContext, '');
|
||||||
|
|
||||||
|
expect(systemInfoUtils.getExtendedSystemInfo).toHaveBeenCalledWith(
|
||||||
|
mockContext,
|
||||||
|
);
|
||||||
expect(mockContext.ui.addItem).toHaveBeenCalledWith(
|
expect(mockContext.ui.addItem).toHaveBeenCalledWith(
|
||||||
{
|
expect.objectContaining({
|
||||||
type: MessageType.ABOUT,
|
type: MessageType.ABOUT,
|
||||||
|
systemInfo: expect.objectContaining({
|
||||||
cliVersion: 'test-version',
|
cliVersion: 'test-version',
|
||||||
osVersion: 'test-os',
|
osPlatform: 'test-os',
|
||||||
|
osArch: 'x64',
|
||||||
|
osRelease: '22.0.0',
|
||||||
|
nodeVersion: 'v20.0.0',
|
||||||
|
npmVersion: '10.0.0',
|
||||||
sandboxEnv: 'no sandbox',
|
sandboxEnv: 'no sandbox',
|
||||||
modelVersion: 'test-model',
|
modelVersion: 'test-model',
|
||||||
selectedAuthType: 'test-auth',
|
selectedAuthType: 'test-auth',
|
||||||
gcpProject: 'test-gcp-project',
|
|
||||||
ideClient: 'test-ide',
|
ideClient: 'test-ide',
|
||||||
},
|
sessionId: 'test-session-id',
|
||||||
|
memoryUsage: '100 MB',
|
||||||
|
baseUrl: undefined,
|
||||||
|
}),
|
||||||
|
}),
|
||||||
expect.any(Number),
|
expect.any(Number),
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should show the correct sandbox environment variable', async () => {
|
it('should show the correct sandbox environment variable', async () => {
|
||||||
process.env['SANDBOX'] = 'gemini-sandbox';
|
vi.mocked(systemInfoUtils.getExtendedSystemInfo).mockResolvedValue({
|
||||||
|
cliVersion: 'test-version',
|
||||||
|
osPlatform: 'test-os',
|
||||||
|
osArch: 'x64',
|
||||||
|
osRelease: '22.0.0',
|
||||||
|
nodeVersion: 'v20.0.0',
|
||||||
|
npmVersion: '10.0.0',
|
||||||
|
sandboxEnv: 'gemini-sandbox',
|
||||||
|
modelVersion: 'test-model',
|
||||||
|
selectedAuthType: 'test-auth',
|
||||||
|
ideClient: 'test-ide',
|
||||||
|
sessionId: 'test-session-id',
|
||||||
|
memoryUsage: '100 MB',
|
||||||
|
baseUrl: undefined,
|
||||||
|
});
|
||||||
|
|
||||||
if (!aboutCommand.action) {
|
if (!aboutCommand.action) {
|
||||||
throw new Error('The about command must have an action.');
|
throw new Error('The about command must have an action.');
|
||||||
}
|
}
|
||||||
@@ -113,15 +126,32 @@ describe('aboutCommand', () => {
|
|||||||
|
|
||||||
expect(mockContext.ui.addItem).toHaveBeenCalledWith(
|
expect(mockContext.ui.addItem).toHaveBeenCalledWith(
|
||||||
expect.objectContaining({
|
expect.objectContaining({
|
||||||
|
type: MessageType.ABOUT,
|
||||||
|
systemInfo: expect.objectContaining({
|
||||||
sandboxEnv: 'gemini-sandbox',
|
sandboxEnv: 'gemini-sandbox',
|
||||||
}),
|
}),
|
||||||
|
}),
|
||||||
expect.any(Number),
|
expect.any(Number),
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should show sandbox-exec profile when applicable', async () => {
|
it('should show sandbox-exec profile when applicable', async () => {
|
||||||
process.env['SANDBOX'] = 'sandbox-exec';
|
vi.mocked(systemInfoUtils.getExtendedSystemInfo).mockResolvedValue({
|
||||||
process.env['SEATBELT_PROFILE'] = 'test-profile';
|
cliVersion: 'test-version',
|
||||||
|
osPlatform: 'test-os',
|
||||||
|
osArch: 'x64',
|
||||||
|
osRelease: '22.0.0',
|
||||||
|
nodeVersion: 'v20.0.0',
|
||||||
|
npmVersion: '10.0.0',
|
||||||
|
sandboxEnv: 'sandbox-exec (test-profile)',
|
||||||
|
modelVersion: 'test-model',
|
||||||
|
selectedAuthType: 'test-auth',
|
||||||
|
ideClient: 'test-ide',
|
||||||
|
sessionId: 'test-session-id',
|
||||||
|
memoryUsage: '100 MB',
|
||||||
|
baseUrl: undefined,
|
||||||
|
});
|
||||||
|
|
||||||
if (!aboutCommand.action) {
|
if (!aboutCommand.action) {
|
||||||
throw new Error('The about command must have an action.');
|
throw new Error('The about command must have an action.');
|
||||||
}
|
}
|
||||||
@@ -130,18 +160,31 @@ describe('aboutCommand', () => {
|
|||||||
|
|
||||||
expect(mockContext.ui.addItem).toHaveBeenCalledWith(
|
expect(mockContext.ui.addItem).toHaveBeenCalledWith(
|
||||||
expect.objectContaining({
|
expect.objectContaining({
|
||||||
|
systemInfo: expect.objectContaining({
|
||||||
sandboxEnv: 'sandbox-exec (test-profile)',
|
sandboxEnv: 'sandbox-exec (test-profile)',
|
||||||
}),
|
}),
|
||||||
|
}),
|
||||||
expect.any(Number),
|
expect.any(Number),
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should not show ide client when it is not detected', async () => {
|
it('should not show ide client when it is not detected', async () => {
|
||||||
vi.mocked(IdeClient.getInstance).mockResolvedValue({
|
vi.mocked(systemInfoUtils.getExtendedSystemInfo).mockResolvedValue({
|
||||||
getDetectedIdeDisplayName: vi.fn().mockReturnValue(undefined),
|
cliVersion: 'test-version',
|
||||||
} as unknown as IdeClient);
|
osPlatform: 'test-os',
|
||||||
|
osArch: 'x64',
|
||||||
|
osRelease: '22.0.0',
|
||||||
|
nodeVersion: 'v20.0.0',
|
||||||
|
npmVersion: '10.0.0',
|
||||||
|
sandboxEnv: 'no sandbox',
|
||||||
|
modelVersion: 'test-model',
|
||||||
|
selectedAuthType: 'test-auth',
|
||||||
|
ideClient: '',
|
||||||
|
sessionId: 'test-session-id',
|
||||||
|
memoryUsage: '100 MB',
|
||||||
|
baseUrl: undefined,
|
||||||
|
});
|
||||||
|
|
||||||
process.env['SANDBOX'] = '';
|
|
||||||
if (!aboutCommand.action) {
|
if (!aboutCommand.action) {
|
||||||
throw new Error('The about command must have an action.');
|
throw new Error('The about command must have an action.');
|
||||||
}
|
}
|
||||||
@@ -151,13 +194,87 @@ describe('aboutCommand', () => {
|
|||||||
expect(mockContext.ui.addItem).toHaveBeenCalledWith(
|
expect(mockContext.ui.addItem).toHaveBeenCalledWith(
|
||||||
expect.objectContaining({
|
expect.objectContaining({
|
||||||
type: MessageType.ABOUT,
|
type: MessageType.ABOUT,
|
||||||
|
systemInfo: expect.objectContaining({
|
||||||
cliVersion: 'test-version',
|
cliVersion: 'test-version',
|
||||||
osVersion: 'test-os',
|
osPlatform: 'test-os',
|
||||||
|
osArch: 'x64',
|
||||||
|
osRelease: '22.0.0',
|
||||||
|
nodeVersion: 'v20.0.0',
|
||||||
|
npmVersion: '10.0.0',
|
||||||
sandboxEnv: 'no sandbox',
|
sandboxEnv: 'no sandbox',
|
||||||
modelVersion: 'test-model',
|
modelVersion: 'test-model',
|
||||||
selectedAuthType: 'test-auth',
|
selectedAuthType: 'test-auth',
|
||||||
gcpProject: 'test-gcp-project',
|
|
||||||
ideClient: '',
|
ideClient: '',
|
||||||
|
sessionId: 'test-session-id',
|
||||||
|
memoryUsage: '100 MB',
|
||||||
|
baseUrl: undefined,
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
expect.any(Number),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should show unknown npmVersion when npm command fails', async () => {
|
||||||
|
vi.mocked(systemInfoUtils.getExtendedSystemInfo).mockResolvedValue({
|
||||||
|
cliVersion: 'test-version',
|
||||||
|
osPlatform: 'test-os',
|
||||||
|
osArch: 'x64',
|
||||||
|
osRelease: '22.0.0',
|
||||||
|
nodeVersion: 'v20.0.0',
|
||||||
|
npmVersion: 'unknown',
|
||||||
|
sandboxEnv: 'no sandbox',
|
||||||
|
modelVersion: 'test-model',
|
||||||
|
selectedAuthType: 'test-auth',
|
||||||
|
ideClient: 'test-ide',
|
||||||
|
sessionId: 'test-session-id',
|
||||||
|
memoryUsage: '100 MB',
|
||||||
|
baseUrl: undefined,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!aboutCommand.action) {
|
||||||
|
throw new Error('The about command must have an action.');
|
||||||
|
}
|
||||||
|
|
||||||
|
await aboutCommand.action(mockContext, '');
|
||||||
|
|
||||||
|
expect(mockContext.ui.addItem).toHaveBeenCalledWith(
|
||||||
|
expect.objectContaining({
|
||||||
|
systemInfo: expect.objectContaining({
|
||||||
|
npmVersion: 'unknown',
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
expect.any(Number),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should show unknown sessionId when config is not available', async () => {
|
||||||
|
vi.mocked(systemInfoUtils.getExtendedSystemInfo).mockResolvedValue({
|
||||||
|
cliVersion: 'test-version',
|
||||||
|
osPlatform: 'test-os',
|
||||||
|
osArch: 'x64',
|
||||||
|
osRelease: '22.0.0',
|
||||||
|
nodeVersion: 'v20.0.0',
|
||||||
|
npmVersion: '10.0.0',
|
||||||
|
sandboxEnv: 'no sandbox',
|
||||||
|
modelVersion: 'Unknown',
|
||||||
|
selectedAuthType: 'test-auth',
|
||||||
|
ideClient: '',
|
||||||
|
sessionId: 'unknown',
|
||||||
|
memoryUsage: '100 MB',
|
||||||
|
baseUrl: undefined,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!aboutCommand.action) {
|
||||||
|
throw new Error('The about command must have an action.');
|
||||||
|
}
|
||||||
|
|
||||||
|
await aboutCommand.action(mockContext, '');
|
||||||
|
|
||||||
|
expect(mockContext.ui.addItem).toHaveBeenCalledWith(
|
||||||
|
expect.objectContaining({
|
||||||
|
systemInfo: expect.objectContaining({
|
||||||
|
sessionId: 'unknown',
|
||||||
|
}),
|
||||||
}),
|
}),
|
||||||
expect.any(Number),
|
expect.any(Number),
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -4,53 +4,23 @@
|
|||||||
* SPDX-License-Identifier: Apache-2.0
|
* SPDX-License-Identifier: Apache-2.0
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { getCliVersion } from '../../utils/version.js';
|
import type { SlashCommand } from './types.js';
|
||||||
import type { CommandContext, SlashCommand } from './types.js';
|
|
||||||
import { CommandKind } from './types.js';
|
import { CommandKind } from './types.js';
|
||||||
import process from 'node:process';
|
|
||||||
import { MessageType, type HistoryItemAbout } from '../types.js';
|
import { MessageType, type HistoryItemAbout } from '../types.js';
|
||||||
import { IdeClient } from '@qwen-code/qwen-code-core';
|
import { getExtendedSystemInfo } from '../../utils/systemInfo.js';
|
||||||
|
|
||||||
export const aboutCommand: SlashCommand = {
|
export const aboutCommand: SlashCommand = {
|
||||||
name: 'about',
|
name: 'about',
|
||||||
description: 'show version info',
|
description: 'show version info',
|
||||||
kind: CommandKind.BUILT_IN,
|
kind: CommandKind.BUILT_IN,
|
||||||
action: async (context) => {
|
action: async (context) => {
|
||||||
const osVersion = process.platform;
|
const systemInfo = await getExtendedSystemInfo(context);
|
||||||
let sandboxEnv = 'no sandbox';
|
|
||||||
if (process.env['SANDBOX'] && process.env['SANDBOX'] !== 'sandbox-exec') {
|
|
||||||
sandboxEnv = process.env['SANDBOX'];
|
|
||||||
} else if (process.env['SANDBOX'] === 'sandbox-exec') {
|
|
||||||
sandboxEnv = `sandbox-exec (${
|
|
||||||
process.env['SEATBELT_PROFILE'] || 'unknown'
|
|
||||||
})`;
|
|
||||||
}
|
|
||||||
const modelVersion = context.services.config?.getModel() || 'Unknown';
|
|
||||||
const cliVersion = await getCliVersion();
|
|
||||||
const selectedAuthType =
|
|
||||||
context.services.settings.merged.security?.auth?.selectedType || '';
|
|
||||||
const gcpProject = process.env['GOOGLE_CLOUD_PROJECT'] || '';
|
|
||||||
const ideClient = await getIdeClientName(context);
|
|
||||||
|
|
||||||
const aboutItem: Omit<HistoryItemAbout, 'id'> = {
|
const aboutItem: Omit<HistoryItemAbout, 'id'> = {
|
||||||
type: MessageType.ABOUT,
|
type: MessageType.ABOUT,
|
||||||
cliVersion,
|
systemInfo,
|
||||||
osVersion,
|
|
||||||
sandboxEnv,
|
|
||||||
modelVersion,
|
|
||||||
selectedAuthType,
|
|
||||||
gcpProject,
|
|
||||||
ideClient,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
context.ui.addItem(aboutItem, Date.now());
|
context.ui.addItem(aboutItem, Date.now());
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
async function getIdeClientName(context: CommandContext) {
|
|
||||||
if (!context.services.config?.getIdeMode()) {
|
|
||||||
return '';
|
|
||||||
}
|
|
||||||
const ideClient = await IdeClient.getInstance();
|
|
||||||
return ideClient?.getDetectedIdeDisplayName() ?? '';
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -8,41 +8,34 @@ import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
|
|||||||
import open from 'open';
|
import open from 'open';
|
||||||
import { bugCommand } from './bugCommand.js';
|
import { bugCommand } from './bugCommand.js';
|
||||||
import { createMockCommandContext } from '../../test-utils/mockCommandContext.js';
|
import { createMockCommandContext } from '../../test-utils/mockCommandContext.js';
|
||||||
import { getCliVersion } from '../../utils/version.js';
|
|
||||||
import { GIT_COMMIT_INFO } from '../../generated/git-commit.js';
|
import { GIT_COMMIT_INFO } from '../../generated/git-commit.js';
|
||||||
import { formatMemoryUsage } from '../utils/formatters.js';
|
|
||||||
import { AuthType } from '@qwen-code/qwen-code-core';
|
import { AuthType } from '@qwen-code/qwen-code-core';
|
||||||
|
import * as systemInfoUtils from '../../utils/systemInfo.js';
|
||||||
|
|
||||||
// Mock dependencies
|
// Mock dependencies
|
||||||
vi.mock('open');
|
vi.mock('open');
|
||||||
vi.mock('../../utils/version.js');
|
vi.mock('../../utils/systemInfo.js');
|
||||||
vi.mock('../utils/formatters.js');
|
|
||||||
vi.mock('@qwen-code/qwen-code-core', async (importOriginal) => {
|
|
||||||
const actual =
|
|
||||||
await importOriginal<typeof import('@qwen-code/qwen-code-core')>();
|
|
||||||
return {
|
|
||||||
...actual,
|
|
||||||
IdeClient: {
|
|
||||||
getInstance: () => ({
|
|
||||||
getDetectedIdeDisplayName: vi.fn().mockReturnValue('VSCode'),
|
|
||||||
}),
|
|
||||||
},
|
|
||||||
};
|
|
||||||
});
|
|
||||||
vi.mock('node:process', () => ({
|
|
||||||
default: {
|
|
||||||
platform: 'test-platform',
|
|
||||||
version: 'v20.0.0',
|
|
||||||
// Keep other necessary process properties if needed by other parts of the code
|
|
||||||
env: process.env,
|
|
||||||
memoryUsage: () => ({ rss: 0 }),
|
|
||||||
},
|
|
||||||
}));
|
|
||||||
|
|
||||||
describe('bugCommand', () => {
|
describe('bugCommand', () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
vi.mocked(getCliVersion).mockResolvedValue('0.1.0');
|
vi.mocked(systemInfoUtils.getExtendedSystemInfo).mockResolvedValue({
|
||||||
vi.mocked(formatMemoryUsage).mockReturnValue('100 MB');
|
cliVersion: '0.1.0',
|
||||||
|
osPlatform: 'test-platform',
|
||||||
|
osArch: 'x64',
|
||||||
|
osRelease: '22.0.0',
|
||||||
|
nodeVersion: 'v20.0.0',
|
||||||
|
npmVersion: '10.0.0',
|
||||||
|
sandboxEnv: 'test',
|
||||||
|
modelVersion: 'qwen3-coder-plus',
|
||||||
|
selectedAuthType: '',
|
||||||
|
ideClient: 'VSCode',
|
||||||
|
sessionId: 'test-session-id',
|
||||||
|
memoryUsage: '100 MB',
|
||||||
|
gitCommit:
|
||||||
|
GIT_COMMIT_INFO && !['N/A'].includes(GIT_COMMIT_INFO)
|
||||||
|
? GIT_COMMIT_INFO
|
||||||
|
: undefined,
|
||||||
|
});
|
||||||
vi.stubEnv('SANDBOX', 'qwen-test');
|
vi.stubEnv('SANDBOX', 'qwen-test');
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -55,19 +48,7 @@ describe('bugCommand', () => {
|
|||||||
const mockContext = createMockCommandContext({
|
const mockContext = createMockCommandContext({
|
||||||
services: {
|
services: {
|
||||||
config: {
|
config: {
|
||||||
getModel: () => 'qwen3-coder-plus',
|
|
||||||
getBugCommand: () => undefined,
|
getBugCommand: () => undefined,
|
||||||
getIdeMode: () => true,
|
|
||||||
getSessionId: () => 'test-session-id',
|
|
||||||
},
|
|
||||||
settings: {
|
|
||||||
merged: {
|
|
||||||
security: {
|
|
||||||
auth: {
|
|
||||||
selectedType: undefined,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
@@ -75,14 +56,21 @@ describe('bugCommand', () => {
|
|||||||
if (!bugCommand.action) throw new Error('Action is not defined');
|
if (!bugCommand.action) throw new Error('Action is not defined');
|
||||||
await bugCommand.action(mockContext, 'A test bug');
|
await bugCommand.action(mockContext, 'A test bug');
|
||||||
|
|
||||||
|
const gitCommitLine =
|
||||||
|
GIT_COMMIT_INFO && !['N/A'].includes(GIT_COMMIT_INFO)
|
||||||
|
? `* **Git Commit:** ${GIT_COMMIT_INFO}\n`
|
||||||
|
: '';
|
||||||
const expectedInfo = `
|
const expectedInfo = `
|
||||||
* **CLI Version:** 0.1.0
|
* **CLI Version:** 0.1.0
|
||||||
* **Git Commit:** ${GIT_COMMIT_INFO}
|
${gitCommitLine}* **Model:** qwen3-coder-plus
|
||||||
|
* **Sandbox:** test
|
||||||
|
* **OS Platform:** test-platform
|
||||||
|
* **OS Arch:** x64
|
||||||
|
* **OS Release:** 22.0.0
|
||||||
|
* **Node.js Version:** v20.0.0
|
||||||
|
* **NPM Version:** 10.0.0
|
||||||
* **Session ID:** test-session-id
|
* **Session ID:** test-session-id
|
||||||
* **Operating System:** test-platform v20.0.0
|
* **Auth Method:**
|
||||||
* **Sandbox Environment:** test
|
|
||||||
* **Auth Type:**
|
|
||||||
* **Model Version:** qwen3-coder-plus
|
|
||||||
* **Memory Usage:** 100 MB
|
* **Memory Usage:** 100 MB
|
||||||
* **IDE Client:** VSCode
|
* **IDE Client:** VSCode
|
||||||
`;
|
`;
|
||||||
@@ -99,19 +87,7 @@ describe('bugCommand', () => {
|
|||||||
const mockContext = createMockCommandContext({
|
const mockContext = createMockCommandContext({
|
||||||
services: {
|
services: {
|
||||||
config: {
|
config: {
|
||||||
getModel: () => 'qwen3-coder-plus',
|
|
||||||
getBugCommand: () => ({ urlTemplate: customTemplate }),
|
getBugCommand: () => ({ urlTemplate: customTemplate }),
|
||||||
getIdeMode: () => true,
|
|
||||||
getSessionId: () => 'test-session-id',
|
|
||||||
},
|
|
||||||
settings: {
|
|
||||||
merged: {
|
|
||||||
security: {
|
|
||||||
auth: {
|
|
||||||
selectedType: undefined,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
@@ -119,14 +95,21 @@ describe('bugCommand', () => {
|
|||||||
if (!bugCommand.action) throw new Error('Action is not defined');
|
if (!bugCommand.action) throw new Error('Action is not defined');
|
||||||
await bugCommand.action(mockContext, 'A custom bug');
|
await bugCommand.action(mockContext, 'A custom bug');
|
||||||
|
|
||||||
|
const gitCommitLine =
|
||||||
|
GIT_COMMIT_INFO && !['N/A'].includes(GIT_COMMIT_INFO)
|
||||||
|
? `* **Git Commit:** ${GIT_COMMIT_INFO}\n`
|
||||||
|
: '';
|
||||||
const expectedInfo = `
|
const expectedInfo = `
|
||||||
* **CLI Version:** 0.1.0
|
* **CLI Version:** 0.1.0
|
||||||
* **Git Commit:** ${GIT_COMMIT_INFO}
|
${gitCommitLine}* **Model:** qwen3-coder-plus
|
||||||
|
* **Sandbox:** test
|
||||||
|
* **OS Platform:** test-platform
|
||||||
|
* **OS Arch:** x64
|
||||||
|
* **OS Release:** 22.0.0
|
||||||
|
* **Node.js Version:** v20.0.0
|
||||||
|
* **NPM Version:** 10.0.0
|
||||||
* **Session ID:** test-session-id
|
* **Session ID:** test-session-id
|
||||||
* **Operating System:** test-platform v20.0.0
|
* **Auth Method:**
|
||||||
* **Sandbox Environment:** test
|
|
||||||
* **Auth Type:**
|
|
||||||
* **Model Version:** qwen3-coder-plus
|
|
||||||
* **Memory Usage:** 100 MB
|
* **Memory Usage:** 100 MB
|
||||||
* **IDE Client:** VSCode
|
* **IDE Client:** VSCode
|
||||||
`;
|
`;
|
||||||
@@ -138,25 +121,30 @@ describe('bugCommand', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('should include Base URL when auth type is OpenAI', async () => {
|
it('should include Base URL when auth type is OpenAI', async () => {
|
||||||
|
vi.mocked(systemInfoUtils.getExtendedSystemInfo).mockResolvedValue({
|
||||||
|
cliVersion: '0.1.0',
|
||||||
|
osPlatform: 'test-platform',
|
||||||
|
osArch: 'x64',
|
||||||
|
osRelease: '22.0.0',
|
||||||
|
nodeVersion: 'v20.0.0',
|
||||||
|
npmVersion: '10.0.0',
|
||||||
|
sandboxEnv: 'test',
|
||||||
|
modelVersion: 'qwen3-coder-plus',
|
||||||
|
selectedAuthType: AuthType.USE_OPENAI,
|
||||||
|
ideClient: 'VSCode',
|
||||||
|
sessionId: 'test-session-id',
|
||||||
|
memoryUsage: '100 MB',
|
||||||
|
baseUrl: 'https://api.openai.com/v1',
|
||||||
|
gitCommit:
|
||||||
|
GIT_COMMIT_INFO && !['N/A'].includes(GIT_COMMIT_INFO)
|
||||||
|
? GIT_COMMIT_INFO
|
||||||
|
: undefined,
|
||||||
|
});
|
||||||
|
|
||||||
const mockContext = createMockCommandContext({
|
const mockContext = createMockCommandContext({
|
||||||
services: {
|
services: {
|
||||||
config: {
|
config: {
|
||||||
getModel: () => 'qwen3-coder-plus',
|
|
||||||
getBugCommand: () => undefined,
|
getBugCommand: () => undefined,
|
||||||
getIdeMode: () => true,
|
|
||||||
getSessionId: () => 'test-session-id',
|
|
||||||
getContentGeneratorConfig: () => ({
|
|
||||||
baseUrl: 'https://api.openai.com/v1',
|
|
||||||
}),
|
|
||||||
},
|
|
||||||
settings: {
|
|
||||||
merged: {
|
|
||||||
security: {
|
|
||||||
auth: {
|
|
||||||
selectedType: AuthType.USE_OPENAI,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
@@ -164,15 +152,22 @@ describe('bugCommand', () => {
|
|||||||
if (!bugCommand.action) throw new Error('Action is not defined');
|
if (!bugCommand.action) throw new Error('Action is not defined');
|
||||||
await bugCommand.action(mockContext, 'OpenAI bug');
|
await bugCommand.action(mockContext, 'OpenAI bug');
|
||||||
|
|
||||||
|
const gitCommitLine =
|
||||||
|
GIT_COMMIT_INFO && !['N/A'].includes(GIT_COMMIT_INFO)
|
||||||
|
? `* **Git Commit:** ${GIT_COMMIT_INFO}\n`
|
||||||
|
: '';
|
||||||
const expectedInfo = `
|
const expectedInfo = `
|
||||||
* **CLI Version:** 0.1.0
|
* **CLI Version:** 0.1.0
|
||||||
* **Git Commit:** ${GIT_COMMIT_INFO}
|
${gitCommitLine}* **Model:** qwen3-coder-plus
|
||||||
|
* **Sandbox:** test
|
||||||
|
* **OS Platform:** test-platform
|
||||||
|
* **OS Arch:** x64
|
||||||
|
* **OS Release:** 22.0.0
|
||||||
|
* **Node.js Version:** v20.0.0
|
||||||
|
* **NPM Version:** 10.0.0
|
||||||
* **Session ID:** test-session-id
|
* **Session ID:** test-session-id
|
||||||
* **Operating System:** test-platform v20.0.0
|
* **Auth Method:** ${AuthType.USE_OPENAI}
|
||||||
* **Sandbox Environment:** test
|
|
||||||
* **Auth Type:** ${AuthType.USE_OPENAI}
|
|
||||||
* **Base URL:** https://api.openai.com/v1
|
* **Base URL:** https://api.openai.com/v1
|
||||||
* **Model Version:** qwen3-coder-plus
|
|
||||||
* **Memory Usage:** 100 MB
|
* **Memory Usage:** 100 MB
|
||||||
* **IDE Client:** VSCode
|
* **IDE Client:** VSCode
|
||||||
`;
|
`;
|
||||||
|
|||||||
@@ -5,17 +5,17 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import open from 'open';
|
import open from 'open';
|
||||||
import process from 'node:process';
|
|
||||||
import {
|
import {
|
||||||
type CommandContext,
|
type CommandContext,
|
||||||
type SlashCommand,
|
type SlashCommand,
|
||||||
CommandKind,
|
CommandKind,
|
||||||
} from './types.js';
|
} from './types.js';
|
||||||
import { MessageType } from '../types.js';
|
import { MessageType } from '../types.js';
|
||||||
import { GIT_COMMIT_INFO } from '../../generated/git-commit.js';
|
import { getExtendedSystemInfo } from '../../utils/systemInfo.js';
|
||||||
import { formatMemoryUsage } from '../utils/formatters.js';
|
import {
|
||||||
import { getCliVersion } from '../../utils/version.js';
|
getSystemInfoFields,
|
||||||
import { IdeClient, AuthType } from '@qwen-code/qwen-code-core';
|
getFieldValue,
|
||||||
|
} from '../../utils/systemInfoFields.js';
|
||||||
|
|
||||||
export const bugCommand: SlashCommand = {
|
export const bugCommand: SlashCommand = {
|
||||||
name: 'bug',
|
name: 'bug',
|
||||||
@@ -23,50 +23,20 @@ export const bugCommand: SlashCommand = {
|
|||||||
kind: CommandKind.BUILT_IN,
|
kind: CommandKind.BUILT_IN,
|
||||||
action: async (context: CommandContext, args?: string): Promise<void> => {
|
action: async (context: CommandContext, args?: string): Promise<void> => {
|
||||||
const bugDescription = (args || '').trim();
|
const bugDescription = (args || '').trim();
|
||||||
const { config } = context.services;
|
const systemInfo = await getExtendedSystemInfo(context);
|
||||||
|
|
||||||
const osVersion = `${process.platform} ${process.version}`;
|
const fields = getSystemInfoFields(systemInfo);
|
||||||
let sandboxEnv = 'no sandbox';
|
|
||||||
if (process.env['SANDBOX'] && process.env['SANDBOX'] !== 'sandbox-exec') {
|
|
||||||
sandboxEnv = process.env['SANDBOX'].replace(/^qwen-(?:code-)?/, '');
|
|
||||||
} else if (process.env['SANDBOX'] === 'sandbox-exec') {
|
|
||||||
sandboxEnv = `sandbox-exec (${
|
|
||||||
process.env['SEATBELT_PROFILE'] || 'unknown'
|
|
||||||
})`;
|
|
||||||
}
|
|
||||||
const modelVersion = config?.getModel() || 'Unknown';
|
|
||||||
const cliVersion = await getCliVersion();
|
|
||||||
const memoryUsage = formatMemoryUsage(process.memoryUsage().rss);
|
|
||||||
const ideClient = await getIdeClientName(context);
|
|
||||||
const selectedAuthType =
|
|
||||||
context.services.settings.merged.security?.auth?.selectedType || '';
|
|
||||||
const baseUrl =
|
|
||||||
selectedAuthType === AuthType.USE_OPENAI
|
|
||||||
? config?.getContentGeneratorConfig()?.baseUrl
|
|
||||||
: undefined;
|
|
||||||
|
|
||||||
let info = `
|
// Generate bug report info using the same field configuration
|
||||||
* **CLI Version:** ${cliVersion}
|
let info = '\n';
|
||||||
* **Git Commit:** ${GIT_COMMIT_INFO}
|
for (const field of fields) {
|
||||||
* **Session ID:** ${config?.getSessionId() || 'unknown'}
|
info += `* **${field.label}:** ${getFieldValue(field, systemInfo)}\n`;
|
||||||
* **Operating System:** ${osVersion}
|
|
||||||
* **Sandbox Environment:** ${sandboxEnv}
|
|
||||||
* **Auth Type:** ${selectedAuthType}`;
|
|
||||||
if (baseUrl) {
|
|
||||||
info += `\n* **Base URL:** ${baseUrl}`;
|
|
||||||
}
|
|
||||||
info += `
|
|
||||||
* **Model Version:** ${modelVersion}
|
|
||||||
* **Memory Usage:** ${memoryUsage}
|
|
||||||
`;
|
|
||||||
if (ideClient) {
|
|
||||||
info += `* **IDE Client:** ${ideClient}\n`;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let bugReportUrl =
|
let bugReportUrl =
|
||||||
'https://github.com/QwenLM/qwen-code/issues/new?template=bug_report.yml&title={title}&info={info}';
|
'https://github.com/QwenLM/qwen-code/issues/new?template=bug_report.yml&title={title}&info={info}';
|
||||||
|
|
||||||
const bugCommandSettings = config?.getBugCommand();
|
const bugCommandSettings = context.services.config?.getBugCommand();
|
||||||
if (bugCommandSettings?.urlTemplate) {
|
if (bugCommandSettings?.urlTemplate) {
|
||||||
bugReportUrl = bugCommandSettings.urlTemplate;
|
bugReportUrl = bugCommandSettings.urlTemplate;
|
||||||
}
|
}
|
||||||
@@ -98,11 +68,3 @@ export const bugCommand: SlashCommand = {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
async function getIdeClientName(context: CommandContext) {
|
|
||||||
if (!context.services.config?.getIdeMode()) {
|
|
||||||
return '';
|
|
||||||
}
|
|
||||||
const ideClient = await IdeClient.getInstance();
|
|
||||||
return ideClient.getDetectedIdeDisplayName() ?? '';
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -7,27 +7,19 @@
|
|||||||
import type React from 'react';
|
import type React from 'react';
|
||||||
import { Box, Text } from 'ink';
|
import { Box, Text } from 'ink';
|
||||||
import { theme } from '../semantic-colors.js';
|
import { theme } from '../semantic-colors.js';
|
||||||
import { GIT_COMMIT_INFO } from '../../generated/git-commit.js';
|
import type { ExtendedSystemInfo } from '../../utils/systemInfo.js';
|
||||||
|
import {
|
||||||
|
getSystemInfoFields,
|
||||||
|
getFieldValue,
|
||||||
|
type SystemInfoField,
|
||||||
|
} from '../../utils/systemInfoFields.js';
|
||||||
|
|
||||||
interface AboutBoxProps {
|
type AboutBoxProps = ExtendedSystemInfo;
|
||||||
cliVersion: string;
|
|
||||||
osVersion: string;
|
|
||||||
sandboxEnv: string;
|
|
||||||
modelVersion: string;
|
|
||||||
selectedAuthType: string;
|
|
||||||
gcpProject: string;
|
|
||||||
ideClient: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export const AboutBox: React.FC<AboutBoxProps> = ({
|
export const AboutBox: React.FC<AboutBoxProps> = (props) => {
|
||||||
cliVersion,
|
const fields = getSystemInfoFields(props);
|
||||||
osVersion,
|
|
||||||
sandboxEnv,
|
return (
|
||||||
modelVersion,
|
|
||||||
selectedAuthType,
|
|
||||||
gcpProject,
|
|
||||||
ideClient,
|
|
||||||
}) => (
|
|
||||||
<Box
|
<Box
|
||||||
borderStyle="round"
|
borderStyle="round"
|
||||||
borderColor={theme.border.default}
|
borderColor={theme.border.default}
|
||||||
@@ -41,93 +33,20 @@ export const AboutBox: React.FC<AboutBoxProps> = ({
|
|||||||
About Qwen Code
|
About Qwen Code
|
||||||
</Text>
|
</Text>
|
||||||
</Box>
|
</Box>
|
||||||
<Box flexDirection="row">
|
{fields.map((field: SystemInfoField) => (
|
||||||
|
<Box key={field.key} flexDirection="row">
|
||||||
<Box width="35%">
|
<Box width="35%">
|
||||||
<Text bold color={theme.text.link}>
|
<Text bold color={theme.text.link}>
|
||||||
CLI Version
|
{field.label}
|
||||||
</Text>
|
|
||||||
</Box>
|
|
||||||
<Box>
|
|
||||||
<Text color={theme.text.primary}>{cliVersion}</Text>
|
|
||||||
</Box>
|
|
||||||
</Box>
|
|
||||||
{GIT_COMMIT_INFO && !['N/A'].includes(GIT_COMMIT_INFO) && (
|
|
||||||
<Box flexDirection="row">
|
|
||||||
<Box width="35%">
|
|
||||||
<Text bold color={theme.text.link}>
|
|
||||||
Git Commit
|
|
||||||
</Text>
|
|
||||||
</Box>
|
|
||||||
<Box>
|
|
||||||
<Text color={theme.text.primary}>{GIT_COMMIT_INFO}</Text>
|
|
||||||
</Box>
|
|
||||||
</Box>
|
|
||||||
)}
|
|
||||||
<Box flexDirection="row">
|
|
||||||
<Box width="35%">
|
|
||||||
<Text bold color={theme.text.link}>
|
|
||||||
Model
|
|
||||||
</Text>
|
|
||||||
</Box>
|
|
||||||
<Box>
|
|
||||||
<Text color={theme.text.primary}>{modelVersion}</Text>
|
|
||||||
</Box>
|
|
||||||
</Box>
|
|
||||||
<Box flexDirection="row">
|
|
||||||
<Box width="35%">
|
|
||||||
<Text bold color={theme.text.link}>
|
|
||||||
Sandbox
|
|
||||||
</Text>
|
|
||||||
</Box>
|
|
||||||
<Box>
|
|
||||||
<Text color={theme.text.primary}>{sandboxEnv}</Text>
|
|
||||||
</Box>
|
|
||||||
</Box>
|
|
||||||
<Box flexDirection="row">
|
|
||||||
<Box width="35%">
|
|
||||||
<Text bold color={theme.text.link}>
|
|
||||||
OS
|
|
||||||
</Text>
|
|
||||||
</Box>
|
|
||||||
<Box>
|
|
||||||
<Text color={theme.text.primary}>{osVersion}</Text>
|
|
||||||
</Box>
|
|
||||||
</Box>
|
|
||||||
<Box flexDirection="row">
|
|
||||||
<Box width="35%">
|
|
||||||
<Text bold color={theme.text.link}>
|
|
||||||
Auth Method
|
|
||||||
</Text>
|
</Text>
|
||||||
</Box>
|
</Box>
|
||||||
<Box>
|
<Box>
|
||||||
<Text color={theme.text.primary}>
|
<Text color={theme.text.primary}>
|
||||||
{selectedAuthType.startsWith('oauth') ? 'OAuth' : selectedAuthType}
|
{getFieldValue(field, props)}
|
||||||
</Text>
|
</Text>
|
||||||
</Box>
|
</Box>
|
||||||
</Box>
|
</Box>
|
||||||
{gcpProject && (
|
))}
|
||||||
<Box flexDirection="row">
|
|
||||||
<Box width="35%">
|
|
||||||
<Text bold color={theme.text.link}>
|
|
||||||
GCP Project
|
|
||||||
</Text>
|
|
||||||
</Box>
|
|
||||||
<Box>
|
|
||||||
<Text color={theme.text.primary}>{gcpProject}</Text>
|
|
||||||
</Box>
|
|
||||||
</Box>
|
|
||||||
)}
|
|
||||||
{ideClient && (
|
|
||||||
<Box flexDirection="row">
|
|
||||||
<Box width="35%">
|
|
||||||
<Text bold color={theme.text.link}>
|
|
||||||
IDE Client
|
|
||||||
</Text>
|
|
||||||
</Box>
|
|
||||||
<Box>
|
|
||||||
<Text color={theme.text.primary}>{ideClient}</Text>
|
|
||||||
</Box>
|
|
||||||
</Box>
|
|
||||||
)}
|
|
||||||
</Box>
|
</Box>
|
||||||
);
|
);
|
||||||
|
};
|
||||||
|
|||||||
@@ -71,15 +71,24 @@ describe('<HistoryItemDisplay />', () => {
|
|||||||
|
|
||||||
it('renders AboutBox for "about" type', () => {
|
it('renders AboutBox for "about" type', () => {
|
||||||
const item: HistoryItem = {
|
const item: HistoryItem = {
|
||||||
...baseItem,
|
id: 1,
|
||||||
type: MessageType.ABOUT,
|
type: MessageType.ABOUT,
|
||||||
|
systemInfo: {
|
||||||
cliVersion: '1.0.0',
|
cliVersion: '1.0.0',
|
||||||
osVersion: 'test-os',
|
osPlatform: 'test-os',
|
||||||
|
osArch: 'x64',
|
||||||
|
osRelease: '22.0.0',
|
||||||
|
nodeVersion: 'v20.0.0',
|
||||||
|
npmVersion: '10.0.0',
|
||||||
sandboxEnv: 'test-env',
|
sandboxEnv: 'test-env',
|
||||||
modelVersion: 'test-model',
|
modelVersion: 'test-model',
|
||||||
selectedAuthType: 'test-auth',
|
selectedAuthType: 'test-auth',
|
||||||
gcpProject: 'test-project',
|
|
||||||
ideClient: 'test-ide',
|
ideClient: 'test-ide',
|
||||||
|
sessionId: 'test-session-id',
|
||||||
|
memoryUsage: '100 MB',
|
||||||
|
baseUrl: undefined,
|
||||||
|
gitCommit: undefined,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
const { lastFrame } = renderWithProviders(
|
const { lastFrame } = renderWithProviders(
|
||||||
<HistoryItemDisplay {...baseItem} item={item} />,
|
<HistoryItemDisplay {...baseItem} item={item} />,
|
||||||
|
|||||||
@@ -95,15 +95,7 @@ const HistoryItemDisplayComponent: React.FC<HistoryItemDisplayProps> = ({
|
|||||||
<ErrorMessage text={itemForDisplay.text} />
|
<ErrorMessage text={itemForDisplay.text} />
|
||||||
)}
|
)}
|
||||||
{itemForDisplay.type === 'about' && (
|
{itemForDisplay.type === 'about' && (
|
||||||
<AboutBox
|
<AboutBox {...itemForDisplay.systemInfo} />
|
||||||
cliVersion={itemForDisplay.cliVersion}
|
|
||||||
osVersion={itemForDisplay.osVersion}
|
|
||||||
sandboxEnv={itemForDisplay.sandboxEnv}
|
|
||||||
modelVersion={itemForDisplay.modelVersion}
|
|
||||||
selectedAuthType={itemForDisplay.selectedAuthType}
|
|
||||||
gcpProject={itemForDisplay.gcpProject}
|
|
||||||
ideClient={itemForDisplay.ideClient}
|
|
||||||
/>
|
|
||||||
)}
|
)}
|
||||||
{itemForDisplay.type === 'help' && commands && (
|
{itemForDisplay.type === 'help' && commands && (
|
||||||
<Help commands={commands} />
|
<Help commands={commands} />
|
||||||
|
|||||||
@@ -138,13 +138,7 @@ export const useSlashCommandProcessor = (
|
|||||||
if (message.type === MessageType.ABOUT) {
|
if (message.type === MessageType.ABOUT) {
|
||||||
historyItemContent = {
|
historyItemContent = {
|
||||||
type: 'about',
|
type: 'about',
|
||||||
cliVersion: message.cliVersion,
|
systemInfo: message.systemInfo,
|
||||||
osVersion: message.osVersion,
|
|
||||||
sandboxEnv: message.sandboxEnv,
|
|
||||||
modelVersion: message.modelVersion,
|
|
||||||
selectedAuthType: message.selectedAuthType,
|
|
||||||
gcpProject: message.gcpProject,
|
|
||||||
ideClient: message.ideClient,
|
|
||||||
};
|
};
|
||||||
} else if (message.type === MessageType.HELP) {
|
} else if (message.type === MessageType.HELP) {
|
||||||
historyItemContent = {
|
historyItemContent = {
|
||||||
|
|||||||
@@ -120,13 +120,22 @@ export type HistoryItemWarning = HistoryItemBase & {
|
|||||||
|
|
||||||
export type HistoryItemAbout = HistoryItemBase & {
|
export type HistoryItemAbout = HistoryItemBase & {
|
||||||
type: 'about';
|
type: 'about';
|
||||||
|
systemInfo: {
|
||||||
cliVersion: string;
|
cliVersion: string;
|
||||||
osVersion: string;
|
osPlatform: string;
|
||||||
|
osArch: string;
|
||||||
|
osRelease: string;
|
||||||
|
nodeVersion: string;
|
||||||
|
npmVersion: string;
|
||||||
sandboxEnv: string;
|
sandboxEnv: string;
|
||||||
modelVersion: string;
|
modelVersion: string;
|
||||||
selectedAuthType: string;
|
selectedAuthType: string;
|
||||||
gcpProject: string;
|
|
||||||
ideClient: string;
|
ideClient: string;
|
||||||
|
sessionId: string;
|
||||||
|
memoryUsage: string;
|
||||||
|
baseUrl?: string;
|
||||||
|
gitCommit?: string;
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
export type HistoryItemHelp = HistoryItemBase & {
|
export type HistoryItemHelp = HistoryItemBase & {
|
||||||
@@ -288,13 +297,22 @@ export type Message =
|
|||||||
| {
|
| {
|
||||||
type: MessageType.ABOUT;
|
type: MessageType.ABOUT;
|
||||||
timestamp: Date;
|
timestamp: Date;
|
||||||
|
systemInfo: {
|
||||||
cliVersion: string;
|
cliVersion: string;
|
||||||
osVersion: string;
|
osPlatform: string;
|
||||||
|
osArch: string;
|
||||||
|
osRelease: string;
|
||||||
|
nodeVersion: string;
|
||||||
|
npmVersion: string;
|
||||||
sandboxEnv: string;
|
sandboxEnv: string;
|
||||||
modelVersion: string;
|
modelVersion: string;
|
||||||
selectedAuthType: string;
|
selectedAuthType: string;
|
||||||
gcpProject: string;
|
|
||||||
ideClient: string;
|
ideClient: string;
|
||||||
|
sessionId: string;
|
||||||
|
memoryUsage: string;
|
||||||
|
baseUrl?: string;
|
||||||
|
gitCommit?: string;
|
||||||
|
};
|
||||||
content?: string; // Optional content, not really used for ABOUT
|
content?: string; // Optional content, not really used for ABOUT
|
||||||
}
|
}
|
||||||
| {
|
| {
|
||||||
|
|||||||
331
packages/cli/src/utils/systemInfo.test.ts
Normal file
331
packages/cli/src/utils/systemInfo.test.ts
Normal file
@@ -0,0 +1,331 @@
|
|||||||
|
/**
|
||||||
|
* @license
|
||||||
|
* Copyright 2025 Qwen
|
||||||
|
* SPDX-License-Identifier: Apache-2.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { describe, it, expect, beforeEach, vi, afterEach } from 'vitest';
|
||||||
|
import {
|
||||||
|
getSystemInfo,
|
||||||
|
getExtendedSystemInfo,
|
||||||
|
getNpmVersion,
|
||||||
|
getSandboxEnv,
|
||||||
|
getIdeClientName,
|
||||||
|
} from './systemInfo.js';
|
||||||
|
import type { CommandContext } from '../ui/commands/types.js';
|
||||||
|
import { createMockCommandContext } from '../test-utils/mockCommandContext.js';
|
||||||
|
import * as child_process from 'node:child_process';
|
||||||
|
import os from 'node:os';
|
||||||
|
import { IdeClient } from '@qwen-code/qwen-code-core';
|
||||||
|
import * as versionUtils from './version.js';
|
||||||
|
import type { ExecSyncOptions } from 'node:child_process';
|
||||||
|
|
||||||
|
vi.mock('node:child_process');
|
||||||
|
|
||||||
|
vi.mock('node:os', () => ({
|
||||||
|
default: {
|
||||||
|
release: vi.fn(),
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
|
||||||
|
vi.mock('./version.js', () => ({
|
||||||
|
getCliVersion: vi.fn(),
|
||||||
|
}));
|
||||||
|
|
||||||
|
vi.mock('@qwen-code/qwen-code-core', async (importOriginal) => {
|
||||||
|
const actual =
|
||||||
|
await importOriginal<typeof import('@qwen-code/qwen-code-core')>();
|
||||||
|
return {
|
||||||
|
...actual,
|
||||||
|
IdeClient: {
|
||||||
|
getInstance: vi.fn(),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('systemInfo', () => {
|
||||||
|
let mockContext: CommandContext;
|
||||||
|
const originalPlatform = process.platform;
|
||||||
|
const originalArch = process.arch;
|
||||||
|
const originalVersion = process.version;
|
||||||
|
const originalEnv = { ...process.env };
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
mockContext = createMockCommandContext({
|
||||||
|
services: {
|
||||||
|
config: {
|
||||||
|
getModel: vi.fn().mockReturnValue('test-model'),
|
||||||
|
getIdeMode: vi.fn().mockReturnValue(true),
|
||||||
|
getSessionId: vi.fn().mockReturnValue('test-session-id'),
|
||||||
|
getContentGeneratorConfig: vi.fn().mockReturnValue({
|
||||||
|
baseUrl: 'https://api.openai.com',
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
settings: {
|
||||||
|
merged: {
|
||||||
|
security: {
|
||||||
|
auth: {
|
||||||
|
selectedType: 'test-auth',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
} as unknown as CommandContext);
|
||||||
|
|
||||||
|
vi.mocked(versionUtils.getCliVersion).mockResolvedValue('test-version');
|
||||||
|
vi.mocked(child_process.execSync).mockImplementation(
|
||||||
|
(command: string, options?: ExecSyncOptions) => {
|
||||||
|
if (
|
||||||
|
options &&
|
||||||
|
typeof options === 'object' &&
|
||||||
|
'encoding' in options &&
|
||||||
|
options.encoding === 'utf-8'
|
||||||
|
) {
|
||||||
|
return '10.0.0';
|
||||||
|
}
|
||||||
|
return Buffer.from('10.0.0', 'utf-8');
|
||||||
|
},
|
||||||
|
);
|
||||||
|
vi.mocked(os.release).mockReturnValue('22.0.0');
|
||||||
|
process.env['GOOGLE_CLOUD_PROJECT'] = 'test-gcp-project';
|
||||||
|
Object.defineProperty(process, 'platform', {
|
||||||
|
value: 'test-os',
|
||||||
|
});
|
||||||
|
Object.defineProperty(process, 'arch', {
|
||||||
|
value: 'x64',
|
||||||
|
});
|
||||||
|
Object.defineProperty(process, 'version', {
|
||||||
|
value: 'v20.0.0',
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
vi.unstubAllEnvs();
|
||||||
|
Object.defineProperty(process, 'platform', {
|
||||||
|
value: originalPlatform,
|
||||||
|
});
|
||||||
|
Object.defineProperty(process, 'arch', {
|
||||||
|
value: originalArch,
|
||||||
|
});
|
||||||
|
Object.defineProperty(process, 'version', {
|
||||||
|
value: originalVersion,
|
||||||
|
});
|
||||||
|
process.env = originalEnv;
|
||||||
|
vi.clearAllMocks();
|
||||||
|
vi.resetAllMocks();
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('getNpmVersion', () => {
|
||||||
|
it('should return npm version when available', async () => {
|
||||||
|
vi.mocked(child_process.execSync).mockImplementation(
|
||||||
|
(command: string, options?: ExecSyncOptions) => {
|
||||||
|
if (
|
||||||
|
options &&
|
||||||
|
typeof options === 'object' &&
|
||||||
|
'encoding' in options &&
|
||||||
|
options.encoding === 'utf-8'
|
||||||
|
) {
|
||||||
|
return '10.0.0';
|
||||||
|
}
|
||||||
|
return Buffer.from('10.0.0', 'utf-8');
|
||||||
|
},
|
||||||
|
);
|
||||||
|
const version = await getNpmVersion();
|
||||||
|
expect(version).toBe('10.0.0');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return unknown when npm command fails', async () => {
|
||||||
|
vi.mocked(child_process.execSync).mockImplementation(() => {
|
||||||
|
throw new Error('npm not found');
|
||||||
|
});
|
||||||
|
const version = await getNpmVersion();
|
||||||
|
expect(version).toBe('unknown');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('getSandboxEnv', () => {
|
||||||
|
it('should return "no sandbox" when SANDBOX is not set', () => {
|
||||||
|
delete process.env['SANDBOX'];
|
||||||
|
expect(getSandboxEnv()).toBe('no sandbox');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return sandbox-exec info when SANDBOX is sandbox-exec', () => {
|
||||||
|
process.env['SANDBOX'] = 'sandbox-exec';
|
||||||
|
process.env['SEATBELT_PROFILE'] = 'test-profile';
|
||||||
|
expect(getSandboxEnv()).toBe('sandbox-exec (test-profile)');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return sandbox name without prefix when stripPrefix is true', () => {
|
||||||
|
process.env['SANDBOX'] = 'qwen-code-test-sandbox';
|
||||||
|
expect(getSandboxEnv(true)).toBe('test-sandbox');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return sandbox name with prefix when stripPrefix is false', () => {
|
||||||
|
process.env['SANDBOX'] = 'qwen-code-test-sandbox';
|
||||||
|
expect(getSandboxEnv(false)).toBe('qwen-code-test-sandbox');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should handle qwen- prefix removal', () => {
|
||||||
|
process.env['SANDBOX'] = 'qwen-custom-sandbox';
|
||||||
|
expect(getSandboxEnv(true)).toBe('custom-sandbox');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('getIdeClientName', () => {
|
||||||
|
it('should return IDE client name when IDE mode is enabled', async () => {
|
||||||
|
vi.mocked(IdeClient.getInstance).mockResolvedValue({
|
||||||
|
getDetectedIdeDisplayName: vi.fn().mockReturnValue('test-ide'),
|
||||||
|
} as unknown as IdeClient);
|
||||||
|
|
||||||
|
const ideClient = await getIdeClientName(mockContext);
|
||||||
|
expect(ideClient).toBe('test-ide');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return empty string when IDE mode is disabled', async () => {
|
||||||
|
vi.mocked(mockContext.services.config!.getIdeMode).mockReturnValue(false);
|
||||||
|
|
||||||
|
const ideClient = await getIdeClientName(mockContext);
|
||||||
|
expect(ideClient).toBe('');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return empty string when IDE client detection fails', async () => {
|
||||||
|
vi.mocked(IdeClient.getInstance).mockRejectedValue(
|
||||||
|
new Error('IDE client error'),
|
||||||
|
);
|
||||||
|
|
||||||
|
const ideClient = await getIdeClientName(mockContext);
|
||||||
|
expect(ideClient).toBe('');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('getSystemInfo', () => {
|
||||||
|
it('should collect all system information', async () => {
|
||||||
|
// Ensure SANDBOX is not set for this test
|
||||||
|
delete process.env['SANDBOX'];
|
||||||
|
vi.mocked(IdeClient.getInstance).mockResolvedValue({
|
||||||
|
getDetectedIdeDisplayName: vi.fn().mockReturnValue('test-ide'),
|
||||||
|
} as unknown as IdeClient);
|
||||||
|
vi.mocked(child_process.execSync).mockImplementation(
|
||||||
|
(command: string, options?: ExecSyncOptions) => {
|
||||||
|
if (
|
||||||
|
options &&
|
||||||
|
typeof options === 'object' &&
|
||||||
|
'encoding' in options &&
|
||||||
|
options.encoding === 'utf-8'
|
||||||
|
) {
|
||||||
|
return '10.0.0';
|
||||||
|
}
|
||||||
|
return Buffer.from('10.0.0', 'utf-8');
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
const systemInfo = await getSystemInfo(mockContext);
|
||||||
|
|
||||||
|
expect(systemInfo).toEqual({
|
||||||
|
cliVersion: 'test-version',
|
||||||
|
osPlatform: 'test-os',
|
||||||
|
osArch: 'x64',
|
||||||
|
osRelease: '22.0.0',
|
||||||
|
nodeVersion: 'v20.0.0',
|
||||||
|
npmVersion: '10.0.0',
|
||||||
|
sandboxEnv: 'no sandbox',
|
||||||
|
modelVersion: 'test-model',
|
||||||
|
selectedAuthType: 'test-auth',
|
||||||
|
ideClient: 'test-ide',
|
||||||
|
sessionId: 'test-session-id',
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should handle missing config gracefully', async () => {
|
||||||
|
mockContext.services.config = null;
|
||||||
|
vi.mocked(IdeClient.getInstance).mockResolvedValue({
|
||||||
|
getDetectedIdeDisplayName: vi.fn().mockReturnValue(''),
|
||||||
|
} as unknown as IdeClient);
|
||||||
|
|
||||||
|
const systemInfo = await getSystemInfo(mockContext);
|
||||||
|
|
||||||
|
expect(systemInfo.modelVersion).toBe('Unknown');
|
||||||
|
expect(systemInfo.sessionId).toBe('unknown');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('getExtendedSystemInfo', () => {
|
||||||
|
it('should include memory usage and base URL', async () => {
|
||||||
|
vi.mocked(IdeClient.getInstance).mockResolvedValue({
|
||||||
|
getDetectedIdeDisplayName: vi.fn().mockReturnValue('test-ide'),
|
||||||
|
} as unknown as IdeClient);
|
||||||
|
vi.mocked(child_process.execSync).mockImplementation(
|
||||||
|
(command: string, options?: ExecSyncOptions) => {
|
||||||
|
if (
|
||||||
|
options &&
|
||||||
|
typeof options === 'object' &&
|
||||||
|
'encoding' in options &&
|
||||||
|
options.encoding === 'utf-8'
|
||||||
|
) {
|
||||||
|
return '10.0.0';
|
||||||
|
}
|
||||||
|
return Buffer.from('10.0.0', 'utf-8');
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
const { AuthType } = await import('@qwen-code/qwen-code-core');
|
||||||
|
// Update the mock context to use OpenAI auth
|
||||||
|
mockContext.services.settings.merged.security!.auth!.selectedType =
|
||||||
|
AuthType.USE_OPENAI;
|
||||||
|
|
||||||
|
const extendedInfo = await getExtendedSystemInfo(mockContext);
|
||||||
|
|
||||||
|
expect(extendedInfo.memoryUsage).toBeDefined();
|
||||||
|
expect(extendedInfo.memoryUsage).toMatch(/\d+\.\d+ (KB|MB|GB)/);
|
||||||
|
expect(extendedInfo.baseUrl).toBe('https://api.openai.com');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should use sandbox env without prefix for bug reports', async () => {
|
||||||
|
process.env['SANDBOX'] = 'qwen-code-test-sandbox';
|
||||||
|
vi.mocked(IdeClient.getInstance).mockResolvedValue({
|
||||||
|
getDetectedIdeDisplayName: vi.fn().mockReturnValue(''),
|
||||||
|
} as unknown as IdeClient);
|
||||||
|
vi.mocked(child_process.execSync).mockImplementation(
|
||||||
|
(command: string, options?: ExecSyncOptions) => {
|
||||||
|
if (
|
||||||
|
options &&
|
||||||
|
typeof options === 'object' &&
|
||||||
|
'encoding' in options &&
|
||||||
|
options.encoding === 'utf-8'
|
||||||
|
) {
|
||||||
|
return '10.0.0';
|
||||||
|
}
|
||||||
|
return Buffer.from('10.0.0', 'utf-8');
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
const extendedInfo = await getExtendedSystemInfo(mockContext);
|
||||||
|
|
||||||
|
expect(extendedInfo.sandboxEnv).toBe('test-sandbox');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should not include base URL for non-OpenAI auth', async () => {
|
||||||
|
vi.mocked(IdeClient.getInstance).mockResolvedValue({
|
||||||
|
getDetectedIdeDisplayName: vi.fn().mockReturnValue(''),
|
||||||
|
} as unknown as IdeClient);
|
||||||
|
vi.mocked(child_process.execSync).mockImplementation(
|
||||||
|
(command: string, options?: ExecSyncOptions) => {
|
||||||
|
if (
|
||||||
|
options &&
|
||||||
|
typeof options === 'object' &&
|
||||||
|
'encoding' in options &&
|
||||||
|
options.encoding === 'utf-8'
|
||||||
|
) {
|
||||||
|
return '10.0.0';
|
||||||
|
}
|
||||||
|
return Buffer.from('10.0.0', 'utf-8');
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
const extendedInfo = await getExtendedSystemInfo(mockContext);
|
||||||
|
|
||||||
|
expect(extendedInfo.baseUrl).toBeUndefined();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
173
packages/cli/src/utils/systemInfo.ts
Normal file
173
packages/cli/src/utils/systemInfo.ts
Normal file
@@ -0,0 +1,173 @@
|
|||||||
|
/**
|
||||||
|
* @license
|
||||||
|
* Copyright 2025 Qwen
|
||||||
|
* SPDX-License-Identifier: Apache-2.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
import process from 'node:process';
|
||||||
|
import os from 'node:os';
|
||||||
|
import { execSync } from 'node:child_process';
|
||||||
|
import type { CommandContext } from '../ui/commands/types.js';
|
||||||
|
import { getCliVersion } from './version.js';
|
||||||
|
import { IdeClient, AuthType } from '@qwen-code/qwen-code-core';
|
||||||
|
import { formatMemoryUsage } from '../ui/utils/formatters.js';
|
||||||
|
import { GIT_COMMIT_INFO } from '../generated/git-commit.js';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* System information interface containing all system-related details
|
||||||
|
* that can be collected for debugging and reporting purposes.
|
||||||
|
*/
|
||||||
|
export interface SystemInfo {
|
||||||
|
cliVersion: string;
|
||||||
|
osPlatform: string;
|
||||||
|
osArch: string;
|
||||||
|
osRelease: string;
|
||||||
|
nodeVersion: string;
|
||||||
|
npmVersion: string;
|
||||||
|
sandboxEnv: string;
|
||||||
|
modelVersion: string;
|
||||||
|
selectedAuthType: string;
|
||||||
|
ideClient: string;
|
||||||
|
sessionId: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Additional system information for bug reports
|
||||||
|
*/
|
||||||
|
export interface ExtendedSystemInfo extends SystemInfo {
|
||||||
|
memoryUsage: string;
|
||||||
|
baseUrl?: string;
|
||||||
|
gitCommit?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the NPM version, handling cases where npm might not be available.
|
||||||
|
* Returns 'unknown' if npm command fails or is not found.
|
||||||
|
*/
|
||||||
|
export async function getNpmVersion(): Promise<string> {
|
||||||
|
try {
|
||||||
|
return execSync('npm --version', { encoding: 'utf-8' }).trim();
|
||||||
|
} catch {
|
||||||
|
return 'unknown';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the IDE client name if IDE mode is enabled.
|
||||||
|
* Returns empty string if IDE mode is disabled or IDE client is not detected.
|
||||||
|
*/
|
||||||
|
export async function getIdeClientName(
|
||||||
|
context: CommandContext,
|
||||||
|
): Promise<string> {
|
||||||
|
if (!context.services.config?.getIdeMode()) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
const ideClient = await IdeClient.getInstance();
|
||||||
|
return ideClient?.getDetectedIdeDisplayName() ?? '';
|
||||||
|
} catch {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the sandbox environment information.
|
||||||
|
* Handles different sandbox types including sandbox-exec and custom sandbox environments.
|
||||||
|
* For bug reports, removes 'qwen-' or 'qwen-code-' prefixes from sandbox names.
|
||||||
|
*
|
||||||
|
* @param stripPrefix - Whether to strip 'qwen-' prefix (used for bug reports)
|
||||||
|
*/
|
||||||
|
export function getSandboxEnv(stripPrefix = false): string {
|
||||||
|
const sandbox = process.env['SANDBOX'];
|
||||||
|
|
||||||
|
if (!sandbox || sandbox === 'sandbox-exec') {
|
||||||
|
if (sandbox === 'sandbox-exec') {
|
||||||
|
const profile = process.env['SEATBELT_PROFILE'] || 'unknown';
|
||||||
|
return `sandbox-exec (${profile})`;
|
||||||
|
}
|
||||||
|
return 'no sandbox';
|
||||||
|
}
|
||||||
|
|
||||||
|
// For bug reports, remove qwen- prefix
|
||||||
|
if (stripPrefix) {
|
||||||
|
return sandbox.replace(/^qwen-(?:code-)?/, '');
|
||||||
|
}
|
||||||
|
|
||||||
|
return sandbox;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Collects comprehensive system information for debugging and reporting.
|
||||||
|
* This function gathers all system-related details including OS, versions,
|
||||||
|
* sandbox environment, authentication, and session information.
|
||||||
|
*
|
||||||
|
* @param context - Command context containing config and settings
|
||||||
|
* @returns Promise resolving to SystemInfo object with all collected information
|
||||||
|
*/
|
||||||
|
export async function getSystemInfo(
|
||||||
|
context: CommandContext,
|
||||||
|
): Promise<SystemInfo> {
|
||||||
|
const osPlatform = process.platform;
|
||||||
|
const osArch = process.arch;
|
||||||
|
const osRelease = os.release();
|
||||||
|
const nodeVersion = process.version;
|
||||||
|
const npmVersion = await getNpmVersion();
|
||||||
|
const sandboxEnv = getSandboxEnv();
|
||||||
|
const modelVersion = context.services.config?.getModel() || 'Unknown';
|
||||||
|
const cliVersion = await getCliVersion();
|
||||||
|
const selectedAuthType =
|
||||||
|
context.services.settings.merged.security?.auth?.selectedType || '';
|
||||||
|
const ideClient = await getIdeClientName(context);
|
||||||
|
const sessionId = context.services.config?.getSessionId() || 'unknown';
|
||||||
|
|
||||||
|
return {
|
||||||
|
cliVersion,
|
||||||
|
osPlatform,
|
||||||
|
osArch,
|
||||||
|
osRelease,
|
||||||
|
nodeVersion,
|
||||||
|
npmVersion,
|
||||||
|
sandboxEnv,
|
||||||
|
modelVersion,
|
||||||
|
selectedAuthType,
|
||||||
|
ideClient,
|
||||||
|
sessionId,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Collects extended system information for bug reports.
|
||||||
|
* Includes all standard system info plus memory usage and optional base URL.
|
||||||
|
*
|
||||||
|
* @param context - Command context containing config and settings
|
||||||
|
* @returns Promise resolving to ExtendedSystemInfo object
|
||||||
|
*/
|
||||||
|
export async function getExtendedSystemInfo(
|
||||||
|
context: CommandContext,
|
||||||
|
): Promise<ExtendedSystemInfo> {
|
||||||
|
const baseInfo = await getSystemInfo(context);
|
||||||
|
const memoryUsage = formatMemoryUsage(process.memoryUsage().rss);
|
||||||
|
|
||||||
|
// For bug reports, use sandbox name without prefix
|
||||||
|
const sandboxEnv = getSandboxEnv(true);
|
||||||
|
|
||||||
|
// Get base URL if using OpenAI auth
|
||||||
|
const baseUrl =
|
||||||
|
baseInfo.selectedAuthType === AuthType.USE_OPENAI
|
||||||
|
? context.services.config?.getContentGeneratorConfig()?.baseUrl
|
||||||
|
: undefined;
|
||||||
|
|
||||||
|
// Get git commit info
|
||||||
|
const gitCommit =
|
||||||
|
GIT_COMMIT_INFO && !['N/A'].includes(GIT_COMMIT_INFO)
|
||||||
|
? GIT_COMMIT_INFO
|
||||||
|
: undefined;
|
||||||
|
|
||||||
|
return {
|
||||||
|
...baseInfo,
|
||||||
|
sandboxEnv,
|
||||||
|
memoryUsage,
|
||||||
|
baseUrl,
|
||||||
|
gitCommit,
|
||||||
|
};
|
||||||
|
}
|
||||||
117
packages/cli/src/utils/systemInfoFields.ts
Normal file
117
packages/cli/src/utils/systemInfoFields.ts
Normal file
@@ -0,0 +1,117 @@
|
|||||||
|
/**
|
||||||
|
* @license
|
||||||
|
* Copyright 2025 Qwen
|
||||||
|
* SPDX-License-Identifier: Apache-2.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
import type { ExtendedSystemInfo } from './systemInfo.js';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Field configuration for system information display
|
||||||
|
*/
|
||||||
|
export interface SystemInfoField {
|
||||||
|
label: string;
|
||||||
|
key: keyof ExtendedSystemInfo;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Unified field configuration for system information display.
|
||||||
|
* This ensures consistent labeling between /about and /bug commands.
|
||||||
|
*/
|
||||||
|
export function getSystemInfoFields(
|
||||||
|
info: ExtendedSystemInfo,
|
||||||
|
): SystemInfoField[] {
|
||||||
|
const allFields: SystemInfoField[] = [
|
||||||
|
{
|
||||||
|
label: 'CLI Version',
|
||||||
|
key: 'cliVersion',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Git Commit',
|
||||||
|
key: 'gitCommit',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Model',
|
||||||
|
key: 'modelVersion',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Sandbox',
|
||||||
|
key: 'sandboxEnv',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'OS Platform',
|
||||||
|
key: 'osPlatform',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'OS Arch',
|
||||||
|
key: 'osArch',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'OS Release',
|
||||||
|
key: 'osRelease',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Node.js Version',
|
||||||
|
key: 'nodeVersion',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'NPM Version',
|
||||||
|
key: 'npmVersion',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Session ID',
|
||||||
|
key: 'sessionId',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Auth Method',
|
||||||
|
key: 'selectedAuthType',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Base URL',
|
||||||
|
key: 'baseUrl',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Memory Usage',
|
||||||
|
key: 'memoryUsage',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'IDE Client',
|
||||||
|
key: 'ideClient',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
// Filter out optional fields that are not present
|
||||||
|
return allFields.filter((field) => {
|
||||||
|
const value = info[field.key];
|
||||||
|
// Optional fields: only show if they exist and are non-empty
|
||||||
|
if (
|
||||||
|
field.key === 'baseUrl' ||
|
||||||
|
field.key === 'gitCommit' ||
|
||||||
|
field.key === 'ideClient'
|
||||||
|
) {
|
||||||
|
return Boolean(value);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the value for a field from system info
|
||||||
|
*/
|
||||||
|
export function getFieldValue(
|
||||||
|
field: SystemInfoField,
|
||||||
|
info: ExtendedSystemInfo,
|
||||||
|
): string {
|
||||||
|
const value = info[field.key];
|
||||||
|
|
||||||
|
if (value === undefined || value === null) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
|
// Special formatting for selectedAuthType
|
||||||
|
if (field.key === 'selectedAuthType') {
|
||||||
|
return String(value).startsWith('oauth') ? 'OAuth' : String(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
return String(value);
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user