Date: 2025-06-11
Audience: Contributors and developers working with Thea Code
This guide explains the architectural changes implemented in Thea Code and how to work with the new unified MCP-integrated system.
Before (Anthropic-Centric):
// Old: All providers had to work with Anthropic's message format
export class OldProvider implements ApiHandler {
createMessage(systemPrompt: string, messages: Anthropic.Messages.MessageParam[]) {
// Convert from Anthropic format to provider format
const converted = this.convertFromAnthropic(messages)
return this.callProviderAPI(converted)
}
}After (Neutral Format):
// New: All providers work with neutral format
export class NewProvider extends BaseProvider {
createMessage(systemPrompt: string, messages: NeutralConversationHistory) {
// Convert from neutral to provider format using dedicated transform
const converted = neutralToProviderFormat(messages)
return this.callProviderAPI(converted)
}
}Before (Manual Tool Handling):
// Old: Each provider implemented its own tool handling
export class OldProvider {
private handleToolUse(toolCall: any) {
switch (toolCall.name) {
case "read_file":
return this.readFileImplementation(toolCall.params)
case "write_file":
return this.writeFileImplementation(toolCall.params)
// ... more tool implementations
}
}
}After (Automatic MCP Integration):
// New: BaseProvider automatically handles all tools via MCP
export class NewProvider extends BaseProvider {
// No tool handling needed - automatically inherited from BaseProvider
// Tools are registered in constructor via this.registerTools()
// Tool execution is handled by McpIntegration
}To create a new provider, extend BaseProvider:
import { BaseProvider } from "./base-provider"
import { NeutralConversationHistory } from "../../shared/neutral-history"
import { ApiStream } from "../transform/stream"
import { ModelInfo } from "../../shared/api"
export class MyNewProvider extends BaseProvider {
constructor(private options: MyProviderOptions) {
super() // This automatically sets up MCP integration
}
async createMessage(systemPrompt: string, messages: NeutralConversationHistory): Promise<ApiStream> {
// 1. Convert neutral format to your provider's format
const providerMessages = neutralToMyProviderFormat(messages, systemPrompt)
// 2. Make API call
const response = await this.callMyProviderAPI(providerMessages)
// 3. Return as ApiStream
return new ApiStream(response)
}
getModel(): { id: string; info: ModelInfo } {
return {
id: this.options.modelId,
info: {
maxTokens: 100000,
contextWindow: 100000,
supportsImages: true,
supportsPromptCache: false,
inputPrice: 0.01,
outputPrice: 0.03,
},
}
}
}Create dedicated transform files for format conversion:
// src/api/transform/neutral-myprovider-format.ts
import { NeutralConversationHistory, NeutralMessage } from "../../shared/neutral-history"
export function neutralToMyProviderFormat(
messages: NeutralConversationHistory,
systemPrompt: string,
): MyProviderMessage[] {
const result: MyProviderMessage[] = []
if (systemPrompt) {
result.push({ role: "system", content: systemPrompt })
}
for (const message of messages) {
result.push({
role: message.role,
content: convertContent(message.content),
})
}
return result
}
function convertContent(content: NeutralMessageContent): string {
if (typeof content === "string") {
return content
}
// Handle block format
return content
.map((block) => {
if (block.type === "text") {
return block.text
}
// Handle other block types...
})
.join("")
}If your provider uses an existing protocol, extend the appropriate handler:
// Example: Provider that uses OpenAI protocol
export class MyOpenAICompatibleProvider extends OpenAiHandler {
constructor(options: MyProviderOptions) {
super({
...options,
baseUrl: "https://my-provider-api.com/v1",
})
}
// Override only what's different
protected getApiKey(): string {
return this.options.myProviderApiKey
}
}Add tools by registering them in the registerTools() method:
export class MyProvider extends BaseProvider {
protected registerTools(): void {
// Call parent to get standard tools
super.registerTools()
// Add custom tools
this.mcpIntegration.registerTool({
name: "analyze_performance",
description: "Analyze code performance metrics",
paramSchema: {
type: "object",
properties: {
file_path: {
type: "string",
description: "Path to the file to analyze",
},
metrics: {
type: "array",
items: { type: "string" },
description: "Performance metrics to check",
},
},
required: ["file_path"],
},
})
}
}Tools are automatically executed by the MCP system. The format conversion is handled transparently:
- XML format:
<analyze_performance><file_path>src/app.ts</file_path></analyze_performance> - JSON format:
{"type": "tool_use", "name": "analyze_performance", "input": {"file_path": "src/app.ts"}} - OpenAI format: Function calls in OpenAI's schema
import { MyNewProvider } from "../my-new-provider"
describe("MyNewProvider", () => {
test("should create messages correctly", async () => {
const provider = new MyNewProvider(testOptions)
const messages = [{ role: "user", content: "Hello" }]
const stream = await provider.createMessage("System prompt", messages)
expect(stream).toBeDefined()
})
test("should have MCP integration", () => {
const provider = new MyNewProvider(testOptions)
expect(provider["mcpIntegration"]).toBeDefined()
})
})test("should register tools automatically", async () => {
const provider = new MyNewProvider(testOptions)
await provider["mcpIntegration"].initialize()
// Tools should be registered
const tools = provider["mcpIntegration"].getRegisteredTools()
expect(tools).toContain("read_file")
expect(tools).toContain("write_file")
})When migrating existing code or creating new providers:
- Extend
BaseProviderinstead of implementingApiHandlerdirectly - Use
NeutralConversationHistoryinstead of provider-specific message formats - Create dedicated transform files for format conversion
- Remove manual tool handling code - use MCP integration
- Register custom tools in the
registerTools()method - Test tool integration works with different formats (XML/JSON/OpenAI)
- Update any existing tests to work with the new architecture
✅ Reduced Code Duplication: Common functionality is in BaseProvider
✅ Consistent Tool Support: All providers automatically support all tools
✅ Format Flexibility: Support for XML, JSON, and OpenAI function calling
✅ Easier Testing: Standardized interfaces and behavior
✅ Better Maintainability: Centralized tool logic and format conversion
✅ Future-Proof: Easy to add new providers and tools
- Check existing providers for implementation examples
- Look at test files for usage patterns
- Review the MCP comprehensive guide for tool development
- Ask questions in GitHub discussions
For more detailed technical information, see: