Skip to content
Closed
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
13 changes: 12 additions & 1 deletion extensions/markdown-language-features/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,14 +15,20 @@
"categories": [
"Programming Languages"
],
"enabledApiProposals": [
"chatSessionProvider"
],
"activationEvents": [
"onLanguage:markdown",
"onLanguage:prompt",
"onLanguage:instructions",
"onLanguage:chatmode",
"onCommand:markdown.api.render",
"onCommand:markdown.api.reloadPlugins",
"onWebviewPanel:markdown.preview"
"onCommand:markdown.openChatSession",
"onWebviewPanel:markdown.preview",
"onChatSessionContentProvider:markdown",
"onChatSessionContentProvider:markdown"
],
"capabilities": {
"virtualWorkspaces": true,
Expand Down Expand Up @@ -120,6 +126,11 @@
}
],
"commands": [
{
"command": "markdown.openChatSession",
"title": "Open test Chat Session",
"category": "Markdown"
},
{
"command": "_markdown.copyImage",
"title": "%markdown.copyImage.title%",
Expand Down
75 changes: 75 additions & 0 deletions extensions/markdown-language-features/src/extension.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,78 @@ import { IMdParser, MarkdownItEngine } from './markdownEngine';
import { getMarkdownExtensionContributions } from './markdownExtensions';
import { githubSlugifier } from './slugify';

// Chat session provider implementation
const sessionProviderType = 'fake-session-provider';
function registerMarkdownChatProvider(context: vscode.ExtensionContext) {
// Create sample history with a request and a response
const sampleHistory: Array<vscode.ChatRequestTurn | vscode.ChatResponseTurn> = [
// Simple request turn with proper ChatRequestTurn structure
new vscode.ChatRequestTurn('Hello, this is a test request to the markdown chat provider', undefined, [], 'markdown', []),
// Simple response turn with proper ChatResponseTurn structure
new vscode.ChatResponseTurn(
[new vscode.ChatResponseMarkdownPart(new vscode.MarkdownString('Hello! I am a simple markdown chat provider. I can help with markdown-related questions.'))],
{},
'markdown'
)
];

// Request handler that reverses the input text
const requestHandler: vscode.ChatRequestHandler = async (
request: vscode.ChatRequest,
_context: unknown,
stream: vscode.ChatResponseStream,
_token: vscode.CancellationToken
): Promise<void> => {
// Extract the text from the request
const text = request.prompt || '';

if (text) {
// Reverse the input text and stream it back
const reversedText = text.split('').reverse().join('');

// First send the original text
await stream.progress('You said: ' + text);

// Wait a bit to simulate processing
await new Promise(resolve => setTimeout(resolve, 500));

// Then send the reversed text
await stream.progress('Here is your text reversed: ');
await stream.progress('```\n' + reversedText + '\n```');

// Complete the response by resolving the promise
return Promise.resolve();
} else {
// Handle empty messages
await stream.progress('I can only process text messages right now.');
return Promise.resolve();
}
};

// Register the chat session provider
const chatSessionProvider: vscode.ChatSessionContentProvider = {
provideChatSessionContent: async (_id: string, _token: vscode.CancellationToken): Promise<vscode.ChatSession> => {
// Create a chat session with our sample history and request handler
return {
history: sampleHistory,
requestHandler: requestHandler
};
}
};

// Register the provider with the 'markdown' type
context.subscriptions.push(
vscode.chat.registerChatSessionContentProvider(sessionProviderType, chatSessionProvider)
);

// Register a command to open a markdown chat session
context.subscriptions.push(
vscode.commands.registerCommand('markdown.openChatSession', async () => {
await vscode.window.openChatSession(sessionProviderType, '123');
})
);
}

export async function activate(context: vscode.ExtensionContext) {
const contributions = getMarkdownExtensionContributions(context);
context.subscriptions.push(contributions);
Expand All @@ -24,6 +96,9 @@ export async function activate(context: vscode.ExtensionContext) {
const client = await startServer(context, engine);
context.subscriptions.push(client);
activateShared(context, client, engine, logger, contributions);

// Register our markdown chat provider
registerMarkdownChatProvider(context);
}

function startServer(context: vscode.ExtensionContext, parser: IMdParser): Promise<MdLanguageClient> {
Expand Down
3 changes: 2 additions & 1 deletion extensions/markdown-language-features/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
},
"include": [
"src/**/*",
"../../src/vscode-dts/vscode.d.ts"
"../../src/vscode-dts/vscode.d.ts",
"../../src/vscode-dts/vscode.proposed.chatSessionProvider.d.ts",
]
}
4 changes: 3 additions & 1 deletion src/vs/base/common/network.ts
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,9 @@ export namespace Schemas {
export const vscodeChatCodeCompareBlock = 'vscode-chat-code-compare-block';

/** Scheme used for the chat input editor. */
export const vscodeChatSesssion = 'vscode-chat-editor';
export const vscodeChatEditor = 'vscode-chat-editor';

export const vscodeChatSession = 'vscode-chat-session';

/** Scheme used for the chat input part */
export const vscodeChatInput = 'chatSessionInput';
Expand Down
3 changes: 3 additions & 0 deletions src/vs/platform/extensions/common/extensionsApiProposals.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,9 @@ const _allApiProposals = {
chatReferenceDiagnostic: {
proposal: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.chatReferenceDiagnostic.d.ts',
},
chatSessionProvider: {
proposal: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.chatSessionProvider.d.ts',
},
chatStatusItem: {
proposal: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.chatStatusItem.d.ts',
},
Expand Down
1 change: 1 addition & 0 deletions src/vs/workbench/api/browser/extensionHost.contribution.ts
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,7 @@ import './mainThreadAiSettingsSearch.js';
import './mainThreadMcp.js';
import './mainThreadChatStatus.js';
import './mainThreadDataChannels.js';
import './mainThreadChatSessionContentProviders.js';

export class ExtensionPoints implements IWorkbenchContribution {

Expand Down
2 changes: 1 addition & 1 deletion src/vs/workbench/api/browser/mainThreadChatAgents2.ts
Original file line number Diff line number Diff line change
Expand Up @@ -424,7 +424,7 @@ namespace ChatNotebookEdit {
export function fromChatEdit(part: IChatNotebookEditDto): IChatNotebookEdit {
return {
kind: 'notebookEdit',
uri: part.uri,
uri: URI.revive(part.uri),
done: part.done,
edits: part.edits.map(NotebookDto.fromCellEditOperationDto)
};
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/

import { Disposable, DisposableMap } from '../../../base/common/lifecycle.js';
import { revive } from '../../../base/common/marshalling.js';
import { IInstantiationService } from '../../../platform/instantiation/common/instantiation.js';
import { ChatModel, IChatModel } from '../../contrib/chat/common/chatModel.js';
import { ChatRequestParser } from '../../contrib/chat/common/chatRequestParser.js';
import { IChatProgress } from '../../contrib/chat/common/chatService.js';
import { IChatSessionContentProviderService } from '../../contrib/chat/common/chatSessionContentProviderService.js';
import { ChatAgentLocation } from '../../contrib/chat/common/constants.js';
import { extHostNamedCustomer } from '../../services/extensions/common/extHostCustomers.js';
import { ExtHostChatSessionContentProvidersShape, ExtHostContext, MainContext, MainThreadChatSessionContentProvidersShape } from '../common/extHost.protocol.js';

@extHostNamedCustomer(MainContext.MainThreadChatSessionContentProviders)
export class MainThreadChatSessionContentProviders implements MainThreadChatSessionContentProvidersShape {

private readonly _providers = new DisposableMap<number>();
private readonly _proxy: ExtHostChatSessionContentProvidersShape;

constructor(
extHostContext: import('../../services/extensions/common/extHostCustomers.js').IExtHostContext,
@IInstantiationService private readonly instantiationService: IInstantiationService,
@IChatSessionContentProviderService private readonly chatSessionContentProviderService: IChatSessionContentProviderService
) {
this._proxy = extHostContext.getProxy(ExtHostContext.ExtHostChatSessionContentProviders);
}

dispose(): void {
this._providers.dispose();
}

$registerChatSessionContentProvider(handle: number, _chatSessionType: string): void {
this._providers.set(handle, Disposable.None);
this.chatSessionContentProviderService.registerChatSessionContentProvider(_chatSessionType, {
provideChatSessionContent: async (id, token): Promise<IChatModel> => {
const parser = this.instantiationService.createInstance(ChatRequestParser);

// TODO: use real data
const results = await this._proxy.$provideChatSessionContent(handle, id, token);
const model = this.instantiationService.createInstance(ChatModel, undefined, ChatAgentLocation.Panel);

for (let i = 0; i < results.history.length; i++) {
const item = results.history[i];
if (item.type === 'response') {
// We can only create request response pairs :(
// Figure out how to add this in the UI
continue;
}
const parsedRequest = parser.parseChatRequest('sessionId', item.prompt).parts;
const request = model.addRequest({
text: item.prompt,
parts: parsedRequest,
}, { variables: [] }, 0);

const next = results.history[i + 1];
if (next && next.type === 'response') {
i++;
for (const responsePart of next.parts) {
const revivedProgress = revive(responsePart) as IChatProgress;
model.acceptResponseProgress(request, revivedProgress);
}
model.completeResponse(request);
}
}

return model;
}
});
}
$unregisterChatSessionContentProvider(handle: number): void {
this._providers.deleteAndDispose(handle);
}

}
24 changes: 21 additions & 3 deletions src/vs/workbench/api/browser/mainThreadWindow.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,16 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/

import { encodeBase64 } from '../../../base/common/buffer.js';
import { Event } from '../../../base/common/event.js';
import { DisposableStore } from '../../../base/common/lifecycle.js';
import { Schemas } from '../../../base/common/network.js';
import { URI, UriComponents } from '../../../base/common/uri.js';
import { IOpenerService } from '../../../platform/opener/common/opener.js';
import { extHostNamedCustomer, IExtHostContext } from '../../services/extensions/common/extHostCustomers.js';
import { ExtHostContext, ExtHostWindowShape, IOpenUriOptions, MainContext, MainThreadWindowShape } from '../common/extHost.protocol.js';
import { IHostService } from '../../services/host/browser/host.js';
import { IUserActivityService } from '../../services/userActivity/common/userActivityService.js';
import { encodeBase64 } from '../../../base/common/buffer.js';
import { ExtHostContext, ExtHostWindowShape, IOpenUriOptions, MainContext, MainThreadWindowShape } from '../common/extHost.protocol.js';

@extHostNamedCustomer(MainContext.MainThreadWindow)
export class MainThreadWindow implements MainThreadWindowShape {
Expand Down Expand Up @@ -73,7 +74,24 @@ export class MainThreadWindow implements MainThreadWindowShape {
}

async $asExternalUri(uriComponents: UriComponents, options: IOpenUriOptions): Promise<UriComponents> {
const result = await this.openerService.resolveExternalUri(URI.revive(uriComponents), options);
const uri = URI.revive(uriComponents);
const result = await this.openerService.resolveExternalUri(uri, options);
return result.resolved;
}

async $openChatSession(sessionType: string, id: string): Promise<void> {
// TODO: should live in chat instead

// Create a URI with the chat session scheme
const chatSessionUri = URI.from({
scheme: Schemas.vscodeChatSession,
authority: sessionType,
path: `/${id}`
});


// TODO: Integrate with the chat service to open the session in the chat view
// For now, we'll just open the URI
await this.openerService.open(chatSessionUri);
}
}
10 changes: 10 additions & 0 deletions src/vs/workbench/api/common/extHost.api.impl.ts
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,7 @@ import { ExtHostWebviewViews } from './extHostWebviewView.js';
import { IExtHostWindow } from './extHostWindow.js';
import { IExtHostWorkspace } from './extHostWorkspace.js';
import { ExtHostAiSettingsSearch } from './extHostAiSettingsSearch.js';
import { ExtHostChatSessionContentProviders } from './extHostChatSessionContentProviders.js';

export interface IExtensionRegistries {
mine: ExtensionDescriptionRegistry;
Expand Down Expand Up @@ -229,6 +230,7 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I
const extHostSpeech = rpcProtocol.set(ExtHostContext.ExtHostSpeech, new ExtHostSpeech(rpcProtocol));
const extHostEmbeddings = rpcProtocol.set(ExtHostContext.ExtHostEmbeddings, new ExtHostEmbeddings(rpcProtocol));
rpcProtocol.set(ExtHostContext.ExtHostMcp, accessor.get(IExtHostMpcService));
const extHostChatSessionContentProviders = rpcProtocol.set(ExtHostContext.ExtHostChatSessionContentProviders, new ExtHostChatSessionContentProviders(rpcProtocol, extHostCommands.converter));

// Check that no named customers are missing
const expected = Object.values<ProxyIdentifier<any>>(ExtHostContext);
Expand Down Expand Up @@ -949,6 +951,10 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I
checkProposedApiEnabled(extension, 'chatStatusItem');
return extHostChatStatus.createChatStatusItem(extension, id);
},
openChatSession: (sessionType: string, id: string) => {
checkProposedApiEnabled(extension, 'chatSessionProvider');
return extHostWindow.openChatSession(sessionType, id);
},
};

// namespace: workspace
Expand Down Expand Up @@ -1498,6 +1504,10 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I
onDidDisposeChatSession: (listeners, thisArgs?, disposables?) => {
checkProposedApiEnabled(extension, 'chatParticipantPrivate');
return _asExtensionEvent(extHostChatAgents2.onDidDisposeChatSession)(listeners, thisArgs, disposables);
},
registerChatSessionContentProvider(id: string, provider: vscode.ChatSessionContentProvider) {
checkProposedApiEnabled(extension, 'chatSessionProvider');
return extHostChatSessionContentProviders.registerChatSessionContentProvider(id, provider);
}
};

Expand Down
Loading
Loading