Customization & Domain-Specific Tools
Overview
theia's architecture enables building fully customized, domain-specific development tools tailored to your specific workflows, data models, and user needs.
Why Custom Tools?
Specialized Tools Definition
A specialized tool is an application that helps domain engineers/experts with their daily tasks by providing:
- Intuitive interfaces for complex data models
- Domain-specific languages and editors
- Specialized analysis tools
- Custom code generators
- Workflow automation
- Integration with domain systems
Benefits
For Domain Experts:
- Work with familiar concepts and terminology
- Reduced cognitive load
- Faster task completion
- Fewer errors
- Better collaboration
For Organizations:
- Enforce standards and best practices
- Capture domain knowledge
- Increase productivity
- Reduce training time
- Competitive advantage
Architecture Patterns
Layered Approach
┌─────────────────────────────────────┐
│ Domain-Specific Features │ ← Your custom code
│ - Custom editors │
│ - Domain models │
│ - AI agents │
│ - Workflows │
├─────────────────────────────────────┤
│ theia Platform Extensions │ ← Reuse/customize
│ - File explorer │
│ - editor │
│ - terminal │
│ - Git │
├─────────────────────────────────────┤
│ theia Core Framework │ ← Foundation
│ - Extension system │
│ - Dependency injection │
│ - UI framework │
└─────────────────────────────────────┘
Component Structure
// Domain-specific tool structure
my-domain-tool/
├── packages/
│ ├── core/ // Domain logic
│ │ ├── domain-model.ts
│ │ ├── validator.ts
│ │ └── services.ts
│ ├── ui/ // Custom editors/views
│ │ ├── diagram-editor/
│ │ ├── form-editor/
│ │ └── tree-view/
│ ├── ai/ // AI integration
│ │ ├── agents/
│ │ ├── tools/
│ │ └── prompts/
│ └── integration/ // External systems
│ ├── api-client.ts
│ └── data-sync.ts
├── browser-app/ // Browser deployment
└── electron-app/ // Desktop deployment
Domain Model Integration
Defining Domain Models
// Example: Task flow modeling tool
export interface Task {
id: string;
name: string;
description: string;
inputs: Input[];
outputs: Output[];
estimatedDuration: number;
dependencies: string[];
}
export interface Node {
id: string;
name: string;
capabilities: string[];
capacity: number;
tasks: string[];
}
export interface TaskFlow {
id: string;
name: string;
tasks: Task[];
nodes: Node[];
allocations: Map<string, string>; // task -> node
}
Storage Patterns
File-Based (JSON/YAML)
@injectable()
export class FileBasedModelService {
@inject(FileService)
protected fileService: FileService;
async saveTas(task: Task): Promise<void> {
const path = `${WORKSPACE}/tasks/${task.id}.json`;
await this.fileService.write(path, JSON.stringify(task, null, 2));
}
async loadTask(id: string): Promise<Task> {
const path = `${WORKSPACE}/tasks/${id}.json`;
const content = await this.fileService.read(path);
return JSON.parse(content);
}
}
Database-Backed
@injectable()
export class DatabaseModelService {
@inject(DatabaseClient)
protected db: DatabaseClient;
async saveTask(task: Task): Promise<void> {
await this.db.tasks.upsert({
where: { id: task.id },
data: task
});
}
async queryTasks(filter: TaskFilter): Promise<Task[]> {
return this.db.tasks.findMany({
where: this.buildFilter(filter)
});
}
}
Custom editors
Text-Based editors
@injectable()
export class DomainDSLeditor extends Monacoeditor {
// Add custom language support
protected override configureMonaco(editor: monaco.editor.IStandaloneCodeeditor) {
super.configureMonaco(editor);
// Register language
monaco.languages.register({ id: 'domain-dsl' });
// Add syntax highlighting
monaco.languages.setMonarchTokensProvider('domain-dsl', {
tokenizer: {
root: [
[/\b(task|node|flow)\b/, 'keyword'],
[/\b(input|output|capability)\b/, 'type'],
// ... more rules
]
}
});
// Add completion
monaco.languages.registerCompletionItemProvider('domain-dsl', {
provideCompletionItems: (model, position) => {
return {
suggestions: this.getDomainSuggestions(model, position)
};
}
});
}
}
Form-Based editors
@injectable()
export class TaskFormeditor extends ReactWidget {
protected render(): React.ReactNode {
return (
<TaskForm
task={this.state.task}
onChange={task => this.updateTask(task)}
onValidate={task => this.validateTask(task)}
/>
);
}
}
// React component
export const TaskForm: React.FC<TaskFormProps> = ({ task, onChange }) => {
return (
<form>
<TextField
label="Task Name"
value={task.name}
onChange={e => onChange({ ...task, name: e.target.value })}
/>
<TextField
label="Description"
multiline
rows={4}
value={task.description}
onChange={e => onChange({ ...task, description: e.target.value })}
/>
<ArrayField
label="Inputs"
items={task.inputs}
onChange={inputs => onChange({ ...task, inputs })}
renderItem={(input, index) => (
<InputField input={input} onChange={i => updateInput(index, i)} />
)}
/>
</form>
);
};
Diagram editors
// Using libraries like:
// - Sprotty (diagrams)
// - Eclipse GLSP (graphical editors)
// - D3.js (visualizations)
@injectable()
export class TaskFlowDiagrameditor extends ReactWidget {
protected render(): React.ReactNode {
return (
<SprottyDiagram
model={this.toSprottyModel(this.state.taskFlow)}
onModelChange={model => this.updateTaskFlow(model)}
/>
);
}
protected toSprottyModel(flow: TaskFlow): SGraph {
return {
type: 'graph',
id: flow.id,
children: [
...flow.tasks.map(task => ({
type: 'node:task',
id: task.id,
position: this.getPosition(task),
size: { width: 100, height: 60 },
data: task
})),
...this.createEdges(flow)
]
};
}
}
AI Integration for Domain Tools
Domain-Specific Agents
@injectable()
export class TaskFlowAgent extends AbstractStreamParsingAgent {
override async getSystemMessageTemplate(): Promise<string> {
return `
You are an AI assistant for the Task Flow Modeling tool.
Domain Model:
- Tasks: Units of work with inputs/outputs
- Nodes: Execution resources with capabilities
- Allocations: Mapping tasks to nodes
Your capabilities:
${this.listToolFunctions()}
Help users:
- Create and modify tasks
- Design task flows
- Optimize allocations
- Answer questions about the model
- Validate configurations
`;
}
}
Domain Tool Functions
@injectable()
export class CreateTaskTool implements ToolProvider {
@inject(ModelService)
protected modelService: ModelService;
getTool(): Tool {
return {
name: 'create_task',
description: 'Create a new task in the workflow',
parameters: {
type: 'object',
properties: {
name: { type: 'string' },
description: { type: 'string' },
inputs: {
type: 'array',
items: {
type: 'object',
properties: {
name: { type: 'string' },
type: { type: 'string' }
}
}
},
outputs: {
type: 'array',
items: {
type: 'object',
properties: {
name: { type: 'string' },
type: { type: 'string' }
}
}
}
},
required: ['name']
},
handler: async (params) => {
const task: Task = {
id: generateId(),
name: params.name,
description: params.description || '',
inputs: params.inputs || [],
outputs: params.outputs || [],
estimatedDuration: 0,
dependencies: []
};
await this.modelService.saveTask(task);
// Open in editor
await this.editorManager.open(
new URI(`task://${task.id}`),
{ mode: 'activate' }
);
return {
success: true,
taskId: task.id,
message: `Created task: ${task.name}`
};
}
};
}
}
Visual Response Rendering
// Custom content renderer for diagrams
@injectable()
export class DiagramContentRenderer implements ChatResponsePartRenderer {
canHandle(response: ResponseContent): number {
return response.type === 'domain-diagram' ? 100 : 0;
}
render(response: DiagramContent): React.ReactNode {
return (
<DiagramViewer
data={response.diagramData}
type={response.diagramType}
interactive={true}
/>
);
}
}
// In agent implementation
export class TaskFlowAgent extends AbstractStreamParsingAgent {
protected override getContentMatches(): ContentMatch[] {
return [
{
start: /<diagram type="([^"]+)">/,
end: /<\/diagram>/,
contentFactory: (match) => {
return new DiagramContent(
match[1], // diagram type
match[2] // diagram data
);
}
}
];
}
}
Workflow Automation
Command Automation
@injectable()
export class WorkflowCommands implements CommandContribution {
registerCommands(commands: CommandRegistry): void {
commands.registerCommand(
{
id: 'workflow.generate-from-template',
label: 'Generate Workflow from Template'
},
{
execute: async () => {
const template = await this.selectTemplate();
const params = await this.promptForParameters(template);
const workflow = await this.generateWorkflow(template, params);
await this.openeditor(workflow);
}
}
);
commands.registerCommand(
{
id: 'workflow.validate',
label: 'Validate Current Workflow'
},
{
execute: async () => {
const workflow = await this.getCurrentWorkflow();
const errors = await this.validator.validate(workflow);
await this.showValidationResults(errors);
}
}
);
}
}
Task Automation
@injectable()
export class TaskAutomationService {
// Auto-save on changes
@postConstruct()
protected init(): void {
this.modelService.onModelChanged(async (model) => {
await this.autoSave(model);
});
}
// Background validation
async startBackgroundValidation(): Promise<void> {
setInterval(async () => {
const models = await this.modelService.getAllModels();
for (const model of models) {
const issues = await this.validator.validate(model);
if (issues.length > 0) {
this.notificationService.warn(
`Found ${issues.length} issues in ${model.name}`
);
}
}
}, 60000); // Every minute
}
// Auto-completion
async suggestCompletions(partial: Partial<Task>): Promise<Task[]> {
// Use AI to suggest completions
return this.aiService.complete('task', partial);
}
}
Code Generation
Template-Based Generation
@injectable()
export class CodeGenerator {
async generateFromModel(model: TaskFlow): Promise<GeneratedCode> {
const files: GeneratedFile[] = [];
// Generate task implementations
for (const task of model.tasks) {
files.push({
path: `src/tasks/${task.id}.ts`,
content: this.generateTaskImpl(task)
});
}
// Generate orchestration code
files.push({
path: 'src/orchestrator.ts',
content: this.generateOrchestrator(model)
});
// Generate configuration
files.push({
path: 'config/workflow.json',
content: JSON.stringify(model, null, 2)
});
return { files };
}
private generateTaskImpl(task: Task): string {
return `
export class ${toPascalCase(task.name)}Task implements Task {
async execute(inputs: {
${task.inputs.map(i => `${i.name}: ${i.type}`).join(',\n ')}
}): Promise<{
${task.outputs.map(o => `${o.name}: ${o.type}`).join(',\n ')}
}> {
// TODO: Implement task logic
throw new Error('Not implemented');
}
}
`.trim();
}
}
AI-Powered Generation
@injectable()
export class AICodeGenerator {
@inject(llmService)
protected llm: llmService;
async generateImplementation(task: Task): Promise<string> {
const prompt = `
Generate TypeScript implementation for this task:
Name: ${task.name}
Description: ${task.description}
Inputs:
${task.inputs.map(i => `- ${i.name}: ${i.type}`).join('\n')}
Outputs:
${task.outputs.map(o => `- ${o.name}: ${o.type}`).join('\n')}
Requirements:
- Include error handling
- Add input validation
- Include type definitions
- Add JSDoc comments
`;
const response = await this.llm.complete(prompt);
return response.code;
}
}
External System Integration
API Integration
@injectable()
export class ExternalAPIService {
private client: APIClient;
async syncToExternalSystem(model: TaskFlow): Promise<void> {
// Transform to external format
const externalFormat = this.transformToExternal(model);
// Send to API
await this.client.post('/workflows', externalFormat);
}
async importFromExternalSystem(id: string): Promise<TaskFlow> {
const data = await this.client.get(`/workflows/${id}`);
return this.transformFromExternal(data);
}
// Real-time sync
async enableLiveSync(model: TaskFlow): Promise<void> {
const ws = await this.client.connectWebSocket(`/workflows/${model.id}/sync`);
ws.on('update', async (update) => {
await this.modelService.applyUpdate(model.id, update);
});
this.modelService.onModelChanged(async (changedModel) => {
if (changedModel.id === model.id) {
ws.send({ type: 'update', data: changedModel });
}
});
}
}
Database Integration
@injectable()
export class DatabaseIntegrationService {
@inject(DatabaseClient)
protected db: DatabaseClient;
// Bidirectional sync
async startSync(): Promise<void> {
// Watch for changes in theia
this.modelService.onModelChanged(async (model) => {
await this.db.upsertModel(model);
});
// Watch for changes in database
this.db.watchChanges('models', async (change) => {
if (change.type === 'update') {
await this.modelService.updateModel(change.id, change.data);
}
});
}
// Query support
async query(filter: ModelFilter): Promise<Model[]> {
return this.db.query({
collection: 'models',
where: this.buildFilter(filter),
orderBy: { createdAt: 'desc' }
});
}
}
Building a Complete Custom Tool
Example: Task Flow Designer
// 1. Define domain model
export interface TaskFlowModel {
tasks: Task[];
nodes: Node[];
allocations: Allocation[];
}
// 2. Create service layer
@injectable()
export class TaskFlowService {
@inject(FileService)
protected fileService: FileService;
async loadModel(uri: string): Promise<TaskFlowModel> {
const content = await this.fileService.read(uri);
return JSON.parse(content);
}
async saveModel(uri: string, model: TaskFlowModel): Promise<void> {
await this.fileService.write(uri, JSON.stringify(model, null, 2));
}
async validate(model: TaskFlowModel): Promise<ValidationError[]> {
// Validation logic
return [];
}
}
// 3. Create custom editor
@injectable()
export class TaskFloweditor extends ReactWidget {
@inject(TaskFlowService)
protected service: TaskFlowService;
protected render(): React.ReactNode {
return (
<TaskFlowDesigner
model={this.state.model}
onChange={model => this.updateModel(model)}
onValidate={() => this.validate()}
/>
);
}
}
// 4. Register editor
@injectable()
export class TaskFloweditorContribution implements OpenHandler {
canHandle(uri: URI): number {
return uri.path.ext === '.taskflow' ? 500 : 0;
}
async open(uri: URI): Promise<TaskFloweditor> {
const editor = this.widgetManager.getOrCreateWidget(
TaskFloweditor.ID,
{ uri }
);
await this.shell.addWidget(editor, { area: 'main' });
return editor;
}
}
// 5. Add AI agent
@injectable()
export class TaskFlowAIAgent extends AbstractStreamParsingAgent {
@inject(TaskFlowService)
protected service: TaskFlowService;
override async getSystemMessageTemplate(): Promise<string> {
return `
You are an AI assistant for task flow design.
Available tools:
- create_task(name, inputs, outputs)
- create_node(name, capabilities)
- allocate_task(taskId, nodeId)
- optimize_allocation()
- validate_flow()
Help users design efficient task flows.
`;
}
}
// 6. Register everything
export default new ContainerModule(bind => {
// Services
bind(TaskFlowService).toSelf().inSingletonScope();
// editor
bind(TaskFloweditor).toSelf();
bind(OpenHandler).to(TaskFloweditorContribution);
// AI
bind(Agent).to(TaskFlowAIAgent);
bind(ToolProvider).to(CreateTaskTool);
bind(ToolProvider).to(AllocateTaskTool);
});
Deployment Strategies
Standalone Application
{
"name": "task-flow-designer",
"version": "1.0.0",
"dependencies": {
"@theia/core": "^1.x",
"@theia/ai-core": "^1.x",
"task-flow-core": "workspace:*",
"task-flow-ui": "workspace:*",
"task-flow-ai": "workspace:*"
},
"theia": {
"target": "electron",
"frontend": {
"config": {
"applicationName": "Task Flow Designer"
}
}
}
}
Cloud-Hosted
FROM node:18
WORKDIR /app
# Install dependencies
COPY package*.json ./
RUN npm install
# Build application
COPY . .
RUN npm run build
# Expose port
EXPOSE 3000
CMD ["npm", "start"]
# kubernetes deployment
apiVersion: apps/v1
kind: Deployment
metadata:
name: task-flow-designer
spec:
replicas: 3
template:
spec:
containers:
- name: app
image: task-flow-designer:latest
ports:
- containerPort: 3000
env:
- name: DATABASE_URL
valueFrom:
secretKeyRef:
name: db-secrets
key: url
Plugin/Extension
{
"name": "task-flow-extension",
"publisher": "your-company",
"version": "1.0.0",
"engines": {
"theia": "^1.x"
},
"contributes": {
"editors": [{
"extension": ".taskflow",
"editorId": "task-flow-editor"
}],
"commands": [{
"command": "taskflow.new",
"title": "New Task Flow"
}]
}
}
Best Practices
1. Separation of Concerns
- Domain logic separate from UI
- Services separate from editors
- AI integration separate from core functionality
2. Extensibility
- Use contribution points
- Provide APIs for extensions
- Document extension points
3. Performance
- Lazy loading of heavy components
- Virtual scrolling for large lists
- Debounce/throttle updates
- Cache computed values
4. User Experience
- Keyboard shortcuts
- Context menus
- Drag and drop
- Undo/redo support
- Progressive disclosure
5. Testing
describe('TaskFlowService', () => {
let service: TaskFlowService;
beforeEach(() => {
service = createTestService();
});
it('should create valid task', async () => {
const task = await service.createTask({
name: 'Test Task',
inputs: [],
outputs: []
});
expect(task.id).toBeDefined();
expect(task.name).toBe('Test Task');
});
it('should validate task flow', async () => {
const flow = createTestFlow();
const errors = await service.validate(flow);
expect(errors).toHaveLength(0);
});
});
Summary
Building domain-specific tools with theia:
✅ Full control over UI and functionality ✅ Reuse theia platform capabilities ✅ Integrate AI naturally into workflows ✅ Deploy as desktop or web application ✅ Extend with custom editors and views ✅ Connect to external systems seamlessly
Key components:
- Domain model definition
- Service layer for business logic
- Custom editors (text, form, diagram)
- AI agents with domain knowledge
- Tool functions for automation
- External system integration
- Deployment configuration
This approach enables creating powerful, specialized tools that dramatically improve productivity for domain experts while maintaining the flexibility and openness of the theia platform.