Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 3 additions & 15 deletions src/vs/sessions/contrib/chat/browser/agentHostInputCompletions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ import { CompletionItem, CompletionItemKind } from '../../../../editor/common/la
import { ITextModel } from '../../../../editor/common/model.js';
import { ILanguageFeaturesService } from '../../../../editor/common/services/languageFeatures.js';
import { CommandsRegistry } from '../../../../platform/commands/common/commands.js';
import { AgentHostCompletionReferenceKind, agentHostCompletionVariableValue, IChatRequestVariableEntry, isAgentHostCompletionVariableEntry } from '../../../../workbench/contrib/chat/common/attachments/chatVariableEntries.js';
import { AgentHostCompletionReferenceKind, IChatRequestVariableEntry, isAgentHostCompletionVariableEntry, toAgentHostCompletionVariableEntry } from '../../../../workbench/contrib/chat/common/attachments/chatVariableEntries.js';
import { IChatInputCompletionItem, IChatSessionsService, isAgentHostTarget } from '../../../../workbench/contrib/chat/common/chatSessionsService.js';
import { getChatSessionType } from '../../../../workbench/contrib/chat/common/model/chatUri.js';
import { chatSlashCommandBackground, chatSlashCommandForeground } from '../../../../workbench/contrib/chat/common/widget/chatColors.js';
Expand Down Expand Up @@ -199,13 +199,7 @@ export class AgentHostInputCompletionHandler extends AgentHostInputCompletionsBa
switch (attachment.kind) {
case 'command': {
const referenceText = item.insertText.trimEnd();
const entry: IChatRequestVariableEntry = {
id: 'agent-host-command:' + attachment.command,
name: referenceText,
value: agentHostCompletionVariableValue(AgentHostCompletionReferenceKind.Command),
kind: 'generic',
_meta: attachment._meta,
};
const entry = toAgentHostCompletionVariableEntry(AgentHostCompletionReferenceKind.Command, referenceText, attachment.command, attachment._meta);
return {
label: item.insertText,
insertText: item.insertText,
Expand All @@ -227,13 +221,7 @@ export class AgentHostInputCompletionHandler extends AgentHostInputCompletionsBa
}
case 'skill': {
const referenceText = item.insertText.trimEnd();
const entry: IChatRequestVariableEntry = {
id: attachment.uri.toString(),
name: referenceText,
value: agentHostCompletionVariableValue(AgentHostCompletionReferenceKind.Skill),
kind: 'generic',
_meta: attachment._meta,
};
const entry = toAgentHostCompletionVariableEntry(AgentHostCompletionReferenceKind.Skill, referenceText, attachment.uri, attachment._meta);
return {
label: { label: item.insertText, description: attachment.description },
insertText: item.insertText,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ import { type ChatExternalEditKind, type IChatExternalEdit, type IChatModifiedFi
import { type IChatSessionHistoryItem } from '../../../common/chatSessionsService.js';
import { ChatToolInvocation } from '../../../common/model/chatProgressTypes/chatToolInvocation.js';
import { type IChatRequestVariableData } from '../../../common/model/chatModel.js';
import type { IChatRequestVariableEntry } from '../../../common/attachments/chatVariableEntries.js';
import { AgentHostCompletionReferenceKind, toAgentHostCompletionVariableEntryFromMetadata, type IChatRequestVariableEntry } from '../../../common/attachments/chatVariableEntries.js';
import { type IToolConfirmationMessages, type IToolData, type IToolResult, type IToolResultInputOutputDetails, ToolDataSource, ToolInvocationPresentation } from '../../../common/tools/languageModelToolsService.js';
import { basename, isEqual } from '../../../../../../base/common/resources.js';
import { hasKey } from '../../../../../../base/common/types.js';
Expand Down Expand Up @@ -320,6 +320,11 @@ function messageAttachmentToVariableEntry(attachment: MessageAttachment, connect
};
}

const agentHostCompletionKind = getAgentHostCompletionKind(attachment);
if (agentHostCompletionKind !== undefined) {
return toAgentHostCompletionVariableEntryFromMetadata(agentHostCompletionKind, attachment.label, attachment._meta);
}

const modelRepresentation = attachment.type === MessageAttachmentKind.Simple ? attachment.modelRepresentation : undefined;
return {
kind: 'generic',
Expand All @@ -330,6 +335,19 @@ function messageAttachmentToVariableEntry(attachment: MessageAttachment, connect
};
}

function getAgentHostCompletionKind(attachment: MessageAttachment): AgentHostCompletionReferenceKind | undefined {
if (attachment.type !== MessageAttachmentKind.Simple) {
return undefined;
}
switch (attachment.displayKind) {
case 'command':
return AgentHostCompletionReferenceKind.Command;
case 'skill':
return AgentHostCompletionReferenceKind.Skill;
}
return undefined;
}

function textRangeToIRange(range: TextRange): IRange {
return {
startLineNumber: range.start.line + 1,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import { URI } from '../../../../../../base/common/uri.js';
import { Range } from '../../../../../../editor/common/core/range.js';
import { IInstantiationService } from '../../../../../../platform/instantiation/common/instantiation.js';
import { ResourceLabels } from '../../../../../browser/labels.js';
import { getImageAttachmentLimit, IChatRequestVariableEntry, isBrowserViewVariableEntry, isElementVariableEntry, isImageVariableEntry, isNotebookOutputVariableEntry, isPasteVariableEntry, isPromptFileVariableEntry, isPromptTextVariableEntry, isSCMHistoryItemChangeRangeVariableEntry, isSCMHistoryItemChangeVariableEntry, isSCMHistoryItemVariableEntry, isTerminalVariableEntry, isWorkspaceVariableEntry, OmittedState } from '../../../common/attachments/chatVariableEntries.js';
import { getImageAttachmentLimit, IChatRequestVariableEntry, isAgentHostCompletionVariableEntry, isBrowserViewVariableEntry, isElementVariableEntry, isImageVariableEntry, isNotebookOutputVariableEntry, isPasteVariableEntry, isPromptFileVariableEntry, isPromptTextVariableEntry, isSCMHistoryItemChangeRangeVariableEntry, isSCMHistoryItemChangeVariableEntry, isSCMHistoryItemVariableEntry, isTerminalVariableEntry, isWorkspaceVariableEntry, OmittedState } from '../../../common/attachments/chatVariableEntries.js';
import { ChatResponseReferencePartStatusKind, IChatContentReference } from '../../../common/chatService/chatService.js';
import { ILanguageModelsService } from '../../../common/languageModels.js';
import { DefaultChatAttachmentWidget, ElementChatAttachmentWidget, FileAttachmentWidget, ImageAttachmentWidget, BrowserViewAttachmentWidget, NotebookCellOutputChatAttachmentWidget, PasteAttachmentWidget, PromptFileAttachmentWidget, PromptTextAttachmentWidget, SCMHistoryItemAttachmentWidget, SCMHistoryItemChangeAttachmentWidget, SCMHistoryItemChangeRangeAttachmentWidget, TerminalCommandAttachmentWidget, ToolSetOrToolItemAttachmentWidget } from '../../attachments/chatAttachmentWidgets.js';
Expand Down Expand Up @@ -75,8 +75,10 @@ export class ChatAttachmentsContentPart extends Disposable {
dom.clearNode(container);
this.attachedContextDisposables.clear();

const visibleAttachments = this.getVisibleAttachments();
const hasMoreAttachments = this.limit && this._variables.length > this.limit && !this._showingAll;
const renderableAttachments = this.getRenderableAttachments();
const visibleAttachments = this.getVisibleAttachments(renderableAttachments);
const remainingCount = renderableAttachments.length - visibleAttachments.length;
const hasMoreAttachments = remainingCount > 0 && !this._showingAll;

this.markImageLimitExceeded(this._variables);

Expand All @@ -85,15 +87,19 @@ export class ChatAttachmentsContentPart extends Disposable {
}

if (hasMoreAttachments) {
this.renderShowMoreButton(container);
this.renderShowMoreButton(container, remainingCount);
}
Comment thread
DonJayamanne marked this conversation as resolved.
}

private getVisibleAttachments(): readonly IChatRequestVariableEntry[] {
private getRenderableAttachments(): readonly IChatRequestVariableEntry[] {
return this._variables.filter(attachment => !isAgentHostCompletionVariableEntry(attachment));
}

private getVisibleAttachments(visibleAttachments: readonly IChatRequestVariableEntry[]): readonly IChatRequestVariableEntry[] {
if (!this.limit || this._showingAll) {
return this._variables;
return visibleAttachments;
}
return this._variables.slice(0, this.limit);
return visibleAttachments.slice(0, this.limit);
}

/**
Expand Down Expand Up @@ -145,9 +151,7 @@ export class ChatAttachmentsContentPart extends Disposable {
return getImageAttachmentLimit(this.languageModelsService.lookupLanguageModel(this.modelId));
}

private renderShowMoreButton(container: HTMLElement) {
const remainingCount = this._variables.length - (this.limit ?? 0);

private renderShowMoreButton(container: HTMLElement, remainingCount: number) {
// Create a button that looks like the attachment pills
const showMoreButton = dom.$('div.chat-attached-context-attachment.chat-attachments-show-more-button');
showMoreButton.setAttribute('role', 'button');
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import { DisposableMap } from '../../../../../../../base/common/lifecycle.js';
import { Schemas } from '../../../../../../../base/common/network.js';
import { assertType } from '../../../../../../../base/common/types.js';
import { URI } from '../../../../../../../base/common/uri.js';
import { AgentHostCompletionReferenceKind, agentHostCompletionVariableValue, type IAgentHostCompletionVariableValue } from '../../../../common/attachments/chatVariableEntries.js';
import { AgentHostCompletionReferenceKind, toAgentHostCompletionVariableEntry, type IAgentHostCompletionVariableValue } from '../../../../common/attachments/chatVariableEntries.js';
import { Position } from '../../../../../../../editor/common/core/position.js';
import { Range } from '../../../../../../../editor/common/core/range.js';
import { CompletionItem, CompletionItemKind } from '../../../../../../../editor/common/languages.js';
Expand Down Expand Up @@ -199,11 +199,13 @@ class AgentHostReferenceArgument {
}

static forSkill(widget: IChatWidget, uri: URI, displayName: string | undefined, range: Range, _meta: Record<string, unknown> | undefined): AgentHostReferenceArgument {
return new AgentHostReferenceArgument(widget, uri.toString(), agentHostCompletionVariableValue(AgentHostCompletionReferenceKind.Skill), displayName, false, false, range, _meta);
const entry = toAgentHostCompletionVariableEntry(AgentHostCompletionReferenceKind.Skill, displayName ?? uri.toString(), uri, _meta);
return new AgentHostReferenceArgument(widget, entry.id, entry.value, displayName, false, false, range, _meta);
}

static forCommand(widget: IChatWidget, command: string, description: string | undefined, range: Range, _meta: Record<string, unknown> | undefined): AgentHostReferenceArgument {
return new AgentHostReferenceArgument(widget, 'agent-host-command:' + command, agentHostCompletionVariableValue(AgentHostCompletionReferenceKind.Command), description, false, false, range, _meta);
const entry = toAgentHostCompletionVariableEntry(AgentHostCompletionReferenceKind.Command, description ?? command, command, _meta);
return new AgentHostReferenceArgument(widget, entry.id, entry.value, description, false, false, range, _meta);
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import { IMarkdownString } from '../../../../../base/common/htmlContent.js';
import { basename } from '../../../../../base/common/resources.js';
import { ThemeIcon } from '../../../../../base/common/themables.js';
import { URI } from '../../../../../base/common/uri.js';
import { generateUuid } from '../../../../../base/common/uuid.js';
import { IRange } from '../../../../../editor/common/core/range.js';
import { IOffsetRange } from '../../../../../editor/common/core/ranges/offsetRange.js';
import { isLocation, Location, SymbolKind } from '../../../../../editor/common/languages.js';
Expand Down Expand Up @@ -63,10 +64,38 @@ export interface IAgentHostCompletionVariableValue {
readonly kind: AgentHostCompletionReferenceKind;
}

export function agentHostCompletionVariableValue(kind: AgentHostCompletionReferenceKind): IAgentHostCompletionVariableValue {
function agentHostCompletionVariableValue(kind: AgentHostCompletionReferenceKind): IAgentHostCompletionVariableValue {
return { $mid: 'agentHostCompletion', kind };
}

function agentHostCompletionVariableId(kind: AgentHostCompletionReferenceKind, reference: URI | string): string {
switch (kind) {
case AgentHostCompletionReferenceKind.Skill:
return reference.toString();
case AgentHostCompletionReferenceKind.Command:
return 'agent-host-command:' + reference.toString();
}
}

export function toAgentHostCompletionVariableEntry(kind: AgentHostCompletionReferenceKind, name: string, reference: URI | string | undefined, _meta: Record<string, unknown> | undefined): IGenericChatRequestVariableEntry & { value: IAgentHostCompletionVariableValue } {
return {
kind: 'generic',
id: reference !== undefined ? agentHostCompletionVariableId(kind, reference) : generateUuid(),
name,
value: agentHostCompletionVariableValue(kind),
_meta,
};
}

export function toAgentHostCompletionVariableEntryFromMetadata(kind: AgentHostCompletionReferenceKind, name: string, _meta: Record<string, unknown> | undefined): IGenericChatRequestVariableEntry & { value: IAgentHostCompletionVariableValue } {
switch (kind) {
case AgentHostCompletionReferenceKind.Skill:
return toAgentHostCompletionVariableEntry(kind, name, typeof _meta?.uri === 'string' ? _meta.uri : undefined, _meta);
case AgentHostCompletionReferenceKind.Command:
return toAgentHostCompletionVariableEntry(kind, name, typeof _meta?.command === 'string' ? _meta.command : undefined, _meta);
}
}

export function getAgentHostCompletionReferenceKind(entry: IChatRequestVariableEntry): AgentHostCompletionReferenceKind | undefined {
if (entry.kind !== 'generic' || typeof entry.value !== 'object' || entry.value === null) {
return undefined;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ import { ChatQuestionCarouselData } from '../../../common/model/chatProgressType
import { ChatElicitationRequestPart } from '../../../common/model/chatProgressTypes/chatElicitationRequestPart.js';
import type { IChatModel, IChatPendingRequest, IChatRequestModel } from '../../../common/model/chatModel.js';
import { convertBufferToScreenshotVariable } from '../../../browser/attachments/chatScreenshotContext.js';
import { AgentHostCompletionReferenceKind, agentHostCompletionVariableValue } from '../../../common/attachments/chatVariableEntries.js';
import { AgentHostCompletionReferenceKind, toAgentHostCompletionVariableEntry } from '../../../common/attachments/chatVariableEntries.js';

// ---- Mock agent host service ------------------------------------------------

Expand Down Expand Up @@ -773,20 +773,12 @@ suite('AgentHostChatContribution', () => {
variables: {
variables: [
{
kind: 'generic',
id: 'file:///skills/author-contributions/SKILL.md',
name: '/author-contributions',
value: agentHostCompletionVariableValue(AgentHostCompletionReferenceKind.Skill),
...toAgentHostCompletionVariableEntry(AgentHostCompletionReferenceKind.Skill, '/author-contributions', 'file:///skills/author-contributions/SKILL.md', skillMeta),
range: { start: skillStart, endExclusive: skillStart + '/author-contributions'.length },
_meta: skillMeta,
},
{
kind: 'generic',
id: 'agent-host-command:rename',
name: '/rename',
value: agentHostCompletionVariableValue(AgentHostCompletionReferenceKind.Command),
...toAgentHostCompletionVariableEntry(AgentHostCompletionReferenceKind.Command, '/rename', 'rename', commandMeta),
range: { start: commandStart, endExclusive: commandStart + '/rename'.length },
_meta: commandMeta,
},
],
},
Expand Down Expand Up @@ -2552,6 +2544,68 @@ suite('AgentHostChatContribution', () => {
}
});

test('restores agent host completion attachments as hidden request variables', async () => {
const { sessionHandler, agentHostService } = createContribution(disposables);
const sessionResource = URI.from({ scheme: 'agent-host-copilot', path: '/completion-history' });
const sessionUri = AgentSession.uri('copilot', 'completion-history');
const skillMeta = {
uri: 'file:///skills/agent-host-docs/SKILL.md',
displayName: 'agent-host-docs',
description: 'Use this skill when working on Agent Host code',
};
const commandMeta = {
command: 'rename',
description: 'Rename this chat',
};

agentHostService.sessionStates.set(sessionUri.toString(), {
...createSessionState({ resource: sessionUri.toString(), provider: 'copilot', title: 'Test', status: SessionStatus.Idle, createdAt: Date.now(), modifiedAt: Date.now() }),
lifecycle: SessionLifecycle.Ready,
turns: [{
id: 'turn-1',
message: {
text: '/agent-host-docs please check this\n/rename Title',
origin: { kind: MessageKind.User },
attachments: [
{
type: MessageAttachmentKind.Simple,
label: '/agent-host-docs',
displayKind: 'skill',
_meta: skillMeta,
},
{
type: MessageAttachmentKind.Simple,
label: '/rename',
displayKind: 'command',
_meta: commandMeta,
},
],
},
responseParts: [],
usage: undefined,
state: TurnState.Complete,
}],
} as SessionState);

const session = await sessionHandler.provideChatSessionContent(sessionResource, CancellationToken.None);
disposables.add(toDisposable(() => session.dispose()));

const request = session.history[0];
assert.strictEqual(request.type, 'request');
if (request.type === 'request') {
assert.deepStrictEqual(request.variableData?.variables.map(variable => ({
kind: variable.kind,
id: variable.id,
name: variable.name,
value: variable.value,
_meta: variable._meta,
})), [
toAgentHostCompletionVariableEntry(AgentHostCompletionReferenceKind.Skill, '/agent-host-docs', skillMeta.uri, skillMeta),
toAgentHostCompletionVariableEntry(AgentHostCompletionReferenceKind.Command, '/rename', 'rename', commandMeta),
]);
}
});

test('untitled sessions have empty history', async () => {
const { sessionHandler } = createContribution(disposables);

Expand Down
Loading
Loading