Personalizado Providers
Extensions pode registrar provedores de modelos personalizados via pi.registerProvider(). Isso permite:
- Proxies - Encaminhe solicitações por meio de proxies corporativos ou gateways API
- Endpoints personalizados – Use implantações de modelo auto-hospedado ou privado
- OAuth/SSO – Adicione fluxos de autenticação para provedores corporativos
- APIs personalizados - Implemente streaming para APIs LLM não padrão
Exemplo Extensions
Veja estes exemplos completos de provedores:
Índice
- 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
Referência rápida
Extensions pode registrar um pi-ai Provider completo ou usar o formulário legado de configuração do provedor. Prefira um provedor completo quando for necessário comportamento personalizado de autenticação, filtragem, atualização ou streaming. Pi compõe models.json substituições acima dos provedores 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
}
]
});
}A fábrica de extensão também pode ser async. Para descoberta de modelo dinâmico, busque e registre modelos na fábrica em vez de session_start. pi espera pela fábrica antes de a inicialização continuar, então o provedor está disponível durante a inicialização interativa e para pi --list-models.
Substituir provedor existente
O caso de uso mais simples: redirecionar um provedor existente por meio de um 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
}
});Quando apenas baseUrl e/ou headers são fornecidos (sem models), todos os modelos existentes para esse provedor são preservados com o novo endpoint.
Cadastrar novo provedor
Para adicionar um provedor completamente novo, especifique models junto com a configuração necessária.
Se a lista de modelos vier de um endpoint remoto, use uma fábrica de extensões assí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,
})),
});
}Isso registra os modelos buscados antes do término da inicialização.
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
}
]
});Quando models é fornecido, ele substitui todos os modelos existentes para esse provedor.
apiKey e valores de cabeçalho personalizados usam a mesma sintaxe de valor de configuração que models.json: !command no início executa um comando para o valor inteiro, $ENV_VAR e ${ENV_VAR} interpolam variáveis de ambiente, $ emite um literal ``apiKeye valores de cabeçalho personalizados usam a mesma sintaxe de valor de configuração quemodels.json: !commandno início executa um comando para o valor inteiro,$ENV_VARe${ENV_VAR}interpolam variáveis de ambiente,$emite um literal e$!emite um literal!`.
Cancelar registro do provedor
Use pi.unregisterProvider(name) para remover um provedor que foi registrado anteriormente via 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");O cancelamento do registro remove os modelos dinâmicos, API key fallback, OAuth registro do provedor e registros do manipulador de fluxo personalizado desse provedor. Quaisquer modelos integrados ou comportamento do provedor que foram substituídos serão restaurados.
As chamadas feitas após a fase inicial de carga do ramal são aplicadas imediatamente, portanto não é necessário /reload.
API Tipos
O campo api determina qual implementação de streaming é usada:
| API | Usar para |
|---|---|
anthropic-messages |
Claude antrópico API e compatíveis |
openai-completions |
Conclusões do OpenAI Chat API e compatíveis |
openai-responses |
Respostas OpenAI API |
azure-openai-responses |
Respostas do Azure OpenAI API |
openai-codex-responses |
Respostas do OpenAI Codex API |
mistral-conversations |
Streaming de conclusões de bate-papo Mistral nativo |
google-generative-ai |
IA generativa do Google API |
google-vertex |
Google Vertex AI API |
bedrock-converse-stream |
Converse Amazon Bedrock API |
A maioria dos provedores compatíveis com OpenAI trabalham com openai-completions. Use thinkingLevelMap no nível do modelo para níveis de pensamento específicos do modelo e compat para peculiaridades do provedor. Os níveis xhigh e max são opcionais, exigem entradas de mapa não nulas e podem ser separados por buracos não suportados:
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
}
}]Use openrouter para controles reasoning: { effort } no estilo OpenRouter. Use together para controles reasoning: { enabled } no estilo Together; com supportsReasoningEffort, também envia reasoning_effort. Use qwen-chat-template para servidores locais compatíveis com Qwen que leem chat_template_kwargs.enable_thinking e precisam de preserve_thinking.
Use cacheControlFormat: "anthropic" para provedores compatíveis com OpenAI que expõem o cache de prompt no estilo Anthropic por meio de cache_control no prompt do sistema, última definição de ferramenta e conteúdo de texto do último usuário, assistente ou resultado da ferramenta.
Para provedores compatíveis com Antrópicos usando api: "anthropic-messages", defina compat.forceAdaptiveThinking: true em modelos ou provedores cujo modelo upstream requer pensamento adaptativo (thinking.type: "adaptive" mais output_config.effort). Os modelos Claude adaptativos integrados definem isso automaticamente. Defina compat.allowEmptySignature: true apenas para provedores que emitem assinaturas de pensamento vazias e esperam signature: "" na repetição.
Nota de migração: Mistral mudou de
openai-completionsparamistral-conversations. Usemistral-conversationspara modelos Mistral nativos. Se você rotear intencionalmente endpoints personalizados/compatíveis com Mistral por meio deopenai-completions, defina sinalizadorescompatexplicitamente conforme necessário.
Cabeçalho de autenticação
Se o seu provedor espera Authorization: Bearer <key> mas não usa um API padrão, defina 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: [...]
});A chave é resolvida para cada solicitação. Um cabeçalho de solicitação explícito Authorization tem precedência sobre o valor gerado.
OAuth Suporte
Adicione autenticação OAuth/SSO que se integra com /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;
}
}
});Após o registro, os usuários podem autenticar via /login corporate-ai.
OAuthLoginCallbacks
O objeto callbacks fornece interações neutras de UI para o fluxo de propriedade do provedor:
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>;
}OAuthCredenciais
As credenciais são persistidas em ~/.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
}Transmissão personalizada API
Para provedores com APIs não padrão, implemente streamSimple. Estude as implementações de provedores existentes antes de escrever as suas próprias:
Implementações de referência:
- anthropic.ts - Mensagens Antrópicas API
- mistral.ts - Conversas Mistral API
- openai-completions.ts - Conclusões do bate-papo OpenAI
- openai-responses.ts - Respostas OpenAI API
- google.ts - IA generativa do Google
- amazon-bedrock.ts - AWS Base
Padrão de fluxo
Todos os provedores seguem o mesmo padrão:
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
Envie eventos via stream.push() nesta ordem:
{ type: "start", partial: output }- Transmissão iniciadaEventos de conteúdo (repetíveis, faixa
contentIndexpara cada bloco):{ type: "text_start", contentIndex, partial }- Bloco de texto iniciado{ type: "text_delta", contentIndex, delta, partial }- Pedaço de texto{ type: "text_end", contentIndex, content, partial }- Bloco de texto encerrado{ type: "thinking_start", contentIndex, partial }- O pensamento começou{ type: "thinking_delta", contentIndex, delta, partial }- Pedaço de pensamento{ type: "thinking_end", contentIndex, content, partial }- Pensamento encerrado{ type: "toolcall_start", contentIndex, partial }- Chamada de ferramenta iniciada{ type: "toolcall_delta", contentIndex, delta, partial }- Chamada de ferramenta JSON pedaço{ type: "toolcall_end", contentIndex, toolCall, partial }- Chamada de ferramenta encerrada
{ type: "done", reason, message }ou{ type: "error", reason, error }- Transmissão encerrada
O campo partial em cada evento contém o estado AssistantMessage atual. Atualize output.content conforme você recebe dados e inclua output como partial.
Blocos de conteúdo
Adicione blocos de conteúdo a output.content conforme eles chegam:
// 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 });Chamadas de ferramentas
As chamadas de ferramenta requerem acumulação JSON e análise:
// 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 e Custo
Atualize o uso da resposta API e calcule o custo:
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);Erros de estouro de contexto
Quando uma solicitação excede a janela de contexto do modelo, pi pode se recuperar automaticamente compactando a conversa e tentando novamente. Essa recuperação só entra em ação se pi reconhecer a falha como um estouro.
A detecção é executada na mensagem do assistente finalizada:
stopReason === "error"errorMessagecorresponde a um dos padrões de estouro conhecidos de pi (consultepackages/ai/src/utils/overflow.ts)
Se o seu provedor retornar erros de overflow com uma mensagem que pi não reconhece, normalize o erro a partir da mesma extensão que registra o provedor. Use um manipulador message_end para reescrever a mensagem do assistente de forma que seu errorMessage comece com uma frase que pi reconhece. O substituto genérico context_length_exceeded é a escolha mais 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 é executado antes de pi rastrear a mensagem do assistente para compactação automática, então o errorMessage reescrito é o que pi verifica. Com isso implementado, pi irá:
- Detecte o estouro de
errorMessage. - Elimine a mensagem do assistente com falha do contexto ao vivo.
- Execute a compactação.
- Tente novamente a solicitação uma vez.
Guarde a reescrita com cuidado:
- Defina o escopo para o seu provedor (
message.providerectx.model?.provider) para que erros não relacionados de outros provedores permaneçam intactos. - Corresponda a um padrão específico do provedor, não aos padrões genéricos de estouro do pi. Reescrever erros de limite de taxa ou limitação (
rate limit,too many requests) acionaria falsamente a compactação em vez do caminho normal de nova tentativa com retirada. - Ignore quando
errorMessagejá incluicontext_length_exceededpara que o manipulador seja idempotente.
Cadastro
Registre sua função de stream:
pi.registerProvider("my-provider", {
baseUrl: "https://api.example.com",
apiKey: "$MY_API_KEY",
api: "my-custom-api",
models: [...],
streamSimple: streamMyProvider
});Testando sua implementação
Teste seu provedor com os mesmos conjuntos de testes usados por provedores integrados. Copie e adapte estes arquivos de teste de packages/ai/test/:
| Teste | Propósito |
|---|---|
stream.test.ts |
Streaming básico, saída de texto |
tokens.test.ts |
Contagem e uso de tokens |
abort.test.ts |
Manipulação de AbortSignal |
empty.test.ts |
Respostas vazias/mínimas |
context-overflow.test.ts |
Limites da janela de contexto |
image-limits.test.ts |
Tratamento de entrada de imagem |
unicode-surrogate.test.ts |
Casos extremos Unicode |
tool-call-without-result.test.ts |
Casos extremos de chamada de ferramenta |
image-tool-result.test.ts |
Imagens nos resultados da ferramenta |
total-tokens.test.ts |
Cálculo total de tokens |
cross-provider-handoff.test.ts |
Transferência de contexto entre provedores |
Execute testes com seus pares provedor/modelo para verificar a compatibilidade.
Referência de configuração
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;
};
}Referência de definição 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 envia reasoning: { effort }. deepseek envia thinking: { type: "enabled" | "disabled" } e reasoning_effort quando habilitado. together envia reasoning: { enabled } e também reasoning_effort quando supportsReasoningEffort está habilitado. qwen é para nível superior do estilo DashScope enable_thinking. Use qwen-chat-template para servidores locais compatíveis com Qwen que leem chat_template_kwargs.enable_thinking e precisam de preserve_thinking. Use chat-template para chat_template_kwargs configurável, por exemplo DeepSeek V3.x atrás de vLLM com chatTemplateKwargs: { "thinking": { "$var": "thinking.enabled" } }. Use thinkingFormat: "baseten" com chatTemplateArgs quando o provedor espera valores de alternância abaixo de chat_template_args e opcionalmente suporta reasoning_effort de nível superior.
cacheControlFormat: "anthropic" aplica marcadores cache_control no estilo antrópico ao prompt do sistema, à última definição de ferramenta e ao conteúdo de texto do último usuário, assistente ou resultado da ferramenta.