Awesome-omni-skill prompt-tsx-patterns
Deep dive into prompt-tsx patterns used in vscode-copilot-chat, including component lifecycle, async rendering, priority system, and token budget management
git clone https://github.com/diegosouzapw/awesome-omni-skill
T=$(mktemp -d) && git clone --depth=1 https://github.com/diegosouzapw/awesome-omni-skill "$T" && mkdir -p ~/.claude/skills && cp -r "$T/skills/ai-agents/prompt-tsx-patterns" ~/.claude/skills/diegosouzapw-awesome-omni-skill-prompt-tsx-patterns && rm -rf "$T"
skills/ai-agents/prompt-tsx-patterns/SKILL.mdThis skill provides comprehensive guidance on using prompt-tsx in the vscode-copilot-chat extension. It covers the specific patterns and conventions used in this codebase.
What is Prompt-TSX?
Prompt-TSX is a React-like framework for building AI prompts using TypeScript and JSX. It provides:
- Component-based prompt composition
- Token budget management
- Priority-based pruning
- Type-safe prompt generation
Core Concepts
PromptElement Base Class
All prompt components extend
PromptElement:
import { PromptElement, BasePromptElementProps } from '@vscode/prompt-tsx'; interface MyPromptProps extends BasePromptElementProps { readonly userQuery: string; readonly files?: string[]; } class MyPrompt extends PromptElement<MyPromptProps> { render() { return ( <> <SystemMessage priority={1000}> System instructions here<br /> </SystemMessage> <UserMessage priority={900}> {this.props.userQuery} </UserMessage> </> ); } }
Key Points:
- Props must extend
BasePromptElementProps - Render method returns JSX (PromptPiece)
- Can be sync or async
- Components are classes, not functions
The Line Break Rule
CRITICAL: JSX collapses whitespace and newlines!
// ❌ WRONG - These will be on the same line <SystemMessage priority={1000}> You are a helpful assistant. Please follow these guidelines: 1. Be concise 2. Be accurate </SystemMessage> // ✅ CORRECT - Use <br /> for line breaks <SystemMessage priority={1000}> You are a helpful assistant.<br /> Please follow these guidelines:<br /> 1. Be concise<br /> 2. Be accurate<br /> </SystemMessage>
Why: Prompt-TSX renders to plain text. Without explicit
<br /> tags, all text is concatenated into a single line.
Priority System
Priority controls:
- Rendering order (high priority first)
- Pruning decisions (low priority pruned first when over budget)
Priority Ranges in This Codebase
Based on patterns in
src/extension/prompts/:
// Core instructions - always included const PRIORITY_SYSTEM_INSTRUCTIONS = 1000; // User's current message - highest user priority const PRIORITY_USER_MESSAGE = 900; // Recent conversation - important context const PRIORITY_RECENT_HISTORY = 800; // Conversation history - context but can be pruned const PRIORITY_HISTORY = 700; // User attachments - files, code snippets const PRIORITY_ATTACHMENTS = 600; // Contextual info - workspace, file listings const PRIORITY_CONTEXT = 500; // Documentation, examples - helpful but optional const PRIORITY_BACKGROUND = 100;
Priority Best Practices
- Space by 10s: Use 700, 710, 720 not 700, 701, 702
- Group related content: Similar priority for related pieces
- Consider pruning: What should be removed first?
- Document choices: Comment why you chose a priority
// Good: Clear priority hierarchy <> {/* Critical instructions - never prune */} <SystemMessage priority={1000}>...</SystemMessage> {/* User query - high priority */} <UserMessage priority={900}>...</UserMessage> {/* Recent context - medium priority */} <History priority={700} flexGrow={1} /> {/* Background info - prune first */} <Documentation priority={100} /> </>
Token Budget Management
FlexGrow
flexGrow allows components to expand to fill available token space:
// Component with flexGrow will use remaining tokens <History priority={700} flexGrow={1} // Take all remaining space /> // Multiple flex components share proportionally <> <History priority={700} flexGrow={2} /> // Gets 2/3 of space <Examples priority={500} flexGrow={1} /> // Gets 1/3 of space </>
FlexReserve
flexReserve reserves tokens before rendering:
<History priority={700} flexGrow={1} flexReserve="/5" // Reserve 1/5 (20%) of budget before rendering />
Use cases:
- When you know minimum tokens needed
- Prevent other components from using all space
- Guarantee space for important but flex content
TextChunk for Large Content
TextChunk enables intelligent truncation:
<TextChunk breakOn="\n\n" // Break on paragraph boundaries breakOnWhitespace // Or break on any whitespace priority={500} > {longDocumentation} </TextChunk>
How it works:
- If content fits in budget: rendered fully
- If too large: truncated at break point
- Preserves readability by breaking cleanly
Async Rendering
Components can perform async operations:
class FileContentPrompt extends PromptElement<FileContentProps> { async render() { // Async work happens IN render const content = await this.readFile(this.props.filePath); const metadata = await this.getMetadata(this.props.filePath); return ( <> <SystemMessage priority={1000}> File: {this.props.filePath}<br /> Size: {metadata.size} bytes<br /> </SystemMessage> <TextChunk priority={500} breakOnWhitespace> {content} </TextChunk> </> ); } private async readFile(path: string): Promise<string> { // Implementation } }
Key points:
- Use
for async operationsasync render() - All async work happens in render method
- Don't store promises in state
- Always await before returning JSX
Special Components
Tag
Create XML-like structured content:
<Tag name="context" attrs={{ type: "file", id: "main.ts" }}> {fileContent} </Tag>
Renders as:
<context type="file" id="main.ts"> [fileContent] </context>
Use for:
- Structured data in prompts
- Semantic markup
- Tool result formatting
References
Track variable usage in prompts:
<references value={[new PromptReference({ variableName: 'fileName' })]} />
Purpose: Tell the system which variables are used in the prompt
Meta
Attach metadata that survives pruning:
<meta value={new ToolResultMetadata(toolCallId, result)} />
Purpose: Preserve important metadata even if content is pruned
KeepWith
Keep related content together during pruning:
const KeepWith = useKeepWith(); <> <KeepWith priority={2}> <ToolCallRequest>...</ToolCallRequest> </KeepWith> <KeepWith priority={1}> <ToolCallResponse>...</ToolCallResponse> </KeepWith> </>
Effect: Both elements pruned together, not separately
Patterns from This Codebase
Pattern: System + User Message
render() { return ( <> <SystemMessage priority={1000}> {this.props.systemInstructions} </SystemMessage> <UserMessage priority={900}> {this.props.userQuery} </UserMessage> </> ); }
Pattern: History with Flex
<History priority={700} flexGrow={1} flexReserve="/5" messages={this.props.conversationHistory} />
Pattern: File Context
<FileContext priority={600} flexGrow={2} files={this.props.attachedFiles} />
Pattern: Tool Results
{this.props.toolResults.map((result, i) => ( <ToolResultComponent key={i} priority={850} // Higher than history, lower than user message result={result} /> ))}
Common Mistakes
1. Forgetting Line Breaks
// ❌ Will render on one line <SystemMessage priority={1000}> Line 1 Line 2 </SystemMessage> // ✅ Explicit line breaks <SystemMessage priority={1000}> Line 1<br /> Line 2<br /> </SystemMessage>
2. Priority Conflicts
// ❌ Same priority - unpredictable order <SystemMessage priority={1000}>...</SystemMessage> <AnotherMessage priority={1000}>...</AnotherMessage> // ✅ Different priorities <SystemMessage priority={1000}>...</SystemMessage> <AnotherMessage priority={990}>...</AnotherMessage>
3. Async Without Await
// ❌ Promise not awaited async render() { const data = this.fetchData(); // Returns Promise! return <>{data}</>; // Renders "[object Promise]" } // ✅ Await the promise async render() { const data = await this.fetchData(); return <>{data}</>; }
4. Large Content Without TextChunk
// ❌ Could exceed token budget <UserMessage priority={900}> {hugeDocument} </UserMessage> // ✅ Use TextChunk for intelligent truncation <TextChunk breakOnWhitespace priority={900}> {hugeDocument} </TextChunk>
Testing Prompt Components
Manual Testing
-
Create test props:
const testProps: MyPromptProps = { userQuery: 'test query', files: ['file1.ts', 'file2.ts'] }; -
Instantiate and render:
const prompt = new MyPrompt(testProps); const result = await prompt.render(); -
Inspect output:
- Check priorities are correct
- Verify line breaks appear
- Confirm token usage reasonable
Testing Strategies
- Unit tests: Test component logic
- Integration tests: Test full prompt composition
- Token budget tests: Test with tight budgets
- Priority tests: Verify pruning order
Real Examples from This Codebase
See the
references/ directory for:
- Actual component implementationscomponent-patterns.md
- Priority usage patternspriority-examples.md
- Async rendering examplesasync-rendering.md
These reference files contain real code from this codebase that you can learn from and adapt.
Quick Reference
Must-know rules:
- ✅ Use
for line breaks<br /> - ✅ Props extend
BasePromptElementProps - ✅ Higher priority = rendered first, pruned last
- ✅ Use
for async operationsasync render() - ✅ Use
for large contentTextChunk - ✅ Space priorities by 10s (700, 710, 720)
Common components:
- System instructions<SystemMessage priority={N}>
- User input<UserMessage priority={N}>
- Truncatable content<TextChunk breakOn="...">
- Structured markup<Tag name="..." attrs={{}}>
- Variable tracking<references value={...} />
- Metadata<meta value={...} />
Remember: Prompt-TSX is your interface to the AI. Master it, and you master how the AI sees and understands requests!