Skip to main content

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:

  1. Domain model definition
  2. Service layer for business logic
  3. Custom editors (text, form, diagram)
  4. AI agents with domain knowledge
  5. Tool functions for automation
  6. External system integration
  7. 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.