Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
microsoft
GitHub Repository: microsoft/vscode
Path: blob/main/src/vs/sessions/contrib/agentHost/browser/agentHostSkillButtons.ts
13401 views
1
/*---------------------------------------------------------------------------------------------
2
* Copyright (c) Microsoft Corporation. All rights reserved.
3
* Licensed under the MIT License. See License.txt in the project root for license information.
4
*--------------------------------------------------------------------------------------------*/
5
6
import { CancellationToken } from '../../../../base/common/cancellation.js';
7
import { Codicon } from '../../../../base/common/codicons.js';
8
import { Disposable } from '../../../../base/common/lifecycle.js';
9
import { ThemeIcon } from '../../../../base/common/themables.js';
10
import { localize2 } from '../../../../nls.js';
11
import { ILocalizedString } from '../../../../platform/action/common/action.js';
12
import { Action2, MenuId, registerAction2 } from '../../../../platform/actions/common/actions.js';
13
import { ContextKeyExpr, ContextKeyExpression, IContextKeyService, RawContextKey } from '../../../../platform/contextkey/common/contextkey.js';
14
import { ServicesAccessor } from '../../../../platform/instantiation/common/instantiation.js';
15
import { bindContextKey } from '../../../../platform/observable/common/platformObservableUtils.js';
16
import { IsSessionsWindowContext } from '../../../../workbench/common/contextkeys.js';
17
import { IWorkbenchContribution, registerWorkbenchContribution2, WorkbenchPhase } from '../../../../workbench/common/contributions.js';
18
import { ChatSendResult, IChatService } from '../../../../workbench/contrib/chat/common/chatService/chatService.js';
19
import { ChatAgentLocation } from '../../../../workbench/contrib/chat/common/constants.js';
20
import { ISessionsProvidersService } from '../../../services/sessions/browser/sessionsProvidersService.js';
21
import { ISessionsManagementService } from '../../../services/sessions/common/sessionsManagement.js';
22
import { ActiveSessionContextKeys, IsolationMode } from '../../changes/common/changes.js';
23
import { BaseAgentHostSessionsProvider } from './baseAgentHostSessionsProvider.js';
24
25
/**
26
* True when the active session (in the Sessions window) is provided by an
27
* agent-host sessions provider (local or remote). Used to gate the built-in
28
* skill toolbar buttons and to suppress the Copilot CLI extension's own
29
* buttons for the same sessions.
30
*/
31
export const IsAgentHostSession = new RawContextKey<boolean>('sessions.isAgentHostSession', false);
32
33
/**
34
* Binds {@link IsAgentHostSession} to the global context key service based on
35
* the active session's provider — true iff the provider is a
36
* {@link BaseAgentHostSessionsProvider}.
37
*/
38
export class IsAgentHostSessionContextContribution extends Disposable implements IWorkbenchContribution {
39
40
static readonly ID = 'sessions.contrib.agentHost.isAgentHostSession';
41
42
constructor(
43
@IContextKeyService contextKeyService: IContextKeyService,
44
@ISessionsManagementService sessionsManagementService: ISessionsManagementService,
45
@ISessionsProvidersService sessionsProvidersService: ISessionsProvidersService,
46
) {
47
super();
48
49
this._register(bindContextKey(IsAgentHostSession, contextKeyService, reader => {
50
const activeSession = sessionsManagementService.activeSession.read(reader);
51
if (!activeSession) {
52
return false;
53
}
54
const provider = sessionsProvidersService.getProvider(activeSession.providerId);
55
return provider instanceof BaseAgentHostSessionsProvider;
56
}));
57
}
58
}
59
60
registerWorkbenchContribution2(IsAgentHostSessionContextContribution.ID, IsAgentHostSessionContextContribution, WorkbenchPhase.AfterRestored);
61
62
/**
63
* Toolbar buttons in the changes view that drive the built-in agent-host skills
64
* (`merge` / `create-pr` / `create-draft-pr` / `update-pr`) for any
65
* `agent-host-*` session.
66
*
67
* They mirror the buttons the Copilot CLI extension contributes for its own
68
* `chatSessionType == copilotcli` sessions, sending the same `/<skill-name>`
69
* prompt as if the user typed it. The skills themselves are bundled into every
70
* agent-host session via the synced customization bundler picking up
71
* {@link PromptsType.skill} entries with `BUILTIN_STORAGE`.
72
*/
73
74
interface IAgentHostSkillButtonSpec {
75
readonly id: string;
76
readonly title: ILocalizedString;
77
readonly skill: string;
78
readonly icon: ThemeIcon;
79
readonly group: string;
80
readonly order: number;
81
readonly extraWhen: ContextKeyExpression | undefined;
82
}
83
84
const AGENT_HOST_SKILL_BUTTON_ID_PREFIX = 'workbench.action.agentSessions.runSkill.';
85
86
const AGENT_HOST_SKILL_BUTTONS: readonly IAgentHostSkillButtonSpec[] = [
87
{
88
id: `${AGENT_HOST_SKILL_BUTTON_ID_PREFIX}merge`,
89
title: localize2('agentSessions.runSkill.merge', "Merge Changes"),
90
skill: 'merge',
91
icon: Codicon.gitMerge,
92
group: 'merge',
93
order: 1,
94
extraWhen: ContextKeyExpr.and(
95
ActiveSessionContextKeys.IsolationMode.isEqualTo(IsolationMode.Worktree),
96
ActiveSessionContextKeys.IsMergeBaseBranchProtected.negate(),
97
ActiveSessionContextKeys.HasPullRequest.negate(),
98
ContextKeyExpr.or(ActiveSessionContextKeys.HasUncommittedChanges, ActiveSessionContextKeys.HasOutgoingChanges),
99
),
100
},
101
{
102
id: `${AGENT_HOST_SKILL_BUTTON_ID_PREFIX}createPR`,
103
title: localize2('agentSessions.runSkill.createPR', "Create Pull Request"),
104
skill: 'create-pr',
105
icon: Codicon.gitPullRequestCreate,
106
group: 'pull_request',
107
order: 1,
108
extraWhen: ContextKeyExpr.and(
109
ActiveSessionContextKeys.IsolationMode.isEqualTo(IsolationMode.Worktree),
110
ActiveSessionContextKeys.HasGitHubRemote,
111
ActiveSessionContextKeys.HasPullRequest.negate(),
112
ContextKeyExpr.or(ActiveSessionContextKeys.HasUncommittedChanges, ActiveSessionContextKeys.HasOutgoingChanges),
113
),
114
},
115
{
116
id: `${AGENT_HOST_SKILL_BUTTON_ID_PREFIX}createDraftPR`,
117
title: localize2('agentSessions.runSkill.createDraftPR', "Create Draft Pull Request"),
118
skill: 'create-draft-pr',
119
icon: Codicon.gitPullRequestDraft,
120
group: 'pull_request',
121
order: 2,
122
extraWhen: ContextKeyExpr.and(
123
ActiveSessionContextKeys.IsolationMode.isEqualTo(IsolationMode.Worktree),
124
ActiveSessionContextKeys.HasGitHubRemote,
125
ActiveSessionContextKeys.HasPullRequest.negate(),
126
ContextKeyExpr.or(ActiveSessionContextKeys.HasUncommittedChanges, ActiveSessionContextKeys.HasOutgoingChanges),
127
),
128
},
129
{
130
id: `${AGENT_HOST_SKILL_BUTTON_ID_PREFIX}updatePR`,
131
title: localize2('agentSessions.runSkill.updatePR', "Sync Pull Request"),
132
skill: 'update-pr',
133
icon: Codicon.repoPush,
134
group: 'pull_request',
135
order: 1,
136
extraWhen: ContextKeyExpr.and(
137
ActiveSessionContextKeys.IsolationMode.isEqualTo(IsolationMode.Worktree),
138
ActiveSessionContextKeys.HasGitHubRemote,
139
ActiveSessionContextKeys.HasPullRequest,
140
ActiveSessionContextKeys.HasOpenPullRequest,
141
),
142
},
143
];
144
145
/**
146
* The `update-pr` button gets the same outgoing-changes count badge styling
147
* as the Copilot CLI extension's Sync PR button. Exported so the changes
148
* view can pick it out of the toolbar without re-deriving the ID.
149
*/
150
export const AGENT_HOST_SKILL_BUTTON_UPDATE_PR_ID = `${AGENT_HOST_SKILL_BUTTON_ID_PREFIX}updatePR`;
151
152
/**
153
* True for any {@link Action2#id} created by this module. Used by the changes
154
* view to apply primary-button styling.
155
*/
156
export function isAgentHostSkillButtonId(actionId: string): boolean {
157
return actionId.startsWith(AGENT_HOST_SKILL_BUTTON_ID_PREFIX);
158
}
159
160
function registerAgentHostSkillButton(spec: IAgentHostSkillButtonSpec): void {
161
registerAction2(class extends Action2 {
162
constructor() {
163
super({
164
id: spec.id,
165
title: spec.title,
166
icon: spec.icon,
167
f1: false,
168
menu: {
169
id: MenuId.ChatEditingSessionApplySubmenu,
170
group: spec.group,
171
order: spec.order,
172
when: ContextKeyExpr.and(
173
IsSessionsWindowContext,
174
IsAgentHostSession,
175
ActiveSessionContextKeys.HasGitRepository,
176
spec.extraWhen,
177
),
178
},
179
});
180
}
181
182
async run(accessor: ServicesAccessor): Promise<void> {
183
const sessionsManagementService = accessor.get(ISessionsManagementService);
184
const chatService = accessor.get(IChatService);
185
186
const activeSession = sessionsManagementService.activeSession.get();
187
if (!activeSession) {
188
return;
189
}
190
191
// `activeSession.resource.scheme` matches the chat session
192
// contribution `type` registered for the agent-host (e.g.
193
// `agent-host-copilotcli`), which is the agent id the chat
194
// service uses for routing. The `sessionType` field is the
195
// logical, user-facing id (e.g. `copilotcli`) that is shared
196
// between the Copilot CLI extension and the local/remote
197
// agent-host providers, so it is NOT a valid agent id here.
198
const agentId = activeSession.resource.scheme;
199
const prompt = `/${spec.skill}`;
200
const ref = await chatService.acquireOrLoadSession(activeSession.resource, ChatAgentLocation.Chat, CancellationToken.None, 'AgentHostSkillButton');
201
try {
202
let result = await chatService.sendRequest(activeSession.resource, prompt, { agentIdSilent: agentId });
203
if (ChatSendResult.isQueued(result)) {
204
result = await result.deferred;
205
}
206
if (ChatSendResult.isSent(result)) {
207
await result.data.responseCompletePromise;
208
}
209
} finally {
210
ref?.dispose();
211
}
212
}
213
});
214
}
215
216
for (const spec of AGENT_HOST_SKILL_BUTTONS) {
217
registerAgentHostSkillButton(spec);
218
}
219
220