This commit is contained in:
Paul Wang
2026-01-21 10:07:04 -08:00
parent 23b418cdf9
commit d65f1e64c3
5 changed files with 140 additions and 59 deletions

View File

@ -0,0 +1,104 @@
import * as vscode from 'vscode';
/**
* A virtual file system provider for serving dynamically generated chat resource content.
* Resources are registered with a URI and content generator function, allowing
* content to be generated on-demand when VS Code reads the file.
*/
export class ChatResourceFileSystemProvider implements vscode.FileSystemProvider {
static readonly scheme = 'my-chat-resource';
private readonly _contentGenerators = new Map<string, () => string>();
private readonly _onDidChangeFile = new vscode.EventEmitter<vscode.FileChangeEvent[]>();
readonly onDidChangeFile = this._onDidChangeFile.event;
/**
* Register a resource with a content generator function.
* @param path The path portion of the URI (e.g., 'agents/workspace-helper.agent.md')
* @param contentGenerator A function that generates the content on demand
* @returns The full URI for the resource
*/
registerResource(path: string, contentGenerator: () => string): vscode.Uri {
this._contentGenerators.set(path, contentGenerator);
return vscode.Uri.parse(`${ChatResourceFileSystemProvider.scheme}:/${path}`);
}
/**
* Notify that a resource has changed, triggering VS Code to re-read the content.
* @param path The path of the resource that changed
*/
notifyResourceChanged(path: string): void {
const uri = vscode.Uri.parse(`${ChatResourceFileSystemProvider.scheme}:/${path}`);
this._onDidChangeFile.fire([{ type: vscode.FileChangeType.Changed, uri }]);
}
// FileSystemProvider implementation
watch(): vscode.Disposable {
return new vscode.Disposable(() => { });
}
stat(uri: vscode.Uri): vscode.FileStat {
const path = uri.path.substring(1); // Remove leading /
if (!this._contentGenerators.has(path)) {
throw vscode.FileSystemError.FileNotFound(uri);
}
return {
type: vscode.FileType.File,
ctime: Date.now(),
mtime: Date.now(),
size: 0 // Size is not known until content is generated
};
}
readDirectory(): [string, vscode.FileType][] {
return [];
}
createDirectory(): void {
throw vscode.FileSystemError.NoPermissions('Read-only file system');
}
readFile(uri: vscode.Uri): Uint8Array {
const path = uri.path.substring(1); // Remove leading /
const generator = this._contentGenerators.get(path);
if (!generator) {
throw vscode.FileSystemError.FileNotFound(uri);
}
const content = generator();
return new TextEncoder().encode(content);
}
writeFile(): void {
throw vscode.FileSystemError.NoPermissions('Read-only file system');
}
delete(): void {
throw vscode.FileSystemError.NoPermissions('Read-only file system');
}
rename(): void {
throw vscode.FileSystemError.NoPermissions('Read-only file system');
}
}
// Singleton instance
let _instance: ChatResourceFileSystemProvider | undefined;
/**
* Get the singleton ChatResourceFileSystemProvider instance.
* Creates and registers it if it doesn't exist.
*/
export function getChatResourceFileSystem(context: vscode.ExtensionContext): ChatResourceFileSystemProvider {
if (!_instance) {
_instance = new ChatResourceFileSystemProvider();
context.subscriptions.push(
vscode.workspace.registerFileSystemProvider(
ChatResourceFileSystemProvider.scheme,
_instance,
{ isReadonly: true }
)
);
}
return _instance;
}

View File

