From 12a00866d121a4c985b82f13639316461b627ce9 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 23 Jan 2026 17:26:31 +0000 Subject: [PATCH 01/10] Initial plan From b6cf72faaffdca37b897d5e3fad5d5ec8a0f8d12 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 23 Jan 2026 17:36:42 +0000 Subject: [PATCH 02/10] Improve commit line break unwrapping for lists - Fix list continuation to use content indent (not marker indent) - Support variable-length numbered list markers (1., 10., 100., etc.) - Allow flexible continuation spacing (1+ spaces, but not 4+ which is code) - Preserve multi-paragraph list formatting (blank lines within list items) - Track list context across blank lines for proper paragraph unwrapping - Add comprehensive test cases for all scenarios Co-authored-by: alexr00 <38270282+alexr00@users.noreply.github.com> --- src/github/folderRepositoryManager.ts | 141 +++++++++++++----- .../github/folderRepositoryManager.test.ts | 104 +++++++++++++ 2 files changed, 209 insertions(+), 36 deletions(-) diff --git a/src/github/folderRepositoryManager.ts b/src/github/folderRepositoryManager.ts index dac84ca592..b144dcf728 100644 --- a/src/github/folderRepositoryManager.ts +++ b/src/github/folderRepositoryManager.ts @@ -3067,8 +3067,8 @@ function unwrapCommitMessageBody(body: string): string { return body; } - // Pattern to detect list item markers at the start of a line - const LIST_ITEM_PATTERN = /^[ \t]*([*+\-]|\d+\.)\s/; + // Pattern to detect list item markers at the start of a line and capture the marker + const LIST_ITEM_PATTERN = /^([ \t]*)([*+\-]|\d+\.)([ \t]+)/; // Pattern to detect blockquote markers const BLOCKQUOTE_PATTERN = /^[ \t]*>/; // Pattern to detect fenced code block markers @@ -3083,11 +3083,22 @@ function unwrapCommitMessageBody(body: string): string { return base.length > 0 && !/\s$/.test(base) ? `${base} ${addition}` : `${base}${addition}`; }; + // Get the content indent for a list item (position where actual content starts) + const getListItemContentIndent = (line: string): number => { + const match = line.match(LIST_ITEM_PATTERN); + if (!match) { + return 0; + } + // Content indent = leading whitespace + marker + space after marker + return match[1].length + match[2].length + match[3].length; + }; + const lines = body.split('\n'); const result: string[] = []; let i = 0; let inFencedBlock = false; - const listIndentStack: number[] = []; + // Stack stores { markerIndent, contentIndent } for each nesting level + const listStack: { markerIndent: number; contentIndent: number }[] = []; const getNextNonBlankLineInfo = ( startIndex: number, @@ -3106,19 +3117,23 @@ function unwrapCommitMessageBody(body: string): string { return undefined; }; - const getActiveListIndent = (lineIndent: number): number | undefined => { - for (let idx = listIndentStack.length - 1; idx >= 0; idx--) { - const indentForLevel = listIndentStack[idx]; - if (lineIndent >= indentForLevel + 2) { - listIndentStack.length = idx + 1; - return indentForLevel; + // Find the active list context for a given line indent + // Returns the content indent if the line is within an active list context + const getActiveListContentIndent = (lineIndent: number): number | undefined => { + for (let idx = listStack.length - 1; idx >= 0; idx--) { + const { markerIndent, contentIndent } = listStack[idx]; + // A line is part of a list item if it has at least 1 space indent + // (but less than contentIndent + 4 which would be a code block) + if (lineIndent >= 1 && lineIndent >= markerIndent) { + listStack.length = idx + 1; + return contentIndent; } - listIndentStack.pop(); + listStack.pop(); } return undefined; }; - const shouldJoinListContinuation = (lineIndex: number, activeIndent: number, baseLine: string): boolean => { + const shouldJoinListContinuation = (lineIndex: number, contentIndent: number, baseLine: string): boolean => { const currentLine = lines[lineIndex]; if (!currentLine) { return false; @@ -3142,12 +3157,13 @@ function unwrapCommitMessageBody(body: string): string { } const currentIndent = getLeadingWhitespaceLength(currentLine); - if (currentIndent < activeIndent + 2) { + // Need at least 1 space to be a continuation + if (currentIndent < 1) { return false; } - // Treat indented code blocks (4+ spaces beyond the bullet) as preserve-only. - if (currentIndent >= activeIndent + 4) { + // 4+ spaces beyond content indent is an indented code block + if (currentIndent >= contentIndent + 4) { return false; } @@ -3156,8 +3172,12 @@ function unwrapCommitMessageBody(body: string): string { return true; } - if (nextInfo.isListItem && nextInfo.indent <= activeIndent) { - return false; + // If next line is a list item at or before the current list level, don't join + if (nextInfo.isListItem) { + const currentListLevel = listStack.length > 0 ? listStack[listStack.length - 1].markerIndent : 0; + if (nextInfo.indent <= currentListLevel) { + return false; + } } return true; @@ -3166,11 +3186,11 @@ function unwrapCommitMessageBody(body: string): string { while (i < lines.length) { const line = lines[i]; - // Preserve blank lines + // Preserve blank lines but don't clear list context + // (multi-paragraph lists are allowed in GitHub markdown) if (line.trim() === '') { result.push(line); i++; - listIndentStack.length = 0; continue; } @@ -3190,26 +3210,25 @@ function unwrapCommitMessageBody(body: string): string { } const lineIndent = getLeadingWhitespaceLength(line); - const isListItem = LIST_ITEM_PATTERN.test(line); + const listItemMatch = line.match(LIST_ITEM_PATTERN); - if (isListItem) { - while (listIndentStack.length && lineIndent < listIndentStack[listIndentStack.length - 1]) { - listIndentStack.pop(); - } + if (listItemMatch) { + const markerIndent = listItemMatch[1].length; + const contentIndent = getListItemContentIndent(line); - if (!listIndentStack.length || lineIndent > listIndentStack[listIndentStack.length - 1]) { - listIndentStack.push(lineIndent); - } else { - listIndentStack[listIndentStack.length - 1] = lineIndent; + // Pop list levels that are at or beyond this indent + while (listStack.length && markerIndent <= listStack[listStack.length - 1].markerIndent) { + listStack.pop(); } + listStack.push({ markerIndent, contentIndent }); result.push(line); i++; continue; } - const activeListIndent = getActiveListIndent(lineIndent); - const codeIndentThreshold = activeListIndent !== undefined ? activeListIndent + 4 : 4; + const activeContentIndent = getActiveListContentIndent(lineIndent); + const codeIndentThreshold = activeContentIndent !== undefined ? activeContentIndent + 4 : 4; const isBlockquote = BLOCKQUOTE_PATTERN.test(line); const isIndentedCode = lineIndent >= codeIndentThreshold; @@ -3219,34 +3238,84 @@ function unwrapCommitMessageBody(body: string): string { continue; } - if (activeListIndent !== undefined && lineIndent >= activeListIndent + 2) { + // Handle list item continuations + if (activeContentIndent !== undefined && lineIndent >= 1) { const baseIndex = result.length - 1; - if (baseIndex >= 0) { - let baseLine = result[baseIndex]; + // Only try to join with previous line if it's not blank + // Multi-paragraph lists have blank lines that should be preserved + const baseLine = baseIndex >= 0 ? result[baseIndex] : ''; + const previousLineIsBlank = baseLine.trim() === ''; + + if (!previousLineIsBlank && baseIndex >= 0) { + let joinedLine = baseLine; let appended = false; let currentIndex = i; while ( currentIndex < lines.length && - shouldJoinListContinuation(currentIndex, activeListIndent, baseLine) + shouldJoinListContinuation(currentIndex, activeContentIndent, joinedLine) ) { const continuationText = lines[currentIndex].trim(); if (continuationText) { - baseLine = appendWithSpace(baseLine, continuationText); + joinedLine = appendWithSpace(joinedLine, continuationText); appended = true; } currentIndex++; } if (appended) { - result[baseIndex] = baseLine; + result[baseIndex] = joinedLine; i = currentIndex; continue; } } - result.push(line); + // For multi-paragraph continuations or standalone indented lines, + // preserve indentation but unwrap consecutive continuation lines + let joinedLine = line; i++; + + while (i < lines.length) { + const nextLine = lines[i]; + + if (nextLine.trim() === '') { + break; + } + + if (FENCE_PATTERN.test(nextLine)) { + break; + } + + if (LIST_ITEM_PATTERN.test(nextLine)) { + break; + } + + if (BLOCKQUOTE_PATTERN.test(nextLine)) { + break; + } + + const nextIndent = getLeadingWhitespaceLength(nextLine); + // Check for code block + if (nextIndent >= activeContentIndent + 4) { + break; + } + + // Must have at least 1 space to be a continuation + if (nextIndent < 1) { + break; + } + + // Check for hard line break + if (hasHardLineBreak(joinedLine)) { + break; + } + + // Join this line - preserve the original indentation for the first line + joinedLine = appendWithSpace(joinedLine, nextLine.trim()); + i++; + } + + result.push(joinedLine); continue; } diff --git a/src/test/github/folderRepositoryManager.test.ts b/src/test/github/folderRepositoryManager.test.ts index 6d6f6c3114..530ffaf9e2 100644 --- a/src/test/github/folderRepositoryManager.test.ts +++ b/src/test/github/folderRepositoryManager.test.ts @@ -230,4 +230,108 @@ describe('titleAndBodyFrom', function () { assert.strictEqual(result?.title, 'title'); assert.strictEqual(result?.body, '* This is a list item with two lines that have a line break between them\n * This is a nested list item that also has two lines that should have been merged'); }); + + it('handles basic numeric list continuation', async function () { + const message = Promise.resolve('title\n\n1. Basic numeric list\n continuation.\n Third line'); + + const result = await titleAndBodyFrom(message); + assert.strictEqual(result?.title, 'title'); + assert.strictEqual(result?.body, '1. Basic numeric list continuation. Third line'); + }); + + it('handles additional spaces OK for continuation', async function () { + const message = Promise.resolve('title\n\n2. Additional spaces are\n OK for a continuation (unless it\'s 4 spaces which would be a code block).\n Third line'); + + const result = await titleAndBodyFrom(message); + assert.strictEqual(result?.title, 'title'); + assert.strictEqual(result?.body, '2. Additional spaces are OK for a continuation (unless it\'s 4 spaces which would be a code block). Third line'); + }); + + it('handles asterisk list with extra spaces', async function () { + const message = Promise.resolve('title\n\n* Additional spaces are\n OK for a continuation (unless it\'s 4 spaces which would be a code block).\n Third line'); + + const result = await titleAndBodyFrom(message); + assert.strictEqual(result?.title, 'title'); + assert.strictEqual(result?.body, '* Additional spaces are OK for a continuation (unless it\'s 4 spaces which would be a code block). Third line'); + }); + + it('handles multi-digit numbers (10.)', async function () { + const message = Promise.resolve('title\n\n10. Multi-digit numbers should also\n work for a continuation.\n Third line'); + + const result = await titleAndBodyFrom(message); + assert.strictEqual(result?.title, 'title'); + assert.strictEqual(result?.body, '10. Multi-digit numbers should also work for a continuation. Third line'); + }); + + it('handles multi-paragraph list - numbered', async function () { + const message = Promise.resolve('title\n\n11. Multi-paragraph lists are also supported.\n\n Second paragraph in the same list item.\n Third line'); + + const result = await titleAndBodyFrom(message); + assert.strictEqual(result?.title, 'title'); + assert.strictEqual(result?.body, '11. Multi-paragraph lists are also supported.\n\n Second paragraph in the same list item. Third line'); + }); + + it('handles multi-paragraph list - asterisk', async function () { + const message = Promise.resolve('title\n\n* Multi-paragraph lists are also supported.\n\n Second paragraph in the same list item.\n Third line'); + + const result = await titleAndBodyFrom(message); + assert.strictEqual(result?.title, 'title'); + assert.strictEqual(result?.body, '* Multi-paragraph lists are also supported.\n\n Second paragraph in the same list item. Third line'); + }); + + it('handles item with code block - numbered', async function () { + const message = Promise.resolve('title\n\n1. Item with code:\n\n ```\n code line\n code line\n ```'); + + const result = await titleAndBodyFrom(message); + assert.strictEqual(result?.title, 'title'); + assert.strictEqual(result?.body, '1. Item with code:\n\n ```\n code line\n code line\n ```'); + }); + + it('handles item with code block - asterisk', async function () { + const message = Promise.resolve('title\n\n* Item with code:\n\n ```\n code line\n code line\n ```'); + + const result = await titleAndBodyFrom(message); + assert.strictEqual(result?.title, 'title'); + assert.strictEqual(result?.body, '* Item with code:\n\n ```\n code line\n code line\n ```'); + }); + + it('handles fewer spaces OK - numbered (1 space)', async function () { + const message = Promise.resolve('title\n\n1. Fewer spaces are also OK\n for a list continuation (as long as there\'s at least one space)'); + + const result = await titleAndBodyFrom(message); + assert.strictEqual(result?.title, 'title'); + assert.strictEqual(result?.body, '1. Fewer spaces are also OK for a list continuation (as long as there\'s at least one space)'); + }); + + it('handles fewer spaces OK - asterisk (1 space)', async function () { + const message = Promise.resolve('title\n\n* Fewer spaces are also OK\n for a list continuation (as long as there\'s at least one space)'); + + const result = await titleAndBodyFrom(message); + assert.strictEqual(result?.title, 'title'); + assert.strictEqual(result?.body, '* Fewer spaces are also OK for a list continuation (as long as there\'s at least one space)'); + }); + + it('handles nested numbered lists', async function () { + const message = Promise.resolve('title\n\n1. First level item\n continuation of first level\n 1. Nested numbered item\n with continuation'); + + const result = await titleAndBodyFrom(message); + assert.strictEqual(result?.title, 'title'); + assert.strictEqual(result?.body, '1. First level item continuation of first level\n 1. Nested numbered item with continuation'); + }); + + it('handles nested multi-digit numbered lists', async function () { + const message = Promise.resolve('title\n\n10. First level item with\n multi-line content\n 10. Nested with multi-digit\n number and continuation'); + + const result = await titleAndBodyFrom(message); + assert.strictEqual(result?.title, 'title'); + assert.strictEqual(result?.body, '10. First level item with multi-line content\n 10. Nested with multi-digit number and continuation'); + }); + + it('handles nested multi-paragraph lists', async function () { + const message = Promise.resolve('title\n\n* Outer item\n\n Second paragraph of outer\n with continuation\n * Inner item\n\n Second paragraph of inner\n with continuation'); + + const result = await titleAndBodyFrom(message); + assert.strictEqual(result?.title, 'title'); + assert.strictEqual(result?.body, '* Outer item\n\n Second paragraph of outer with continuation\n * Inner item\n\n Second paragraph of inner with continuation'); + }); }); From f6b1a779c446b30005ed9c7baa431dea3dbe791b Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 23 Jan 2026 17:39:29 +0000 Subject: [PATCH 03/10] Fix plain text joining to trim whitespace Use appendWithSpace with trimmed text to avoid double spaces when joining lines with leading whitespace. Co-authored-by: alexr00 <38270282+alexr00@users.noreply.github.com> --- src/github/folderRepositoryManager.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/github/folderRepositoryManager.ts b/src/github/folderRepositoryManager.ts index b144dcf728..84e7420615 100644 --- a/src/github/folderRepositoryManager.ts +++ b/src/github/folderRepositoryManager.ts @@ -3356,7 +3356,7 @@ function unwrapCommitMessageBody(body: string): string { } // Join this line with a space - joinedLine += ' ' + nextLine; + joinedLine = appendWithSpace(joinedLine, nextLine.trim()); i++; } From f64389c394077df3f8fe00a50b6c59d317b871ef Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 28 Jan 2026 11:04:50 +0000 Subject: [PATCH 04/10] Use named captures in LIST_ITEM_PATTERN regex Renamed capture groups for clarity: - leadingWhitespace: spaces/tabs before marker - marker: the list item marker (*, +, -, or digits.) - markerTrailingWhitespace: spaces after marker Co-authored-by: alexr00 <38270282+alexr00@users.noreply.github.com> --- .../vscode.proposed.chatContextProvider.d.ts | 99 +++++++++++++-- ...ode.proposed.chatParticipantAdditions.d.ts | 113 +++++++++++++++++- ...scode.proposed.chatParticipantPrivate.d.ts | 2 +- .../vscode.proposed.chatSessionsProvider.d.ts | 21 +--- src/github/folderRepositoryManager.ts | 10 +- 5 files changed, 209 insertions(+), 36 deletions(-) diff --git a/src/@types/vscode.proposed.chatContextProvider.d.ts b/src/@types/vscode.proposed.chatContextProvider.d.ts index 47a9284099..b1f3ea5806 100644 --- a/src/@types/vscode.proposed.chatContextProvider.d.ts +++ b/src/@types/vscode.proposed.chatContextProvider.d.ts @@ -11,16 +11,45 @@ declare module 'vscode' { export namespace chat { /** - * Register a chat context provider. Chat context can be provided: - * - For a resource. Make sure to pass a selector that matches the resource you want to provide context for. - * Providers registered without a selector will not be called for resource-based context. - * - Explicitly. These context items are shown as options when the user explicitly attaches context. + * Register a chat workspace context provider. Workspace context is automatically included in all chat requests. * * To ensure your extension is activated when chat context is requested, make sure to include the following activations events: * - If your extension implements `provideWorkspaceChatContext` or `provideChatContextForResource`, find an activation event which is a good signal to activate. * Ex: `onLanguage:`, `onWebviewPanel:`, etc.` * - If your extension implements `provideChatContextExplicit`, your extension will be automatically activated when the user requests explicit context. * + * @param id Unique identifier for the provider. + * @param provider The chat workspace context provider. + */ + export function registerChatWorkspaceContextProvider(id: string, provider: ChatWorkspaceContextProvider): Disposable; + + /** + * Register a chat explicit context provider. Explicit context items are shown as options when the user explicitly attaches context. + * + * To ensure your extension is activated when chat context is requested, make sure to include the `onChatContextProvider:` activation event in your `package.json`. + * + * @param id Unique identifier for the provider. + * @param provider The chat explicit context provider. + */ + export function registerChatExplicitContextProvider(id: string, provider: ChatExplicitContextProvider): Disposable; + + /** + * Register a chat resource context provider. Resource context is provided for a specific resource. + * Make sure to pass a selector that matches the resource you want to provide context for. + * + * To ensure your extension is activated when chat context is requested, make sure to include the `onChatContextProvider:` activation event in your `package.json`. + * + * @param selector Document selector to filter which resources the provider is called for. + * @param id Unique identifier for the provider. + * @param provider The chat resource context provider. + */ + export function registerChatResourceContextProvider(selector: DocumentSelector, id: string, provider: ChatResourceContextProvider): Disposable; + + /** + * Register a chat context provider. + * + * @deprecated Use {@link registerChatWorkspaceContextProvider}, {@link registerChatExplicitContextProvider}, or {@link registerChatResourceContextProvider} instead. + * * @param selector Optional document selector to filter which resources the provider is called for. If omitted, the provider will only be called for explicit context requests. * @param id Unique identifier for the provider. * @param provider The chat context provider. @@ -57,7 +86,7 @@ declare module 'vscode' { command?: Command; } - export interface ChatContextProvider { + export interface ChatWorkspaceContextProvider { /** * An optional event that should be fired when the workspace chat context has changed. @@ -65,15 +94,16 @@ declare module 'vscode' { onDidChangeWorkspaceChatContext?: Event; /** - * TODO @API: should this be a separate provider interface? - * * Provide a list of chat context items to be included as workspace context for all chat requests. * This should be used very sparingly to avoid providing useless context and to avoid using up the context window. * A good example use case is to provide information about which branch the user is working on in a source control context. * * @param token A cancellation token. */ - provideWorkspaceChatContext?(token: CancellationToken): ProviderResult; + provideChatContext(token: CancellationToken): ProviderResult; + } + + export interface ChatExplicitContextProvider { /** * Provide a list of chat context items that a user can choose from. These context items are shown as options when the user explicitly attaches context. @@ -82,7 +112,18 @@ declare module 'vscode' { * * @param token A cancellation token. */ - provideChatContextExplicit?(token: CancellationToken): ProviderResult; + provideChatContext(token: CancellationToken): ProviderResult; + + /** + * If a chat context item is provided without a `value`, this method is called to resolve the `value` for the item. + * + * @param context The context item to resolve. + * @param token A cancellation token. + */ + resolveChatContext(context: T, token: CancellationToken): ProviderResult; + } + + export interface ChatResourceContextProvider { /** * Given a particular resource, provide a chat context item for it. This is used for implicit context (see the settings `chat.implicitContext.enabled` and `chat.implicitContext.suggestedContext`). @@ -94,10 +135,10 @@ declare module 'vscode' { * @param options Options include the resource for which to provide context. * @param token A cancellation token. */ - provideChatContextForResource?(options: { resource: Uri }, token: CancellationToken): ProviderResult; + provideChatContext(options: { resource: Uri }, token: CancellationToken): ProviderResult; /** - * If a chat context item is provided without a `value`, from either of the `provide` methods, this method is called to resolve the `value` for the item. + * If a chat context item is provided without a `value`, this method is called to resolve the `value` for the item. * * @param context The context item to resolve. * @param token A cancellation token. @@ -105,4 +146,40 @@ declare module 'vscode' { resolveChatContext(context: T, token: CancellationToken): ProviderResult; } + /** + * @deprecated Use {@link ChatWorkspaceContextProvider}, {@link ChatExplicitContextProvider}, or {@link ChatResourceContextProvider} instead. + */ + export interface ChatContextProvider { + + /** + * An optional event that should be fired when the workspace chat context has changed. + * @deprecated Use {@link ChatWorkspaceContextProvider.onDidChangeWorkspaceChatContext} instead. + */ + onDidChangeWorkspaceChatContext?: Event; + + /** + * Provide a list of chat context items to be included as workspace context for all chat requests. + * @deprecated Use {@link ChatWorkspaceContextProvider.provideChatContext} instead. + */ + provideWorkspaceChatContext?(token: CancellationToken): ProviderResult; + + /** + * Provide a list of chat context items that a user can choose from. + * @deprecated Use {@link ChatExplicitContextProvider.provideChatContext} instead. + */ + provideChatContextExplicit?(token: CancellationToken): ProviderResult; + + /** + * Given a particular resource, provide a chat context item for it. + * @deprecated Use {@link ChatResourceContextProvider.provideChatContext} instead. + */ + provideChatContextForResource?(options: { resource: Uri }, token: CancellationToken): ProviderResult; + + /** + * If a chat context item is provided without a `value`, this method is called to resolve the `value` for the item. + * @deprecated Use the `resolveChatContext` method on the specific provider type instead. + */ + resolveChatContext?(context: T, token: CancellationToken): ProviderResult; + } + } diff --git a/src/@types/vscode.proposed.chatParticipantAdditions.d.ts b/src/@types/vscode.proposed.chatParticipantAdditions.d.ts index 8ef3f9335e..1a81d83783 100644 --- a/src/@types/vscode.proposed.chatParticipantAdditions.d.ts +++ b/src/@types/vscode.proposed.chatParticipantAdditions.d.ts @@ -96,6 +96,108 @@ declare module 'vscode' { constructor(title: string, message: string | MarkdownString, data: any, buttons?: string[]); } + /** + * An option for a question in a carousel. + */ + export interface ChatQuestionOption { + /** + * Unique identifier for the option. + */ + id: string; + /** + * The display label for the option. + */ + label: string; + /** + * The value returned when this option is selected. + */ + value: unknown; + } + + /** + * The type of question for a chat question carousel. + */ + export enum ChatQuestionType { + /** + * A free-form text input question. + */ + Text = 1, + /** + * A single-select question with radio buttons. + */ + SingleSelect = 2, + /** + * A multi-select question with checkboxes. + */ + MultiSelect = 3 + } + + /** + * A question to be displayed in a question carousel. + */ + export class ChatQuestion { + /** + * Unique identifier for the question. + */ + id: string; + /** + * The type of question: Text for free-form input, SingleSelect for radio buttons, MultiSelect for checkboxes. + */ + type: ChatQuestionType; + /** + * The title/header of the question. + */ + title: string; + /** + * Optional detailed message or description for the question. + */ + message?: string | MarkdownString; + /** + * Options for singleSelect or multiSelect questions. + */ + options?: ChatQuestionOption[]; + /** + * The id(s) of the default selected option(s). + * For SingleSelect, this should be a single option id. + * For MultiSelect, this can be an array of option ids. + */ + defaultValue?: string | string[]; + /** + * Whether to allow free-form text input in addition to predefined options. + * When true, users can provide their own text answer even for SingleSelect or MultiSelect questions. + */ + allowFreeformInput?: boolean; + + constructor( + id: string, + type: ChatQuestionType, + title: string, + options?: { + message?: string | MarkdownString; + options?: ChatQuestionOption[]; + defaultValue?: string | string[]; + allowFreeformInput?: boolean; + } + ); + } + + /** + * A carousel view for presenting multiple questions inline in the chat. + * The UI is displayed but does not block the chat input. + */ + export class ChatResponseQuestionCarouselPart { + /** + * The questions to display in the carousel. + */ + questions: ChatQuestion[]; + /** + * Whether users can skip answering the questions. + */ + allowSkip: boolean; + + constructor(questions: ChatQuestion[], allowSkip?: boolean); + } + export class ChatResponseCodeCitationPart { value: Uri; license: string; @@ -244,7 +346,7 @@ declare module 'vscode' { constructor(uris: Uri[], callback: () => Thenable); } - export type ExtendedChatResponsePart = ChatResponsePart | ChatResponseTextEditPart | ChatResponseNotebookEditPart | ChatResponseWorkspaceEditPart | ChatResponseConfirmationPart | ChatResponseCodeCitationPart | ChatResponseReferencePart2 | ChatResponseMovePart | ChatResponseExtensionsPart | ChatResponsePullRequestPart | ChatToolInvocationPart | ChatResponseMultiDiffPart | ChatResponseThinkingProgressPart | ChatResponseExternalEditPart; + export type ExtendedChatResponsePart = ChatResponsePart | ChatResponseTextEditPart | ChatResponseNotebookEditPart | ChatResponseWorkspaceEditPart | ChatResponseConfirmationPart | ChatResponseCodeCitationPart | ChatResponseReferencePart2 | ChatResponseMovePart | ChatResponseExtensionsPart | ChatResponsePullRequestPart | ChatToolInvocationPart | ChatResponseMultiDiffPart | ChatResponseThinkingProgressPart | ChatResponseExternalEditPart | ChatResponseQuestionCarouselPart; export class ChatResponseWarningPart { value: MarkdownString; constructor(value: string | MarkdownString); @@ -408,6 +510,15 @@ declare module 'vscode' { */ confirmation(title: string, message: string | MarkdownString, data: any, buttons?: string[]): void; + /** + * Show an inline carousel of questions to gather information from the user. + * This is a blocking call that waits for the user to submit or skip the questions. + * @param questions Array of questions to display to the user + * @param allowSkip Whether the user can skip questions without answering + * @returns A promise that resolves with the user's answers, or undefined if skipped + */ + questionCarousel(questions: ChatQuestion[], allowSkip?: boolean): Thenable | undefined>; + /** * Push a warning to this stream. Short-hand for * `push(new ChatResponseWarningPart(message))`. diff --git a/src/@types/vscode.proposed.chatParticipantPrivate.d.ts b/src/@types/vscode.proposed.chatParticipantPrivate.d.ts index 4ab722c122..bbdff837f5 100644 --- a/src/@types/vscode.proposed.chatParticipantPrivate.d.ts +++ b/src/@types/vscode.proposed.chatParticipantPrivate.d.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -// version: 11 +// version: 12 declare module 'vscode' { diff --git a/src/@types/vscode.proposed.chatSessionsProvider.d.ts b/src/@types/vscode.proposed.chatSessionsProvider.d.ts index 84cd547599..2025d2a66e 100644 --- a/src/@types/vscode.proposed.chatSessionsProvider.d.ts +++ b/src/@types/vscode.proposed.chatSessionsProvider.d.ts @@ -42,7 +42,7 @@ declare module 'vscode' { /** * Creates a new {@link ChatSessionItemController chat session item controller} with the given unique identifier. */ - export function createChatSessionItemController(id: string, refreshHandler: () => Thenable): ChatSessionItemController; + export function createChatSessionItemController(id: string, refreshHandler: (token: CancellationToken) => Thenable): ChatSessionItemController; } /** @@ -97,7 +97,7 @@ declare module 'vscode' { * * This is also called on first load to get the initial set of items. */ - refreshHandler: () => Thenable; + refreshHandler: (token: CancellationToken) => Thenable; /** * Fired when an item's archived state changes. @@ -231,22 +231,7 @@ declare module 'vscode' { /** * Statistics about the chat session. */ - changes?: readonly ChatSessionChangedFile[] | readonly ChatSessionChangedFile2[] | { - /** - * Number of files edited during the session. - */ - files: number; - - /** - * Number of insertions made during the session. - */ - insertions: number; - - /** - * Number of deletions made during the session. - */ - deletions: number; - }; + changes?: readonly ChatSessionChangedFile[] | readonly ChatSessionChangedFile2[]; } export class ChatSessionChangedFile { diff --git a/src/github/folderRepositoryManager.ts b/src/github/folderRepositoryManager.ts index 84e7420615..9bf04e0fe0 100644 --- a/src/github/folderRepositoryManager.ts +++ b/src/github/folderRepositoryManager.ts @@ -3068,7 +3068,7 @@ function unwrapCommitMessageBody(body: string): string { } // Pattern to detect list item markers at the start of a line and capture the marker - const LIST_ITEM_PATTERN = /^([ \t]*)([*+\-]|\d+\.)([ \t]+)/; + const LIST_ITEM_PATTERN = /^(?[ \t]*)(?[*+\-]|\d+\.)(?[ \t]+)/; // Pattern to detect blockquote markers const BLOCKQUOTE_PATTERN = /^[ \t]*>/; // Pattern to detect fenced code block markers @@ -3086,11 +3086,11 @@ function unwrapCommitMessageBody(body: string): string { // Get the content indent for a list item (position where actual content starts) const getListItemContentIndent = (line: string): number => { const match = line.match(LIST_ITEM_PATTERN); - if (!match) { + if (!match?.groups) { return 0; } // Content indent = leading whitespace + marker + space after marker - return match[1].length + match[2].length + match[3].length; + return match.groups.leadingWhitespace.length + match.groups.marker.length + match.groups.markerTrailingWhitespace.length; }; const lines = body.split('\n'); @@ -3212,8 +3212,8 @@ function unwrapCommitMessageBody(body: string): string { const lineIndent = getLeadingWhitespaceLength(line); const listItemMatch = line.match(LIST_ITEM_PATTERN); - if (listItemMatch) { - const markerIndent = listItemMatch[1].length; + if (listItemMatch?.groups) { + const markerIndent = listItemMatch.groups.leadingWhitespace.length; const contentIndent = getListItemContentIndent(line); // Pop list levels that are at or beyond this indent From a6d16e04bac9e80204bf59698028a9091fd5b4ff Mon Sep 17 00:00:00 2001 From: Alex Ross <38270282+alexr00@users.noreply.github.com> Date: Wed, 4 Feb 2026 11:39:14 +0100 Subject: [PATCH 05/10] Undo proposal changes --- .../vscode.proposed.chatContextProvider.d.ts | 99 ++------------- ...ode.proposed.chatParticipantAdditions.d.ts | 113 +----------------- ...scode.proposed.chatParticipantPrivate.d.ts | 2 +- .../vscode.proposed.chatSessionsProvider.d.ts | 21 +++- 4 files changed, 31 insertions(+), 204 deletions(-) diff --git a/src/@types/vscode.proposed.chatContextProvider.d.ts b/src/@types/vscode.proposed.chatContextProvider.d.ts index b1f3ea5806..47a9284099 100644 --- a/src/@types/vscode.proposed.chatContextProvider.d.ts +++ b/src/@types/vscode.proposed.chatContextProvider.d.ts @@ -11,45 +11,16 @@ declare module 'vscode' { export namespace chat { /** - * Register a chat workspace context provider. Workspace context is automatically included in all chat requests. + * Register a chat context provider. Chat context can be provided: + * - For a resource. Make sure to pass a selector that matches the resource you want to provide context for. + * Providers registered without a selector will not be called for resource-based context. + * - Explicitly. These context items are shown as options when the user explicitly attaches context. * * To ensure your extension is activated when chat context is requested, make sure to include the following activations events: * - If your extension implements `provideWorkspaceChatContext` or `provideChatContextForResource`, find an activation event which is a good signal to activate. * Ex: `onLanguage:`, `onWebviewPanel:`, etc.` * - If your extension implements `provideChatContextExplicit`, your extension will be automatically activated when the user requests explicit context. * - * @param id Unique identifier for the provider. - * @param provider The chat workspace context provider. - */ - export function registerChatWorkspaceContextProvider(id: string, provider: ChatWorkspaceContextProvider): Disposable; - - /** - * Register a chat explicit context provider. Explicit context items are shown as options when the user explicitly attaches context. - * - * To ensure your extension is activated when chat context is requested, make sure to include the `onChatContextProvider:` activation event in your `package.json`. - * - * @param id Unique identifier for the provider. - * @param provider The chat explicit context provider. - */ - export function registerChatExplicitContextProvider(id: string, provider: ChatExplicitContextProvider): Disposable; - - /** - * Register a chat resource context provider. Resource context is provided for a specific resource. - * Make sure to pass a selector that matches the resource you want to provide context for. - * - * To ensure your extension is activated when chat context is requested, make sure to include the `onChatContextProvider:` activation event in your `package.json`. - * - * @param selector Document selector to filter which resources the provider is called for. - * @param id Unique identifier for the provider. - * @param provider The chat resource context provider. - */ - export function registerChatResourceContextProvider(selector: DocumentSelector, id: string, provider: ChatResourceContextProvider): Disposable; - - /** - * Register a chat context provider. - * - * @deprecated Use {@link registerChatWorkspaceContextProvider}, {@link registerChatExplicitContextProvider}, or {@link registerChatResourceContextProvider} instead. - * * @param selector Optional document selector to filter which resources the provider is called for. If omitted, the provider will only be called for explicit context requests. * @param id Unique identifier for the provider. * @param provider The chat context provider. @@ -86,7 +57,7 @@ declare module 'vscode' { command?: Command; } - export interface ChatWorkspaceContextProvider { + export interface ChatContextProvider { /** * An optional event that should be fired when the workspace chat context has changed. @@ -94,16 +65,15 @@ declare module 'vscode' { onDidChangeWorkspaceChatContext?: Event; /** + * TODO @API: should this be a separate provider interface? + * * Provide a list of chat context items to be included as workspace context for all chat requests. * This should be used very sparingly to avoid providing useless context and to avoid using up the context window. * A good example use case is to provide information about which branch the user is working on in a source control context. * * @param token A cancellation token. */ - provideChatContext(token: CancellationToken): ProviderResult; - } - - export interface ChatExplicitContextProvider { + provideWorkspaceChatContext?(token: CancellationToken): ProviderResult; /** * Provide a list of chat context items that a user can choose from. These context items are shown as options when the user explicitly attaches context. @@ -112,18 +82,7 @@ declare module 'vscode' { * * @param token A cancellation token. */ - provideChatContext(token: CancellationToken): ProviderResult; - - /** - * If a chat context item is provided without a `value`, this method is called to resolve the `value` for the item. - * - * @param context The context item to resolve. - * @param token A cancellation token. - */ - resolveChatContext(context: T, token: CancellationToken): ProviderResult; - } - - export interface ChatResourceContextProvider { + provideChatContextExplicit?(token: CancellationToken): ProviderResult; /** * Given a particular resource, provide a chat context item for it. This is used for implicit context (see the settings `chat.implicitContext.enabled` and `chat.implicitContext.suggestedContext`). @@ -135,10 +94,10 @@ declare module 'vscode' { * @param options Options include the resource for which to provide context. * @param token A cancellation token. */ - provideChatContext(options: { resource: Uri }, token: CancellationToken): ProviderResult; + provideChatContextForResource?(options: { resource: Uri }, token: CancellationToken): ProviderResult; /** - * If a chat context item is provided without a `value`, this method is called to resolve the `value` for the item. + * If a chat context item is provided without a `value`, from either of the `provide` methods, this method is called to resolve the `value` for the item. * * @param context The context item to resolve. * @param token A cancellation token. @@ -146,40 +105,4 @@ declare module 'vscode' { resolveChatContext(context: T, token: CancellationToken): ProviderResult; } - /** - * @deprecated Use {@link ChatWorkspaceContextProvider}, {@link ChatExplicitContextProvider}, or {@link ChatResourceContextProvider} instead. - */ - export interface ChatContextProvider { - - /** - * An optional event that should be fired when the workspace chat context has changed. - * @deprecated Use {@link ChatWorkspaceContextProvider.onDidChangeWorkspaceChatContext} instead. - */ - onDidChangeWorkspaceChatContext?: Event; - - /** - * Provide a list of chat context items to be included as workspace context for all chat requests. - * @deprecated Use {@link ChatWorkspaceContextProvider.provideChatContext} instead. - */ - provideWorkspaceChatContext?(token: CancellationToken): ProviderResult; - - /** - * Provide a list of chat context items that a user can choose from. - * @deprecated Use {@link ChatExplicitContextProvider.provideChatContext} instead. - */ - provideChatContextExplicit?(token: CancellationToken): ProviderResult; - - /** - * Given a particular resource, provide a chat context item for it. - * @deprecated Use {@link ChatResourceContextProvider.provideChatContext} instead. - */ - provideChatContextForResource?(options: { resource: Uri }, token: CancellationToken): ProviderResult; - - /** - * If a chat context item is provided without a `value`, this method is called to resolve the `value` for the item. - * @deprecated Use the `resolveChatContext` method on the specific provider type instead. - */ - resolveChatContext?(context: T, token: CancellationToken): ProviderResult; - } - } diff --git a/src/@types/vscode.proposed.chatParticipantAdditions.d.ts b/src/@types/vscode.proposed.chatParticipantAdditions.d.ts index 1a81d83783..8ef3f9335e 100644 --- a/src/@types/vscode.proposed.chatParticipantAdditions.d.ts +++ b/src/@types/vscode.proposed.chatParticipantAdditions.d.ts @@ -96,108 +96,6 @@ declare module 'vscode' { constructor(title: string, message: string | MarkdownString, data: any, buttons?: string[]); } - /** - * An option for a question in a carousel. - */ - export interface ChatQuestionOption { - /** - * Unique identifier for the option. - */ - id: string; - /** - * The display label for the option. - */ - label: string; - /** - * The value returned when this option is selected. - */ - value: unknown; - } - - /** - * The type of question for a chat question carousel. - */ - export enum ChatQuestionType { - /** - * A free-form text input question. - */ - Text = 1, - /** - * A single-select question with radio buttons. - */ - SingleSelect = 2, - /** - * A multi-select question with checkboxes. - */ - MultiSelect = 3 - } - - /** - * A question to be displayed in a question carousel. - */ - export class ChatQuestion { - /** - * Unique identifier for the question. - */ - id: string; - /** - * The type of question: Text for free-form input, SingleSelect for radio buttons, MultiSelect for checkboxes. - */ - type: ChatQuestionType; - /** - * The title/header of the question. - */ - title: string; - /** - * Optional detailed message or description for the question. - */ - message?: string | MarkdownString; - /** - * Options for singleSelect or multiSelect questions. - */ - options?: ChatQuestionOption[]; - /** - * The id(s) of the default selected option(s). - * For SingleSelect, this should be a single option id. - * For MultiSelect, this can be an array of option ids. - */ - defaultValue?: string | string[]; - /** - * Whether to allow free-form text input in addition to predefined options. - * When true, users can provide their own text answer even for SingleSelect or MultiSelect questions. - */ - allowFreeformInput?: boolean; - - constructor( - id: string, - type: ChatQuestionType, - title: string, - options?: { - message?: string | MarkdownString; - options?: ChatQuestionOption[]; - defaultValue?: string | string[]; - allowFreeformInput?: boolean; - } - ); - } - - /** - * A carousel view for presenting multiple questions inline in the chat. - * The UI is displayed but does not block the chat input. - */ - export class ChatResponseQuestionCarouselPart { - /** - * The questions to display in the carousel. - */ - questions: ChatQuestion[]; - /** - * Whether users can skip answering the questions. - */ - allowSkip: boolean; - - constructor(questions: ChatQuestion[], allowSkip?: boolean); - } - export class ChatResponseCodeCitationPart { value: Uri; license: string; @@ -346,7 +244,7 @@ declare module 'vscode' { constructor(uris: Uri[], callback: () => Thenable); } - export type ExtendedChatResponsePart = ChatResponsePart | ChatResponseTextEditPart | ChatResponseNotebookEditPart | ChatResponseWorkspaceEditPart | ChatResponseConfirmationPart | ChatResponseCodeCitationPart | ChatResponseReferencePart2 | ChatResponseMovePart | ChatResponseExtensionsPart | ChatResponsePullRequestPart | ChatToolInvocationPart | ChatResponseMultiDiffPart | ChatResponseThinkingProgressPart | ChatResponseExternalEditPart | ChatResponseQuestionCarouselPart; + export type ExtendedChatResponsePart = ChatResponsePart | ChatResponseTextEditPart | ChatResponseNotebookEditPart | ChatResponseWorkspaceEditPart | ChatResponseConfirmationPart | ChatResponseCodeCitationPart | ChatResponseReferencePart2 | ChatResponseMovePart | ChatResponseExtensionsPart | ChatResponsePullRequestPart | ChatToolInvocationPart | ChatResponseMultiDiffPart | ChatResponseThinkingProgressPart | ChatResponseExternalEditPart; export class ChatResponseWarningPart { value: MarkdownString; constructor(value: string | MarkdownString); @@ -510,15 +408,6 @@ declare module 'vscode' { */ confirmation(title: string, message: string | MarkdownString, data: any, buttons?: string[]): void; - /** - * Show an inline carousel of questions to gather information from the user. - * This is a blocking call that waits for the user to submit or skip the questions. - * @param questions Array of questions to display to the user - * @param allowSkip Whether the user can skip questions without answering - * @returns A promise that resolves with the user's answers, or undefined if skipped - */ - questionCarousel(questions: ChatQuestion[], allowSkip?: boolean): Thenable | undefined>; - /** * Push a warning to this stream. Short-hand for * `push(new ChatResponseWarningPart(message))`. diff --git a/src/@types/vscode.proposed.chatParticipantPrivate.d.ts b/src/@types/vscode.proposed.chatParticipantPrivate.d.ts index bbdff837f5..4ab722c122 100644 --- a/src/@types/vscode.proposed.chatParticipantPrivate.d.ts +++ b/src/@types/vscode.proposed.chatParticipantPrivate.d.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -// version: 12 +// version: 11 declare module 'vscode' { diff --git a/src/@types/vscode.proposed.chatSessionsProvider.d.ts b/src/@types/vscode.proposed.chatSessionsProvider.d.ts index 2025d2a66e..84cd547599 100644 --- a/src/@types/vscode.proposed.chatSessionsProvider.d.ts +++ b/src/@types/vscode.proposed.chatSessionsProvider.d.ts @@ -42,7 +42,7 @@ declare module 'vscode' { /** * Creates a new {@link ChatSessionItemController chat session item controller} with the given unique identifier. */ - export function createChatSessionItemController(id: string, refreshHandler: (token: CancellationToken) => Thenable): ChatSessionItemController; + export function createChatSessionItemController(id: string, refreshHandler: () => Thenable): ChatSessionItemController; } /** @@ -97,7 +97,7 @@ declare module 'vscode' { * * This is also called on first load to get the initial set of items. */ - refreshHandler: (token: CancellationToken) => Thenable; + refreshHandler: () => Thenable; /** * Fired when an item's archived state changes. @@ -231,7 +231,22 @@ declare module 'vscode' { /** * Statistics about the chat session. */ - changes?: readonly ChatSessionChangedFile[] | readonly ChatSessionChangedFile2[]; + changes?: readonly ChatSessionChangedFile[] | readonly ChatSessionChangedFile2[] | { + /** + * Number of files edited during the session. + */ + files: number; + + /** + * Number of insertions made during the session. + */ + insertions: number; + + /** + * Number of deletions made during the session. + */ + deletions: number; + }; } export class ChatSessionChangedFile { From 9ff7c152fc82ec97dadb4fe6e2465c239bd56cb2 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 4 Feb 2026 11:34:11 +0000 Subject: [PATCH 06/10] Fix list continuation unwrapping logic The previous implementation incorrectly prevented joining list continuations when the next line was a list item at the same level. This caused wrapped list content to not be unwrapped properly. Changes: - Remove incorrect check that prevented joining based on next line - Remove unused getNextNonBlankLineInfo function - Update test names and expectations to reflect correct behavior: - List continuations should be unwrapped (joined), not preserved - Renamed 'preserves list item continuations' to 'unwraps list item continuations' - Renamed 'unwraps regular text but preserves list item continuations' to 'unwraps regular text and list item continuations' - Fixed expected output for nested list content tests Co-authored-by: alexr00 <38270282+alexr00@users.noreply.github.com> --- .../vscode.proposed.chatContextProvider.d.ts | 112 ++++++++++++-- ...ode.proposed.chatParticipantAdditions.d.ts | 146 ++++++++++++++++-- ...scode.proposed.chatParticipantPrivate.d.ts | 20 ++- .../vscode.proposed.chatSessionsProvider.d.ts | 26 ++-- src/github/folderRepositoryManager.ts | 30 ---- .../github/folderRepositoryManager.test.ts | 10 +- 6 files changed, 269 insertions(+), 75 deletions(-) diff --git a/src/@types/vscode.proposed.chatContextProvider.d.ts b/src/@types/vscode.proposed.chatContextProvider.d.ts index 47a9284099..d9e6583c09 100644 --- a/src/@types/vscode.proposed.chatContextProvider.d.ts +++ b/src/@types/vscode.proposed.chatContextProvider.d.ts @@ -11,16 +11,45 @@ declare module 'vscode' { export namespace chat { /** - * Register a chat context provider. Chat context can be provided: - * - For a resource. Make sure to pass a selector that matches the resource you want to provide context for. - * Providers registered without a selector will not be called for resource-based context. - * - Explicitly. These context items are shown as options when the user explicitly attaches context. + * Register a chat workspace context provider. Workspace context is automatically included in all chat requests. * * To ensure your extension is activated when chat context is requested, make sure to include the following activations events: * - If your extension implements `provideWorkspaceChatContext` or `provideChatContextForResource`, find an activation event which is a good signal to activate. * Ex: `onLanguage:`, `onWebviewPanel:`, etc.` * - If your extension implements `provideChatContextExplicit`, your extension will be automatically activated when the user requests explicit context. * + * @param id Unique identifier for the provider. + * @param provider The chat workspace context provider. + */ + export function registerChatWorkspaceContextProvider(id: string, provider: ChatWorkspaceContextProvider): Disposable; + + /** + * Register a chat explicit context provider. Explicit context items are shown as options when the user explicitly attaches context. + * + * To ensure your extension is activated when chat context is requested, make sure to include the `onChatContextProvider:` activation event in your `package.json`. + * + * @param id Unique identifier for the provider. + * @param provider The chat explicit context provider. + */ + export function registerChatExplicitContextProvider(id: string, provider: ChatExplicitContextProvider): Disposable; + + /** + * Register a chat resource context provider. Resource context is provided for a specific resource. + * Make sure to pass a selector that matches the resource you want to provide context for. + * + * To ensure your extension is activated when chat context is requested, make sure to include the `onChatContextProvider:` activation event in your `package.json`. + * + * @param selector Document selector to filter which resources the provider is called for. + * @param id Unique identifier for the provider. + * @param provider The chat resource context provider. + */ + export function registerChatResourceContextProvider(selector: DocumentSelector, id: string, provider: ChatResourceContextProvider): Disposable; + + /** + * Register a chat context provider. + * + * @deprecated Use {@link registerChatWorkspaceContextProvider}, {@link registerChatExplicitContextProvider}, or {@link registerChatResourceContextProvider} instead. + * * @param selector Optional document selector to filter which resources the provider is called for. If omitted, the provider will only be called for explicit context requests. * @param id Unique identifier for the provider. * @param provider The chat context provider. @@ -32,12 +61,21 @@ declare module 'vscode' { export interface ChatContextItem { /** * Icon for the context item. + * - If `icon` is not defined, no icon is shown. + * - If `icon` is defined and is a file or folder icon, the icon is derived from {@link resourceUri} if `resourceUri` is defined. + * - Otherwise, `icon` is used. */ - icon: ThemeIcon; + icon?: ThemeIcon; /** * Human readable label for the context item. + * If not set, the label is derived from {@link resourceUri}. + */ + label?: string; + /** + * A resource URI for the context item. + * Used to derive the {@link label} and {@link icon} if they are not set. */ - label: string; + resourceUri?: Uri; /** * An optional description of the context item, e.g. to describe the item to the language model. */ @@ -57,7 +95,7 @@ declare module 'vscode' { command?: Command; } - export interface ChatContextProvider { + export interface ChatWorkspaceContextProvider { /** * An optional event that should be fired when the workspace chat context has changed. @@ -65,15 +103,16 @@ declare module 'vscode' { onDidChangeWorkspaceChatContext?: Event; /** - * TODO @API: should this be a separate provider interface? - * * Provide a list of chat context items to be included as workspace context for all chat requests. * This should be used very sparingly to avoid providing useless context and to avoid using up the context window. * A good example use case is to provide information about which branch the user is working on in a source control context. * * @param token A cancellation token. */ - provideWorkspaceChatContext?(token: CancellationToken): ProviderResult; + provideChatContext(token: CancellationToken): ProviderResult; + } + + export interface ChatExplicitContextProvider { /** * Provide a list of chat context items that a user can choose from. These context items are shown as options when the user explicitly attaches context. @@ -82,7 +121,18 @@ declare module 'vscode' { * * @param token A cancellation token. */ - provideChatContextExplicit?(token: CancellationToken): ProviderResult; + provideChatContext(token: CancellationToken): ProviderResult; + + /** + * If a chat context item is provided without a `value`, this method is called to resolve the `value` for the item. + * + * @param context The context item to resolve. + * @param token A cancellation token. + */ + resolveChatContext(context: T, token: CancellationToken): ProviderResult; + } + + export interface ChatResourceContextProvider { /** * Given a particular resource, provide a chat context item for it. This is used for implicit context (see the settings `chat.implicitContext.enabled` and `chat.implicitContext.suggestedContext`). @@ -94,10 +144,10 @@ declare module 'vscode' { * @param options Options include the resource for which to provide context. * @param token A cancellation token. */ - provideChatContextForResource?(options: { resource: Uri }, token: CancellationToken): ProviderResult; + provideChatContext(options: { resource: Uri }, token: CancellationToken): ProviderResult; /** - * If a chat context item is provided without a `value`, from either of the `provide` methods, this method is called to resolve the `value` for the item. + * If a chat context item is provided without a `value`, this method is called to resolve the `value` for the item. * * @param context The context item to resolve. * @param token A cancellation token. @@ -105,4 +155,40 @@ declare module 'vscode' { resolveChatContext(context: T, token: CancellationToken): ProviderResult; } + /** + * @deprecated Use {@link ChatWorkspaceContextProvider}, {@link ChatExplicitContextProvider}, or {@link ChatResourceContextProvider} instead. + */ + export interface ChatContextProvider { + + /** + * An optional event that should be fired when the workspace chat context has changed. + * @deprecated Use {@link ChatWorkspaceContextProvider.onDidChangeWorkspaceChatContext} instead. + */ + onDidChangeWorkspaceChatContext?: Event; + + /** + * Provide a list of chat context items to be included as workspace context for all chat requests. + * @deprecated Use {@link ChatWorkspaceContextProvider.provideChatContext} instead. + */ + provideWorkspaceChatContext?(token: CancellationToken): ProviderResult; + + /** + * Provide a list of chat context items that a user can choose from. + * @deprecated Use {@link ChatExplicitContextProvider.provideChatContext} instead. + */ + provideChatContextExplicit?(token: CancellationToken): ProviderResult; + + /** + * Given a particular resource, provide a chat context item for it. + * @deprecated Use {@link ChatResourceContextProvider.provideChatContext} instead. + */ + provideChatContextForResource?(options: { resource: Uri }, token: CancellationToken): ProviderResult; + + /** + * If a chat context item is provided without a `value`, this method is called to resolve the `value` for the item. + * @deprecated Use the `resolveChatContext` method on the specific provider type instead. + */ + resolveChatContext?(context: T, token: CancellationToken): ProviderResult; + } + } diff --git a/src/@types/vscode.proposed.chatParticipantAdditions.d.ts b/src/@types/vscode.proposed.chatParticipantAdditions.d.ts index df0f1045c8..a5b9e66d57 100644 --- a/src/@types/vscode.proposed.chatParticipantAdditions.d.ts +++ b/src/@types/vscode.proposed.chatParticipantAdditions.d.ts @@ -3,6 +3,8 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +// version: 1 + declare module 'vscode' { export interface ChatParticipant { @@ -96,6 +98,108 @@ declare module 'vscode' { constructor(title: string, message: string | MarkdownString, data: any, buttons?: string[]); } + /** + * An option for a question in a carousel. + */ + export interface ChatQuestionOption { + /** + * Unique identifier for the option. + */ + id: string; + /** + * The display label for the option. + */ + label: string; + /** + * The value returned when this option is selected. + */ + value: unknown; + } + + /** + * The type of question for a chat question carousel. + */ + export enum ChatQuestionType { + /** + * A free-form text input question. + */ + Text = 1, + /** + * A single-select question with radio buttons. + */ + SingleSelect = 2, + /** + * A multi-select question with checkboxes. + */ + MultiSelect = 3 + } + + /** + * A question to be displayed in a question carousel. + */ + export class ChatQuestion { + /** + * Unique identifier for the question. + */ + id: string; + /** + * The type of question: Text for free-form input, SingleSelect for radio buttons, MultiSelect for checkboxes. + */ + type: ChatQuestionType; + /** + * The title/header of the question. + */ + title: string; + /** + * Optional detailed message or description for the question. + */ + message?: string | MarkdownString; + /** + * Options for singleSelect or multiSelect questions. + */ + options?: ChatQuestionOption[]; + /** + * The id(s) of the default selected option(s). + * For SingleSelect, this should be a single option id. + * For MultiSelect, this can be an array of option ids. + */ + defaultValue?: string | string[]; + /** + * Whether to allow free-form text input in addition to predefined options. + * When true, users can provide their own text answer even for SingleSelect or MultiSelect questions. + */ + allowFreeformInput?: boolean; + + constructor( + id: string, + type: ChatQuestionType, + title: string, + options?: { + message?: string | MarkdownString; + options?: ChatQuestionOption[]; + defaultValue?: string | string[]; + allowFreeformInput?: boolean; + } + ); + } + + /** + * A carousel view for presenting multiple questions inline in the chat. + * The UI is displayed but does not block the chat input. + */ + export class ChatResponseQuestionCarouselPart { + /** + * The questions to display in the carousel. + */ + questions: ChatQuestion[]; + /** + * Whether users can skip answering the questions. + */ + allowSkip: boolean; + + constructor(questions: ChatQuestion[], allowSkip?: boolean); + } + export class ChatResponseCodeCitationPart { value: Uri; license: string; @@ -162,6 +266,20 @@ declare module 'vscode' { output: McpToolInvocationContentData[]; } + export enum ChatTodoStatus { + NotStarted = 1, + InProgress = 2, + Completed = 3 + } + + export interface ChatTodoToolInvocationData { + todoList: Array<{ + id: number; + title: string; + status: ChatTodoStatus; + }>; + } + export class ChatToolInvocationPart { toolName: string; toolCallId: string; @@ -171,8 +289,8 @@ declare module 'vscode' { pastTenseMessage?: string | MarkdownString; isConfirmed?: boolean; isComplete?: boolean; - toolSpecificData?: ChatTerminalToolInvocationData; - fromSubAgent?: boolean; + toolSpecificData?: ChatTerminalToolInvocationData | ChatMcpToolInvocationData | ChatTodoToolInvocationData; + subAgentInvocationId?: string; presentation?: 'hidden' | 'hiddenAfterComplete' | undefined; constructor(toolName: string, toolCallId: string, isError?: boolean); @@ -244,7 +362,7 @@ declare module 'vscode' { constructor(uris: Uri[], callback: () => Thenable); } - export type ExtendedChatResponsePart = ChatResponsePart | ChatResponseTextEditPart | ChatResponseNotebookEditPart | ChatResponseConfirmationPart | ChatResponseCodeCitationPart | ChatResponseReferencePart2 | ChatResponseMovePart | ChatResponseExtensionsPart | ChatResponsePullRequestPart | ChatPrepareToolInvocationPart | ChatToolInvocationPart | ChatResponseMultiDiffPart | ChatResponseThinkingProgressPart | ChatResponseExternalEditPart; + export type ExtendedChatResponsePart = ChatResponsePart | ChatResponseTextEditPart | ChatResponseNotebookEditPart | ChatResponseWorkspaceEditPart | ChatResponseConfirmationPart | ChatResponseCodeCitationPart | ChatResponseReferencePart2 | ChatResponseMovePart | ChatResponseExtensionsPart | ChatResponsePullRequestPart | ChatToolInvocationPart | ChatResponseMultiDiffPart | ChatResponseThinkingProgressPart | ChatResponseExternalEditPart | ChatResponseQuestionCarouselPart; export class ChatResponseWarningPart { value: MarkdownString; constructor(value: string | MarkdownString); @@ -408,6 +526,15 @@ declare module 'vscode' { */ confirmation(title: string, message: string | MarkdownString, data: any, buttons?: string[]): void; + /** + * Show an inline carousel of questions to gather information from the user. + * This is a blocking call that waits for the user to submit or skip the questions. + * @param questions Array of questions to display to the user + * @param allowSkip Whether the user can skip questions without answering + * @returns A promise that resolves with the user's answers, or undefined if skipped + */ + questionCarousel(questions: ChatQuestion[], allowSkip?: boolean): Thenable | undefined>; + /** * Push a warning to this stream. Short-hand for * `push(new ChatResponseWarningPart(message))`. @@ -442,6 +569,13 @@ declare module 'vscode' { push(part: ExtendedChatResponsePart): void; clearToPreviousToolInvocation(reason: ChatResponseClearToPreviousToolInvocationReason): void; + + /** + * Report token usage information for this request. + * This is typically called when the underlying language model provides usage statistics. + * @param usage Token usage information including prompt and completion tokens + */ + usage(usage: ChatResultUsage): void; } export enum ChatResponseReferencePartStatusKind { @@ -633,12 +767,6 @@ declare module 'vscode' { * An optional detail string that will be rendered at the end of the response in certain UI contexts. */ details?: string; - - /** - * Token usage information for this request, if available. - * This is typically provided by the underlying language model. - */ - readonly usage?: ChatResultUsage; } export namespace chat { diff --git a/src/@types/vscode.proposed.chatParticipantPrivate.d.ts b/src/@types/vscode.proposed.chatParticipantPrivate.d.ts index 4ab722c122..4b117fcb27 100644 --- a/src/@types/vscode.proposed.chatParticipantPrivate.d.ts +++ b/src/@types/vscode.proposed.chatParticipantPrivate.d.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -// version: 11 +// version: 12 declare module 'vscode' { @@ -110,6 +110,11 @@ declare module 'vscode' { * Display name of the subagent that is invoking this request. */ readonly subAgentName?: string; + + /** + * The request ID of the parent request that invoked this subagent. + */ + readonly parentRequestId?: string; } export enum ChatRequestEditedFileEventKind { @@ -339,4 +344,17 @@ declare module 'vscode' { } // #endregion + + // #region Steering + + export interface ChatContext { + /** + * Set to `true` by the editor to request the language model gracefully + * stop after its next opportunity. When set, it's likely that the editor + * will immediately follow up with a new request in the same conversation. + */ + readonly yieldRequested: boolean; + } + + // #endregion } diff --git a/src/@types/vscode.proposed.chatSessionsProvider.d.ts b/src/@types/vscode.proposed.chatSessionsProvider.d.ts index 84cd547599..a2dc921d69 100644 --- a/src/@types/vscode.proposed.chatSessionsProvider.d.ts +++ b/src/@types/vscode.proposed.chatSessionsProvider.d.ts @@ -42,7 +42,7 @@ declare module 'vscode' { /** * Creates a new {@link ChatSessionItemController chat session item controller} with the given unique identifier. */ - export function createChatSessionItemController(id: string, refreshHandler: () => Thenable): ChatSessionItemController; + export function createChatSessionItemController(id: string, refreshHandler: (token: CancellationToken) => Thenable): ChatSessionItemController; } /** @@ -97,7 +97,7 @@ declare module 'vscode' { * * This is also called on first load to get the initial set of items. */ - refreshHandler: () => Thenable; + refreshHandler: (token: CancellationToken) => Thenable; /** * Fired when an item's archived state changes. @@ -231,22 +231,14 @@ declare module 'vscode' { /** * Statistics about the chat session. */ - changes?: readonly ChatSessionChangedFile[] | readonly ChatSessionChangedFile2[] | { - /** - * Number of files edited during the session. - */ - files: number; - - /** - * Number of insertions made during the session. - */ - insertions: number; + changes?: readonly ChatSessionChangedFile[] | readonly ChatSessionChangedFile2[]; - /** - * Number of deletions made during the session. - */ - deletions: number; - }; + /** + * Arbitrary metadata for the chat session. Can be anything, but must be JSON-stringifyable. + * + * To update the metadata you must re-set this property. + */ + metadata?: { readonly [key: string]: any }; } export class ChatSessionChangedFile { diff --git a/src/github/folderRepositoryManager.ts b/src/github/folderRepositoryManager.ts index df1e71c549..0148d579b9 100644 --- a/src/github/folderRepositoryManager.ts +++ b/src/github/folderRepositoryManager.ts @@ -3124,23 +3124,6 @@ function unwrapCommitMessageBody(body: string): string { // Stack stores { markerIndent, contentIndent } for each nesting level const listStack: { markerIndent: number; contentIndent: number }[] = []; - const getNextNonBlankLineInfo = ( - startIndex: number, - ): { line: string; indent: number; isListItem: boolean } | undefined => { - for (let idx = startIndex; idx < lines.length; idx++) { - const candidate = lines[idx]; - if (candidate.trim() === '') { - continue; - } - return { - line: candidate, - indent: getLeadingWhitespaceLength(candidate), - isListItem: LIST_ITEM_PATTERN.test(candidate), - }; - } - return undefined; - }; - // Find the active list context for a given line indent // Returns the content indent if the line is within an active list context const getActiveListContentIndent = (lineIndent: number): number | undefined => { @@ -3191,19 +3174,6 @@ function unwrapCommitMessageBody(body: string): string { return false; } - const nextInfo = getNextNonBlankLineInfo(lineIndex + 1); - if (!nextInfo) { - return true; - } - - // If next line is a list item at or before the current list level, don't join - if (nextInfo.isListItem) { - const currentListLevel = listStack.length > 0 ? listStack[listStack.length - 1].markerIndent : 0; - if (nextInfo.indent <= currentListLevel) { - return false; - } - } - return true; }; diff --git a/src/test/github/folderRepositoryManager.test.ts b/src/test/github/folderRepositoryManager.test.ts index 530ffaf9e2..9832a221a8 100644 --- a/src/test/github/folderRepositoryManager.test.ts +++ b/src/test/github/folderRepositoryManager.test.ts @@ -191,12 +191,12 @@ describe('titleAndBodyFrom', function () { assert.strictEqual(result?.body, '- Item 1\n - Nested item 1.1\n - Nested item 1.2\n- Item 2'); }); - it('preserves list item continuations', async function () { + it('unwraps list item continuations', async function () { const message = Promise.resolve('title\n\n- This is a list item that is long\n and continues on the next line\n- Second item'); const result = await titleAndBodyFrom(message); assert.strictEqual(result?.title, 'title'); - assert.strictEqual(result?.body, '- This is a list item that is long\n and continues on the next line\n- Second item'); + assert.strictEqual(result?.body, '- This is a list item that is long and continues on the next line\n- Second item'); }); it('preserves indented code blocks but not list continuations', async function () { @@ -207,12 +207,12 @@ describe('titleAndBodyFrom', function () { assert.strictEqual(result?.body, 'Regular paragraph.\n\n This is code\n More code\n\nAnother paragraph.'); }); - it('unwraps regular text but preserves list item continuations', async function () { + it('unwraps regular text and list item continuations', async function () { const message = Promise.resolve('title\n\nThis is wrapped text\nthat should be joined.\n\n- List item with\n continuation\n- Another item'); const result = await titleAndBodyFrom(message); assert.strictEqual(result?.title, 'title'); - assert.strictEqual(result?.body, 'This is wrapped text that should be joined.\n\n- List item with\n continuation\n- Another item'); + assert.strictEqual(result?.body, 'This is wrapped text that should be joined.\n\n- List item with continuation\n- Another item'); }); it('handles complex nested lists with wrapped paragraphs', async function () { @@ -220,7 +220,7 @@ describe('titleAndBodyFrom', function () { const result = await titleAndBodyFrom(message); assert.strictEqual(result?.title, 'title'); - assert.strictEqual(result?.body, 'Wrapped paragraph across lines.\n\n- Item 1\n - Nested item\n More nested content\n- Item 2\n\nAnother wrapped paragraph here.'); + assert.strictEqual(result?.body, 'Wrapped paragraph across lines.\n\n- Item 1\n - Nested item More nested content\n- Item 2\n\nAnother wrapped paragraph here.'); }); it('handles nested lists', async function () { From 3b8286a066442857a8e2c358ebd32cbd6ae83111 Mon Sep 17 00:00:00 2001 From: Alex Ross <38270282+alexr00@users.noreply.github.com> Date: Wed, 4 Feb 2026 12:42:15 +0100 Subject: [PATCH 07/10] Add test --- src/test/github/folderRepositoryManager.test.ts | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/test/github/folderRepositoryManager.test.ts b/src/test/github/folderRepositoryManager.test.ts index 9832a221a8..4a43d8ad15 100644 --- a/src/test/github/folderRepositoryManager.test.ts +++ b/src/test/github/folderRepositoryManager.test.ts @@ -334,4 +334,12 @@ describe('titleAndBodyFrom', function () { assert.strictEqual(result?.title, 'title'); assert.strictEqual(result?.body, '* Outer item\n\n Second paragraph of outer with continuation\n * Inner item\n\n Second paragraph of inner with continuation'); }); + + it('handles first list item needs to be unwrapped', async function () { + const message = Promise.resolve('This is a test\n\n- A fslilenfilnf flen felslnf lsefl fnels Leknef\nLkdfnle lfkenSlefn Lnkef LefnLienf LIfnels\n- B\n- C'); + + const result = await titleAndBodyFrom(message); + assert.strictEqual(result?.title, 'This is a test'); + assert.strictEqual(result?.body, '- A fslilenfilnf flen felslnf lsefl fnels Leknef Lkdfnle lfkenSlefn Lnkef LefnLienf LIfnels\n- B\n- C'); + }); }); From a10759de81d3fa68233d9fc1a5d772886a65dee3 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 4 Feb 2026 11:49:59 +0000 Subject: [PATCH 08/10] Fix unwrapping of non-indented list continuations When a list item is followed by a wrapped line that has no indentation (but is not itself a list item), the line should be joined to the previous list item. This is common in commit messages wrapped at 72 chars. The fix moves the handling of non-indented continuations BEFORE calling getActiveListContentIndent, which was clearing the list stack and preventing the join from happening. Co-authored-by: alexr00 <38270282+alexr00@users.noreply.github.com> --- src/github/folderRepositoryManager.ts | 58 +++++++++++++++++++++++++++ 1 file changed, 58 insertions(+) diff --git a/src/github/folderRepositoryManager.ts b/src/github/folderRepositoryManager.ts index 0148d579b9..8eb9c762d4 100644 --- a/src/github/folderRepositoryManager.ts +++ b/src/github/folderRepositoryManager.ts @@ -3221,6 +3221,64 @@ function unwrapCommitMessageBody(body: string): string { continue; } + // Handle non-indented lines that should be joined to a previous list item + // This happens when commit messages are wrapped at 72 characters + // Check this BEFORE calling getActiveListContentIndent which would clear the stack + if (listStack.length > 0 && lineIndent === 0 && !LIST_ITEM_PATTERN.test(line)) { + const isBlockquote = BLOCKQUOTE_PATTERN.test(line); + if (!isBlockquote) { + const baseIndex = result.length - 1; + const baseLine = baseIndex >= 0 ? result[baseIndex] : ''; + const previousLineIsBlank = baseLine.trim() === ''; + + if (!previousLineIsBlank && baseIndex >= 0) { + // Join this line and any following non-list-item lines with the previous list item + let joinedLine = baseLine; + let currentIndex = i; + + while (currentIndex < lines.length) { + const currentLine = lines[currentIndex]; + const trimmed = currentLine.trim(); + + // Stop at blank lines + if (!trimmed) { + break; + } + + // Stop at list items + if (LIST_ITEM_PATTERN.test(currentLine)) { + break; + } + + // Stop at blockquotes or fences + if (BLOCKQUOTE_PATTERN.test(currentLine) || FENCE_PATTERN.test(currentLine)) { + break; + } + + // Stop at indented code blocks + const currentLineIndent = getLeadingWhitespaceLength(currentLine); + if (currentLineIndent >= 4) { + break; + } + + // Stop if previous line has hard line break + if (hasHardLineBreak(joinedLine)) { + break; + } + + joinedLine = appendWithSpace(joinedLine, trimmed); + currentIndex++; + } + + if (currentIndex > i) { + result[baseIndex] = joinedLine; + i = currentIndex; + continue; + } + } + } + } + const activeContentIndent = getActiveListContentIndent(lineIndent); const codeIndentThreshold = activeContentIndent !== undefined ? activeContentIndent + 4 : 4; const isBlockquote = BLOCKQUOTE_PATTERN.test(line); From 2b0f8588482f958f8fe40670ad6e995029838dad Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 4 Feb 2026 15:20:35 +0000 Subject: [PATCH 09/10] Refactor unwrapCommitMessageBody to gitUtils.ts Move the unwrapCommitMessageBody function from folderRepositoryManager.ts to gitUtils.ts as a reusable utility function. Co-authored-by: alexr00 <38270282+alexr00@users.noreply.github.com> --- src/common/gitUtils.ts | 344 +++++++++++++++++++++++++ src/github/folderRepositoryManager.ts | 345 +------------------------- 2 files changed, 345 insertions(+), 344 deletions(-) diff --git a/src/common/gitUtils.ts b/src/common/gitUtils.ts index dc4539e1ec..766939a0d7 100644 --- a/src/common/gitUtils.ts +++ b/src/common/gitUtils.ts @@ -7,6 +7,350 @@ import * as vscode from 'vscode'; import { Repository } from '../api/api'; import { GitApiImpl } from '../api/api1'; +/** + * Unwraps lines that were wrapped for conventional commit message formatting (typically at 72 characters). + * Similar to GitHub's behavior when converting commit messages to PR descriptions. + * + * Rules: + * - Preserves blank lines as paragraph breaks + * - Preserves fenced code blocks (```) + * - Preserves list items (-, *, +, numbered) + * - Preserves blockquotes (>) + * - Preserves indented code blocks (4+ spaces at start, when not in a list context) + * - Joins consecutive plain text lines that appear to be wrapped mid-sentence + */ +export function unwrapCommitMessageBody(body: string): string { + if (!body) { + return body; + } + + // Pattern to detect list item markers at the start of a line and capture the marker + const LIST_ITEM_PATTERN = /^(?[ \t]*)(?[*+\-]|\d+\.)(?[ \t]+)/; + // Pattern to detect blockquote markers + const BLOCKQUOTE_PATTERN = /^[ \t]*>/; + // Pattern to detect fenced code block markers + const FENCE_PATTERN = /^[ \t]*```/; + + const getLeadingWhitespaceLength = (text: string): number => text.match(/^[ \t]*/)?.[0].length ?? 0; + const hasHardLineBreak = (text: string): boolean => / {2}$/.test(text); + const appendWithSpace = (base: string, addition: string): string => { + if (!addition) { + return base; + } + return base.length > 0 && !/\s$/.test(base) ? `${base} ${addition}` : `${base}${addition}`; + }; + + // Get the content indent for a list item (position where actual content starts) + const getListItemContentIndent = (line: string): number => { + const match = line.match(LIST_ITEM_PATTERN); + if (!match?.groups) { + return 0; + } + // Content indent = leading whitespace + marker + space after marker + return match.groups.leadingWhitespace.length + match.groups.marker.length + match.groups.markerTrailingWhitespace.length; + }; + + const lines = body.split('\n'); + const result: string[] = []; + let i = 0; + let inFencedBlock = false; + // Stack stores { markerIndent, contentIndent } for each nesting level + const listStack: { markerIndent: number; contentIndent: number }[] = []; + + // Find the active list context for a given line indent + // Returns the content indent if the line is within an active list context + const getActiveListContentIndent = (lineIndent: number): number | undefined => { + for (let idx = listStack.length - 1; idx >= 0; idx--) { + const { markerIndent, contentIndent } = listStack[idx]; + // A line is part of a list item if it has at least 1 space indent + // (but less than contentIndent + 4 which would be a code block) + if (lineIndent >= 1 && lineIndent >= markerIndent) { + listStack.length = idx + 1; + return contentIndent; + } + listStack.pop(); + } + return undefined; + }; + + const shouldJoinListContinuation = (lineIndex: number, contentIndent: number, baseLine: string): boolean => { + const currentLine = lines[lineIndex]; + if (!currentLine) { + return false; + } + + const trimmed = currentLine.trim(); + if (!trimmed) { + return false; + } + + if (hasHardLineBreak(baseLine) || hasHardLineBreak(currentLine)) { + return false; + } + + if (LIST_ITEM_PATTERN.test(currentLine)) { + return false; + } + + if (BLOCKQUOTE_PATTERN.test(currentLine) || FENCE_PATTERN.test(currentLine)) { + return false; + } + + const currentIndent = getLeadingWhitespaceLength(currentLine); + // Need at least 1 space to be a continuation + if (currentIndent < 1) { + return false; + } + + // 4+ spaces beyond content indent is an indented code block + if (currentIndent >= contentIndent + 4) { + return false; + } + + return true; + }; + + while (i < lines.length) { + const line = lines[i]; + + // Preserve blank lines but don't clear list context + // (multi-paragraph lists are allowed in GitHub markdown) + if (line.trim() === '') { + result.push(line); + i++; + continue; + } + + // Check for fenced code block markers + if (FENCE_PATTERN.test(line)) { + inFencedBlock = !inFencedBlock; + result.push(line); + i++; + continue; + } + + // Preserve everything inside fenced code blocks + if (inFencedBlock) { + result.push(line); + i++; + continue; + } + + const lineIndent = getLeadingWhitespaceLength(line); + const listItemMatch = line.match(LIST_ITEM_PATTERN); + + if (listItemMatch?.groups) { + const markerIndent = listItemMatch.groups.leadingWhitespace.length; + const contentIndent = getListItemContentIndent(line); + + // Pop list levels that are at or beyond this indent + while (listStack.length && markerIndent <= listStack[listStack.length - 1].markerIndent) { + listStack.pop(); + } + + listStack.push({ markerIndent, contentIndent }); + result.push(line); + i++; + continue; + } + + // Handle non-indented lines that should be joined to a previous list item + // This happens when commit messages are wrapped at 72 characters + // Check this BEFORE calling getActiveListContentIndent which would clear the stack + if (listStack.length > 0 && lineIndent === 0 && !LIST_ITEM_PATTERN.test(line)) { + const isBlockquote = BLOCKQUOTE_PATTERN.test(line); + if (!isBlockquote) { + const baseIndex = result.length - 1; + const baseLine = baseIndex >= 0 ? result[baseIndex] : ''; + const previousLineIsBlank = baseLine.trim() === ''; + + if (!previousLineIsBlank && baseIndex >= 0) { + // Join this line and any following non-list-item lines with the previous list item + let joinedLine = baseLine; + let currentIndex = i; + + while (currentIndex < lines.length) { + const currentLine = lines[currentIndex]; + const trimmed = currentLine.trim(); + + // Stop at blank lines + if (!trimmed) { + break; + } + + // Stop at list items + if (LIST_ITEM_PATTERN.test(currentLine)) { + break; + } + + // Stop at blockquotes or fences + if (BLOCKQUOTE_PATTERN.test(currentLine) || FENCE_PATTERN.test(currentLine)) { + break; + } + + // Stop at indented code blocks + const currentLineIndent = getLeadingWhitespaceLength(currentLine); + if (currentLineIndent >= 4) { + break; + } + + // Stop if previous line has hard line break + if (hasHardLineBreak(joinedLine)) { + break; + } + + joinedLine = appendWithSpace(joinedLine, trimmed); + currentIndex++; + } + + if (currentIndex > i) { + result[baseIndex] = joinedLine; + i = currentIndex; + continue; + } + } + } + } + + const activeContentIndent = getActiveListContentIndent(lineIndent); + const codeIndentThreshold = activeContentIndent !== undefined ? activeContentIndent + 4 : 4; + const isBlockquote = BLOCKQUOTE_PATTERN.test(line); + const isIndentedCode = lineIndent >= codeIndentThreshold; + + if (isBlockquote || isIndentedCode) { + result.push(line); + i++; + continue; + } + + // Handle list item continuations + if (activeContentIndent !== undefined && lineIndent >= 1) { + const baseIndex = result.length - 1; + // Only try to join with previous line if it's not blank + // Multi-paragraph lists have blank lines that should be preserved + const baseLine = baseIndex >= 0 ? result[baseIndex] : ''; + const previousLineIsBlank = baseLine.trim() === ''; + + if (!previousLineIsBlank && baseIndex >= 0) { + let joinedLine = baseLine; + let appended = false; + let currentIndex = i; + + while ( + currentIndex < lines.length && + shouldJoinListContinuation(currentIndex, activeContentIndent, joinedLine) + ) { + const continuationText = lines[currentIndex].trim(); + if (continuationText) { + joinedLine = appendWithSpace(joinedLine, continuationText); + appended = true; + } + currentIndex++; + } + + if (appended) { + result[baseIndex] = joinedLine; + i = currentIndex; + continue; + } + } + + // For multi-paragraph continuations or standalone indented lines, + // preserve indentation but unwrap consecutive continuation lines + let joinedLine = line; + i++; + + while (i < lines.length) { + const nextLine = lines[i]; + + if (nextLine.trim() === '') { + break; + } + + if (FENCE_PATTERN.test(nextLine)) { + break; + } + + if (LIST_ITEM_PATTERN.test(nextLine)) { + break; + } + + if (BLOCKQUOTE_PATTERN.test(nextLine)) { + break; + } + + const nextIndent = getLeadingWhitespaceLength(nextLine); + // Check for code block + if (nextIndent >= activeContentIndent + 4) { + break; + } + + // Must have at least 1 space to be a continuation + if (nextIndent < 1) { + break; + } + + // Check for hard line break + if (hasHardLineBreak(joinedLine)) { + break; + } + + // Join this line - preserve the original indentation for the first line + joinedLine = appendWithSpace(joinedLine, nextLine.trim()); + i++; + } + + result.push(joinedLine); + continue; + } + + // Start accumulating lines that should be joined (plain text) + let joinedLine = line; + i++; + + // Keep joining lines until we hit a blank line or a line that shouldn't be joined + while (i < lines.length) { + const nextLine = lines[i]; + + // Stop at blank lines + if (nextLine.trim() === '') { + break; + } + + // Stop at fenced code blocks + if (FENCE_PATTERN.test(nextLine)) { + break; + } + + // Stop at list items + if (LIST_ITEM_PATTERN.test(nextLine)) { + break; + } + + // Stop at blockquotes + if (BLOCKQUOTE_PATTERN.test(nextLine)) { + break; + } + + // Check if next line is indented code (4+ spaces, when not in a list context) + const nextLeadingSpaces = getLeadingWhitespaceLength(nextLine); + const nextIsIndentedCode = nextLeadingSpaces >= 4; + + if (nextIsIndentedCode) { + break; + } + + // Join this line with a space + joinedLine = appendWithSpace(joinedLine, nextLine.trim()); + i++; + } + + result.push(joinedLine); + } + + return result.join('\n'); +} + /** * Determines if a repository is a submodule by checking if its path * appears in any other repository's submodules list. diff --git a/src/github/folderRepositoryManager.ts b/src/github/folderRepositoryManager.ts index 8eb9c762d4..31b70fc892 100644 --- a/src/github/folderRepositoryManager.ts +++ b/src/github/folderRepositoryManager.ts @@ -34,6 +34,7 @@ import { AuthProvider, GitHubServerType } from '../common/authentication'; import { commands, contexts } from '../common/executeCommands'; import { InMemFileChange, SlimFileChange } from '../common/file'; import { findLocalRepoRemoteFromGitHubRef } from '../common/githubRef'; +import { unwrapCommitMessageBody } from '../common/gitUtils'; import { Disposable, disposeAll } from '../common/lifecycle'; import Logger from '../common/logger'; import { Protocol, ProtocolType } from '../common/protocol'; @@ -3074,350 +3075,6 @@ const ownedByMe: AsyncPredicate = async repo => { export const byRemoteName = (name: string): Predicate => ({ remote: { remoteName } }) => remoteName === name; -/** - * Unwraps lines that were wrapped for conventional commit message formatting (typically at 72 characters). - * Similar to GitHub's behavior when converting commit messages to PR descriptions. - * - * Rules: - * - Preserves blank lines as paragraph breaks - * - Preserves fenced code blocks (```) - * - Preserves list items (-, *, +, numbered) - * - Preserves blockquotes (>) - * - Preserves indented code blocks (4+ spaces at start, when not in a list context) - * - Joins consecutive plain text lines that appear to be wrapped mid-sentence - */ -function unwrapCommitMessageBody(body: string): string { - if (!body) { - return body; - } - - // Pattern to detect list item markers at the start of a line and capture the marker - const LIST_ITEM_PATTERN = /^(?[ \t]*)(?[*+\-]|\d+\.)(?[ \t]+)/; - // Pattern to detect blockquote markers - const BLOCKQUOTE_PATTERN = /^[ \t]*>/; - // Pattern to detect fenced code block markers - const FENCE_PATTERN = /^[ \t]*```/; - - const getLeadingWhitespaceLength = (text: string): number => text.match(/^[ \t]*/)?.[0].length ?? 0; - const hasHardLineBreak = (text: string): boolean => / {2}$/.test(text); - const appendWithSpace = (base: string, addition: string): string => { - if (!addition) { - return base; - } - return base.length > 0 && !/\s$/.test(base) ? `${base} ${addition}` : `${base}${addition}`; - }; - - // Get the content indent for a list item (position where actual content starts) - const getListItemContentIndent = (line: string): number => { - const match = line.match(LIST_ITEM_PATTERN); - if (!match?.groups) { - return 0; - } - // Content indent = leading whitespace + marker + space after marker - return match.groups.leadingWhitespace.length + match.groups.marker.length + match.groups.markerTrailingWhitespace.length; - }; - - const lines = body.split('\n'); - const result: string[] = []; - let i = 0; - let inFencedBlock = false; - // Stack stores { markerIndent, contentIndent } for each nesting level - const listStack: { markerIndent: number; contentIndent: number }[] = []; - - // Find the active list context for a given line indent - // Returns the content indent if the line is within an active list context - const getActiveListContentIndent = (lineIndent: number): number | undefined => { - for (let idx = listStack.length - 1; idx >= 0; idx--) { - const { markerIndent, contentIndent } = listStack[idx]; - // A line is part of a list item if it has at least 1 space indent - // (but less than contentIndent + 4 which would be a code block) - if (lineIndent >= 1 && lineIndent >= markerIndent) { - listStack.length = idx + 1; - return contentIndent; - } - listStack.pop(); - } - return undefined; - }; - - const shouldJoinListContinuation = (lineIndex: number, contentIndent: number, baseLine: string): boolean => { - const currentLine = lines[lineIndex]; - if (!currentLine) { - return false; - } - - const trimmed = currentLine.trim(); - if (!trimmed) { - return false; - } - - if (hasHardLineBreak(baseLine) || hasHardLineBreak(currentLine)) { - return false; - } - - if (LIST_ITEM_PATTERN.test(currentLine)) { - return false; - } - - if (BLOCKQUOTE_PATTERN.test(currentLine) || FENCE_PATTERN.test(currentLine)) { - return false; - } - - const currentIndent = getLeadingWhitespaceLength(currentLine); - // Need at least 1 space to be a continuation - if (currentIndent < 1) { - return false; - } - - // 4+ spaces beyond content indent is an indented code block - if (currentIndent >= contentIndent + 4) { - return false; - } - - return true; - }; - - while (i < lines.length) { - const line = lines[i]; - - // Preserve blank lines but don't clear list context - // (multi-paragraph lists are allowed in GitHub markdown) - if (line.trim() === '') { - result.push(line); - i++; - continue; - } - - // Check for fenced code block markers - if (FENCE_PATTERN.test(line)) { - inFencedBlock = !inFencedBlock; - result.push(line); - i++; - continue; - } - - // Preserve everything inside fenced code blocks - if (inFencedBlock) { - result.push(line); - i++; - continue; - } - - const lineIndent = getLeadingWhitespaceLength(line); - const listItemMatch = line.match(LIST_ITEM_PATTERN); - - if (listItemMatch?.groups) { - const markerIndent = listItemMatch.groups.leadingWhitespace.length; - const contentIndent = getListItemContentIndent(line); - - // Pop list levels that are at or beyond this indent - while (listStack.length && markerIndent <= listStack[listStack.length - 1].markerIndent) { - listStack.pop(); - } - - listStack.push({ markerIndent, contentIndent }); - result.push(line); - i++; - continue; - } - - // Handle non-indented lines that should be joined to a previous list item - // This happens when commit messages are wrapped at 72 characters - // Check this BEFORE calling getActiveListContentIndent which would clear the stack - if (listStack.length > 0 && lineIndent === 0 && !LIST_ITEM_PATTERN.test(line)) { - const isBlockquote = BLOCKQUOTE_PATTERN.test(line); - if (!isBlockquote) { - const baseIndex = result.length - 1; - const baseLine = baseIndex >= 0 ? result[baseIndex] : ''; - const previousLineIsBlank = baseLine.trim() === ''; - - if (!previousLineIsBlank && baseIndex >= 0) { - // Join this line and any following non-list-item lines with the previous list item - let joinedLine = baseLine; - let currentIndex = i; - - while (currentIndex < lines.length) { - const currentLine = lines[currentIndex]; - const trimmed = currentLine.trim(); - - // Stop at blank lines - if (!trimmed) { - break; - } - - // Stop at list items - if (LIST_ITEM_PATTERN.test(currentLine)) { - break; - } - - // Stop at blockquotes or fences - if (BLOCKQUOTE_PATTERN.test(currentLine) || FENCE_PATTERN.test(currentLine)) { - break; - } - - // Stop at indented code blocks - const currentLineIndent = getLeadingWhitespaceLength(currentLine); - if (currentLineIndent >= 4) { - break; - } - - // Stop if previous line has hard line break - if (hasHardLineBreak(joinedLine)) { - break; - } - - joinedLine = appendWithSpace(joinedLine, trimmed); - currentIndex++; - } - - if (currentIndex > i) { - result[baseIndex] = joinedLine; - i = currentIndex; - continue; - } - } - } - } - - const activeContentIndent = getActiveListContentIndent(lineIndent); - const codeIndentThreshold = activeContentIndent !== undefined ? activeContentIndent + 4 : 4; - const isBlockquote = BLOCKQUOTE_PATTERN.test(line); - const isIndentedCode = lineIndent >= codeIndentThreshold; - - if (isBlockquote || isIndentedCode) { - result.push(line); - i++; - continue; - } - - // Handle list item continuations - if (activeContentIndent !== undefined && lineIndent >= 1) { - const baseIndex = result.length - 1; - // Only try to join with previous line if it's not blank - // Multi-paragraph lists have blank lines that should be preserved - const baseLine = baseIndex >= 0 ? result[baseIndex] : ''; - const previousLineIsBlank = baseLine.trim() === ''; - - if (!previousLineIsBlank && baseIndex >= 0) { - let joinedLine = baseLine; - let appended = false; - let currentIndex = i; - - while ( - currentIndex < lines.length && - shouldJoinListContinuation(currentIndex, activeContentIndent, joinedLine) - ) { - const continuationText = lines[currentIndex].trim(); - if (continuationText) { - joinedLine = appendWithSpace(joinedLine, continuationText); - appended = true; - } - currentIndex++; - } - - if (appended) { - result[baseIndex] = joinedLine; - i = currentIndex; - continue; - } - } - - // For multi-paragraph continuations or standalone indented lines, - // preserve indentation but unwrap consecutive continuation lines - let joinedLine = line; - i++; - - while (i < lines.length) { - const nextLine = lines[i]; - - if (nextLine.trim() === '') { - break; - } - - if (FENCE_PATTERN.test(nextLine)) { - break; - } - - if (LIST_ITEM_PATTERN.test(nextLine)) { - break; - } - - if (BLOCKQUOTE_PATTERN.test(nextLine)) { - break; - } - - const nextIndent = getLeadingWhitespaceLength(nextLine); - // Check for code block - if (nextIndent >= activeContentIndent + 4) { - break; - } - - // Must have at least 1 space to be a continuation - if (nextIndent < 1) { - break; - } - - // Check for hard line break - if (hasHardLineBreak(joinedLine)) { - break; - } - - // Join this line - preserve the original indentation for the first line - joinedLine = appendWithSpace(joinedLine, nextLine.trim()); - i++; - } - - result.push(joinedLine); - continue; - } - - // Start accumulating lines that should be joined (plain text) - let joinedLine = line; - i++; - - // Keep joining lines until we hit a blank line or a line that shouldn't be joined - while (i < lines.length) { - const nextLine = lines[i]; - - // Stop at blank lines - if (nextLine.trim() === '') { - break; - } - - // Stop at fenced code blocks - if (FENCE_PATTERN.test(nextLine)) { - break; - } - - // Stop at list items - if (LIST_ITEM_PATTERN.test(nextLine)) { - break; - } - - // Stop at blockquotes - if (BLOCKQUOTE_PATTERN.test(nextLine)) { - break; - } - - // Check if next line is indented code (4+ spaces, when not in a list context) - const nextLeadingSpaces = getLeadingWhitespaceLength(nextLine); - const nextIsIndentedCode = nextLeadingSpaces >= 4; - - if (nextIsIndentedCode) { - break; - } - - // Join this line with a space - joinedLine = appendWithSpace(joinedLine, nextLine.trim()); - i++; - } - - result.push(joinedLine); - } - - return result.join('\n'); -} - export const titleAndBodyFrom = async (promise: Promise): Promise<{ title: string; body: string } | undefined> => { const message = await promise; if (!message) { From 6c9b1289fe7a180e600002d67ce6e1b30669ddbb Mon Sep 17 00:00:00 2001 From: Alex Ross <38270282+alexr00@users.noreply.github.com> Date: Thu, 5 Feb 2026 10:36:57 +0100 Subject: [PATCH 10/10] Revert proposal changes --- .../vscode.proposed.chatContextProvider.d.ts | 112 ++------------ ...ode.proposed.chatParticipantAdditions.d.ts | 146 ++---------------- ...scode.proposed.chatParticipantPrivate.d.ts | 20 +-- .../vscode.proposed.chatSessionsProvider.d.ts | 26 ++-- 4 files changed, 40 insertions(+), 264 deletions(-) diff --git a/src/@types/vscode.proposed.chatContextProvider.d.ts b/src/@types/vscode.proposed.chatContextProvider.d.ts index d9e6583c09..47a9284099 100644 --- a/src/@types/vscode.proposed.chatContextProvider.d.ts +++ b/src/@types/vscode.proposed.chatContextProvider.d.ts @@ -11,45 +11,16 @@ declare module 'vscode' { export namespace chat { /** - * Register a chat workspace context provider. Workspace context is automatically included in all chat requests. + * Register a chat context provider. Chat context can be provided: + * - For a resource. Make sure to pass a selector that matches the resource you want to provide context for. + * Providers registered without a selector will not be called for resource-based context. + * - Explicitly. These context items are shown as options when the user explicitly attaches context. * * To ensure your extension is activated when chat context is requested, make sure to include the following activations events: * - If your extension implements `provideWorkspaceChatContext` or `provideChatContextForResource`, find an activation event which is a good signal to activate. * Ex: `onLanguage:`, `onWebviewPanel:`, etc.` * - If your extension implements `provideChatContextExplicit`, your extension will be automatically activated when the user requests explicit context. * - * @param id Unique identifier for the provider. - * @param provider The chat workspace context provider. - */ - export function registerChatWorkspaceContextProvider(id: string, provider: ChatWorkspaceContextProvider): Disposable; - - /** - * Register a chat explicit context provider. Explicit context items are shown as options when the user explicitly attaches context. - * - * To ensure your extension is activated when chat context is requested, make sure to include the `onChatContextProvider:` activation event in your `package.json`. - * - * @param id Unique identifier for the provider. - * @param provider The chat explicit context provider. - */ - export function registerChatExplicitContextProvider(id: string, provider: ChatExplicitContextProvider): Disposable; - - /** - * Register a chat resource context provider. Resource context is provided for a specific resource. - * Make sure to pass a selector that matches the resource you want to provide context for. - * - * To ensure your extension is activated when chat context is requested, make sure to include the `onChatContextProvider:` activation event in your `package.json`. - * - * @param selector Document selector to filter which resources the provider is called for. - * @param id Unique identifier for the provider. - * @param provider The chat resource context provider. - */ - export function registerChatResourceContextProvider(selector: DocumentSelector, id: string, provider: ChatResourceContextProvider): Disposable; - - /** - * Register a chat context provider. - * - * @deprecated Use {@link registerChatWorkspaceContextProvider}, {@link registerChatExplicitContextProvider}, or {@link registerChatResourceContextProvider} instead. - * * @param selector Optional document selector to filter which resources the provider is called for. If omitted, the provider will only be called for explicit context requests. * @param id Unique identifier for the provider. * @param provider The chat context provider. @@ -61,21 +32,12 @@ declare module 'vscode' { export interface ChatContextItem { /** * Icon for the context item. - * - If `icon` is not defined, no icon is shown. - * - If `icon` is defined and is a file or folder icon, the icon is derived from {@link resourceUri} if `resourceUri` is defined. - * - Otherwise, `icon` is used. */ - icon?: ThemeIcon; + icon: ThemeIcon; /** * Human readable label for the context item. - * If not set, the label is derived from {@link resourceUri}. - */ - label?: string; - /** - * A resource URI for the context item. - * Used to derive the {@link label} and {@link icon} if they are not set. */ - resourceUri?: Uri; + label: string; /** * An optional description of the context item, e.g. to describe the item to the language model. */ @@ -95,7 +57,7 @@ declare module 'vscode' { command?: Command; } - export interface ChatWorkspaceContextProvider { + export interface ChatContextProvider { /** * An optional event that should be fired when the workspace chat context has changed. @@ -103,16 +65,15 @@ declare module 'vscode' { onDidChangeWorkspaceChatContext?: Event; /** + * TODO @API: should this be a separate provider interface? + * * Provide a list of chat context items to be included as workspace context for all chat requests. * This should be used very sparingly to avoid providing useless context and to avoid using up the context window. * A good example use case is to provide information about which branch the user is working on in a source control context. * * @param token A cancellation token. */ - provideChatContext(token: CancellationToken): ProviderResult; - } - - export interface ChatExplicitContextProvider { + provideWorkspaceChatContext?(token: CancellationToken): ProviderResult; /** * Provide a list of chat context items that a user can choose from. These context items are shown as options when the user explicitly attaches context. @@ -121,18 +82,7 @@ declare module 'vscode' { * * @param token A cancellation token. */ - provideChatContext(token: CancellationToken): ProviderResult; - - /** - * If a chat context item is provided without a `value`, this method is called to resolve the `value` for the item. - * - * @param context The context item to resolve. - * @param token A cancellation token. - */ - resolveChatContext(context: T, token: CancellationToken): ProviderResult; - } - - export interface ChatResourceContextProvider { + provideChatContextExplicit?(token: CancellationToken): ProviderResult; /** * Given a particular resource, provide a chat context item for it. This is used for implicit context (see the settings `chat.implicitContext.enabled` and `chat.implicitContext.suggestedContext`). @@ -144,10 +94,10 @@ declare module 'vscode' { * @param options Options include the resource for which to provide context. * @param token A cancellation token. */ - provideChatContext(options: { resource: Uri }, token: CancellationToken): ProviderResult; + provideChatContextForResource?(options: { resource: Uri }, token: CancellationToken): ProviderResult; /** - * If a chat context item is provided without a `value`, this method is called to resolve the `value` for the item. + * If a chat context item is provided without a `value`, from either of the `provide` methods, this method is called to resolve the `value` for the item. * * @param context The context item to resolve. * @param token A cancellation token. @@ -155,40 +105,4 @@ declare module 'vscode' { resolveChatContext(context: T, token: CancellationToken): ProviderResult; } - /** - * @deprecated Use {@link ChatWorkspaceContextProvider}, {@link ChatExplicitContextProvider}, or {@link ChatResourceContextProvider} instead. - */ - export interface ChatContextProvider { - - /** - * An optional event that should be fired when the workspace chat context has changed. - * @deprecated Use {@link ChatWorkspaceContextProvider.onDidChangeWorkspaceChatContext} instead. - */ - onDidChangeWorkspaceChatContext?: Event; - - /** - * Provide a list of chat context items to be included as workspace context for all chat requests. - * @deprecated Use {@link ChatWorkspaceContextProvider.provideChatContext} instead. - */ - provideWorkspaceChatContext?(token: CancellationToken): ProviderResult; - - /** - * Provide a list of chat context items that a user can choose from. - * @deprecated Use {@link ChatExplicitContextProvider.provideChatContext} instead. - */ - provideChatContextExplicit?(token: CancellationToken): ProviderResult; - - /** - * Given a particular resource, provide a chat context item for it. - * @deprecated Use {@link ChatResourceContextProvider.provideChatContext} instead. - */ - provideChatContextForResource?(options: { resource: Uri }, token: CancellationToken): ProviderResult; - - /** - * If a chat context item is provided without a `value`, this method is called to resolve the `value` for the item. - * @deprecated Use the `resolveChatContext` method on the specific provider type instead. - */ - resolveChatContext?(context: T, token: CancellationToken): ProviderResult; - } - } diff --git a/src/@types/vscode.proposed.chatParticipantAdditions.d.ts b/src/@types/vscode.proposed.chatParticipantAdditions.d.ts index a5b9e66d57..df0f1045c8 100644 --- a/src/@types/vscode.proposed.chatParticipantAdditions.d.ts +++ b/src/@types/vscode.proposed.chatParticipantAdditions.d.ts @@ -3,8 +3,6 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -// version: 1 - declare module 'vscode' { export interface ChatParticipant { @@ -98,108 +96,6 @@ declare module 'vscode' { constructor(title: string, message: string | MarkdownString, data: any, buttons?: string[]); } - /** - * An option for a question in a carousel. - */ - export interface ChatQuestionOption { - /** - * Unique identifier for the option. - */ - id: string; - /** - * The display label for the option. - */ - label: string; - /** - * The value returned when this option is selected. - */ - value: unknown; - } - - /** - * The type of question for a chat question carousel. - */ - export enum ChatQuestionType { - /** - * A free-form text input question. - */ - Text = 1, - /** - * A single-select question with radio buttons. - */ - SingleSelect = 2, - /** - * A multi-select question with checkboxes. - */ - MultiSelect = 3 - } - - /** - * A question to be displayed in a question carousel. - */ - export class ChatQuestion { - /** - * Unique identifier for the question. - */ - id: string; - /** - * The type of question: Text for free-form input, SingleSelect for radio buttons, MultiSelect for checkboxes. - */ - type: ChatQuestionType; - /** - * The title/header of the question. - */ - title: string; - /** - * Optional detailed message or description for the question. - */ - message?: string | MarkdownString; - /** - * Options for singleSelect or multiSelect questions. - */ - options?: ChatQuestionOption[]; - /** - * The id(s) of the default selected option(s). - * For SingleSelect, this should be a single option id. - * For MultiSelect, this can be an array of option ids. - */ - defaultValue?: string | string[]; - /** - * Whether to allow free-form text input in addition to predefined options. - * When true, users can provide their own text answer even for SingleSelect or MultiSelect questions. - */ - allowFreeformInput?: boolean; - - constructor( - id: string, - type: ChatQuestionType, - title: string, - options?: { - message?: string | MarkdownString; - options?: ChatQuestionOption[]; - defaultValue?: string | string[]; - allowFreeformInput?: boolean; - } - ); - } - - /** - * A carousel view for presenting multiple questions inline in the chat. - * The UI is displayed but does not block the chat input. - */ - export class ChatResponseQuestionCarouselPart { - /** - * The questions to display in the carousel. - */ - questions: ChatQuestion[]; - /** - * Whether users can skip answering the questions. - */ - allowSkip: boolean; - - constructor(questions: ChatQuestion[], allowSkip?: boolean); - } - export class ChatResponseCodeCitationPart { value: Uri; license: string; @@ -266,20 +162,6 @@ declare module 'vscode' { output: McpToolInvocationContentData[]; } - export enum ChatTodoStatus { - NotStarted = 1, - InProgress = 2, - Completed = 3 - } - - export interface ChatTodoToolInvocationData { - todoList: Array<{ - id: number; - title: string; - status: ChatTodoStatus; - }>; - } - export class ChatToolInvocationPart { toolName: string; toolCallId: string; @@ -289,8 +171,8 @@ declare module 'vscode' { pastTenseMessage?: string | MarkdownString; isConfirmed?: boolean; isComplete?: boolean; - toolSpecificData?: ChatTerminalToolInvocationData | ChatMcpToolInvocationData | ChatTodoToolInvocationData; - subAgentInvocationId?: string; + toolSpecificData?: ChatTerminalToolInvocationData; + fromSubAgent?: boolean; presentation?: 'hidden' | 'hiddenAfterComplete' | undefined; constructor(toolName: string, toolCallId: string, isError?: boolean); @@ -362,7 +244,7 @@ declare module 'vscode' { constructor(uris: Uri[], callback: () => Thenable); } - export type ExtendedChatResponsePart = ChatResponsePart | ChatResponseTextEditPart | ChatResponseNotebookEditPart | ChatResponseWorkspaceEditPart | ChatResponseConfirmationPart | ChatResponseCodeCitationPart | ChatResponseReferencePart2 | ChatResponseMovePart | ChatResponseExtensionsPart | ChatResponsePullRequestPart | ChatToolInvocationPart | ChatResponseMultiDiffPart | ChatResponseThinkingProgressPart | ChatResponseExternalEditPart | ChatResponseQuestionCarouselPart; + export type ExtendedChatResponsePart = ChatResponsePart | ChatResponseTextEditPart | ChatResponseNotebookEditPart | ChatResponseConfirmationPart | ChatResponseCodeCitationPart | ChatResponseReferencePart2 | ChatResponseMovePart | ChatResponseExtensionsPart | ChatResponsePullRequestPart | ChatPrepareToolInvocationPart | ChatToolInvocationPart | ChatResponseMultiDiffPart | ChatResponseThinkingProgressPart | ChatResponseExternalEditPart; export class ChatResponseWarningPart { value: MarkdownString; constructor(value: string | MarkdownString); @@ -526,15 +408,6 @@ declare module 'vscode' { */ confirmation(title: string, message: string | MarkdownString, data: any, buttons?: string[]): void; - /** - * Show an inline carousel of questions to gather information from the user. - * This is a blocking call that waits for the user to submit or skip the questions. - * @param questions Array of questions to display to the user - * @param allowSkip Whether the user can skip questions without answering - * @returns A promise that resolves with the user's answers, or undefined if skipped - */ - questionCarousel(questions: ChatQuestion[], allowSkip?: boolean): Thenable | undefined>; - /** * Push a warning to this stream. Short-hand for * `push(new ChatResponseWarningPart(message))`. @@ -569,13 +442,6 @@ declare module 'vscode' { push(part: ExtendedChatResponsePart): void; clearToPreviousToolInvocation(reason: ChatResponseClearToPreviousToolInvocationReason): void; - - /** - * Report token usage information for this request. - * This is typically called when the underlying language model provides usage statistics. - * @param usage Token usage information including prompt and completion tokens - */ - usage(usage: ChatResultUsage): void; } export enum ChatResponseReferencePartStatusKind { @@ -767,6 +633,12 @@ declare module 'vscode' { * An optional detail string that will be rendered at the end of the response in certain UI contexts. */ details?: string; + + /** + * Token usage information for this request, if available. + * This is typically provided by the underlying language model. + */ + readonly usage?: ChatResultUsage; } export namespace chat { diff --git a/src/@types/vscode.proposed.chatParticipantPrivate.d.ts b/src/@types/vscode.proposed.chatParticipantPrivate.d.ts index 4b117fcb27..4ab722c122 100644 --- a/src/@types/vscode.proposed.chatParticipantPrivate.d.ts +++ b/src/@types/vscode.proposed.chatParticipantPrivate.d.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -// version: 12 +// version: 11 declare module 'vscode' { @@ -110,11 +110,6 @@ declare module 'vscode' { * Display name of the subagent that is invoking this request. */ readonly subAgentName?: string; - - /** - * The request ID of the parent request that invoked this subagent. - */ - readonly parentRequestId?: string; } export enum ChatRequestEditedFileEventKind { @@ -344,17 +339,4 @@ declare module 'vscode' { } // #endregion - - // #region Steering - - export interface ChatContext { - /** - * Set to `true` by the editor to request the language model gracefully - * stop after its next opportunity. When set, it's likely that the editor - * will immediately follow up with a new request in the same conversation. - */ - readonly yieldRequested: boolean; - } - - // #endregion } diff --git a/src/@types/vscode.proposed.chatSessionsProvider.d.ts b/src/@types/vscode.proposed.chatSessionsProvider.d.ts index a2dc921d69..84cd547599 100644 --- a/src/@types/vscode.proposed.chatSessionsProvider.d.ts +++ b/src/@types/vscode.proposed.chatSessionsProvider.d.ts @@ -42,7 +42,7 @@ declare module 'vscode' { /** * Creates a new {@link ChatSessionItemController chat session item controller} with the given unique identifier. */ - export function createChatSessionItemController(id: string, refreshHandler: (token: CancellationToken) => Thenable): ChatSessionItemController; + export function createChatSessionItemController(id: string, refreshHandler: () => Thenable): ChatSessionItemController; } /** @@ -97,7 +97,7 @@ declare module 'vscode' { * * This is also called on first load to get the initial set of items. */ - refreshHandler: (token: CancellationToken) => Thenable; + refreshHandler: () => Thenable; /** * Fired when an item's archived state changes. @@ -231,14 +231,22 @@ declare module 'vscode' { /** * Statistics about the chat session. */ - changes?: readonly ChatSessionChangedFile[] | readonly ChatSessionChangedFile2[]; + changes?: readonly ChatSessionChangedFile[] | readonly ChatSessionChangedFile2[] | { + /** + * Number of files edited during the session. + */ + files: number; - /** - * Arbitrary metadata for the chat session. Can be anything, but must be JSON-stringifyable. - * - * To update the metadata you must re-set this property. - */ - metadata?: { readonly [key: string]: any }; + /** + * Number of insertions made during the session. + */ + insertions: number; + + /** + * Number of deletions made during the session. + */ + deletions: number; + }; } export class ChatSessionChangedFile {