Personalizado Providers
Extensions puede registrar proveedores de modelos personalizados a través de pi.registerProvider(). Esto permite:
- Proxies: enrute solicitudes a través de proxies corporativos o puertas de enlace API
- Puntos finales personalizados: utilice implementaciones de modelos privados o autohospedados
- OAuth/SSO: agregar flujos de autenticación para proveedores empresariales
- Personalizado APIs: implementar la transmisión para LLM APIs no estándar
Ejemplo Extensions
Vea estos ejemplos completos de proveedores:
Tabla de contenido
- Example Extensions
- Quick Reference
- Override Existing Provider
- Register New Provider
- Unregister Provider
- OAuth Support
- Custom Streaming API
- Context Overflow Errors
- Testing Your Implementation
- Config Reference
- Model Definition Reference
Referencia rápida
Extensions puede registrar un pi-ai completo Provider o utilizar el formulario de configuración de proveedor heredado. Prefiera un proveedor completo cuando se requiera autenticación personalizada, filtrado, actualización o comportamiento de transmisión. Pi compone models.json anula los proveedores nativos registrados.
import { createProvider, openAICompletionsApi } from "@earendil-works/pi-ai";
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
export default function (pi: ExtensionAPI) {
pi.registerProvider(createProvider({
id: "native-local",
name: "Native Local",
baseUrl: "http://localhost:8080/v1",
auth: {
apiKey: {
name: "Local server API key",
async login(interaction) {
return {
type: "api_key",
key: await interaction.prompt({ type: "secret", message: "API key" })
};
},
async resolve({ credential }) {
return credential?.key
? { auth: { apiKey: credential.key }, source: "stored API key" }
: undefined;
}
}
},
models: [],
api: openAICompletionsApi()
}));
// Legacy provider-config form:
// Override baseUrl for existing provider
pi.registerProvider("anthropic", {
baseUrl: "https://proxy.example.com"
});
// Register new provider with models
pi.registerProvider("my-provider", {
name: "My Provider",
baseUrl: "https://api.example.com",
apiKey: "$MY_API_KEY",
api: "openai-completions",
models: [
{
id: "my-model",
name: "My Model",
reasoning: false,
input: ["text", "image"],
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
contextWindow: 128000,
maxTokens: 4096
}
]
});
}La fábrica de extensiones también puede ser async. Para el descubrimiento dinámico de modelos, busque y registre modelos en la fábrica en lugar de session_start. pi espera a la fábrica antes de que continúe el inicio, por lo que el proveedor está disponible durante el inicio interactivo y para pi --list-models.
Anular proveedor existente
El caso de uso más simple: redirigir a un proveedor existente a través de un proxy.
// All Anthropic requests now go through your proxy
pi.registerProvider("anthropic", {
baseUrl: "https://proxy.example.com"
});
// Add custom headers to OpenAI requests
pi.registerProvider("openai", {
headers: {
"X-Custom-Header": "value"
}
});
// Both baseUrl and headers
pi.registerProvider("google", {
baseUrl: "https://ai-gateway.corp.com/google",
headers: {
"X-Corp-Auth": "$CORP_AUTH_TOKEN" // env var or literal
}
});Cuando solo se proporcionan baseUrl y/o headers (no models), todos los modelos existentes para ese proveedor se conservan con el nuevo punto final.
Registrar nuevo proveedor
Para agregar un proveedor completamente nuevo, especifique models junto con la configuración requerida.
Si la lista de modelos proviene de un punto final remoto, use una fábrica de extensiones asíncrona:
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
export default async function (pi: ExtensionAPI) {
const response = await fetch("http://localhost:1234/v1/models");
const payload = (await response.json()) as {
data: Array<{
id: string;
name?: string;
context_window?: number;
max_tokens?: number;
}>;
};
pi.registerProvider("local-openai", {
baseUrl: "http://localhost:1234/v1",
apiKey: "$LOCAL_OPENAI_API_KEY",
api: "openai-completions",
models: payload.data.map((model) => ({
id: model.id,
name: model.name ?? model.id,
reasoning: false,
input: ["text"],
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
contextWindow: model.context_window ?? 128000,
maxTokens: model.max_tokens ?? 4096,
})),
});
}Esto registra los modelos recuperados antes de que finalice el inicio.
pi.registerProvider("my-llm", {
baseUrl: "https://api.my-llm.com/v1",
apiKey: "$MY_LLM_API_KEY", // env var reference
api: "openai-completions", // which streaming API to use
models: [
{
id: "my-llm-large",
name: "My LLM Large",
reasoning: true, // supports extended thinking
input: ["text", "image"],
cost: {
input: 3.0, // $/million tokens
output: 15.0,
cacheRead: 0.3,
cacheWrite: 3.75
},
contextWindow: 200000,
maxTokens: 16384
}
]
});Cuando se proporciona models, reemplaza todos los modelos existentes para ese proveedor.
apiKey y los valores de encabezado personalizados usan la misma sintaxis de valor de configuración que models.json: !command al principio ejecuta un comando para el valor completo, $ENV_VAR y ${ENV_VAR} interpolan variables de entorno, $ emite un literal ``apiKeyy los valores de encabezado personalizados usan la misma sintaxis de valor de configuración quemodels.json: !commandal principio ejecuta un comando para el valor completo,$ENV_VARy${ENV_VAR}interpolan variables de entorno,$emite un literal y$!emite un literal!`.
Darse de baja del proveedor
Utilice pi.unregisterProvider(name) para eliminar un proveedor que se registró previamente mediante pi.registerProvider(name,...):
// Register
pi.registerProvider("my-llm", {
baseUrl: "https://api.my-llm.com/v1",
apiKey: "$MY_LLM_API_KEY",
api: "openai-completions",
models: [
{
id: "my-llm-large",
name: "My LLM Large",
reasoning: true,
input: ["text", "image"],
cost: { input: 3.0, output: 15.0, cacheRead: 0.3, cacheWrite: 3.75 },
contextWindow: 200000,
maxTokens: 16384
}
]
});
// Later, remove it
pi.unregisterProvider("my-llm");Al cancelar el registro se eliminan los modelos dinámicos de ese proveedor, el respaldo API key, el registro de proveedor OAuth y los registros de controladores de flujo personalizados. Se restauran todos los modelos integrados o comportamiento del proveedor que se anularon.
Las llamadas realizadas después de la fase de carga de extensión inicial se aplican inmediatamente, por lo que no se requiere /reload.
API Tipos
El campo api determina qué implementación de transmisión se utiliza:
| API | Usar para |
|---|---|
anthropic-messages |
Claude antrópico API y compatibles |
openai-completions |
Finalizaciones de OpenAI Chat API y compatibles |
openai-responses |
Respuestas de OpenAI API |
azure-openai-responses |
Respuestas de Azure OpenAI API |
openai-codex-responses |
Respuestas del Códice OpenAI API |
mistral-conversations |
Transmisión de terminaciones de chat nativo de Mistral |
google-generative-ai |
IA generativa de Google API |
google-vertex |
Google Vertex AI API |
bedrock-converse-stream |
converse amazonas API |
La mayoría de los proveedores compatibles con OpenAI funcionan con openai-completions. Utilice el nivel de modelo thinkingLevelMap para niveles de pensamiento específicos del modelo y compat para las peculiaridades del proveedor. Los niveles xhigh y max son opcionales, requieren entradas de mapa no nulas y pueden estar separados por agujeros no admitidos:
models: [{
id: "custom-model",
// ...
reasoning: true,
thinkingLevelMap: { // map pi levels to provider values; null hides unsupported levels
minimal: null,
low: null,
medium: null,
high: "default",
xhigh: null,
max: "max"
},
compat: {
supportsDeveloperRole: false, // use "system" instead of "developer"
supportsReasoningEffort: true,
maxTokensField: "max_tokens", // instead of "max_completion_tokens"
requiresToolResultName: true, // tool results need name field
thinkingFormat: "qwen", // top-level enable_thinking: true
cacheControlFormat: "anthropic" // Anthropic-style cache_control markers
}
}]Utilice openrouter para controles reasoning: { effort } estilo OpenRouter. Utilice together para controles reasoning: { enabled } estilo Together; con supportsReasoningEffort, también envía reasoning_effort. Utilice qwen-chat-template para servidores locales compatibles con Qwen que lean chat_template_kwargs.enable_thinking y necesiten preserve_thinking.
Utilice cacheControlFormat: "anthropic" para proveedores compatibles con OpenAI que exponen el almacenamiento en caché de mensajes de estilo Anthropic a través de cache_control en el mensaje del sistema, la última definición de herramienta y el contenido de texto del último usuario, asistente o resultado de la herramienta.
Para proveedores compatibles con Anthropic que utilizan api: "anthropic-messages", establezca compat.forceAdaptiveThinking: true en modelos o proveedores cuyo modelo ascendente requiere pensamiento adaptativo (thinking.type: "adaptive" más output_config.effort). Los modelos Claude adaptables incorporados configuran esto automáticamente. Configure compat.allowEmptySignature: true solo para proveedores que emiten firmas de pensamiento vacías y esperan signature: "" en la reproducción.
Nota de migración: Mistral pasó de
openai-completionsamistral-conversations. Utilicemistral-conversationspara modelos Mistral nativos. Si enruta intencionalmente puntos finales personalizados/compatibles con Mistral a través deopenai-completions, configure los indicadorescompatexplícitamente según sea necesario.
Encabezado de autenticación
Si su proveedor espera Authorization: Bearer <key> pero no utiliza un estándar API, establezca authHeader: true:
pi.registerProvider("custom-api", {
baseUrl: "https://api.example.com",
apiKey: "$MY_API_KEY",
authHeader: true, // adds Authorization: Bearer header
api: "openai-completions",
models: [...]
});La clave se resuelve para cada solicitud. Un encabezado de solicitud explícita Authorization tiene prioridad sobre el valor generado.
OAuth Soporte
Agregue autenticación OAuth/SSO que se integra con /login:
import type { OAuthCredentials, OAuthLoginCallbacks } from "@earendil-works/pi-ai";
pi.registerProvider("corporate-ai", {
baseUrl: "https://ai.corp.com/v1",
api: "openai-responses",
models: [...],
oauth: {
name: "Corporate AI (SSO)",
async login(callbacks: OAuthLoginCallbacks): Promise<OAuthCredentials> {
const method = await callbacks.onSelect({
message: "Select login method:",
options: [
{ id: "browser", label: "Browser OAuth" },
{ id: "device", label: "Device code" }
]
});
if (!method) throw new Error("Login cancelled");
let code: string;
if (method === "device") {
callbacks.onDeviceCode({
userCode: "ABCD-1234",
verificationUri: "https://sso.corp.com/device",
intervalSeconds: 5,
expiresInSeconds: 900
});
code = await pollDeviceCodeUntilComplete();
} else {
callbacks.onAuth({ url: "https://sso.corp.com/authorize?..." });
code = await callbacks.onPrompt({ message: "Enter SSO code:" });
}
// Exchange for tokens (your implementation)
const tokens = await exchangeCodeForTokens(code);
return {
refresh: tokens.refreshToken,
access: tokens.accessToken,
expires: Date.now() + tokens.expiresIn * 1000
};
},
async refreshToken(credentials: OAuthCredentials, signal: AbortSignal): Promise<OAuthCredentials> {
const tokens = await refreshAccessToken(credentials.refresh, signal);
return {
refresh: tokens.refreshToken ?? credentials.refresh,
access: tokens.accessToken,
expires: Date.now() + tokens.expiresIn * 1000
};
},
getApiKey(credentials: OAuthCredentials): string {
return credentials.access;
}
}
});Después del registro, los usuarios pueden autenticarse a través de /login corporate-ai.
OAuthIniciar sesiónDevoluciones de llamada
El objeto callbacks proporciona interacciones neutrales en la interfaz de usuario para el flujo propiedad del proveedor:
interface OAuthLoginCallbacks {
// Open URL in browser (for OAuth redirects)
onAuth(params: { url: string }): void;
// Show device code (for device authorization flow)
onDeviceCode(params: {
userCode: string;
verificationUri: string;
intervalSeconds?: number;
expiresInSeconds?: number;
}): void;
// Show transient progress
onProgress?(message: string): void;
// Prompt user for input (for manual token entry)
onPrompt(params: { message: string }): Promise<string>;
// Show an interactive selector, e.g. to choose browser OAuth vs device code
onSelect(params: {
message: string;
options: { id: string; label: string }[];
}): Promise<string | undefined>;
}OAuthCredenciales
Las credenciales persisten en ~/.pi/agent/auth.json:
interface OAuthCredentials {
refresh: string; // Refresh token (for refreshToken())
access: string; // Access token (returned by getApiKey())
expires: number; // Expiration timestamp in milliseconds
}Transmisión personalizada API
Para proveedores con API no estándar, implemente streamSimple. Estudie las implementaciones de proveedores existentes antes de escribir la suya propia:
Implementaciones de referencia:
- anthropic.ts - Mensajes Antrópicos API
- mistral.ts - Conversaciones Mistral API
- openai-completions.ts - Finalizaciones del chat OpenAI
- openai-responses.ts - Respuestas de OpenAI API
- google.ts - IA generativa de Google
- amazon-bedrock.ts - Base de AWS
Patrón de corriente
Todos los proveedores siguen el mismo patrón:
import {
type AssistantMessage,
type AssistantMessageEventStream,
type Context,
type Model,
type SimpleStreamOptions,
calculateCost,
createAssistantMessageEventStream,
} from "@earendil-works/pi-ai";
function streamMyProvider(
model: Model<any>,
context: Context,
options?: SimpleStreamOptions
): AssistantMessageEventStream {
const stream = createAssistantMessageEventStream();
(async () => {
// Initialize output message
const output: AssistantMessage = {
role: "assistant",
content: [],
api: model.api,
provider: model.provider,
model: model.id,
usage: {
input: 0,
output: 0,
cacheRead: 0,
cacheWrite: 0,
totalTokens: 0,
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 },
},
stopReason: "pending",
timestamp: Date.now(),
};
try {
// Push start event
stream.push({ type: "start", partial: output });
// Make API request and process response...
// Push content events as they arrive and set stopReason from the terminal event.
if (output.stopReason === "pending") {
throw new Error("Provider stream ended without a stop reason");
}
if (output.stopReason === "error" || output.stopReason === "aborted") {
throw new Error(output.errorMessage || "An unknown error occurred");
}
// Push done event
stream.push({
type: "done",
reason: output.stopReason,
message: output
});
stream.end();
} catch (error) {
output.stopReason = options?.signal?.aborted ? "aborted" : "error";
output.errorMessage = error instanceof Error ? error.message : String(error);
stream.push({ type: "error", reason: output.stopReason, error: output });
stream.end();
}
})();
return stream;
}Tipos de eventos
Envíe eventos a través de stream.push() en este orden:
{ type: "start", partial: output }- Transmisión iniciadaEventos de contenido (repetibles, seguimiento
contentIndexpara cada bloque):{ type: "text_start", contentIndex, partial }- Bloque de texto iniciado{ type: "text_delta", contentIndex, delta, partial }- Fragmento de texto{ type: "text_end", contentIndex, content, partial }- Bloque de texto finalizado{ type: "thinking_start", contentIndex, partial }- El pensamiento comenzó{ type: "thinking_delta", contentIndex, delta, partial }- Fragmento de pensamiento{ type: "thinking_end", contentIndex, content, partial }- Se acabó el pensamiento{ type: "toolcall_start", contentIndex, partial }- Se inició la llamada a la herramienta{ type: "toolcall_delta", contentIndex, delta, partial }- Llamada a herramienta JSON fragmento{ type: "toolcall_end", contentIndex, toolCall, partial }- Llamada de herramienta finalizada
{ type: "done", reason, message }o{ type: "error", reason, error }- Transmisión finalizada
El campo partial en cada evento contiene el estado AssistantMessage actual. Actualice output.content a medida que reciba datos, luego incluya output como partial.
Bloques de contenido
Agregue bloques de contenido a output.content a medida que lleguen:
// Text block
output.content.push({ type: "text", text: "" });
stream.push({ type: "text_start", contentIndex: output.content.length - 1, partial: output });
// As text arrives
const block = output.content[contentIndex];
if (block.type === "text") {
block.text += delta;
stream.push({ type: "text_delta", contentIndex, delta, partial: output });
}
// When block completes
stream.push({ type: "text_end", contentIndex, content: block.text, partial: output });Llamadas de herramientas
Las llamadas a herramientas requieren acumular JSON y analizar:
// Start tool call
output.content.push({
type: "toolCall",
id: toolCallId,
name: toolName,
arguments: {}
});
stream.push({ type: "toolcall_start", contentIndex: output.content.length - 1, partial: output });
// Accumulate JSON
let partialJson = "";
partialJson += jsonDelta;
try {
block.arguments = JSON.parse(partialJson);
} catch {}
stream.push({ type: "toolcall_delta", contentIndex, delta: jsonDelta, partial: output });
// Complete
stream.push({
type: "toolcall_end",
contentIndex,
toolCall: { type: "toolCall", id, name, arguments: block.arguments },
partial: output
});Uso y costo
Actualice el uso desde la respuesta API y calcule el costo:
output.usage.input = response.usage.input_tokens;
output.usage.output = response.usage.output_tokens;
output.usage.cacheRead = response.usage.cache_read_tokens ?? 0;
output.usage.cacheWrite = response.usage.cache_write_tokens ?? 0;
output.usage.totalTokens = output.usage.input + output.usage.output +
output.usage.cacheRead + output.usage.cacheWrite;
calculateCost(model, output.usage);Errores de desbordamiento de contexto
Cuando una solicitud excede la ventana de contexto del modelo, pi puede recuperarse automáticamente compactando la conversación y volviendo a intentarlo. Esta recuperación solo se activa si pi reconoce la falla como un desbordamiento.
La detección se ejecuta en el mensaje del asistente finalizado:
stopReason === "error"errorMessagecoincide con uno de los patrones de desbordamiento conocidos de pi (verpackages/ai/src/utils/overflow.ts)
Si su proveedor devuelve errores de desbordamiento con un mensaje que pi no reconoce, normalice el error desde la misma extensión que registra el proveedor. Utilice un controlador message_end para reescribir el mensaje del asistente de modo que errorMessage comience con una frase que pi reconozca. La alternativa genérica context_length_exceeded es la opción más segura.
const MY_PROVIDER_OVERFLOW_PATTERN = /your provider's overflow phrase/i;
export default function (pi: ExtensionAPI) {
pi.registerProvider("my-provider", { /* ... */ });
pi.on("message_end", (event, ctx) => {
const message = event.message;
if (message.role !== "assistant") return;
if (message.stopReason !== "error") return;
if (
message.provider !== "my-provider" &&
ctx.model?.provider !== "my-provider"
)
return;
const errorMessage = message.errorMessage ?? "";
if (errorMessage.includes("context_length_exceeded")) return;
if (!MY_PROVIDER_OVERFLOW_PATTERN.test(errorMessage)) return;
return {
message: {
...message,
errorMessage: `context_length_exceeded: ${errorMessage}`,
},
};
});
}message_end se ejecuta antes de que pi rastree el mensaje del asistente para la autocompactación, por lo que el errorMessage reescrito es lo que pi verifica. Con esto en su lugar, pi:
- Detecta el desbordamiento de
errorMessage. - Suelta el mensaje del asistente fallido desde el contexto en vivo.
- Ejecute la compactación.
- Vuelva a intentar la solicitud una vez.
Guarde la reescritura con cuidado:
- Ámbite a tu proveedor (
message.provideryctx.model?.provider) para que los errores no relacionados de otros proveedores no se modifiquen. - Haga coincidir un patrón específico del proveedor, no los patrones de desbordamiento genéricos de pi. Reescribir los errores de límite de velocidad o limitación (
rate limit,too many requests) activaría falsamente la compactación en lugar de la ruta normal de reintento con retroceso de pi. - Omita cuando
errorMessageya incluyacontext_length_exceededpara que el controlador sea idempotente.
Registro
Registre su función de transmisión:
pi.registerProvider("my-provider", {
baseUrl: "https://api.example.com",
apiKey: "$MY_API_KEY",
api: "my-custom-api",
models: [...],
streamSimple: streamMyProvider
});Probando su implementación
Pruebe su proveedor con los mismos conjuntos de pruebas utilizados por los proveedores integrados. Copie y adapte estos archivos de prueba desde packages/ai/test/:
| Prueba | Objetivo |
|---|---|
stream.test.ts |
Transmisión básica, salida de texto |
tokens.test.ts |
Recuento y uso de tokens |
abort.test.ts |
Abortar Manejo de señales |
empty.test.ts |
Respuestas vacías/mínimas |
context-overflow.test.ts |
Límites de la ventana de contexto |
image-limits.test.ts |
Manejo de entrada de imágenes |
unicode-surrogate.test.ts |
Casos extremos Unicode |
tool-call-without-result.test.ts |
Casos extremos de llamada de herramientas |
image-tool-result.test.ts |
Imágenes en resultados de herramientas |
total-tokens.test.ts |
Cálculo total de tokens |
cross-provider-handoff.test.ts |
Transferencia de contexto entre proveedores |
Ejecute pruebas con sus pares de proveedor/modelo para verificar la compatibilidad.
Referencia de configuración
interface ProviderConfig {
/** Display name for the provider in UI such as /login. */
name?: string;
/** API endpoint URL. Required when defining models. */
baseUrl?: string;
/** API key literal, env interpolation ($ENV_VAR or ${ENV_VAR}), or !command. Required when defining models (unless oauth). */
apiKey?: string;
/** API type for streaming. Required at provider or model level when defining models. */
api?: Api;
/** Custom streaming implementation for non-standard APIs. */
streamSimple?: (
model: Model<Api>,
context: Context,
options?: SimpleStreamOptions
) => AssistantMessageEventStream;
/** Custom headers to include in requests. Values use the same resolution syntax as apiKey. */
headers?: Record<string, string>;
/** If true, adds Authorization: Bearer header with the resolved API key. */
authHeader?: boolean;
/** Models to register. If provided, replaces all existing models for this provider. */
models?: ProviderModelConfig[];
/** OAuth provider for /login support. */
oauth?: {
name: string;
login(callbacks: OAuthLoginCallbacks): Promise<OAuthCredentials>;
refreshToken(credentials: OAuthCredentials, signal: AbortSignal): Promise<OAuthCredentials>;
getApiKey(credentials: OAuthCredentials): string;
};
}Referencia de definición de modelo
interface ProviderModelConfig {
/** Model ID (e.g., "claude-sonnet-4-20250514"). */
id: string;
/** Display name (e.g., "Claude 4 Sonnet"). */
name: string;
/** API type override for this specific model. */
api?: Api;
/** API endpoint URL override for this specific model. */
baseUrl?: string;
/** Whether the model supports extended thinking. */
reasoning: boolean;
/** Maps pi thinking levels to provider/model-specific values; null marks a level unsupported. */
thinkingLevelMap?: Partial<Record<"off" | "minimal" | "low" | "medium" | "high" | "xhigh" | "max", string | null>>;
/** Supported input types. */
input: ("text" | "image")[];
/** Cost per million tokens (for usage tracking). */
cost: {
input: number;
output: number;
cacheRead: number;
cacheWrite: number;
};
/** Maximum context window size in tokens. */
contextWindow: number;
/** Maximum output tokens. */
maxTokens: number;
/** Custom headers for this specific model. */
headers?: Record<string, string>;
/** Compatibility settings for the selected API. */
compat?: {
// openai-completions
supportsStore?: boolean;
supportsDeveloperRole?: boolean;
supportsReasoningEffort?: boolean;
supportsUsageInStreaming?: boolean;
supportsFinishReason?: boolean;
supportsStrictMode?: boolean;
supportsOpenAIGrammarTools?: boolean; // openai-completions/openai-responses; false falls back to normal function tools
maxTokensField?: "max_completion_tokens" | "max_tokens";
requiresToolResultName?: boolean;
requiresAssistantAfterToolResult?: boolean;
requiresThinkingAsText?: boolean;
requiresReasoningContentOnAssistantMessages?: boolean;
thinkingFormat?: "openai" | "openrouter" | "deepseek" | "together" | "baseten" | "zai" | "qwen" | "chat-template" | "qwen-chat-template" | "string-thinking" | "ant-ling";
chatTemplateKwargs?: Record<string, string | number | boolean | null | { "$var": "thinking.enabled" | "thinking.effort"; omitWhenOff?: boolean }>;
chatTemplateArgs?: Record<string, string | number | boolean | null | { "$var": "thinking.enabled" | "thinking.effort"; omitWhenOff?: boolean }>;
cacheControlFormat?: "anthropic";
sessionAffinityFormat?: "openai" | "openai-nosession" | "openrouter";
sendSessionAffinityHeaders?: boolean;
// anthropic-messages
supportsEagerToolInputStreaming?: boolean;
supportsLongCacheRetention?: boolean;
sendSessionAffinityHeaders?: boolean;
supportsCacheControlOnTools?: boolean;
forceAdaptiveThinking?: boolean;
allowEmptySignature?: boolean;
supportsStrictTools?: boolean;
};
}openrouter envía reasoning: { effort }. deepseek envía thinking: { type: "enabled" | "disabled" } y reasoning_effort cuando está habilitado. together envía reasoning: { enabled } y también reasoning_effort cuando supportsReasoningEffort está habilitado. qwen es para el nivel superior estilo DashScope enable_thinking. Utilice qwen-chat-template para servidores locales compatibles con Qwen que lean chat_template_kwargs.enable_thinking y necesiten preserve_thinking. Utilice chat-template para chat_template_kwargs configurable, por ejemplo DeepSeek V3.x detrás de vLLM con chatTemplateKwargs: { "thinking": { "$var": "thinking.enabled" } }. Utilice thinkingFormat: "baseten" con chatTemplateArgs cuando el proveedor espere valores de alternancia inferiores a chat_template_args y, opcionalmente, admita el nivel superior reasoning_effort.
cacheControlFormat: "anthropic" aplica marcadores cache_control de estilo antrópico al mensaje del sistema, a la última definición de herramienta y al contenido de texto del último usuario, asistente o resultado de herramienta.