@ -1,20 +1,15 @@
import * as vscode from 'vscode';
import { getChatResourceFileSystem } from './chatResourceFileSystem';
const AGENT_PATH = 'agents/workspace-helper.agent.md';
export function createCustomAgentProvider(context: vscode.ExtensionContext): vscode.ChatCustomAgentProvider {
const fs = getChatResourceFileSystem(context);
const agentUri = fs.registerResource(AGENT_PATH, generateDynamicAgentContent);
export function createCustomAgentProvider(_context: vscode.ExtensionContext): vscode.CustomAgentProvider {
return {
label: 'Dynamic Workspace Agent Provider',
async provideCustomAgents(_options, _token) {
const agents: vscode.CustomAgentChatResource[] = [];
// Dynamic agent with generated content
const dynamicContent = generateDynamicAgentContent();
agents.push(new vscode.CustomAgentChatResource({
id: 'workspace-helper',
content: dynamicContent
}));
return agents;
async provideCustomAgents(_context, _token): Promise<vscode.ChatResource[]> {
return [{ uri: agentUri }];
}
};
}

View File

@ -1,21 +1,15 @@
import * as vscode from 'vscode';
import { getChatResourceFileSystem } from './chatResourceFileSystem';
const INSTRUCTIONS_PATH = 'instructions/workspace-context.instructions.md';
export function createInstructionsProvider(context: vscode.ExtensionContext): vscode.ChatInstructionsProvider {
const fs = getChatResourceFileSystem(context);
const instructionsUri = fs.registerResource(INSTRUCTIONS_PATH, generateDynamicInstructions);
export function createInstructionsProvider(_context: vscode.ExtensionContext): vscode.InstructionsProvider {
return {
label: 'Dynamic Workspace Instructions Provider',
async provideInstructions(_options, _token) {
const instructions: vscode.InstructionsChatResource[] = [];
// Dynamic instructions with current workspace info
const dynamicContent = generateDynamicInstructions();
instructions.push(
new vscode.InstructionsChatResource({
id: 'workspace-context',
content: dynamicContent
}));
return instructions;
async provideInstructions(_context, _token): Promise<vscode.ChatResource[]> {
return [{ uri: instructionsUri }];
}
};
}

View File

@ -1,22 +1,15 @@
import * as vscode from 'vscode';
import { getChatResourceFileSystem } from './chatResourceFileSystem';
const PROMPT_PATH = 'prompts/time-aware.prompt.md';
export function createPromptFileProvider(context: vscode.ExtensionContext): vscode.ChatPromptFileProvider {
const fs = getChatResourceFileSystem(context);
const promptUri = fs.registerResource(PROMPT_PATH, generateDynamicPrompt);
export function createPromptFileProvider(_context: vscode.ExtensionContext): vscode.PromptFileProvider {
return {
label: 'Dynamic Time-Aware Prompt File Provider',
async providePromptFiles(_options, _token) {
const prompts: vscode.PromptFileChatResource[] = [];
// Dynamic prompt with time-based content
const dynamicContent = generateDynamicPrompt();
prompts.push(
new vscode.PromptFileChatResource({
id: 'time-aware',
content: dynamicContent
})
);
return prompts;
async providePromptFiles(_context, _token): Promise<vscode.ChatResource[]> {
return [{ uri: promptUri }];
}
};
}

View File

@ -1,20 +1,15 @@
import * as vscode from 'vscode';
import { getChatResourceFileSystem } from './chatResourceFileSystem';
const SKILL_PATH = 'skills/workspace-analysis/SKILL.md';
export function createSkillProvider(context: vscode.ExtensionContext): vscode.ChatSkillProvider {
const fs = getChatResourceFileSystem(context);
const skillUri = fs.registerResource(SKILL_PATH, generateDynamicSkillContent);
export function createSkillProvider(_context: vscode.ExtensionContext): vscode.SkillProvider {
return {
label: 'Dynamic Workspace Skills Provider',
async provideSkills(_options, _token) {
const skills: vscode.SkillChatResource[] = [];
// Dynamic skill with generated content
const dynamicContent = generateDynamicSkillContent();
skills.push(new vscode.SkillChatResource({
id: 'workspace-analysis',
content: dynamicContent
}));
return skills;
async provideSkills(_context, _token): Promise<vscode.ChatResource[]> {
return [{ uri: skillUri }];
}
};
}