Adding a New LLM Provider: The Hidden TypeScript Traps You Need to Know
Integrating Moonshot AI's Kimi K2 into our platform revealed the sneaky places where TypeScript types can hide—and why a simple grep saved the day.
Adding a New LLM Provider: The Hidden TypeScript Traps You Need to Know
Last week, I embarked on what seemed like a straightforward task: integrating Moonshot AI's Kimi K2 model as a new LLM provider in our prompt-building platform. What started as a "quick integration" turned into a fascinating deep dive into the hidden corners where TypeScript types love to lurk.
The Mission: Kimi K2 Integration
The goal was simple enough—add support for Kimi K2 alongside our existing providers like OpenAI and Anthropic. This meant:
- Creating a new provider adapter
- Updating type definitions
- Wiring everything through our service registry
- Adding UI controls for the new provider
The Implementation Journey
Building the Foundation
First up was creating the core adapter in src/server/services/llm/adapters/kimi.ts. Like our other providers, the KimiProvider needed to implement three key methods:
class KimiProvider implements LLMProvider {
async complete(params: LLMParams): Promise<LLMResponse>
async *stream(params: LLMParams): AsyncGenerator<LLMStreamChunk>
async isAvailable(): Promise<boolean>
}
The beauty of having a well-defined interface meant the actual implementation was straightforward—just adapting Moonshot's API format to our internal structure.
Type System Updates
Next came updating our type definitions. This seemed like the easy part:
// src/server/services/llm/types.ts
const llmProviderSchema = z.enum([
"openai",
"anthropic",
"kimi" // Added this
]);
I also added the cost rates for the kimi-k2-0711 model and wired the provider into our registry system.
The UI Layer
The frontend updates were equally straightforward—adding "Kimi K2" to dropdown options and updating local type definitions. Everything compiled cleanly, tests were passing, and I was feeling pretty good about the integration.
The Plot Twist: When TypeScript Fights Back
Just when I thought I was done, TypeScript threw me a curveball. Despite updating what I thought were all the relevant types, I was still getting compilation errors about compareProviders not including "kimi".
Here's where it got interesting. I had updated the obvious places:
- The main constants file
- The workflow creation page
- The core type definitions
But TypeScript kept complaining. The errors were pointing to some tRPC router code that I hadn't touched.
Lessons Learned: The Hidden Type Dependencies
After some detective work, I discovered the culprit: hardcoded Zod enums in unexpected places.
The workflows tRPC router (src/server/trpc/routers/workflows.ts) had not one, but two additional compareProviders enums that I had completely missed:
// Line 50 - Workflow creation schema
compareProviders: z.array(z.enum(["openai", "anthropic"])).optional(),
// Line 99 - Workflow update schema
compareProviders: z.array(z.enum(["openai", "anthropic"])).optional(),
These weren't referencing the main schema—they were standalone enums that needed manual updates.
The Grep Solution
This experience taught me a valuable lesson: when adding a new provider, grep is your friend.
grep -r "compareProviders" src/
This revealed all four locations that needed updates:
- Main constants file
- Workflow creation page (frontend)
- tRPC router - creation schema
- tRPC router - update schema
The Testing Safety Net
One thing that saved me throughout this process was comprehensive testing. I created a full test suite for the Kimi provider with 9 unit tests covering:
- Successful API responses
- Error handling
- Stream processing
- Rate limiting scenarios
Having that safety net meant I could refactor and update with confidence, knowing that any breaking changes would be caught immediately.
Key Takeaways for LLM Integrations
- Grep for hardcoded enums: Don't assume all type references go through a central schema
- Test early and often: Provider integrations have many moving parts—comprehensive tests catch edge cases
- Follow the data flow: Trace how your new provider type flows from API to UI to database
- Document the integration points: Future you (and your teammates) will thank you
The Final Result
After navigating these TypeScript traps, the Kimi K2 integration was complete and robust. The full test suite showed 80/80 passing tests, TypeScript compilation was clean, and we had a new powerful LLM option for our users.
Sometimes the most educational development sessions are the ones where everything almost works perfectly. Those hidden gotchas—like scattered Zod enums—are exactly the kind of tribal knowledge that makes experienced developers valuable.
Next time you're integrating a new provider or service, remember: the type system is your friend, but it's also really good at hide-and-seek. A well-placed grep command might just save your afternoon.
Have you run into similar TypeScript surprises in your integrations? I'd love to hear about your experiences in the comments below.