Integración de Proveedor Personalizado
Integra proveedores LLM personalizados con OpenCode. Aprende la API del proveedor, patrones de autenticación, respuestas en streaming y cómo agregar soporte para nuevos servicios de IA.
Arquitectura del Proveedor
Interfaz del Proveedor
Métodos Requeridos
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>;
}Estructura de Solicitud
interface ChatRequest {
model: string;
messages: Message[];
temperature?: number;
maxTokens?: number;
stream?: boolean;
}
interface Message {
role: "system" | "user" | "assistant";
content: string;
}Estructura de Respuesta
interface ChatResponse {
content: string;
usage: {
promptTokens: number;
completionTokens: number;
totalTokens: number;
};
model: string;
}Creando un Proveedor Personalizado
Paso 1: Crear Directorio del Proveedor
mkdir -p .opencode/providers/my-provider
cd .opencode/providers/my-provider
npm init -yPaso 2: Implementar el Proveedor
Crear src/index.ts:
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;
}
}
}Paso 3: Registrar el Proveedor
Agregar a opencode.json:
{
"providers": {
"my-provider": {
"path": ".opencode/providers/my-provider",
"apiKey": "${MY_PROVIDER_API_KEY}",
"baseUrl": "https://api.my-provider.com"
}
}
}Patrones de Autenticación
API Key
{
"providers": {
"my-provider": {
"apiKey": "${MY_PROVIDER_API_KEY}"
}
}
}OAuth2
{
"providers": {
"my-provider": {
"oauth": {
"clientId": "${CLIENT_ID}",
"clientSecret": "${CLIENT_SECRET}",
"tokenUrl": "https://auth.my-provider.com/token"
}
}
}
}Headers Personalizados
async chat(request: ChatRequest): Promise<ChatResponse> {
const response = await fetch(this.baseUrl, {
headers: {
"X-API-Key": this.apiKey,
"X-Custom-Header": "value"
}
});
}Respuestas en Streaming
Server-Sent Events (SSE)
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
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 };
}
}Probando Proveedores Personalizados
Pruebas Unitarias
import MyProvider from "../src/index";
describe("MyProvider", () => {
let provider: MyProvider;
beforeEach(() => {
provider = new MyProvider();
provider.initialize({ apiKey: "test-key" });
});
it("debería retornar respuesta", 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);
});
});Pruebas de Integración
npm testMejores Prácticas
Práctica | Razón |
|---|---|
Manejo de errores | Degradación elegante |
Lógica de reintento | Manejar fallos transitorios |
Manejo de timeouts | Prevenir bloqueos |
Soporte de streaming | Mejor experiencia de usuario |
Seguimiento de costos | Gestión de presupuesto |
Preguntas de Práctica (Practice Questions)
Cómo responder
Pregunta de opción múltiple con 4 opciones. Selecciona una opción y elige Comprobar respuesta.
¿Qué métodos debe implementar un proveedor personalizado?
Cómo responder
Pregunta de opción múltiple con 4 opciones. Selecciona una opción y elige Comprobar respuesta.
¿Cuál es el beneficio de las respuestas en streaming?
Cómo responder
Pregunta de opción múltiple con 4 opciones. Selecciona una opción y elige Comprobar respuesta.
¿Cómo registras un proveedor personalizado?
Cómo responder
Pregunta de opción múltiple con 4 opciones. Selecciona una opción y elige Comprobar respuesta.
¿Qué debe hacer el método validate()?
Cómo responder
Pregunta de opción múltiple con 4 opciones. Selecciona una opción y elige Comprobar respuesta.
¿Para qué se usa el campo usage de ChatResponse?
Key Takeaways
- Los proveedores personalizados implementan métodos chat(), stream() y validate()
- Las respuestas en streaming mejoran la experiencia del usuario con salida progresiva
- Registra proveedores en opencode.json bajo la clave providers
- Soporta múltiples patrones de autenticación: API key, OAuth2, headers personalizados
- Maneja errores elegantemente con lógica de reintento y timeouts
- Rastrea uso de tokens para gestión de costos
- Prueba proveedores con pruebas unitarias y de integración