diff --git a/.github/workflows/sessions-e2e.yml b/.github/workflows/sessions-e2e.yml index 87d3acf3b8203..2a7e05b1760ba 100644 --- a/.github/workflows/sessions-e2e.yml +++ b/.github/workflows/sessions-e2e.yml @@ -1,16 +1,14 @@ name: Sessions E2E Tests -# on: -# pull_request: -# branches: -# - main -# - 'release/*' -# paths: -# - 'src/vs/sessions/**' -# - 'scripts/code-sessions-web.*' - -# just commenting "on" property causes emails to be sent to everyone -on: workflow_dispatch +on: + pull_request: + branches: + - main + - 'release/*' + paths: + - 'src/vs/sessions/**' + - 'scripts/code-sessions-web.*' + workflow_dispatch: permissions: contents: read diff --git a/PR_305961_FINAL_VERIFICATION.md b/PR_305961_FINAL_VERIFICATION.md new file mode 100644 index 0000000000000..e69de29bb2d1d diff --git a/build/gulpfile.extensions.ts b/build/gulpfile.extensions.ts index 4e09c70f9208f..335b983274d8f 100644 --- a/build/gulpfile.extensions.ts +++ b/build/gulpfile.extensions.ts @@ -85,6 +85,7 @@ const compilations = [ 'extensions/references-view/tsconfig.json', 'extensions/search-result/tsconfig.json', 'extensions/simple-browser/tsconfig.json', + 'extensions/toolpipe-mcp-server/tsconfig.json', 'extensions/tunnel-forwarding/tsconfig.json', 'extensions/typescript-language-features/web/tsconfig.json', 'extensions/typescript-language-features/tsconfig.json', diff --git a/extensions/toolpipe-mcp-server/README.md b/extensions/toolpipe-mcp-server/README.md new file mode 100644 index 0000000000000..e00f2ecb1761a --- /dev/null +++ b/extensions/toolpipe-mcp-server/README.md @@ -0,0 +1,220 @@ +# ToolPipe MCP Server Extension + +This VS Code extension provides seamless integration with **ToolPipe MCP Server**, giving you access to 120+ developer utilities through the Model Context Protocol (MCP). + +## Features + +### ✨ 120+ Developer Utilities + +ToolPipe MCP Server provides comprehensive developer tools organized into five categories: + +#### 🔧 Code Tools +- JavaScript/TypeScript: Formatting, minification, code review, AST analysis +- Python: Code formatting, linting, syntax checking +- SQL: Query formatting, optimization, validation +- CSS/SCSS/LESS: Formatting, minification, validation +- HTML: Formatting, validation, optimization + +#### 📊 Data Tools +- JSON: Formatting, validation, transformation, schema analysis +- CSV: Parsing, formatting, conversion to JSON/XML +- XML: Formatting, validation, transformation +- YAML: Formatting, validation, conversion +- Encoding: Base64 encode/decode, hex conversion, URL encoding +- Generators: UUID generation, random data generation + +#### 🔒 Security Tools +- Hash Generation: MD5, SHA-1, SHA-256, SHA-512, HMAC +- JWT: Decode and validate JWT tokens +- SSL/TLS: Certificate validation and analysis +- Security Headers: Analysis and validation +- Password Generation: Secure password creation + +#### 🌐 API Tools +- HTTP Client: Make HTTP requests with custom headers +- OpenAPI: Spec generation and documentation +- Webhook Testing: Webhook URL generation and testing +- API Documentation: Auto-generate from code comments +- REST Builder: Interactive REST API builder + +#### 🚀 DevOps Tools +- Docker: Docker Compose generation, container commands +- GitHub Actions: Workflow generation and validation +- Nginx: Configuration generation and validation +- Kubernetes: YAML generation and validation +- Environment: System information and diagnostics + +## Installation + +1. Install this extension from the VS Code Marketplace +2. Reload VS Code +3. The extension will be automatically activated + +## Configuration + +### Quick Start (Remote Mode) + +To use ToolPipe with a remote server, configure the URL of your MCP server: + +```json +{ + "toolpipeMcpServer.enabled": true, + "toolpipeMcpServer.mode": "remote", + "toolpipeMcpServer.remoteUrl": "https://example.com/mcp" +} +``` + +Replace `https://example.com/mcp` with your actual ToolPipe server URL. + +### Advanced Configuration + +#### Using Local Server + +To run ToolPipe locally (requires Node.js and npm): + +```bash +npm install -g @cosai-labs/toolpipe-mcp-server +``` + +Then configure VS Code: + +```json +{ + "toolpipeMcpServer.enabled": true, + "toolpipeMcpServer.mode": "local", + "toolpipeMcpServer.localCommand": "npx", + "toolpipeMcpServer.localArgs": ["@cosai-labs/toolpipe-mcp-server"] +} +``` + +#### Disabling the Extension + +To disable ToolPipe integration: + +```json +{ + "toolpipeMcpServer.enabled": false +} +``` + +## Usage with AI Assistants + +Once configured, ToolPipe tools are automatically available to: +- **Copilot Chat**: Use `/explain` or chat tools to access utilities +- **Claude Desktop**: Via MCP server connection +- **Cursor/Windsurf**: Integrated through MCP protocol +- **Cline**: Via MCP server endpoints + +## Configuration Reference + +### Settings + +| Setting | Type | Default | Description | +|---------|------|---------|-------------| +| `toolpipeMcpServer.enabled` | boolean | `true` | Enable/disable the extension | +| `toolpipeMcpServer.mode` | string | `remote` | Connection mode: `remote` or `local` | +| `toolpipeMcpServer.remoteUrl` | string | `` (empty) | Remote server URL | +| `toolpipeMcpServer.localCommand` | string | `npx` | Command for local server | +| `toolpipeMcpServer.localArgs` | array | `["@cosai-labs/toolpipe-mcp-server"]` | Arguments for local server | + +## Direct MCP Configuration + +You can also configure ToolPipe directly in your `.vscode/mcp.json`: + +### Remote Server (HTTP) +```json +{ + "mcp": { + "servers": { + "toolpipe": { + "type": "http", + "url": "https://example.com/mcp" + } + } + } +} +``` + +### Local Server (Stdio) +```json +{ + "mcp": { + "servers": { + "toolpipe": { + "type": "stdio", + "command": "npx", + "args": ["@cosai-labs/toolpipe-mcp-server"] + } + } + } +} +``` + +## Examples + +### JSON Formatting with Copilot Chat +``` +@copilot Format this JSON: +{ + "name":"John", + "age":30 +} +``` +Copilot will use ToolPipe's JSON formatting tool automatically. + +### Code Review +``` +@copilot Can you review this TypeScript code? +[paste code] +``` +ToolPipe's code review tools will be available in the context. + +### Hash Generation +``` +@copilot Generate SHA-256 hash of "my-password" +``` +ToolPipe provides instant hash generation. + +## Troubleshooting + +### Server not connecting +1. Check if the extension is enabled in settings +2. For remote mode: verify internet connection +3. For local mode: ensure Node.js and npm are installed +4. Check VS Code's output panel for error messages + +### Tools not appearing in chat +1. Reload VS Code window (Cmd+Shift+P → "Developer: Reload Window") +2. Verify the MCP server started successfully by checking the Extension Host or Developer Tools logs for error messages +3. Restart Copilot Chat + +### Local server not starting +```bash +# Test if the package is installed +npx @cosai-labs/toolpipe-mcp-server --help + +# Install globally if needed +npm install -g @cosai-labs/toolpipe-mcp-server +``` + +## Links + +- **npm**: https://www.npmjs.com/package/@cosai-labs/toolpipe-mcp-server +- **GitHub**: https://github.com/COSAI-Labs/make-money-30day-challenge/tree/master/products/mcp-server +- **MCP Protocol**: https://modelcontextprotocol.io/ +- **VS Code Docs**: https://code.visualstudio.com/docs + +## License + +MIT - See LICENSE file for details + +## Contributing + +Contributions are welcome! Please submit issues and pull requests to the VS Code repository. + +## Support + +For issues with: +- **ToolPipe Server**: https://github.com/COSAI-Labs/make-money-30day-challenge/issues +- **VS Code Integration**: https://github.com/microsoft/vscode/issues +- **MCP Protocol**: https://github.com/modelcontextprotocol/specification/issues diff --git a/extensions/toolpipe-mcp-server/package.json b/extensions/toolpipe-mcp-server/package.json new file mode 100644 index 0000000000000..c86eefd17e55f --- /dev/null +++ b/extensions/toolpipe-mcp-server/package.json @@ -0,0 +1,97 @@ +{ + "name": "toolpipe-mcp-server", + "displayName": "%displayName%", + "description": "%description%", + "version": "1.0.0", + "publisher": "vscode", + "license": "MIT", + "engines": { + "vscode": "^1.97.0" + }, + "categories": [ + "Other", + "Developer Tools" + ], + "keywords": [ + "mcp", + "model-context-protocol", + "developer-tools", + "utilities", + "json", + "formatting", + "linting" + ], + "repository": { + "type": "git", + "url": "https://github.com/microsoft/vscode" + }, + "bugs": { + "url": "https://github.com/microsoft/vscode/issues" + }, + "main": "./out/extension.js", + "activationEvents": [ + "onStartupFinished" + ], + "contributes": { + "mcpServerDefinitionProviders": [ + { + "id": "toolpipe", + "label": "%mcpServerDefinition.label%" + } + ], + "configuration": [ + { + "title": "%configuration.title%", + "properties": { + "toolpipeMcpServer.enabled": { + "type": "boolean", + "default": true, + "description": "%configuration.enabled.description%" + }, + "toolpipeMcpServer.mode": { + "type": "string", + "enum": [ + "remote", + "local" + ], + "default": "remote", + "description": "%configuration.mode.description%" + }, + "toolpipeMcpServer.remoteUrl": { + "type": "string", + "default": "", + "description": "%configuration.remoteUrl.description%" + }, + "toolpipeMcpServer.localCommand": { + "type": "string", + "default": "npx", + "description": "%configuration.localCommand.description%" + }, + "toolpipeMcpServer.localArgs": { + "type": "array", + "default": [ + "@cosai-labs/toolpipe-mcp-server" + ], + "description": "%configuration.localArgs.description%" + } + } + } + ] + }, + "scripts": { + "vscode:prepublish": "npm run compile", + "compile": "tsc -p ./", + "watch": "tsc -w -p ./", + "pretest": "npm run compile && npm run lint", + "test": "vscode-test", + "lint": "eslint src --ext ts" + }, + "devDependencies": { + "@types/node": "^18.0.0", + "@types/vscode": "^1.96.0", + "@typescript-eslint/eslint-plugin": "^7.0.0", + "@typescript-eslint/parser": "^7.0.0", + "eslint": "^8.0.0", + "typescript": "~5.3.0" + } +} diff --git a/extensions/toolpipe-mcp-server/package.nls.json b/extensions/toolpipe-mcp-server/package.nls.json new file mode 100644 index 0000000000000..3a78e475c6377 --- /dev/null +++ b/extensions/toolpipe-mcp-server/package.nls.json @@ -0,0 +1,11 @@ +{ + "displayName": "ToolPipe MCP Server", + "description": "Integrates ToolPipe MCP Server - 120+ developer utilities via Model Context Protocol", + "configuration.title": "ToolPipe MCP Server", + "configuration.enabled.description": "Enable ToolPipe MCP Server integration", + "configuration.mode.description": "ToolPipe connection mode: 'remote' for cloud-hosted server, 'local' for npm-based server", + "configuration.remoteUrl.description": "Remote URL for ToolPipe MCP Server (HTTPS endpoint). Provide your own server URL, for example: https://example.com/mcp", + "configuration.localCommand.description": "Command to run local ToolPipe server (e.g., 'npx' or full path)", + "configuration.localArgs.description": "Arguments for local ToolPipe server command", + "mcpServerDefinition.label": "ToolPipe MCP Server" +} diff --git a/extensions/toolpipe-mcp-server/src/extension.ts b/extensions/toolpipe-mcp-server/src/extension.ts new file mode 100644 index 0000000000000..7e43761575a3c --- /dev/null +++ b/extensions/toolpipe-mcp-server/src/extension.ts @@ -0,0 +1,79 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import * as vscode from 'vscode'; + +/** + * ToolPipe MCP Server Extension + * + * Provides integration with ToolPipe MCP Server, which offers 120+ developer utilities: + * - Code tools: Review, minification, formatting (JS/TS/Python/SQL/CSS/HTML) + * - Data tools: JSON/CSV/XML/YAML conversion, Base64, UUID generation + * - Security: Hash generation, JWT decode, SSL checking, security headers + * - API tools: HTTP client, OpenAPI spec generation, webhook testing + * - DevOps: Docker Compose generation, GitHub Actions workflows, Nginx configs + */ + +export async function activate(context: vscode.ExtensionContext) { + const config = vscode.workspace.getConfiguration('toolpipeMcpServer'); + const enabled = config.get('enabled', true); + + if (!enabled) { + console.log('ToolPipe MCP Server extension disabled in settings'); + return; + } + + // Register MCP Server Definition Provider + const provider = { + provideMcpServerDefinitions: async (): Promise => { + return getMcpServerDefinitions(); + } + }; + + context.subscriptions.push( + vscode.lm.registerMcpServerDefinitionProvider('toolpipe', provider) + ); + + console.log('ToolPipe MCP Server extension activated'); +} + +/** + * Generates MCP Server definitions based on current configuration + */ +function getMcpServerDefinitions(): vscode.McpServerDefinition[] { + const config = vscode.workspace.getConfiguration('toolpipeMcpServer'); + const mode = config.get('mode', 'remote'); + + const definitions: vscode.McpServerDefinition[] = []; + + if (mode === 'remote') { + const remoteUrl = config.get('remoteUrl', ''); + if (remoteUrl) { + definitions.push( + new vscode.McpHttpServerDefinition( + 'ToolPipe Developer Tools', + vscode.Uri.parse(remoteUrl) + ) + ); + } + } else if (mode === 'local') { + const command = config.get('localCommand', 'npx'); + const args = config.get('localArgs', ['@cosai-labs/toolpipe-mcp-server']); + + definitions.push( + new vscode.McpStdioServerDefinition( + 'ToolPipe Developer Tools (Local)', + command, + args + ) + ); + } + + return definitions; +} + +export function deactivate() { + // Cleanup +} diff --git a/extensions/toolpipe-mcp-server/tsconfig.json b/extensions/toolpipe-mcp-server/tsconfig.json new file mode 100644 index 0000000000000..b7dfd0c8d1d9c --- /dev/null +++ b/extensions/toolpipe-mcp-server/tsconfig.json @@ -0,0 +1,17 @@ +{ + "extends": "../tsconfig.base.json", + "compilerOptions": { + "outDir": "./out", + "rootDir": "./src", + "skipLibCheck": true + }, + "include": [ + "src/**/*", + "../../src/vscode-dts/vscode.d.ts", + "../../src/vscode-dts/vscode.proposed.mcpServerDefinitions.d.ts" + ], + "exclude": [ + "node_modules", + "out" + ] +} diff --git a/extensions/typescript-language-features/src/configuration/configuration.electron.ts b/extensions/typescript-language-features/src/configuration/configuration.electron.ts index 6f5bbd356ce23..7a78d9f01cf63 100644 --- a/extensions/typescript-language-features/src/configuration/configuration.electron.ts +++ b/extensions/typescript-language-features/src/configuration/configuration.electron.ts @@ -6,11 +6,76 @@ import * as os from 'os'; import * as path from 'path'; import * as vscode from 'vscode'; -import * as child_process from 'child_process'; import * as fs from 'fs'; import { BaseServiceConfigurationProvider } from './configuration'; import { RelativeWorkspacePathResolver } from '../utils/relativePathResolver'; +type IsExecutableFile = (candidate: string) => boolean; + +function defaultIsExecutableFile(candidate: string): boolean { + try { + if (!fs.existsSync(candidate)) { + return false; + } + + const stat = fs.lstatSync(candidate); + if (stat.isDirectory()) { + return false; + } + + if (process.platform !== 'win32') { + fs.accessSync(candidate, fs.constants.X_OK); + } + + return true; + } catch { + return false; + } +} + +function getCaseInsensitiveEnvValue(env: NodeJS.ProcessEnv, key: string): string | undefined { + const foundKey = Object.keys(env).find(k => k.toUpperCase() === key.toUpperCase()); + return foundKey ? env[foundKey] : undefined; +} + +export function resolveNodeExecutableFromPath( + env: NodeJS.ProcessEnv, + cwd: string, + isExecutableFile: IsExecutableFile = defaultIsExecutableFile, + platform: NodeJS.Platform = process.platform, +): string | null { + const pathLib = platform === 'win32' ? path.win32 : path.posix; + const pathValue = getCaseInsensitiveEnvValue(env, 'PATH'); + if (!pathValue) { + return null; + } + + const searchPaths = pathValue.split(pathLib.delimiter).filter(Boolean); + const windowsExecutableSuffixes = platform === 'win32' + ? (getCaseInsensitiveEnvValue(env, 'PATHEXT') || '.COM;.EXE;.BAT;.CMD').split(';').filter(Boolean) + : []; + + for (const pathEntry of searchPaths) { + const baseDir = pathLib.isAbsolute(pathEntry) ? pathEntry : pathLib.join(cwd, pathEntry); + + if (platform === 'win32') { + for (const ext of windowsExecutableSuffixes) { + const candidate = pathLib.join(baseDir, `node${ext}`); + if (isExecutableFile(candidate)) { + return candidate; + } + } + } + + const candidate = pathLib.join(baseDir, 'node'); + if (isExecutableFile(candidate)) { + return candidate; + } + } + + return null; +} + export class ElectronServiceConfigurationProvider extends BaseServiceConfigurationProvider { private fixPathPrefixes(inspectValue: string): string { @@ -92,18 +157,12 @@ export class ElectronServiceConfigurationProvider extends BaseServiceConfigurati } private findNodePath(): string | null { - try { - const out = child_process.execFileSync('node', ['-e', 'console.log(process.execPath)'], { - windowsHide: true, - timeout: 2000, - cwd: vscode.workspace.workspaceFolders?.[0].uri.fsPath, - encoding: 'utf-8', - }); - return out.trim(); - } catch (error) { + const cwd = vscode.workspace.workspaceFolders?.[0].uri.fsPath ?? process.cwd(); + const resolvedNodePath = resolveNodeExecutableFromPath(process.env, cwd); + if (!resolvedNodePath) { vscode.window.showWarningMessage(vscode.l10n.t("Could not detect a Node installation to run TS Server.")); - return null; } + return resolvedNodePath; } private validatePath(nodePath: string | null): string | null { diff --git a/extensions/typescript-language-features/src/test/unit/configuration.electron.test.ts b/extensions/typescript-language-features/src/test/unit/configuration.electron.test.ts new file mode 100644 index 0000000000000..030b847167cd6 --- /dev/null +++ b/extensions/typescript-language-features/src/test/unit/configuration.electron.test.ts @@ -0,0 +1,43 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import * as assert from 'assert'; +import 'mocha'; +import { resolveNodeExecutableFromPath } from '../../configuration/configuration.electron'; + +suite('typescript.configuration.electron', () => { + test('resolves node from PATH on win32', () => { + const found = resolveNodeExecutableFromPath( + { PATH: 'C:\\Windows;C:\\Tools', PATHEXT: '.COM;.EXE;.BAT;.CMD' }, + 'C:\\workspace', + candidate => candidate.toLowerCase() === 'c:\\tools\\node.exe', + 'win32', + ); + + assert.strictEqual(found, 'C:\\Tools\\node.EXE'); + }); + + test('resolves node from PATH on non-win32', () => { + const found = resolveNodeExecutableFromPath( + { PATH: '/bin:/usr/local/bin' }, + '/workspace', + candidate => candidate === '/usr/local/bin/node', + 'linux', + ); + + assert.strictEqual(found, '/usr/local/bin/node'); + }); + + test('returns null when node is not found', () => { + const found = resolveNodeExecutableFromPath( + { PATH: '/bin:/usr/local/bin' }, + '/workspace', + () => false, + 'linux', + ); + + assert.strictEqual(found, null); + }); +}); diff --git a/src/vs/workbench/contrib/chat/common/chatService/chatService.ts b/src/vs/workbench/contrib/chat/common/chatService/chatService.ts index 5f6e5ebabd71b..84dcd8b406fd3 100644 --- a/src/vs/workbench/contrib/chat/common/chatService/chatService.ts +++ b/src/vs/workbench/contrib/chat/common/chatService/chatService.ts @@ -162,6 +162,16 @@ export interface IChatContentInlineReference { resolveId?: string; inlineReference: URI | Location | IWorkspaceSymbol; name?: string; + /** + * Optional code snippet content extracted from the referenced file/location. + * When present, this content should be displayed alongside the reference link. + */ + snippet?: string; + /** + * Optional language identifier for syntax highlighting the code snippet. + * If not provided, will be inferred from the file extension. + */ + languageId?: string; kind: 'inlineReference'; } diff --git a/src/vs/workbench/contrib/chat/common/widget/annotations.ts b/src/vs/workbench/contrib/chat/common/widget/annotations.ts index eb013e2b76666..27dde45a90c98 100644 --- a/src/vs/workbench/contrib/chat/common/widget/annotations.ts +++ b/src/vs/workbench/contrib/chat/common/widget/annotations.ts @@ -12,6 +12,67 @@ import { IChatAgentVulnerabilityDetails } from '../chatService/chatService.js'; export const contentRefUrl = 'http://_vscodecontentref_'; // must be lowercase for URI +/** + * Infers the language identifier from a file name or label. + * Common language mappings based on file extensions. + */ +function inferLanguageFromLabel(label: string): string | undefined { + if (!label) { + return undefined; + } + + const ext = label.substring(label.lastIndexOf('.') + 1).toLowerCase(); + const languageMap: Record = { + // Programming languages + 'js': 'javascript', + 'jsx': 'javascript', + 'ts': 'typescript', + 'tsx': 'typescript', + 'py': 'python', + 'java': 'java', + 'c': 'c', + 'cpp': 'cpp', + 'cc': 'cpp', + 'cxx': 'cpp', + 'h': 'c', + 'hpp': 'cpp', + 'cs': 'csharp', + 'go': 'go', + 'rs': 'rust', + 'rb': 'ruby', + 'php': 'php', + 'swift': 'swift', + 'kt': 'kotlin', + 'scala': 'scala', + 'pl': 'perl', + 'sh': 'bash', + 'bash': 'bash', + 'zsh': 'bash', + 'fish': 'bash', + 'ps1': 'powershell', + 'psm1': 'powershell', + 'lua': 'lua', + 'r': 'r', + 'sql': 'sql', + 'html': 'html', + 'htm': 'html', + 'xml': 'xml', + 'css': 'css', + 'scss': 'scss', + 'sass': 'sass', + 'less': 'less', + 'json': 'json', + 'jsonc': 'jsonc', + 'yaml': 'yaml', + 'yml': 'yaml', + 'toml': 'toml', + 'md': 'markdown', + 'markdown': 'markdown', + }; + + return languageMap[ext]; +} + export function annotateSpecialMarkdownContent(response: Iterable): IChatProgressRenderableResponseContent[] { let refIdPool = 0; @@ -46,7 +107,16 @@ export function annotateSpecialMarkdownContent(response: Iterable Math.max(max, run.length), 0) ?? 0) + 1); + const fence = '`'.repeat(fenceLength); + markdownText += `\n${fence}${languageId}\n${item.snippet}\n${fence}\n`; + } const annotationMetadata = { [refId]: item }; diff --git a/src/vs/workbench/contrib/chat/test/common/widget/annotations.test.ts b/src/vs/workbench/contrib/chat/test/common/widget/annotations.test.ts index 8229a6ae77981..068f67594518e 100644 --- a/src/vs/workbench/contrib/chat/test/common/widget/annotations.test.ts +++ b/src/vs/workbench/contrib/chat/test/common/widget/annotations.test.ts @@ -324,4 +324,162 @@ suite('Annotations', function () { }); }); + + suite('annotateSpecialMarkdownContent - inline references with snippets', () => { + test('inline reference with snippet renders code block', () => { + const result = annotateSpecialMarkdownContent([ + content('Check out '), + { + kind: 'inlineReference', + inlineReference: URI.parse('file:///example.ts'), + name: 'example.ts', + snippet: 'export const greeting = "hello world";', + languageId: 'typescript' + }, + content(' for details'), + ]); + + assert.strictEqual(result.length, 1); + const md = result[0] as IChatMarkdownContent; + // Should contain the file reference + assert.ok(md.content.value.includes('[example.ts]')); + // Should contain the code fence with language ID + assert.ok(md.content.value.includes('```typescript')); + // Should contain the snippet + assert.ok(md.content.value.includes('export const greeting = "hello world";')); + // Should contain closing fence + assert.ok(md.content.value.includes('```')); + }); + + test('snippet with backticks uses appropriate fence length', () => { + const snippetWithBackticks = 'const code = `nested template literal`;'; + const result = annotateSpecialMarkdownContent([ + content('See '), + { + kind: 'inlineReference', + inlineReference: URI.parse('file:///test.ts'), + name: 'test.ts', + snippet: snippetWithBackticks, + languageId: 'typescript' + }, + ]); + + assert.strictEqual(result.length, 1); + const md = result[0] as IChatMarkdownContent; + const value = md.content.value; + // Should use 3 backticks (minimum fence, since max backtick run is 1: max(3, 1+1) = 3) + assert.ok(value.includes('```typescript')); + assert.ok(value.includes(snippetWithBackticks)); + assert.ok(value.includes('```\n'), 'Should have closing fence with newline'); + }); + + test('snippet with multiple backtick sequences uses longest length', () => { + const snippetWithMultipleBackticks = 'const a = ``; const b = ```; const c = `;'; + const result = annotateSpecialMarkdownContent([ + content('Example: '), + { + kind: 'inlineReference', + inlineReference: URI.parse('file:///multi.ts'), + name: 'multi.ts', + snippet: snippetWithMultipleBackticks, + languageId: 'typescript' + }, + ]); + + assert.strictEqual(result.length, 1); + const md = result[0] as IChatMarkdownContent; + const value = md.content.value; + // Should use 4 backticks (one more than the max run of 3) + assert.ok(value.includes('````typescript')); + assert.ok(value.includes(snippetWithMultipleBackticks)); + assert.ok(value.includes('````')); + }); + + test('snippet without backticks uses standard 3-backtick fence', () => { + const cleanSnippet = 'function hello() {\n console.log("world");\n}'; + const result = annotateSpecialMarkdownContent([ + content('Function: '), + { + kind: 'inlineReference', + inlineReference: URI.parse('file:///func.ts'), + name: 'func.ts', + snippet: cleanSnippet, + languageId: 'typescript' + }, + ]); + + assert.strictEqual(result.length, 1); + const md = result[0] as IChatMarkdownContent; + const value = md.content.value; + // Should use standard 3-backtick fence + assert.ok(value.includes('```typescript')); + assert.ok(value.includes(cleanSnippet)); + // Count occurrences of closing fence + const closeMatches = value.match(/^```$/gm); + assert.ok(closeMatches && closeMatches.length >= 1, 'Should have at least one closing fence'); + }); + + test('snippet with code block markers renders with longer fence', () => { + const snippetWithCodeBlock = 'const desc = "```javascript\ncode\n```";'; + const result = annotateSpecialMarkdownContent([ + content('Code: '), + { + kind: 'inlineReference', + inlineReference: URI.parse('file:///code.ts'), + name: 'code.ts', + snippet: snippetWithCodeBlock, + languageId: 'typescript' + }, + ]); + + assert.strictEqual(result.length, 1); + const md = result[0] as IChatMarkdownContent; + const value = md.content.value; + // Should use 4 backticks to escape the 3-backtick sequence inside + assert.ok(value.includes('````typescript')); + assert.ok(value.includes(snippetWithCodeBlock)); + assert.ok(value.includes('````')); + }); + + test('snippet without language ID still renders with backtick escaping', () => { + const snippetWithBackticks = 'const template = `Hello ${name}`;'; + const result = annotateSpecialMarkdownContent([ + content('Value: '), + { + kind: 'inlineReference', + inlineReference: URI.parse('file:///value.js'), + name: 'value.js', + snippet: snippetWithBackticks + // Note: no languageId provided + }, + ]); + + assert.strictEqual(result.length, 1); + const md = result[0] as IChatMarkdownContent; + const value = md.content.value; + // Should use 3 backticks (minimum fence, since max backtick run is 1) + assert.ok(value.includes('```')); + assert.ok(value.includes(snippetWithBackticks)); + }); + + test('empty snippet still renders correctly', () => { + const result = annotateSpecialMarkdownContent([ + content('Example: '), + { + kind: 'inlineReference', + inlineReference: URI.parse('file:///empty.ts'), + name: 'empty.ts', + snippet: '', + languageId: 'typescript' + }, + ]); + + assert.strictEqual(result.length, 1); + const md = result[0] as IChatMarkdownContent; + const value = md.content.value; + // Should still have fence markers for empty content + assert.ok(value.includes('```typescript')); + assert.ok(value.includes('```')); + }); + }); }); diff --git a/src/vs/workbench/contrib/mcp/common/mcpConfiguration.ts b/src/vs/workbench/contrib/mcp/common/mcpConfiguration.ts index 8b1559c8f8454..2aeb0e4cb8d2b 100644 --- a/src/vs/workbench/contrib/mcp/common/mcpConfiguration.ts +++ b/src/vs/workbench/contrib/mcp/common/mcpConfiguration.ts @@ -66,6 +66,11 @@ export const mcpSchemaExampleServers = { command: 'python', args: ['-m', 'mcp_server_time', '--local-timezone=America/Los_Angeles'], env: {}, + }, + 'toolpipe': { + type: 'stdio', + command: 'npx', + args: ['@cosai-labs/toolpipe-mcp-server'], } }; @@ -73,6 +78,10 @@ const httpSchemaExamples = { 'my-mcp-server': { url: 'http://localhost:3001/mcp', headers: {}, + }, + 'toolpipe-remote': { + type: 'http', + url: 'https://example.com/mcp', } }; diff --git a/src/vs/workbench/contrib/terminal/browser/media/xterm.css b/src/vs/workbench/contrib/terminal/browser/media/xterm.css index 9da68d13f61db..3428c0735e118 100644 --- a/src/vs/workbench/contrib/terminal/browser/media/xterm.css +++ b/src/vs/workbench/contrib/terminal/browser/media/xterm.css @@ -81,8 +81,8 @@ } .xterm .composition-view { - /* TODO: Composition position got messed up somewhere */ - background: #000; + /* Use terminal theme background instead of hardcoded black to prevent black background from showing after spaces */ + background: var(--vscode-terminal-background, var(--vscode-panel-background, #000)); color: #FFF; display: none; position: absolute;