Merge branch 'main' into dev/mjbvz/straightforward-flyingfish

This commit is contained in:
Matt Bierner
2026-01-29 13:41:21 -08:00
committed by GitHub
7 changed files with 52 additions and 6 deletions
@@ -27,7 +27,7 @@ import { Lazy } from '../../../../../../base/common/lazy.js';
import { Emitter } from '../../../../../../base/common/event.js';
import { DisposableMap, DisposableStore, IDisposable } from '../../../../../../base/common/lifecycle.js';
import { autorun } from '../../../../../../base/common/observable.js';
import { CancellationToken } from '../../../../../../base/common/cancellation.js';
import { CancellationTokenSource } from '../../../../../../base/common/cancellation.js';
import { IChatMarkdownAnchorService } from './chatMarkdownAnchorService.js';
import { ChatMessageRole, ILanguageModelsService } from '../../../common/languageModels.js';
import { ExtensionIdentifier } from '../../../../../../platform/extensions/common/extensions.js';
@@ -189,6 +189,10 @@ export class ChatThinkingContentPart extends ChatCollapsibleContentPart implemen
}
this.currentThinkingValue = initialText;
if (initialText.trim()) {
this.appendedItemCount++;
}
// Alert screen reader users that thinking has started
alert(localize('chat.thinking.started', 'Thinking'));
@@ -618,6 +622,9 @@ export class ChatThinkingContentPart extends ChatCollapsibleContentPart implemen
}
private async generateTitleViaLLM(): Promise<void> {
const cts = new CancellationTokenSource();
const timeout = setTimeout(() => cts.cancel(), 5000);
try {
let models = await this.languageModelsService.selectLanguageModels({ vendor: 'copilot', id: 'copilot-fast' });
if (!models.length) {
@@ -628,6 +635,11 @@ export class ChatThinkingContentPart extends ChatCollapsibleContentPart implemen
return;
}
if (cts.token.isCancellationRequested) {
this.setFallbackTitle();
return;
}
let context: string;
if (this.extractedTitles.length > 0) {
context = this.extractedTitles.join(', ');
@@ -720,11 +732,14 @@ export class ChatThinkingContentPart extends ChatCollapsibleContentPart implemen
new ExtensionIdentifier('core'),
[{ role: ChatMessageRole.User, content: [{ type: 'text', value: prompt }] }],
{},
CancellationToken.None
cts.token
);
let generatedTitle = '';
for await (const part of response.stream) {
if (cts.token.isCancellationRequested) {
break;
}
if (Array.isArray(part)) {
for (const p of part) {
if (p.type === 'text') {
@@ -736,6 +751,11 @@ export class ChatThinkingContentPart extends ChatCollapsibleContentPart implemen
}
}
if (cts.token.isCancellationRequested) {
this.setFallbackTitle();
return;
}
await response.result;
generatedTitle = generatedTitle.trim();
@@ -755,6 +775,9 @@ export class ChatThinkingContentPart extends ChatCollapsibleContentPart implemen
}
} catch (error) {
// fall through to default title
} finally {
clearTimeout(timeout);
cts.dispose();
}
this.setFallbackTitle();
@@ -784,8 +807,8 @@ export class ChatThinkingContentPart extends ChatCollapsibleContentPart implemen
}
private setFallbackTitle(): void {
const finalLabel = this.toolInvocationCount > 0
? localize('chat.thinking.finished.withTools', 'Finished working and invoked {0} tool{1}', this.toolInvocationCount, this.toolInvocationCount === 1 ? '' : 's')
const finalLabel = this.appendedItemCount > 0
? localize('chat.thinking.finished.withSteps', 'Finished with {0} step{1}', this.appendedItemCount, this.appendedItemCount === 1 ? '' : 's')
: localize('chat.thinking.finished', 'Finished Working');
this.currentTitle = finalLabel;
@@ -1150,6 +1173,7 @@ export class ChatThinkingContentPart extends ChatCollapsibleContentPart implemen
if (this._store.isDisposed) {
return;
}
this.appendedItemCount++;
this.textContainer = $('.chat-thinking-item.markdown-content');
if (content.value) {
// Use lazy rendering when collapsed to preserve order with tool items
@@ -17,6 +17,20 @@
margin: 0px;
}
> .chat-used-context-label .monaco-button.monaco-icon-button {
line-height: 1.5em;
font-size: var(--vscode-chat-font-size-body-m);
margin-top: 1px;
.codicon {
font-size: 13px;
}
.codicon::before {
font-size: var(--vscode-chat-font-size-body-s);
}
}
/* shimmer animation stuffs */
.chat-thinking-spinner-item .chat-thinking-spinner-label {
background: linear-gradient(90deg,
@@ -274,7 +274,7 @@ export class ChatTerminalToolProgressPart extends BaseChatToolInvocationSubPart
]);
this._titleElement = elements.title;
const command = (terminalData.commandLine.userEdited ?? terminalData.commandLine.toolEdited ?? terminalData.commandLine.original).trimStart();
const command = (terminalData.commandLine.forDisplay ?? terminalData.commandLine.userEdited ?? terminalData.commandLine.toolEdited ?? terminalData.commandLine.original).trimStart();
this._commandText = command;
this._terminalOutputContextKey = ChatContextKeys.inChatTerminalToolOutput.bindTo(this._contextKeyService);
@@ -413,6 +413,8 @@ export interface IChatTerminalToolInvocationData {
original: string;
userEdited?: string;
toolEdited?: string;
// command to show in the chat UI (potentially different from what is actually run in the terminal)
forDisplay?: string;
};
/** The working directory URI for the terminal */
cwd?: UriComponents;
@@ -22,4 +22,6 @@ export interface ICommandLineRewriterOptions {
export interface ICommandLineRewriterResult {
rewritten: string;
reasoning: string;
//for scenarios where we want to show a different command in the chat UI than what is actually run in the terminal
forDisplay?: string;
}
@@ -29,7 +29,8 @@ export class CommandLineSandboxRewriter extends Disposable implements ICommandLi
const wrappedCommand = this._sandboxService.wrapCommand(options.commandLine);
return {
rewritten: wrappedCommand,
reasoning: 'Wrapped command for sandbox execution'
reasoning: 'Wrapped command for sandbox execution',
forDisplay: options.commandLine, // show the command that is passed as input. In this case, the output from CommandLinePreventHistoryRewriter
};
}
}
@@ -456,6 +456,7 @@ export class RunInTerminalTool extends Disposable implements IToolImpl {
const terminalCommandId = `tool-${generateUuid()}`;
let rewrittenCommand: string | undefined = args.command;
let forDisplayCommand: string | undefined = undefined;
for (const rewriter of this._commandLineRewriters) {
const rewriteResult = await rewriter.rewrite({
commandLine: rewrittenCommand,
@@ -465,6 +466,7 @@ export class RunInTerminalTool extends Disposable implements IToolImpl {
});
if (rewriteResult) {
rewrittenCommand = rewriteResult.rewritten;
forDisplayCommand = rewriteResult.forDisplay;
this._logService.info(`RunInTerminalTool: Command rewritten by ${rewriter.constructor.name}: ${rewriteResult.reasoning}`);
}
}
@@ -476,6 +478,7 @@ export class RunInTerminalTool extends Disposable implements IToolImpl {
commandLine: {
original: args.command,
toolEdited: rewrittenCommand === args.command ? undefined : rewrittenCommand,
forDisplay: forDisplayCommand,
},
cwd,
language,