Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
3f197a6
Initial sketch of a controller based chat session item API
mjbvz Jan 9, 2026
e45ac30
Hookup basic proxy impl
mjbvz Jan 12, 2026
25bdc74
Cleanup
mjbvz Jan 12, 2026
242baf7
Add archived property
mjbvz Jan 12, 2026
93ff336
Cleanup
mjbvz Jan 12, 2026
bfd6eed
Bump api version
mjbvz Jan 12, 2026
49ee0d0
Also update proposal file
mjbvz Jan 13, 2026
4c7b7c7
Bump api notebook milestone
mjbvz Jan 13, 2026
067cb03
[json] add trustedDomains settings (#287639)
aeschli Jan 13, 2026
45aced5
Merge pull request #287646 from mjbvz/dev/mjbvz/suspicious-hummingbird
mjbvz Jan 13, 2026
b39ecc3
Merge pull request #286642 from microsoft/dev/mjbvz/chat-session-item…
mjbvz Jan 13, 2026
96a75ab
fix races in prompt for input (#287651)
meganrogge Jan 13, 2026
d729111
fix edge case showing "Open Picker" with chatSession optionGroups (#2…
joshspicer Jan 13, 2026
7b7243f
Add better timing info to chat sessions
mjbvz Jan 14, 2026
38f6584
Cleanup
mjbvz Jan 14, 2026
4212deb
Only keep around text models for live code blocks
mjbvz Jan 14, 2026
e64d583
vscode mcp: support custom workspace path; enable restart tool (#286297)
rebornix Jan 14, 2026
85a14f9
Merge pull request #287669 from mjbvz/dev/mjbvz/relevant-turkey
mjbvz Jan 14, 2026
90a7324
Update mock
mjbvz Jan 14, 2026
81f7af4
Merge pull request #287668 from mjbvz/dev/mjbvz/eventual-sparrow
mjbvz Jan 14, 2026
0169cdc
Context key for vaild option groups
osortega Jan 14, 2026
7b573c6
Merge pull request #287678 from microsoft/osortega/flat-rook
osortega Jan 14, 2026
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 .vscode/notebooks/api.github-issues
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
{
"kind": 2,
"language": "github-issues",
"value": "$REPO=repo:microsoft/vscode\n$MILESTONE=milestone:\"October 2025\""
"value": "$REPO=repo:microsoft/vscode\n$MILESTONE=milestone:\"January 2026\""
},
{
"kind": 1,
Expand Down
1 change: 1 addition & 0 deletions eslint.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -899,6 +899,7 @@ export default tseslint.config(
],
'verbs': [
'accept',
'archive',
'change',
'close',
'collapse',
Expand Down
329 changes: 236 additions & 93 deletions extensions/json-language-features/client/src/jsonClient.ts

Large diffs are not rendered by default.

91 changes: 87 additions & 4 deletions extensions/json-language-features/client/src/languageStatus.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,9 @@
import {
window, languages, Uri, Disposable, commands, QuickPickItem,
extensions, workspace, Extension, WorkspaceFolder, QuickPickItemKind,
ThemeIcon, TextDocument, LanguageStatusSeverity, l10n, DocumentSelector
ThemeIcon, TextDocument, LanguageStatusSeverity, l10n, DocumentSelector, Diagnostic
} from 'vscode';
import { JSONLanguageStatus, JSONSchemaSettings } from './jsonClient';
import { CommandIds, ErrorCodes, isSchemaResolveError, JSONLanguageStatus, JSONSchemaSettings, SettingIds } from './jsonClient';

type ShowSchemasInput = {
schemas: string[];
Expand Down Expand Up @@ -168,7 +168,7 @@ export function createLanguageStatusItem(documentSelector: DocumentSelector, sta
statusItem.name = l10n.t('JSON Validation Status');
statusItem.severity = LanguageStatusSeverity.Information;

const showSchemasCommand = commands.registerCommand('_json.showAssociatedSchemaList', showSchemaList);
const showSchemasCommand = commands.registerCommand(CommandIds.showAssociatedSchemaList, showSchemaList);

const activeEditorListener = window.onDidChangeActiveTextEditor(() => {
updateLanguageStatus();
Expand All @@ -195,7 +195,7 @@ export function createLanguageStatusItem(documentSelector: DocumentSelector, sta
statusItem.detail = l10n.t('multiple JSON schemas configured');
}
statusItem.command = {
command: '_json.showAssociatedSchemaList',
command: CommandIds.showAssociatedSchemaList,
title: l10n.t('Show Schemas'),
arguments: [{ schemas, uri: document.uri.toString() } satisfies ShowSchemasInput]
};
Expand Down Expand Up @@ -279,3 +279,86 @@ export function createDocumentSymbolsLimitItem(documentSelector: DocumentSelecto
}


export function createSchemaLoadStatusItem(newItem: (fileSchemaError: Diagnostic) => Disposable) {
let statusItem: Disposable | undefined;
const fileSchemaErrors: Map<string, Diagnostic> = new Map();

const toDispose: Disposable[] = [];
toDispose.push(window.onDidChangeActiveTextEditor(textEditor => {
statusItem?.dispose();
statusItem = undefined;
const doc = textEditor?.document;
if (doc) {
const fileSchemaError = fileSchemaErrors.get(doc.uri.toString());
if (fileSchemaError !== undefined) {
statusItem = newItem(fileSchemaError);
}
}
}));
toDispose.push(workspace.onDidCloseTextDocument(document => {
fileSchemaErrors.delete(document.uri.toString());
}));

function update(uri: Uri, diagnostics: Diagnostic[]) {
const fileSchemaError = diagnostics.find(isSchemaResolveError);
const uriString = uri.toString();

if (fileSchemaError === undefined) {
fileSchemaErrors.delete(uriString);
if (statusItem && uriString === window.activeTextEditor?.document.uri.toString()) {
statusItem.dispose();
statusItem = undefined;
}
} else {
const current = fileSchemaErrors.get(uriString);
if (current?.message === fileSchemaError.message) {
return;
}
fileSchemaErrors.set(uriString, fileSchemaError);
if (uriString === window.activeTextEditor?.document.uri.toString()) {
statusItem?.dispose();
statusItem = newItem(fileSchemaError);
}
}
}
return {
update,
dispose() {
statusItem?.dispose();
toDispose.forEach(d => d.dispose());
toDispose.length = 0;
statusItem = undefined;
fileSchemaErrors.clear();
}
};
}



export function createSchemaLoadIssueItem(documentSelector: DocumentSelector, schemaDownloadEnabled: boolean | undefined, diagnostic: Diagnostic): Disposable {
const statusItem = languages.createLanguageStatusItem('json.documentSymbolsStatus', documentSelector);
statusItem.name = l10n.t('JSON Outline Status');
statusItem.severity = LanguageStatusSeverity.Error;
statusItem.text = 'Schema download issue';
if (!workspace.isTrusted) {
statusItem.detail = l10n.t('Workspace untrusted');
statusItem.command = { command: CommandIds.workbenchTrustManage, title: 'Configure Trust' };
} else if (!schemaDownloadEnabled) {
statusItem.detail = l10n.t('Download disabled');
statusItem.command = { command: CommandIds.workbenchActionOpenSettings, arguments: [SettingIds.enableSchemaDownload], title: 'Configure' };
} else if (typeof diagnostic.code === 'number' && diagnostic.code === ErrorCodes.UntrustedSchemaError) {
statusItem.detail = l10n.t('Location untrusted');
const schemaUri = diagnostic.relatedInformation?.[0]?.location.uri;
if (schemaUri) {
statusItem.command = { command: CommandIds.configureTrustedDomainsCommandId, arguments: [schemaUri.toString()], title: 'Configure Trusted Domains' };
} else {
statusItem.command = { command: CommandIds.workbenchActionOpenSettings, arguments: [SettingIds.trustedDomains], title: 'Configure Trusted Domains' };
}
} else {
statusItem.detail = l10n.t('Unable to resolve schema');
statusItem.command = { command: CommandIds.retryResolveSchemaCommandId, title: 'Retry' };
}
return Disposable.from(statusItem);
}


107 changes: 107 additions & 0 deletions extensions/json-language-features/client/src/utils/urlMatch.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/

import { Uri } from 'vscode';

/**
* Check whether a URL matches the list of trusted domains or URIs.
*
* trustedDomains is an object where:
* - Keys are full domains (https://www.microsoft.com) or full URIs (https://www.test.com/schemas/mySchema.json)
* - Keys can include wildcards (https://*.microsoft.com) or glob patterns
* - Values are booleans indicating if the domain/URI is trusted (true) or blocked (false)
*
* @param url The URL to check
* @param trustedDomains Object mapping domain patterns to boolean trust values
*/
export function matchesUrlPattern(url: Uri, trustedDomains: Record<string, boolean>): boolean {
// Check localhost
if (isLocalhostAuthority(url.authority)) {
return true;
}

for (const [pattern, isTrusted] of Object.entries(trustedDomains)) {
if (typeof pattern !== 'string' || pattern.trim() === '') {
continue;
}

// Wildcard matches everything
if (pattern === '*') {
return isTrusted;
}

try {
const patternUri = Uri.parse(pattern);

// Scheme must match
if (url.scheme !== patternUri.scheme) {
continue;
}

// Check authority (host:port)
if (!matchesAuthority(url.authority, patternUri.authority)) {
continue;
}

// Check path
if (!matchesPath(url.path, patternUri.path)) {
continue;
}

return isTrusted;
} catch {
// Invalid pattern, skip
continue;
}
}

return false;
}

function matchesAuthority(urlAuthority: string, patternAuthority: string): boolean {
urlAuthority = urlAuthority.toLowerCase();
patternAuthority = patternAuthority.toLowerCase();

if (patternAuthority === urlAuthority) {
return true;
}
// Handle wildcard subdomains (e.g., *.github.com)
if (patternAuthority.startsWith('*.')) {
const patternDomain = patternAuthority.substring(2);
// Exact match or subdomain match
return urlAuthority === patternDomain || urlAuthority.endsWith('.' + patternDomain);
}

return false;
}

function matchesPath(urlPath: string, patternPath: string): boolean {
// Empty pattern path or just "/" matches any path
if (!patternPath || patternPath === '/') {
return true;
}

// Exact match
if (urlPath === patternPath) {
return true;
}

// If pattern ends with '/', it matches any path starting with it
if (patternPath.endsWith('/')) {
return urlPath.startsWith(patternPath);
}

// Otherwise, pattern must be a prefix
return urlPath.startsWith(patternPath + '/') || urlPath === patternPath;
}


const rLocalhost = /^(.+\.)?localhost(:\d+)?$/i;
const r127 = /^127\.0\.0\.1(:\d+)?$/;
const rIPv6Localhost = /^\[::1\](:\d+)?$/;

function isLocalhostAuthority(authority: string): boolean {
return rLocalhost.test(authority) || r127.test(authority) || rIPv6Localhost.test(authority);
}
16 changes: 16 additions & 0 deletions extensions/json-language-features/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,22 @@
"tags": [
"usesOnlineServices"
]
},
"json.schemaDownload.trustedDomains": {
"type": "object",
"default": {
"https://schemastore.azurewebsites.net/": true,
"https://raw.githubusercontent.com/": true,
"https://www.schemastore.org/": true,
"https://json-schema.org/": true
},
"additionalProperties": {
"type": "boolean"
},
"description": "%json.schemaDownload.trustedDomains.desc%",
"tags": [
"usesOnlineServices"
]
}
}
},
Expand Down
4 changes: 2 additions & 2 deletions extensions/json-language-features/package.nls.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,6 @@
"json.enableSchemaDownload.desc": "When enabled, JSON schemas can be fetched from http and https locations.",
"json.command.clearCache": "Clear Schema Cache",
"json.command.sort": "Sort Document",
"json.workspaceTrust": "The extension requires workspace trust to load schemas from http and https."

"json.workspaceTrust": "The extension requires workspace trust to load schemas from http and https.",
"json.schemaDownload.trustedDomains.desc": "List of trusted domains for downloading JSON schemas over http(s). Use '*' to trust all domains. '*' can also be used as a wildcard in domain names."
}
8 changes: 4 additions & 4 deletions extensions/json-language-features/server/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion extensions/json-language-features/server/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
"@vscode/l10n": "^0.0.18",
"jsonc-parser": "^3.3.1",
"request-light": "^0.8.0",
"vscode-json-languageservice": "^5.6.4",
"vscode-json-languageservice": "^5.7.1",
"vscode-languageserver": "^10.0.0-next.15",
"vscode-uri": "^3.1.0"
},
Expand Down
18 changes: 13 additions & 5 deletions extensions/json-language-features/server/src/jsonServer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

import {
Connection,
TextDocuments, InitializeParams, InitializeResult, NotificationType, RequestType,
TextDocuments, InitializeParams, InitializeResult, NotificationType, RequestType, ResponseError,
DocumentRangeFormattingRequest, Disposable, ServerCapabilities, TextDocumentSyncKind, TextEdit, DocumentFormattingRequest, TextDocumentIdentifier, FormattingOptions, Diagnostic, CodeAction, CodeActionKind
} from 'vscode-languageserver';

Expand Down Expand Up @@ -36,6 +36,10 @@ namespace ForceValidateRequest {
export const type: RequestType<string, Diagnostic[], any> = new RequestType('json/validate');
}

namespace ForceValidateAllRequest {
export const type: RequestType<void, void, any> = new RequestType('json/validateAll');
}

namespace LanguageStatusRequest {
export const type: RequestType<string, JSONLanguageStatus, any> = new RequestType('json/languageStatus');
}
Expand Down Expand Up @@ -102,8 +106,8 @@ export function startServer(connection: Connection, runtime: RuntimeEnvironment)
}
return connection.sendRequest(VSCodeContentRequest.type, uri).then(responseText => {
return responseText;
}, error => {
return Promise.reject(error.message);
}, (error: ResponseError<any>) => {
return Promise.reject(error);
});
};
}
Expand Down Expand Up @@ -298,6 +302,10 @@ export function startServer(connection: Connection, runtime: RuntimeEnvironment)
});

