advanced⏱60 minLição 5 de 5
Integração de Provider Personalizado
Integre providers de LLM personalizados com o OpenCode. Aprenda a API de providers, padrões de autenticação, respostas em streaming e como adicionar suporte para novos serviços de IA.
Integração de Provider Personalizado
Arquitetura do Provider
Interface do Provider
Métodos Obrigatórios
typescript
interface LLMProvider {
name: string;
models: string[];
initialize(config: ProviderConfig): Promise<void>;
chat(request: ChatRequest): Promise<ChatResponse>;
stream(request: ChatRequest): AsyncGenerator<ChatChunk>;
validate(apiKey: string): Promise<boolean>;
}Estrutura da Requisição
typescript
interface ChatRequest {
model: string;
messages: Message[];
temperature?: number;
maxTokens?: number;
stream?: boolean;
}
interface Message {
role: "system" | "user" | "assistant";
content: string;
}Estrutura da Resposta
typescript
interface ChatResponse {
content: string;
usage: {
promptTokens: number;
completionTokens: number;
totalTokens: number;
};
model: string;
}Criando um Provider Personalizado
Passo 1: Criar Diretório do Provider
bash
mkdir -p .opencode/providers/my-provider
cd .opencode/providers/my-provider
npm init -yPasso 2: Implementar o Provider
Crie src/index.ts:
typescript
import { LLMProvider, ChatRequest, ChatResponse, ChatChunk } from "opencode";
export default class MyProvider implements LLMProvider {
name = "my-provider";
models = ["my-model-1", "my-model-2"];
private apiKey: string;
private baseUrl: string;
async initialize(config: any) {
this.apiKey = config.apiKey;
this.baseUrl = config.baseUrl || "https://api.my-provider.com";
}
async chat(request: ChatRequest): Promise<ChatResponse> {
const response = await fetch(`${this.baseUrl}/chat/completions`, {
method: "POST",
headers: {
"Authorization": `Bearer ${this.apiKey}`,
"Content-Type": "application/json"
},
body: JSON.stringify({
model: request.model,
messages: request.messages,
temperature: request.temperature,
max_tokens: request.maxTokens
})
});
const data = await response.json();
return {
content: data.choices[0].message.content,
usage: {
promptTokens: data.usage.prompt_tokens,
completionTokens: data.usage.completion_tokens,
totalTokens: data.usage.total_tokens
},
model: request.model
};
}
async *stream(request: ChatRequest): AsyncGenerator<ChatChunk> {
const response = await fetch(`${this.baseUrl}/chat/completions`, {
method: "POST",
headers: {
"Authorization": `Bearer ${this.apiKey}`,
"Content-Type": "application/json"
},
body: JSON.stringify({
model: request.model,
messages: request.messages,
temperature: request.temperature,
max_tokens: request.maxTokens,
stream: true
})
});
const reader = response.body?.getReader();
if (!reader) throw new Error("No response body");
while (true) {
const { done, value } = await reader.read();
if (done) break;
const chunk = new TextDecoder().decode(value);
const lines = chunk.split("\n").filter(line => line.trim());
for (const line of lines) {
if (line.startsWith("data: ")) {
const data = line.slice(6);
if (data === "[DONE]") return;
try {
const parsed = JSON.parse(data);
yield {
content: parsed.choices[0]?.delta?.content || "",
done: false
};
} catch {}
}
}
}
}
async validate(apiKey: string): Promise<boolean> {
try {
const response = await fetch(`${this.baseUrl}/models`, {
headers: {
"Authorization": `Bearer ${apiKey}`
}
});
return response.ok;
} catch {
return false;
}
}
}Passo 3: Registrar o Provider
Adicione ao opencode.json:
json
{
"providers": {
"my-provider": {
"path": ".opencode/providers/my-provider",
"apiKey": "${MY_PROVIDER_API_KEY}",
"baseUrl": "https://api.my-provider.com"
}
}
}Padrões de Autenticação
API Key
json
{
"providers": {
"my-provider": {
"apiKey": "${MY_PROVIDER_API_KEY}"
}
}
}OAuth2
json
{
"providers": {
"my-provider": {
"oauth": {
"clientId": "${CLIENT_ID}",
"clientSecret": "${CLIENT_SECRET}",
"tokenUrl": "https://auth.my-provider.com/token"
}
}
}
}Headers Personalizados
typescript
async chat(request: ChatRequest): Promise<ChatResponse> {
const response = await fetch(this.baseUrl, {
headers: {
"X-API-Key": this.apiKey,
"X-Custom-Header": "value"
}
});
}Respostas em Streaming
Server-Sent Events (SSE)
typescript
async *stream(request: ChatRequest): AsyncGenerator<ChatChunk> {
const response = await fetch(this.baseUrl, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ ...request, stream: true })
});
const reader = response.body!.getReader();
const decoder = new TextDecoder();
while (true) {
const { done, value } = await reader.read();
if (done) break;
const chunk = decoder.decode(value);
const lines = chunk.split("\n");
for (const line of lines) {
if (line.startsWith("data: ")) {
const data = JSON.parse(line.slice(6));
yield { content: data.content, done: false };
}
}
}
}WebSocket
typescript
async *stream(request: ChatRequest): AsyncGenerator<ChatChunk> {
const ws = new WebSocket("wss://api.my-provider.com/stream");
ws.onopen = () => {
ws.send(JSON.stringify(request));
};
while (true) {
const message = await new Promise<any>((resolve) => {
ws.onmessage = (event) => resolve(JSON.parse(event.data));
});
if (message.done) {
ws.close();
return;
}
yield { content: message.content, done: false };
}
}Testando Providers Personalizados
Testes Unitários
typescript
import MyProvider from "../src/index";
describe("MyProvider", () => {
let provider: MyProvider;
beforeEach(() => {
provider = new MyProvider();
provider.initialize({ apiKey: "test-key" });
});
it("should return response", async () => {
const response = await provider.chat({
model: "my-model-1",
messages: [{ role: "user", content: "Hello" }]
});
expect(response.content).toBeDefined();
expect(response.usage.totalTokens).toBeGreaterThan(0);
});
});Testes de Integração
bash
npm testMelhores Práticas
| Prática | Motivo |
|---|---|
| Tratamento de erros | Degradation graciosa |
| Lógica de retry | Lidar com falhas transitórias |
| Tratamento de timeout | Prevenir travamento |
| Suporte a streaming | Melhor experiência do usuário |
| Rastreamento de custos | Gerenciamento de orçamento |
Practice Questions
Practice Question
Quais métodos um provider personalizado deve implementar?
Practice Question
Qual é o benefício das respostas em streaming?
Practice Question
Como você registra um provider personalizado?
Practice Question
O que o método validate() deve fazer?
Practice Question
Para que serve o campo usage na ChatResponse?
Success
Key Takeaways
- Providers personalizados implementam os métodos chat(), stream() e validate()
- Respostas em streaming melhoram a experiência do usuário com saída progressiva
- Registre providers no opencode.json sob a chave providers
- Suporte a múltiplos padrões de autenticação: API key, OAuth2, headers personalizados
- Lidere com erros de forma graciosa com lógica de retry e timeouts
- Rastreie o consumo de tokens para gerenciamento de custos
- Teste providers com testes unitários e de integração
Progresso100%