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

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "code-oss-dev",
"version": "1.110.0",
"distro": "7ae76fff0c4611dd384784edb63189ef932327d6",
"distro": "6c461e78a091f00670d7513c1eeb1fc0f4d3e8b7",
"author": {
"name": "Microsoft Corporation"
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -214,7 +214,13 @@ export class ChatMarkdownContentPart extends Disposable implements IChatContentP
try {
const parsedBody = parseLocalFileData(text);
range = parsedBody.range && Range.lift(parsedBody.range);
textModel = this.textModelService.createModelReference(parsedBody.uri).then(ref => ref.object.textEditorModel);
const modelRefPromise = this.textModelService.createModelReference(parsedBody.uri);
textModel = modelRefPromise.then(ref => {
if (!this._store.isDisposed) {
this._register(ref);
}
return ref.object.textEditorModel;
});
} catch (e) {
return $('div');
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import { $, AnimationFrameScheduler, DisposableResizeObserver } from '../../../.
import { Codicon } from '../../../../../../base/common/codicons.js';
import { MarkdownString } from '../../../../../../base/common/htmlContent.js';
import { Lazy } from '../../../../../../base/common/lazy.js';
import { IRenderedMarkdown } from '../../../../../../base/browser/markdownRenderer.js';
import { IDisposable, MutableDisposable } from '../../../../../../base/common/lifecycle.js';
import { autorun } from '../../../../../../base/common/observable.js';
import { rcut } from '../../../../../../base/common/strings.js';
Expand Down Expand Up @@ -100,6 +101,7 @@ export class ChatSubagentContentPart extends ChatCollapsibleContentPart implemen
// Persistent title elements for shimmer
private titleShimmerSpan: HTMLElement | undefined;
private titleDetailContainer: HTMLElement | undefined;
private titleDetailRendered: IRenderedMarkdown | undefined;

/**
* Check if a tool invocation is the parent subagent tool (the tool that spawns a subagent).
Expand Down Expand Up @@ -366,11 +368,8 @@ export class ChatSubagentContentPart extends ChatCollapsibleContentPart implemen

private updateTitle(): void {
const prefix = this.agentName || localize('chat.subagent.prefix', 'Subagent');
const shimmerText = `${prefix}: `;
let detailText = this.description;
if (this.currentRunningToolMessage && this.isActive) {
detailText += ` \u2014 ${this.currentRunningToolMessage}`;
}
const shimmerText = `${prefix}: ${this.description}`;
const toolCallText = this.currentRunningToolMessage && this.isActive ? ` \u2014 ${this.currentRunningToolMessage}` : ``;

if (!this._collapseButton) {
return;
Expand All @@ -386,18 +385,32 @@ export class ChatSubagentContentPart extends ChatCollapsibleContentPart implemen
}
this.titleShimmerSpan.textContent = shimmerText;

const result = this.chatContentMarkdownRenderer.render(new MarkdownString(detailText));
result.element.classList.add('collapsible-title-content', 'chat-thinking-title-detail');
renderFileWidgets(result.element, this.instantiationService, this.chatMarkdownAnchorService, this._store);
// Dispose previous detail rendering
if (this.titleDetailRendered) {
this.titleDetailRendered.dispose();
this.titleDetailRendered = undefined;
}

if (this.titleDetailContainer) {
this.titleDetailContainer.replaceWith(result.element);
if (!toolCallText) {
if (this.titleDetailContainer) {
this.titleDetailContainer.remove();
this.titleDetailContainer = undefined;
}
} else {
labelElement.appendChild(result.element);
const result = this.chatContentMarkdownRenderer.render(new MarkdownString(toolCallText));
result.element.classList.add('collapsible-title-content', 'chat-thinking-title-detail');
renderFileWidgets(result.element, this.instantiationService, this.chatMarkdownAnchorService, this._store);
this.titleDetailRendered = result;

if (this.titleDetailContainer) {
this.titleDetailContainer.replaceWith(result.element);
} else {
labelElement.appendChild(result.element);
}
this.titleDetailContainer = result.element;
}
this.titleDetailContainer = result.element;

const fullLabel = `${shimmerText}${detailText}`;
const fullLabel = `${shimmerText}${toolCallText}`;
this._collapseButton.element.ariaLabel = fullLabel;
this._collapseButton.element.ariaExpanded = String(this.isExpanded());
}
Expand Down Expand Up @@ -650,6 +663,17 @@ export class ChatSubagentContentPart extends ChatCollapsibleContentPart implemen
factory: () => { domNode: HTMLElement; disposable?: IDisposable },
hookPart: IChatHookPart
): void {
// update title with hook message
const hookMessage = hookPart.stopReason
? (hookPart.toolDisplayName
? localize('hook.subagent.blocked', 'Blocked {0}', hookPart.toolDisplayName)
: localize('hook.subagent.blockedGeneric', 'Blocked by hook'))
: (hookPart.toolDisplayName
? localize('hook.subagent.warning', 'Warning for {0}', hookPart.toolDisplayName)
: localize('hook.subagent.warningGeneric', 'Hook warning'));
this.currentRunningToolMessage = hookMessage;
this.updateTitle();

if (this.isExpanded() || this.hasExpandedOnce) {
const result = factory();
this.appendHookItemToDOM(result.domNode, hookPart);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,7 @@ interface ILazyToolItem {
toolInvocationId?: string;
toolInvocationOrMarkdown?: IChatToolInvocation | IChatToolInvocationSerialized | IChatMarkdownContent;
originalParent?: HTMLElement;
isHook?: boolean;
}

interface ILazyThinkingItem {
Expand Down Expand Up @@ -160,6 +161,7 @@ export class ChatThinkingContentPart extends ChatCollapsibleContentPart implemen
private appendedItemCount: number = 0;
private isActive: boolean = true;
private toolInvocations: (IChatToolInvocation | IChatToolInvocationSerialized)[] = [];
private hookCount: number = 0;
private singleItemInfo: { element: HTMLElement; originalParent: HTMLElement; originalNextSibling: Node | null } | undefined;
private lazyItems: ILazyItem[] = [];
private hasExpandedOnce: boolean = false;
Expand Down Expand Up @@ -807,13 +809,13 @@ export class ChatThinkingContentPart extends ChatCollapsibleContentPart implemen
- For reasoning/thinking: "Considered", "Planned", "Analyzed", "Reviewed", "Evaluated"
- Choose the synonym that best fits the context

PRIORITY RULE - BLOCKED/DENIED CONTENT:
- If any item mentions being "blocked" (e.g. "Tried to use X, but was blocked"), it MUST be reflected in the title
- Blocked content takes priority over all other tool calls
- Use natural phrasing like "Tried to <action>, but was blocked" or "Attempted <tool> but was denied"
- If there are both blocked items AND normal tool calls, mention both: e.g. "Tried to run terminal but was blocked, edited file.ts"
${this.hookCount > 0 ? `BLOCKED/DENIED CONTENT (hooks detected):
- Only mention "blocked" if the content explicitly includes hook results that blocked or warned about a tool (e.g. "Blocked terminal" or "Warning for read_file")
- If blocked items are present alongside normal tool calls, briefly note the block but do NOT let it dominate the summary: e.g. "Updated file.ts, blocked terminal"

RULES FOR TOOL CALLS:
` : `IMPORTANT: Do NOT use words like "blocked", "denied", or "tried" in the summary - there are no hooks or blocked items in this content. Just summarize normally.

`}RULES FOR TOOL CALLS:
1. If the SAME file was both edited AND read: Use a combined phrase like "Reviewed and updated <filename>"
2. If exactly ONE file was edited: Start with an edit synonym + "<filename>" (include actual filename)
3. If exactly ONE file was read: Start with a read synonym + "<filename>" (include actual filename)
Expand Down Expand Up @@ -852,13 +854,12 @@ export class ChatThinkingContentPart extends ChatCollapsibleContentPart implemen
- "Edited Button.tsx, Edited Button.css, Edited index.ts" → "Modified 3 files"
- "Searched codebase for error handling" → "Looked up error handling"

EXAMPLES WITH BLOCKED CONTENT:
- "Tried to use Run in Terminal, but was blocked" → "Tried to run command, but was blocked"
- "Tried to use Run in Terminal, but was blocked, Edited config.ts" → "Tried to run command but was blocked, edited config.ts"
- "Tried to use Edit File, but was blocked, Tried to use Run in Terminal, but was blocked" → "Tried to use 2 tools, but was blocked"
- "Used Read File, but received a warning, Edited utils.ts" → "Read file with a warning, edited utils.ts"
${this.hookCount > 0 ? `EXAMPLES WITH BLOCKED CONTENT (from hooks):
- "Blocked terminal, Edited config.ts" → "Edited config.ts, terminal was blocked"
- "Blocked terminal, Blocked read_file" → "Two tools were blocked by hooks"
- "Warning for read_file, Edited utils.ts" → "Edited utils.ts with a hook warning"

EXAMPLES WITH REASONING HEADERS (no tools):
` : ''}EXAMPLES WITH REASONING HEADERS (no tools):
- "Analyzing component architecture" → "Considered component architecture"
- "Planning refactor strategy" → "Planned refactor strategy"
- "Reviewing error handling approach, Considering edge cases" → "Analyzed error handling approach"
Expand Down Expand Up @@ -1016,7 +1017,8 @@ export class ChatThinkingContentPart extends ChatCollapsibleContentPart implemen
lazy: new Lazy(factory),
toolInvocationId,
toolInvocationOrMarkdown,
originalParent
originalParent,
isHook: !toolInvocationOrMarkdown && !!toolInvocationId
};
this.lazyItems.push(item);
}
Expand All @@ -1034,9 +1036,14 @@ export class ChatThinkingContentPart extends ChatCollapsibleContentPart implemen
return false;
}

const removedItem = this.lazyItems[index];
this.lazyItems.splice(index, 1);
this.appendedItemCount--;
this.toolInvocationCount--;
if (removedItem.kind === 'tool' && removedItem.isHook) {
this.hookCount = Math.max(0, this.hookCount - 1);
} else {
this.toolInvocationCount--;
}

const toolInvocationsIndex = this.toolInvocations.findIndex(t =>
(t.kind === 'toolInvocation' || t.kind === 'toolInvocationSerialized') && t.toolId === toolInvocationId
Expand Down Expand Up @@ -1103,7 +1110,14 @@ export class ChatThinkingContentPart extends ChatCollapsibleContentPart implemen
return;
}

this.toolInvocationCount++;
// Track hooks separately: if toolInvocationOrMarkdown is undefined, it's a hook item
const isHook = !toolInvocationOrMarkdown;
if (isHook) {
this.hookCount++;
} else {
this.toolInvocationCount++;
}

let toolCallLabel: string;

const isToolInvocation = toolInvocationOrMarkdown && (toolInvocationOrMarkdown.kind === 'toolInvocation' || toolInvocationOrMarkdown.kind === 'toolInvocationSerialized');
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2172,7 +2172,8 @@ export class ChatInputPart extends Disposable implements IHistoryNavigationWidge
this.setCurrentLanguageModel(model);
this.renderAttachedContext();
},
getModels: () => this.getModels()
getModels: () => this.getModels(),
canManageModels: () => !this.getCurrentSessionType()
};
return this.modelWidget = this.instantiationService.createInstance(ModelPickerActionItem, action, undefined, itemDelegate, pickerOptions);
} else if (action.id === OpenModePickerAction.ID && action instanceof MenuItemAction) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ export interface IModelPickerDelegate {
readonly currentModel: IObservable<ILanguageModelChatMetadataAndIdentifier | undefined>;
setModel(model: ILanguageModelChatMetadataAndIdentifier): void;
getModels(): ILanguageModelChatMetadataAndIdentifier[];
canManageModels(): boolean;
}

type ChatModelChangeClassification = {
Expand Down Expand Up @@ -165,9 +166,10 @@ export class ModelPickerActionItem extends ChatInputPickerActionViewItem {
run: () => { }
};

const baseActionBarActionProvider = getModelPickerActionBarActionProvider(commandService, chatEntitlementService, productService);
const modelPickerActionWidgetOptions: Omit<IActionWidgetDropdownOptions, 'label' | 'labelRenderer'> = {
actionProvider: modelDelegateToWidgetActionsProvider(delegate, telemetryService, pickerOptions),
actionBarActionProvider: getModelPickerActionBarActionProvider(commandService, chatEntitlementService, productService),
actionBarActionProvider: { getActions: () => delegate.canManageModels() ? baseActionBarActionProvider.getActions() : [] },
reporter: { id: 'ChatModelPicker', name: 'ChatModelPicker', includeOptions: true },
};

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -317,7 +317,7 @@ export class PromptHeaderAutocompletion implements CompletionItemProvider {
const result = [];
for (const model of this.languageModelsService.getLanguageModelIds()) {
const metadata = this.languageModelsService.lookupLanguageModel(model);
if (metadata && metadata.isUserSelectable !== false) {
if (metadata && metadata.isUserSelectable !== false && !metadata.targetChatSessionType) {
if (!agentModeOnly || ILanguageModelChatMetadata.suitableForAgentMode(metadata)) {
result.push({
name: ILanguageModelChatMetadata.asQualifiedName(metadata),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ suite('PromptHeaderAutocompletion', () => {
{ id: 'mae-4', name: 'MAE 4', vendor: 'olama', version: '1.0', family: 'mae', modelPickerCategory: undefined, extension: new ExtensionIdentifier('a.b'), isUserSelectable: true, maxInputTokens: 8192, maxOutputTokens: 1024, capabilities: { agentMode: true, toolCalling: true }, isDefaultForLocation: { [ChatAgentLocation.Chat]: true } } satisfies ILanguageModelChatMetadata,
{ id: 'mae-4.1', name: 'MAE 4.1', vendor: 'copilot', version: '1.0', family: 'mae', modelPickerCategory: undefined, extension: new ExtensionIdentifier('a.b'), isUserSelectable: true, maxInputTokens: 8192, maxOutputTokens: 1024, capabilities: { agentMode: true, toolCalling: true }, isDefaultForLocation: { [ChatAgentLocation.Chat]: true } } satisfies ILanguageModelChatMetadata,
{ id: 'gpt-4', name: 'GPT 4', vendor: 'openai', version: '1.0', family: 'gpt', modelPickerCategory: undefined, extension: new ExtensionIdentifier('a.b'), isUserSelectable: true, maxInputTokens: 8192, maxOutputTokens: 1024, capabilities: { agentMode: false, toolCalling: true }, isDefaultForLocation: { [ChatAgentLocation.Chat]: true } } satisfies ILanguageModelChatMetadata,
{ id: 'bg-agent-model', name: 'BG Agent Model', vendor: 'copilot', version: '1.0', family: 'bg', modelPickerCategory: undefined, extension: new ExtensionIdentifier('a.b'), isUserSelectable: true, maxInputTokens: 8192, maxOutputTokens: 1024, capabilities: { agentMode: true, toolCalling: true }, isDefaultForLocation: { [ChatAgentLocation.Chat]: true }, targetChatSessionType: 'background' } satisfies ILanguageModelChatMetadata,
];

instaService.stub(ILanguageModelsService, {
Expand Down Expand Up @@ -361,6 +362,33 @@ suite('PromptHeaderAutocompletion', () => {
{ label: 'true', result: 'disable-model-invocation: true' },
].sort(sortByLabel));
});

test('exclude models with targetChatSessionType from agent model completions', async () => {
const content = [
'---',
'description: "Test"',
'model: |',
'---',
].join('\n');

const actual = await getCompletions(content, PromptsType.agent);
const labels = actual.map(a => a.label);
// BG Agent Model has targetChatSessionType set, so it should be excluded
assert.ok(!labels.includes('BG Agent Model (copilot)'), 'Models with targetChatSessionType should be excluded from agent model completions');
});

test('exclude models with targetChatSessionType from agent model array completions', async () => {
const content = [
'---',
'description: "Test"',
'model: [|]',
'---',
].join('\n');

const actual = await getCompletions(content, PromptsType.agent);
const labels = actual.map(a => a.label);
assert.ok(!labels.includes('BG Agent Model (copilot)'), 'Models with targetChatSessionType should be excluded from agent model array completions');
});
});

suite('claude agent header completions', () => {
Expand Down Expand Up @@ -546,5 +574,18 @@ suite('PromptHeaderAutocompletion', () => {
{ label: 'GPT 4 (openai)', result: 'model: GPT 4 (openai)' },
].sort(sortByLabel));
});

test('exclude models with targetChatSessionType from prompt model completions', async () => {
const content = [
'---',
'description: "Test"',
'model: |',
'---',
].join('\n');

const actual = await getCompletions(content, PromptsType.prompt);
const labels = actual.map(a => a.label);
assert.ok(!labels.includes('BG Agent Model (copilot)'), 'Models with targetChatSessionType should be excluded from prompt model completions');
});
});
});
9 changes: 7 additions & 2 deletions src/vs/workbench/contrib/debug/browser/media/repl.css
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,13 @@
overflow: hidden;
}

.monaco-workbench .repl .repl-tree .monaco-tl-contents {
.monaco-workbench .repl .repl-tree .monaco-tl-contents,
.monaco-workbench .repl .repl-tree .monaco-tl-twistie {
user-select: text;
-webkit-user-select: text;
}

.monaco-workbench .repl .repl-tree .monaco-tl-contents {
white-space: pre;
}

Expand Down Expand Up @@ -44,7 +48,7 @@
}

.monaco-workbench .repl .repl-tree .monaco-tl-twistie.collapsible + .monaco-tl-contents,
.monaco-workbench .repl .repl-tree .monaco-tl-twistie {
.monaco-workbench .repl .repl-tree .monaco-tl-twistie.collapsible {
cursor: pointer;
}

Expand All @@ -69,6 +73,7 @@
.monaco-workbench .repl .repl-tree .monaco-tl-contents .arrow {
position:absolute;
left: 2px;
pointer-events: none;
}

.monaco-workbench .repl .repl-tree .output.expression.value-and-source .source,
Expand Down
Loading
Loading