diff --git a/chat-prompt-files-sample/src/chatResourceFileSystem.ts b/chat-prompt-files-sample/src/chatResourceFileSystem.ts new file mode 100644 index 00000000..77fa337d --- /dev/null +++ b/chat-prompt-files-sample/src/chatResourceFileSystem.ts @@ -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>(); + private readonly _onDidChangeFile = new vscode.EventEmitter(); + 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; +} diff --git a/chat-prompt-files-sample/src/customAgentProvider.ts b/chat-prompt-files-sample/src/customAgentProvider.ts index a2f2b2dd..eb438d54 100644 --- a/chat-prompt-files-sample/src/customAgentProvider.ts +++ b/chat-prompt-files-sample/src/customAgentProvider.ts @@ -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 { + return [{ uri: agentUri }]; } }; } diff --git a/chat-prompt-files-sample/src/instructionsProvider.ts b/chat-prompt-files-sample/src/instructionsProvider.ts index b319f200..5047fe39 100644 --- a/chat-prompt-files-sample/src/instructionsProvider.ts +++ b/chat-prompt-files-sample/src/instructionsProvider.ts @@ -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 { + return [{ uri: instructionsUri }]; } }; } diff --git a/chat-prompt-files-sample/src/promptFileProvider.ts b/chat-prompt-files-sample/src/promptFileProvider.ts index 7e7884ba..8aeed018 100644 --- a/chat-prompt-files-sample/src/promptFileProvider.ts +++ b/chat-prompt-files-sample/src/promptFileProvider.ts @@ -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 { + return [{ uri: promptUri }]; } }; } diff --git a/chat-prompt-files-sample/src/skillProvider.ts b/chat-prompt-files-sample/src/skillProvider.ts index c904aa26..fc03817d 100644 --- a/chat-prompt-files-sample/src/skillProvider.ts +++ b/chat-prompt-files-sample/src/skillProvider.ts @@ -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 { + return [{ uri: skillUri }]; } }; }