mirror of
https://github.com/microsoft/vscode.git
synced 2026-09-30 18:46:00 +01:00
chat: Add setting for subagent credit usage (#334384)
* chat: Add setting for subagent credit usage Allow users to hide subagent credit usage from the pill, tooltip, and accessible label while preserving the existing default. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * chat: Default subagent credit usage to hidden Keep credit usage opt-in and cover the tooltip alongside the visible and accessible presentation paths. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --------- Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
@@ -10,6 +10,9 @@ import { Event } from '../../../../../../base/common/event.js';
|
||||
import { Disposable } from '../../../../../../base/common/lifecycle.js';
|
||||
import { observableValue } from '../../../../../../base/common/observable.js';
|
||||
import { ensureNoDisposablesAreLeakedInTestSuite } from '../../../../../../base/test/common/utils.js';
|
||||
import { IConfigurationService } from '../../../../../../platform/configuration/common/configuration.js';
|
||||
import { TestConfigurationService } from '../../../../../../platform/configuration/test/common/testConfigurationService.js';
|
||||
import { ChatConfiguration } from '../../../../../../workbench/contrib/chat/common/constants.js';
|
||||
import { ILanguageModelsService } from '../../../../../../workbench/contrib/chat/common/languageModels.js';
|
||||
import { workbenchInstantiationService } from '../../../../../../workbench/test/browser/workbenchTestServices.js';
|
||||
import { ISessionsService } from '../../../../../services/sessions/browser/sessionsService.js';
|
||||
@@ -235,8 +238,9 @@ suite('OpenSubagentChatActionViewItem', () => {
|
||||
});
|
||||
});
|
||||
|
||||
test('renders the credit cost alongside the model', () => {
|
||||
test('renders the credit cost alongside the model when enabled', () => {
|
||||
const instantiationService = workbenchInstantiationService(undefined, store);
|
||||
(instantiationService.get(IConfigurationService) as TestConfigurationService).setUserConfiguration(ChatConfiguration.SubagentsShowCreditUsage, true);
|
||||
instantiationService.stub(ISessionsService, {
|
||||
activeSession: observableValue<IActiveSession | undefined>('activeSession', undefined),
|
||||
visibleSessions: observableValue<readonly (IActiveSession | undefined)[]>('visibleSessions', []),
|
||||
|
||||
@@ -2450,6 +2450,11 @@ configurationRegistry.registerConfiguration({
|
||||
description: nls.localize('chat.subagents.useRichRendering', "Controls whether subagents in chat editors use a rich presentation that opens each subagent in its own editor instead of rendering its full activity inline in the parent chat."),
|
||||
default: true,
|
||||
},
|
||||
[ChatConfiguration.SubagentsShowCreditUsage]: {
|
||||
type: 'boolean',
|
||||
description: nls.localize('chat.subagents.showCreditUsage', "Controls whether AI credit usage is shown next to the duration for subagents."),
|
||||
default: false,
|
||||
},
|
||||
[ChatConfiguration.TerminalAgentHostEnabled]: {
|
||||
type: 'boolean',
|
||||
description: nls.localize('chat.terminal.agentHost.enabled', "Controls whether Terminal Chat is backed by the Agent Host instead of the extension host. Applied on startup."),
|
||||
|
||||
+21
-4
@@ -21,6 +21,7 @@ import { IAccessibilityService } from '../../../../../../platform/accessibility/
|
||||
import { IActionViewItemService } from '../../../../../../platform/actions/browser/actionViewItemService.js';
|
||||
import { Action2, MenuId, MenuItemAction, registerAction2 } from '../../../../../../platform/actions/common/actions.js';
|
||||
import { parseChatUri } from '../../../../../../platform/agentHost/common/state/sessionState.js';
|
||||
import { IConfigurationService } from '../../../../../../platform/configuration/common/configuration.js';
|
||||
import { IHoverService } from '../../../../../../platform/hover/browser/hover.js';
|
||||
import { IInstantiationService, ServicesAccessor } from '../../../../../../platform/instantiation/common/instantiation.js';
|
||||
import { IMarkdownRendererService } from '../../../../../../platform/markdown/browser/markdownRenderer.js';
|
||||
@@ -30,7 +31,7 @@ import { ACTIVE_GROUP } from '../../../../../services/editor/common/editorServic
|
||||
import { IWorkbenchEnvironmentService } from '../../../../../services/environment/common/environmentService.js';
|
||||
import { formatElapsedTime } from '../../../common/chatProgressFormatting.js';
|
||||
import { formatCopilotCreditsLabel } from '../../../common/chatService/chatService.js';
|
||||
import { CHAT_OPEN_AGENT_HOST_CHAT_COMMAND_ID, CHAT_SUBAGENT_RESOURCE_QUERY_PARAM } from '../../../common/constants.js';
|
||||
import { CHAT_OPEN_AGENT_HOST_CHAT_COMMAND_ID, CHAT_SUBAGENT_RESOURCE_QUERY_PARAM, ChatConfiguration } from '../../../common/constants.js';
|
||||
import { AUTO_RAW_MODEL_ID, ILanguageModelsService } from '../../../common/languageModels.js';
|
||||
import { IChatWidgetService } from '../../chat.js';
|
||||
import { getChatMarkdownRenderOptions } from '../chatContentMarkdownRenderer.js';
|
||||
@@ -256,6 +257,7 @@ export class OpenSubagentChatActionViewItem extends BaseActionViewItem {
|
||||
@INotificationService notificationService: INotificationService,
|
||||
@ILanguageModelsService private readonly languageModelsService: ILanguageModelsService,
|
||||
@IHoverService private readonly hoverService: IHoverService,
|
||||
@IConfigurationService private readonly configurationService: IConfigurationService,
|
||||
) {
|
||||
super(context, openInEditor ? createEditorOpenSubagentAction(action, chatWidgetService, notificationService) : createOpenSubagentAction(action), options);
|
||||
this._sourceAction = action;
|
||||
@@ -268,6 +270,12 @@ export class OpenSubagentChatActionViewItem extends BaseActionViewItem {
|
||||
this._finishToolTransition();
|
||||
}
|
||||
}));
|
||||
this._register(this.configurationService.onDidChangeConfiguration(event => {
|
||||
if (event.affectsConfiguration(ChatConfiguration.SubagentsShowCreditUsage)) {
|
||||
this._updateCredits();
|
||||
this.updateTooltip();
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
override render(container: HTMLElement): void {
|
||||
@@ -423,14 +431,23 @@ export class OpenSubagentChatActionViewItem extends BaseActionViewItem {
|
||||
|
||||
private _setCredits(credits: number | undefined): void {
|
||||
// Zero-cost subagents report 0 rather than nothing, so normalize both to hidden.
|
||||
const show = typeof credits === 'number' && credits > 0;
|
||||
this._reportedCredits = show ? credits : undefined;
|
||||
this._reportedCredits = typeof credits === 'number' && credits > 0 ? credits : undefined;
|
||||
this._updateCredits();
|
||||
}
|
||||
|
||||
private _updateCredits(): void {
|
||||
const credits = this._reportedCredits;
|
||||
const show = credits !== undefined && this._showCreditUsage;
|
||||
if (this._creditsElement) {
|
||||
this._creditsElement.textContent = show ? formatCopilotCreditsLabel(credits) : '';
|
||||
this._creditsElement.classList.toggle('hidden', !show);
|
||||
}
|
||||
}
|
||||
|
||||
private get _showCreditUsage(): boolean {
|
||||
return this.configurationService.getValue<boolean>(ChatConfiguration.SubagentsShowCreditUsage) === true;
|
||||
}
|
||||
|
||||
private _setAgentType(agentType: string | undefined): void {
|
||||
this._reportedAgentType = agentType;
|
||||
if (this._agentTypeElement) {
|
||||
@@ -662,7 +679,7 @@ export class OpenSubagentChatActionViewItem extends BaseActionViewItem {
|
||||
if (this._reportedModelName) {
|
||||
details.push(localize('chat.subagent.modelTooltip', "Model: {0}", this._reportedModelName));
|
||||
}
|
||||
if (this._reportedCredits !== undefined) {
|
||||
if (this._reportedCredits !== undefined && this._showCreditUsage) {
|
||||
details.push(formatCopilotCreditsLabel(this._reportedCredits));
|
||||
}
|
||||
if (this._displayedToolAccessibleLabel && this._displayedActivityIsTool) {
|
||||
|
||||
@@ -94,6 +94,7 @@ export enum ChatConfiguration {
|
||||
SubagentsAllowInvocationsFromSubagents = 'chat.subagents.allowInvocationsFromSubagents',
|
||||
SubagentsDefaultToAuto = 'chat.subagents.defaultToAuto',
|
||||
SubagentsUseRichRendering = 'chat.subagents.useRichRendering',
|
||||
SubagentsShowCreditUsage = 'chat.subagents.showCreditUsage',
|
||||
ShowCodeBlockProgressAnimation = 'chat.agent.codeBlockProgress',
|
||||
RestoreLastPanelSession = 'chat.restoreLastPanelSession',
|
||||
ExitAfterDelegation = 'chat.exitAfterDelegation',
|
||||
|
||||
+81
-1
@@ -30,7 +30,7 @@ import { IRenderedMarkdown, MarkdownRenderOptions } from '../../../../../../../b
|
||||
import { IMarkdownString, isMarkdownString } from '../../../../../../../base/common/htmlContent.js';
|
||||
import { EditorPool, DiffEditorPool } from '../../../../browser/widget/chatContentParts/chatContentCodePools.js';
|
||||
import { IHoverService } from '../../../../../../../platform/hover/browser/hover.js';
|
||||
import { IConfigurationService } from '../../../../../../../platform/configuration/common/configuration.js';
|
||||
import { ConfigurationTarget, IConfigurationChangeEvent, IConfigurationService } from '../../../../../../../platform/configuration/common/configuration.js';
|
||||
import { TestConfigurationService } from '../../../../../../../platform/configuration/test/common/testConfigurationService.js';
|
||||
import { AccessibilityWorkbenchSettingId } from '../../../../../accessibility/browser/accessibilityConfiguration.js';
|
||||
import { URI } from '../../../../../../../base/common/uri.js';
|
||||
@@ -55,6 +55,12 @@ class TestOpenChatActionViewItem extends ActionViewItem {
|
||||
}
|
||||
}
|
||||
|
||||
class TestOpenSubagentChatActionViewItem extends OpenSubagentChatActionViewItem {
|
||||
get tooltip(): string | undefined {
|
||||
return this.getTooltip();
|
||||
}
|
||||
}
|
||||
|
||||
class TestActionViewItemService implements IActionViewItemService {
|
||||
declare _serviceBrand: undefined;
|
||||
private readonly _onDidChange = new Emitter<MenuId>();
|
||||
@@ -340,6 +346,7 @@ suite('ChatSubagentContentPart', () => {
|
||||
));
|
||||
instantiationService.stub(IMenuService, menuService);
|
||||
(instantiationService.get(IConfigurationService) as TestConfigurationService).setUserConfiguration(ChatConfiguration.SubagentsUseRichRendering, true);
|
||||
(instantiationService.get(IConfigurationService) as TestConfigurationService).setUserConfiguration(ChatConfiguration.SubagentsShowCreditUsage, true);
|
||||
|
||||
// Mock list pool and editor pool
|
||||
mockListPool = {} as CollapsibleListPool;
|
||||
@@ -524,6 +531,79 @@ suite('ChatSubagentContentPart', () => {
|
||||
});
|
||||
});
|
||||
|
||||
test('should update displayed and accessible credit usage when the setting changes', () => {
|
||||
const configService = instantiationService.get(IConfigurationService) as TestConfigurationService;
|
||||
const setShowCreditUsage = (value: boolean) => {
|
||||
configService.setUserConfiguration(ChatConfiguration.SubagentsShowCreditUsage, value);
|
||||
configService.onDidChangeConfigurationEmitter.fire({
|
||||
source: ConfigurationTarget.USER,
|
||||
affectedKeys: new Set([ChatConfiguration.SubagentsShowCreditUsage]),
|
||||
change: { keys: [ChatConfiguration.SubagentsShowCreditUsage], overrides: [] },
|
||||
affectsConfiguration: key => key === ChatConfiguration.SubagentsShowCreditUsage,
|
||||
} satisfies IConfigurationChangeEvent);
|
||||
};
|
||||
const action = store.add(new Action('openSubagent', 'Open Subagent'));
|
||||
const viewItem = store.add(instantiationService.createInstance(
|
||||
TestOpenSubagentChatActionViewItem,
|
||||
{
|
||||
chatResource: 'ahp-chat://subagent/Y29waWxvdGNsaTovc2Vzc2lvbg/tool-call',
|
||||
parentSessionResource: 'agent-host-copilotcli:/session',
|
||||
startedAt: 1_000,
|
||||
duration: 65_000,
|
||||
credits: 2.5,
|
||||
},
|
||||
action,
|
||||
{},
|
||||
false,
|
||||
));
|
||||
const container = mainWindow.document.createElement('div');
|
||||
viewItem.render(container);
|
||||
const credits = container.querySelector('.chat-subagent-pill-credits');
|
||||
const before = {
|
||||
text: credits?.textContent,
|
||||
hidden: credits?.classList.contains('hidden'),
|
||||
tooltip: viewItem.tooltip,
|
||||
ariaLabel: container.getAttribute('aria-label'),
|
||||
};
|
||||
|
||||
setShowCreditUsage(false);
|
||||
const hidden = {
|
||||
text: credits?.textContent,
|
||||
hidden: credits?.classList.contains('hidden'),
|
||||
tooltip: viewItem.tooltip,
|
||||
ariaLabel: container.getAttribute('aria-label'),
|
||||
};
|
||||
|
||||
setShowCreditUsage(true);
|
||||
const restored = {
|
||||
text: credits?.textContent,
|
||||
hidden: credits?.classList.contains('hidden'),
|
||||
tooltip: viewItem.tooltip,
|
||||
ariaLabel: container.getAttribute('aria-label'),
|
||||
};
|
||||
|
||||
assert.deepStrictEqual({ before, hidden, restored }, {
|
||||
before: {
|
||||
text: '2.5 credits',
|
||||
hidden: false,
|
||||
tooltip: 'Open Subagent\n2.5 credits',
|
||||
ariaLabel: 'Open Subagent. Worked for 1m 5s. 2.5 credits',
|
||||
},
|
||||
hidden: {
|
||||
text: '',
|
||||
hidden: true,
|
||||
tooltip: 'Open Subagent',
|
||||
ariaLabel: 'Open Subagent. Worked for 1m 5s',
|
||||
},
|
||||
restored: {
|
||||
text: '2.5 credits',
|
||||
hidden: false,
|
||||
tooltip: 'Open Subagent\n2.5 credits',
|
||||
ariaLabel: 'Open Subagent. Worked for 1m 5s. 2.5 credits',
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
test('should render the specialized subagent type before the title', () => {
|
||||
const action = store.add(new Action('openSubagent', 'Open Subagent'));
|
||||
const viewItem = store.add(instantiationService.createInstance(
|
||||
|
||||
Reference in New Issue
Block a user