// Retry schema validation on all open documents
connection.onRequest(ForceValidateAllRequest.type, async () => {
diagnosticsSupport?.requestRefresh();
});

connection.onRequest(ForceValidateRequest.type, async uri => {
const document = documents.get(uri);
if (document) {
Expand Down Expand Up @@ -387,11 +395,11 @@ export function startServer(connection: Connection, runtime: RuntimeEnvironment)
connection.onDidChangeWatchedFiles((change) => {
// Monitored files have changed in VSCode
let hasChanges = false;
change.changes.forEach(c => {
for (const c of change.changes) {
if (languageService.resetSchema(c.uri)) {
hasChanges = true;
}
});
}
if (hasChanges) {
diagnosticsSupport?.requestRefresh();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ const _allApiProposals = {
},
chatSessionsProvider: {
proposal: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.chatSessionsProvider.d.ts',
version: 3
version: 4
},
chatStatusItem: {
proposal: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.chatStatusItem.d.ts',
Expand Down
2 changes: 1 addition & 1 deletion src/vs/workbench/api/browser/mainThreadChatSessions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -382,7 +382,6 @@ export class MainThreadChatSessions extends Disposable implements MainThreadChat
));
}


$onDidChangeChatSessionItems(handle: number): void {
this._itemProvidersRegistrations.get(handle)?.onDidChangeItems.fire();
}
Expand Down Expand Up @@ -491,6 +490,7 @@ export class MainThreadChatSessions extends Disposable implements MainThreadChat
resource: uri,
iconPath: session.iconPath,
tooltip: session.tooltip ? this._reviveTooltip(session.tooltip) : undefined,
archived: session.archived,
} satisfies IChatSessionItem;
}));
} catch (error) {
Expand Down
4 changes: 4 additions & 0 deletions src/vs/workbench/api/common/extHost.api.impl.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1530,6 +1530,10 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I
checkProposedApiEnabled(extension, 'chatSessionsProvider');
return extHostChatSessions.registerChatSessionItemProvider(extension, chatSessionType, provider);
},
createChatSessionItemController: (chatSessionType: string, refreshHandler: () => Thenable<void>) => {
checkProposedApiEnabled(extension, 'chatSessionsProvider');
return extHostChatSessions.createChatSessionItemController(extension, chatSessionType, refreshHandler);
},
registerChatSessionContentProvider(scheme: string, provider: vscode.ChatSessionContentProvider, chatParticipant: vscode.ChatParticipant, capabilities?: vscode.ChatSessionCapabilities) {
checkProposedApiEnabled(extension, 'chatSessionsProvider');
return extHostChatSessions.registerChatSessionContentProvider(extension, scheme, chatParticipant, provider, capabilities);
Expand Down
Loading
Loading