Skip to content

Commit c5543a9

Browse files
committed
End to end
1 parent 215b4c8 commit c5543a9

File tree

10 files changed

+95
-48
lines changed

10 files changed

+95
-48
lines changed

extensions/markdown-language-features/package.json

Lines changed: 20 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,8 @@
2727
"onLanguage:chatmode",
2828
"onCommand:markdown.api.render",
2929
"onCommand:markdown.api.reloadPlugins",
30-
"onWebviewPanel:markdown.preview"
30+
"onWebviewPanel:markdown.preview",
31+
"onChatOutputRenderer:application/vnd.test-output"
3132
],
3233
"capabilities": {
3334
"virtualWorkspaces": true,
@@ -42,9 +43,24 @@
4243
"contributes": {
4344
"languageModelTools": [
4445
{
45-
"name": "test-renderer",
46-
"displayName": "Test renderer",
47-
"modelDescription": "Renders test data to the output"
46+
"name": "renderMarkdown",
47+
"displayName": "Markdown Renderer",
48+
"toolReferenceName": "renderMarkdown",
49+
"modelDescription": "Renders markdown to the output",
50+
"userDescription": "Renders markdown to the output",
51+
"canBeReferencedInPrompt": true,
52+
"inputSchema": {
53+
"type": "object",
54+
"properties": {
55+
"markdown": {
56+
"type": "string",
57+
"description": "The markdown content to render."
58+
}
59+
},
60+
"required": [
61+
"markdown"
62+
]
63+
}
4864
}
4965
],
5066
"notebookRenderer": [

extensions/markdown-language-features/src/extension.ts

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,14 +18,13 @@ export async function activate(context: vscode.ExtensionContext) {
1818

1919
const logger = new VsCodeOutputLogger();
2020
context.subscriptions.push(logger);
21-
2221
const engine = new MarkdownItEngine(contributions, githubSlugifier, logger);
22+
registerTestOutputRenderer(context);
2323

2424
const client = await startServer(context, engine);
2525
context.subscriptions.push(client);
2626
activateShared(context, client, engine, logger, contributions);
2727

28-
registerTestOutputRenderer(context);
2928
}
3029

3130
function startServer(context: vscode.ExtensionContext, parser: IMdParser): Promise<MdLanguageClient> {
@@ -59,7 +58,7 @@ function startServer(context: vscode.ExtensionContext, parser: IMdParser): Promi
5958

6059

6160
function registerTestOutputRenderer(context: vscode.ExtensionContext) {
62-
vscode.lm.registerTool('test-renderer', {
61+
vscode.lm.registerTool('renderMarkdown', {
6362
invoke: (options, token) => {
6463
const result = new vscode.ExtendedLanguageModelToolResult([]);
6564

src/vs/workbench/api/common/extHostTypeConverters.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3347,7 +3347,7 @@ export namespace LanguageModelToolResult2 {
33473347
}));
33483348
}
33493349

3350-
export function from(result: vscode.ExtendedLanguageModelToolResult, extension: IExtensionDescription): Dto<IToolResult> | SerializableObjectWithBuffers<Dto<IToolResult>> {
3350+
export function from(result: vscode.ExtendedLanguageModelToolResult2, extension: IExtensionDescription): Dto<IToolResult> | SerializableObjectWithBuffers<Dto<IToolResult>> {
33513351
if (result.toolResultMessage) {
33523352
checkProposedApiEnabled(extension, 'chatParticipantPrivate');
33533353
}
@@ -3359,12 +3359,12 @@ export namespace LanguageModelToolResult2 {
33593359
return URI.isUri(detail) ? detail : Location.from(detail as vscode.Location);
33603360
});
33613361
} else {
3362-
if (result.toolResultDetails) {
3362+
if (result.toolResultDetails2) {
33633363
detailsDto = {
33643364
output: {
33653365
type: 'data',
3366-
mimeType: (result.toolResultDetails as { mime: string; value: Uint8Array }).mime,
3367-
value: VSBuffer.wrap((result.toolResultDetails as { mime: string; value: Uint8Array }).value),
3366+
mimeType: (result.toolResultDetails2 as vscode.ToolResultDataOutput).mime,
3367+
value: VSBuffer.wrap((result.toolResultDetails2 as vscode.ToolResultDataOutput).value),
33683368
}
33693369
} satisfies IToolResultOutputDetails;
33703370
hasBuffers = true;

src/vs/workbench/contrib/chat/browser/chatContentParts/toolInvocationParts/chatToolInvocationPart.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,10 @@ export class ChatToolInvocationPart extends Disposable implements IChatContentPa
101101
return this.instantiationService.createInstance(ChatResultListSubPart, this.toolInvocation, this.context, this.toolInvocation.pastTenseMessage ?? this.toolInvocation.invocationMessage, this.toolInvocation.resultDetails, this.listPool);
102102
}
103103

104+
if (isToolResultOutputDetails(this.toolInvocation.resultDetails)) {
105+
return this.instantiationService.createInstance(ChatToolOutputSubPart, this.toolInvocation, this.context);
106+
}
107+
104108
if (isToolResultInputOutputDetails(this.toolInvocation.resultDetails)) {
105109
return this.instantiationService.createInstance(
106110
ChatInputOutputMarkdownProgressPart,
@@ -133,10 +137,6 @@ export class ChatToolInvocationPart extends Disposable implements IChatContentPa
133137
);
134138
}
135139

136-
if (isToolResultOutputDetails(this.toolInvocation.resultDetails)) {
137-
return this.instantiationService.createInstance(ChatToolOutputSubPart, this.toolInvocation, this.toolInvocation.resultDetails, this.context);
138-
}
139-
140140
return this.instantiationService.createInstance(ChatToolProgressSubPart, this.toolInvocation, this.context, this.renderer);
141141
}
142142

src/vs/workbench/contrib/chat/browser/chatContentParts/toolInvocationParts/chatToolOutputPart.ts

Lines changed: 24 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,9 @@
44
*--------------------------------------------------------------------------------------------*/
55

66
import * as dom from '../../../../../../base/browser/dom.js';
7+
import { decodeBase64 } from '../../../../../../base/common/buffer.js';
78
import { CancellationToken } from '../../../../../../base/common/cancellation.js';
8-
import { IChatToolInvocation, IChatToolInvocationSerialized } from '../../../common/chatService.js';
9+
import { IChatToolInvocation, IChatToolInvocationSerialized, IToolResultOutputDetailsSerialized } from '../../../common/chatService.js';
910
import { IToolResultOutputDetails } from '../../../common/languageModelToolsService.js';
1011
import { IChatCodeBlockInfo } from '../../chat.js';
1112
import { IChatOutputRendererService } from '../../chatOutputItemRenderer.js';
@@ -19,23 +20,40 @@ export class ChatToolOutputSubPart extends BaseChatToolInvocationSubPart {
1920

2021
constructor(
2122
toolInvocation: IChatToolInvocation | IChatToolInvocationSerialized,
22-
output: IToolResultOutputDetails,
2323
_context: IChatContentPartRenderContext,
2424
@IChatOutputRendererService private readonly chatOutputItemRendererService: IChatOutputRendererService,
2525
) {
2626
super(toolInvocation);
2727

28-
this.domNode = this.createOutputPart(output);
28+
29+
const details: IToolResultOutputDetails = toolInvocation.kind === 'toolInvocation'
30+
? toolInvocation.resultDetails as IToolResultOutputDetails
31+
: {
32+
output: {
33+
type: 'data',
34+
mimeType: (toolInvocation.resultDetails as IToolResultOutputDetailsSerialized).mimeType,
35+
value: decodeBase64((toolInvocation.resultDetails as IToolResultOutputDetailsSerialized).base64Data),
36+
},
37+
};
38+
39+
this.domNode = this.createOutputPart(details);
2940
}
3041

31-
private createOutputPart(detauls: IToolResultOutputDetails): HTMLElement {
42+
private createOutputPart(details: IToolResultOutputDetails): HTMLElement {
43+
// TODO: Show progress while rendering
44+
3245
const parent = dom.$('div.webview-output');
3346
parent.style.maxHeight = '80vh';
3447

35-
this.chatOutputItemRendererService.renderOutputPart(detauls.output.mimeType, detauls.output.value.buffer, parent, CancellationToken.None).then((disposable) => {
36-
this._register(disposable);
48+
this.chatOutputItemRendererService.renderOutputPart(details.output.mimeType, details.output.value.buffer, parent, CancellationToken.None).then((renderedItem) => {
49+
this._register(renderedItem);
50+
3751
this._onDidChangeHeight.fire();
52+
this._register(renderedItem.onDidChangeHeight(() => {
53+
this._onDidChangeHeight.fire();
54+
}));
3855
});
56+
3957
return parent;
4058
}
4159
}

src/vs/workbench/contrib/chat/browser/chatOutputItemRenderer.ts

Lines changed: 24 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,9 @@
55

66
import { getWindow } from '../../../../base/browser/dom.js';
77
import { CancellationToken } from '../../../../base/common/cancellation.js';
8-
import { Disposable, IDisposable } from '../../../../base/common/lifecycle.js';
8+
import { Emitter, Event } from '../../../../base/common/event.js';
9+
import { Disposable, DisposableStore, IDisposable } from '../../../../base/common/lifecycle.js';
10+
import { autorun } from '../../../../base/common/observable.js';
911
import { URI } from '../../../../base/common/uri.js';
1012
import { generateUuid } from '../../../../base/common/uuid.js';
1113
import * as nls from '../../../../nls.js';
@@ -29,6 +31,10 @@ interface RegisterOptions {
2931
};
3032
}
3133

34+
export interface RenderedOutputPart extends IDisposable {
35+
readonly onDidChangeHeight: Event<number>;
36+
}
37+
3238
/**
3339
* Service for rendering chat output items with special MIME types using registered renderers from extensions.
3440
*/
@@ -37,7 +43,7 @@ export interface IChatOutputRendererService {
3743

3844
registerRenderer(mime: string, renderer: IChatOutputItemRenderer, options: RegisterOptions): IDisposable;
3945

40-
renderOutputPart(mime: string, data: Uint8Array, parent: HTMLElement, token: CancellationToken): Promise<IDisposable>;
46+
renderOutputPart(mime: string, data: Uint8Array, parent: HTMLElement, token: CancellationToken): Promise<RenderedOutputPart>;
4147
}
4248

4349
/**
@@ -72,7 +78,7 @@ export class ChatOutputRendererService extends Disposable implements IChatOutput
7278
};
7379
}
7480

75-
async renderOutputPart(mime: string, data: Uint8Array, parent: HTMLElement, token: CancellationToken): Promise<IDisposable> {
81+
async renderOutputPart(mime: string, data: Uint8Array, parent: HTMLElement, token: CancellationToken): Promise<RenderedOutputPart> {
7682
// Activate extensions that contribute to chatOutputRenderer for this mime type
7783
await this._extensionService.activateByEvent(`onChatOutputRenderer:${mime}`);
7884

@@ -81,7 +87,9 @@ export class ChatOutputRendererService extends Disposable implements IChatOutput
8187
throw new Error(`No renderer registered for mime type: ${mime}`);
8288
}
8389

84-
const webview = this._webviewService.createWebviewElement({
90+
const store = new DisposableStore();
91+
92+
const webview = store.add(this._webviewService.createWebviewElement({
8593
title: '',
8694
origin: generateUuid(),
8795
options: {
@@ -91,15 +99,24 @@ export class ChatOutputRendererService extends Disposable implements IChatOutput
9199
},
92100
contentOptions: {},
93101
extension: rendererData.options.extension ? rendererData.options.extension : undefined,
94-
});
102+
}));
103+
104+
const onDidChangeHeight = store.add(new Emitter<number>());
105+
store.add(autorun(reader => {
106+
const height = reader.readObservable(webview.intrinsicContentSize);
107+
if (height) {
108+
onDidChangeHeight.fire(height.height);
109+
parent.style.height = `${height.height}px`;
110+
}
111+
}));
95112

96113
webview.mountTo(parent, getWindow(parent));
97-
98114
await rendererData.renderer.renderOutputPart(mime, data, webview, token);
99115

100116
return {
117+
onDidChangeHeight: onDidChangeHeight.event,
101118
dispose: () => {
102-
webview.dispose();
119+
store.dispose();
103120
}
104121
};
105122
}

src/vs/workbench/contrib/chat/common/chatProgressTypes/chatToolInvocation.ts

Lines changed: 5 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,12 @@
44
*--------------------------------------------------------------------------------------------*/
55

66
import { DeferredPromise } from '../../../../../base/common/async.js';
7+
import { encodeBase64 } from '../../../../../base/common/buffer.js';
78
import { IMarkdownString } from '../../../../../base/common/htmlContent.js';
89
import { observableValue } from '../../../../../base/common/observable.js';
910
import { localize } from '../../../../../nls.js';
1011
import { IChatExtensionsContent, IChatTerminalToolInvocationData, IChatToolInputInvocationData, IChatToolInvocation, IChatToolInvocationSerialized, type IChatTerminalToolInvocationData2 } from '../chatService.js';
11-
import { IPreparedToolInvocation, IToolConfirmationMessages, IToolData, IToolProgressStep, IToolResult } from '../languageModelToolsService.js';
12+
import { IPreparedToolInvocation, isToolResultOutputDetails, IToolConfirmationMessages, IToolData, IToolProgressStep, IToolResult } from '../languageModelToolsService.js';
1213

1314
export class ChatToolInvocation implements IChatToolInvocation {
1415
public readonly kind: 'toolInvocation' = 'toolInvocation';
@@ -82,18 +83,6 @@ export class ChatToolInvocation implements IChatToolInvocation {
8283
}
8384

8485
this._resultDetails = result?.toolResultDetails;
85-
86-
// Hack to convert data part over
87-
const data = result?.content.find((part) => part.kind === 'data');
88-
if (data) {
89-
this._resultDetails = {
90-
output: {
91-
type: 'data',
92-
mimeType: data.value.mimeType,
93-
value: data.value.data,
94-
}
95-
};
96-
}
9786
this._isCompleteDeferred.complete();
9887
}
9988

@@ -118,7 +107,9 @@ export class ChatToolInvocation implements IChatToolInvocation {
118107
originMessage: this.originMessage,
119108
isConfirmed: this._isConfirmed,
120109
isComplete: this._isComplete,
121-
resultDetails: this._resultDetails,
110+
resultDetails: isToolResultOutputDetails(this._resultDetails)
111+
? { type: 'data', mimeType: this._resultDetails.output.mimeType, base64Data: encodeBase64(this._resultDetails.output.value) }
112+
: this._resultDetails,
122113
toolSpecificData: this.toolSpecificData,
123114
toolCallId: this.toolCallId,
124115
toolId: this.toolId,

src/vs/workbench/contrib/chat/common/chatService.ts

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ import { IChatParserContext } from './chatRequestParser.js';
2424
import { IChatRequestVariableEntry } from './chatVariableEntries.js';
2525
import { IChatRequestVariableValue } from './chatVariables.js';
2626
import { ChatAgentLocation, ChatModeKind } from './constants.js';
27-
import { IPreparedToolInvocation, IToolConfirmationMessages, IToolResult } from './languageModelToolsService.js';
27+
import { IPreparedToolInvocation, IToolConfirmationMessages, IToolResult, IToolResultInputOutputDetails } from './languageModelToolsService.js';
2828

2929
export interface IChatRequest {
3030
message: string;
@@ -281,6 +281,12 @@ export interface IChatToolInvocation {
281281
kind: 'toolInvocation';
282282
}
283283

284+
export interface IToolResultOutputDetailsSerialized {
285+
type: 'data';
286+
mimeType: string;
287+
base64Data: string;
288+
}
289+
284290
/**
285291
* This is a IChatToolInvocation that has been serialized, like after window reload, so it is no longer an active tool invocation.
286292
*/
@@ -290,7 +296,7 @@ export interface IChatToolInvocationSerialized {
290296
invocationMessage: string | IMarkdownString;
291297
originMessage: string | IMarkdownString | undefined;
292298
pastTenseMessage: string | IMarkdownString | undefined;
293-
resultDetails: IToolResult['toolResultDetails'];
299+
resultDetails?: Array<URI | Location> | IToolResultInputOutputDetails | IToolResultOutputDetailsSerialized;
294300
isConfirmed: boolean | undefined;
295301
isComplete: boolean;
296302
toolCallId: string;

src/vs/workbench/contrib/chat/common/languageModelToolsService.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -157,7 +157,7 @@ export function isToolResultInputOutputDetails(obj: any): obj is IToolResultInpu
157157
}
158158

159159
export function isToolResultOutputDetails(obj: any): obj is IToolResultOutputDetails {
160-
return typeof obj === 'object' && (typeof obj?.output === 'object');
160+
return typeof obj === 'object' && typeof obj?.mimeType === 'string' && obj?.type === 'data';
161161
}
162162

163163
export interface IToolResult {

src/vs/workbench/contrib/webview/browser/pre/index.html

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
<meta charset="UTF-8">
66

77
<meta http-equiv="Content-Security-Policy"
8-
content="default-src 'none'; script-src 'sha256-kvB8+4EUtyEDThMIgb61H4fMe04EqjRhttNuYd028mo=' 'self'; frame-src 'self'; style-src 'unsafe-inline';">
8+
content="default-src 'none'; script-src 'sha256-qzQMf4WjRXHohkk4Hg1T0LJIElTDtjITLXbR/RuGA/Q=' 'self'; frame-src 'self'; style-src 'unsafe-inline';">
99

1010
<!-- Disable pinch zooming -->
1111
<meta name="viewport"
@@ -1127,8 +1127,8 @@
11271127
if (docEl) {
11281128
const postSize = () => {
11291129
hostMessaging.postMessage('updated-intrinsic-content-size', {
1130-
width: docEl.scrollWidth,
1131-
height: docEl.scrollHeight
1130+
width: docEl.offsetWidth,
1131+
height: docEl.offsetHeight
11321132
});
11331133
};
11341134

0 commit comments

Comments
 (0)