Integración de Proveedor Personalizado
Avanzado⏱60 min📖2 min de lecturaLección 5 de 5

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

100%

Interfaz del Proveedor

Métodos Requeridos

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>; }

Estructura de Solicitud

typescript
interface ChatRequest { model: string; messages: Message[]; temperature?: number; maxTokens?: number; stream?: boolean; } interface Message { role: "system" | "user" | "assistant"; content: string; }

Estructura de Respuesta

typescript
interface ChatResponse { content: string; usage: { promptTokens: number; completionTokens: number; totalTokens: number; }; model: string; }

Creando un Proveedor Personalizado

Paso 1: Crear Directorio del Proveedor

bash
mkdir -p .opencode/providers/my-provider cd .opencode/providers/my-provider npm init -y

Paso 2: Implementar el Proveedor

Crear 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; } } }

Paso 3: Registrar el Proveedor

Agregar a opencode.json:

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

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" } }); }

Respuestas en 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 }; } }

Probando Proveedores Personalizados

Pruebas Unitarias

typescript
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

bash
npm test

Mejores 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)

Pregunta de Práctica
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?

Pregunta de Práctica
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?

Pregunta de Práctica
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?

Pregunta de Práctica
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()?

Pregunta de Práctica
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?


✅Éxito

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
Progreso100%