T21 — Multi-provider SDK lazy loading
BUNDLED_PROVIDERS: Record Promise> — 20+ provider, mỗi cái là 1 lazy lambda. Chỉ SDK của provider đang dùng được load. User pick Anthropic → chỉ Anthropic SDK.Tổng quan Provider
Tại sao quan trọng. opencode hỗ trợ 20+ provider: Anthropic, OpenAI, Google Gemini, AWS Bedrock, Mistral, Groq, Together, Ollama... Mỗi SDK có size 0.5–5MB. Import eager tất cả → bundle ~50MB+, startup 2–5 giây. Dynamic import giải quyết: mỗi provider là 1 lambda
() => import("./loaders/anthropic"). Chỉ lambda được defined lúc bundle — SDK thật sự chỉ load khi user chọn provider đó.
Vercel AI SDK underneath: opencode dùng Vercel AI SDK (
ai package) làm abstraction layer. Provider loaders adapter Anthropic SDK, OpenAI SDK... vào unified Vercel AI interface — core logic không biết đang dùng provider nào.
Phân tích code chi tiết Anatomy
BUNDLED_PROVIDERS map — lazy lambdas
provider/provider.ts
TS
{`
// Mỗi entry = 1 lambda trả Promise<Loader>
// Lambda được define lúc bundle — SDK chỉ import khi lambda được gọi
const BUNDLED_PROVIDERS: Record<string, () => Promise<Loader>> = {
anthropic: () => import("./loaders/anthropic").then((m) => m.default),
openai: () => import("./loaders/openai").then((m) => m.default),
google: () => import("./loaders/google").then((m) => m.default),
bedrock: () => import("./loaders/bedrock").then((m) => m.default),
mistral: () => import("./loaders/mistral").then((m) => m.default),
groq: () => import("./loaders/groq").then((m) => m.default),
together: () => import("./loaders/together").then((m) => m.default),
ollama: () => import("./loaders/ollama").then((m) => m.default),
// ... 15+ more
}
export async function loadProvider(id: string): Promise<Loader> {
const loader = BUNDLED_PROVIDERS[id]
if (!loader) throw new UnknownProviderError(id)
return await loader() // ← import chỉ xảy ra ở đây
}
`}Provider Loader interface — unified abstraction
provider/loader.ts — Loader interface
TS
{`
export interface Loader {
// Tạo Vercel AI compatible provider instance
create(config: ProviderConfig): LanguageModel
// Danh sách models hỗ trợ (cho UI model picker)
models(): ModelInfo[]
// Auth flow nếu cần (vd Anthropic API key)
auth?: AuthConfig
}
// Anthropic loader — adapter sang Vercel AI interface
// provider/loaders/anthropic.ts
export default {
create(config) {
const { createAnthropic } = await import("@ai-sdk/anthropic")
const anthropic = createAnthropic({ apiKey: config.apiKey })
return anthropic(config.model) // returns LanguageModel
},
models: () => ANTHROPIC_MODELS,
} satisfies Loader
`}
User config: provider = "anthropic", model = "claude-opus-4"
│
▼
loadProvider("anthropic")
│
├── BUNDLED_PROVIDERS["anthropic"] ← look up lambda (no import yet)
│
└── lambda() ← trigger import NOW
│
▼
import("./loaders/anthropic") ← load anthropic loader
│
▼
loader.create({ apiKey, model })
│
▼
import("@ai-sdk/anthropic") ← load actual SDK
│
▼
LanguageModel (Vercel AI interface)
│
▼
AI SDK streamText(model, messages) ← unified API
Tương tác với kỹ thuật khác Interaction
- T22 (Provider transform): Sau khi load provider, messages phải đi qua transform layer trước khi gửi — mỗi provider có quirks riêng trong message format.
- T23 (Overflow + retry): Provider loaders cũng wrap call với error handling — overflow detection và retry xảy ra ở tầng trên loader, không phải trong loader.
- T26 (Cost matrix): Mỗi loader có thể expose cost information (input/output token price) → cost-aware routing có thể chọn provider rẻ hơn cho task đơn giản.
- T10 (Cache-aware prompt): Anthropic loader cần xử lý cache headers đặc biệt —
anthropic-beta: prompt-caching-2024-07-31. Google Gemini, OpenAI không có equivalent.
Failure modes Failure
1. First-call latency
Lần đầu gọi provider có thêm latency do dynamic import (~50–200ms tùy SDK size). Lần sau cache module. Với interactive CLI, latency 200ms cảm nhận được.
Mitigation: Preload provider config khi app khởi động — không cần load SDK ngay, nhưng validate config và kiểm tra API key sớm để fail fast thay vì fail tại lần gọi đầu tiên.
2. Loader load error chỉ xuất hiện runtime
Nếu ./loaders/anthropic.ts có syntax error hoặc import lỗi, chỉ phát hiện khi user thật sự dùng Anthropic. TypeScript không check dynamic import path at compile time.
3. Provider update phá API
Khi @ai-sdk/anthropic update major version, loader phải update. Nếu miss, dynamic import pass (file tồn tại) nhưng runtime fail khi gọi API.
So sánh với các harness khác Compare
| Harness | Provider support | Lazy loading | Abstraction layer |
|---|---|---|---|
| opencode | 20+ providers, dynamic import map | ✅ per-provider lazy | Vercel AI SDK |
| Claude Code | Anthropic only (native) | N/A | Anthropic SDK direct |
| Aider | 30+ providers via litellm | ✅ litellm lazy | litellm abstraction |
| Cline | Anthropic + OpenAI + custom | ⚠️ partial | Direct SDK calls |
| OpenHarness | Anthropic + OpenAI (pluggable) | ⚠️ partial | Anthropic/OpenAI SDK |
Implementation recipe Recipe
TS
{`
// Multi-provider lazy loading pattern
interface ProviderLoader {
create(config: { apiKey: string; model: string }): LanguageModel
}
// Registry = map of lazy lambdas
const PROVIDERS: Record<string, () => Promise<ProviderLoader>> = {
anthropic: () => import("./providers/anthropic"),
openai: () => import("./providers/openai"),
google: () => import("./providers/google"),
}
// Cache loaded providers (avoid re-import per call)
const cache = new Map<string, ProviderLoader>()
export async function getProvider(id: string): Promise<ProviderLoader> {
if (cache.has(id)) return cache.get(id)!
const factory = PROVIDERS[id]
if (!factory) throw new Error(`Unknown provider: ${id}`)
const loader = (await factory()).default
cache.set(id, loader)
return loader
}
// Usage:
const provider = await getProvider("anthropic")
const model = provider.create({ apiKey: process.env.ANTHROPIC_KEY!, model: "claude-opus-4" })
// model is now a unified LanguageModel interface
`}