Kustom Providers
Extensions dapat mendaftarkan penyedia model khusus melalui pi.registerProvider(). Hal ini memungkinkan:
- Proxy - Merutekan permintaan melalui proxy perusahaan atau gateway API
- Titik akhir khusus - Gunakan penerapan model yang dihosting sendiri atau pribadi
- OAuth/SSO - Tambahkan alur autentikasi untuk penyedia perusahaan
- Kustom APIs - Terapkan streaming untuk LLM non-standar APIs
Contoh Extensions
Lihat contoh penyedia lengkap ini:
Daftar isi
- 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
Referensi Cepat
Extensions dapat mendaftarkan pi-ai lengkap Provider atau menggunakan formulir konfigurasi penyedia lama. Pilih penyedia yang lengkap ketika autentikasi khusus, pemfilteran, penyegaran, atau perilaku streaming diperlukan. Pi menyusun models.json menimpa penyedia asli terdaftar di atas.
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
}
]
});
}Pabrik ekstensi juga bisa async. Untuk penemuan model dinamis, ambil dan daftarkan model di pabrik, bukan di session_start. pi menunggu pabrik sebelum pengaktifan dilanjutkan, sehingga penyedia tersedia selama pengaktifan interaktif dan ke pi --list-models.
Ganti Penyedia yang Ada
Kasus penggunaan paling sederhana: mengarahkan ulang penyedia yang ada melalui 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
}
});Jika hanya baseUrl dan/atau headers yang disediakan (tidak ada models), semua model yang ada untuk penyedia tersebut akan dipertahankan dengan titik akhir baru.
Daftarkan Penyedia Baru
Untuk menambahkan penyedia yang benar-benar baru, tentukan models beserta konfigurasi yang diperlukan.
Jika daftar model berasal dari titik akhir jarak jauh, gunakan pabrik ekstensi async:
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,
})),
});
}Ini mendaftarkan model yang diambil sebelum startup selesai.
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
}
]
});Jika models disediakan, ini menggantikan semua model yang ada untuk penyedia tersebut.
apiKey dan nilai header khusus menggunakan sintaks nilai konfigurasi yang sama seperti models.json: !command di awal menjalankan perintah untuk seluruh nilai, $ENV_VAR dan ${ENV_VAR} menginterpolasi variabel lingkungan, $ memancarkan ``apiKeydan nilai header khusus menggunakan sintaks nilai konfigurasi yang sama sepertimodels.json: !commanddi awal menjalankan perintah untuk seluruh nilai,$ENV_VARdan${ENV_VAR}menginterpolasi variabel lingkungan,$memancarkan literal, dan$!memancarkan!` literal.
Batalkan Pendaftaran Penyedia
Gunakan pi.unregisterProvider(name) untuk menghapus penyedia yang sebelumnya terdaftar melalui 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");Membatalkan pendaftaran akan menghapus model dinamis penyedia tersebut, API key fallback, OAuth pendaftaran penyedia, dan pendaftaran pengendali aliran khusus. Model bawaan atau perilaku penyedia apa pun yang diganti akan dipulihkan.
Panggilan yang dilakukan setelah fase beban ekstensi awal diterapkan segera, jadi tidak diperlukan /reload.
API Jenis
Bidang api menentukan implementasi streaming mana yang digunakan:
| API | Gunakan untuk |
|---|---|
anthropic-messages |
Claude Antropik API dan yang kompatibel |
openai-completions |
Penyelesaian Obrolan OpenAI API dan yang kompatibel |
openai-responses |
Respons OpenAI API |
azure-openai-responses |
Respons Azure OpenAI API |
openai-codex-responses |
Respons Kodeks OpenAI API |
mistral-conversations |
Streaming Penyelesaian Obrolan Mistral Asli |
google-generative-ai |
AI Generatif Google API |
google-vertex |
Google Vertex AI API |
bedrock-converse-stream |
Batu Dasar Amazon Converse API |
Sebagian besar penyedia yang kompatibel dengan OpenAI bekerja dengan openai-completions. Gunakan tingkat model thinkingLevelMap untuk tingkat pemikiran khusus model, dan compat untuk kebiasaan penyedia layanan. Level xhigh dan max bersifat opt-in, memerlukan entri peta bukan nol, dan dapat dipisahkan oleh lubang yang tidak didukung:
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
}
}]Gunakan openrouter untuk kontrol reasoning: { effort } gaya OpenRouter. Gunakan together untuk kontrol gaya Together reasoning: { enabled }; dengan supportsReasoningEffort, ia juga mengirimkan reasoning_effort. Gunakan qwen-chat-template untuk server lokal yang kompatibel dengan Qwen yang membaca chat_template_kwargs.enable_thinking dan memerlukan preserve_thinking.
Gunakan cacheControlFormat: "anthropic" untuk penyedia yang kompatibel dengan OpenAI yang mengekspos cache cepat gaya Antropik melalui cache_control pada perintah sistem, definisi alat terakhir, dan konten teks pengguna, asisten, atau hasil alat terakhir.
Untuk penyedia yang kompatibel dengan Antropik yang menggunakan api: "anthropic-messages", tetapkan compat.forceAdaptiveThinking: true pada model atau penyedia yang model hulunya memerlukan pemikiran adaptif (thinking.type: "adaptive" plus output_config.effort). Model Claude adaptif bawaan mengatur ini secara otomatis. Tetapkan compat.allowEmptySignature: true hanya untuk penyedia yang mengeluarkan tanda tangan berpikir kosong dan mengharapkan signature: "" diputar ulang.
Catatan migrasi: Mistral berpindah dari
openai-completionskemistral-conversations. Gunakanmistral-conversationsuntuk model Mistral asli. Jika Anda sengaja merutekan titik akhir yang kompatibel/khusus Mistral melaluiopenai-completions, setel tandacompatsecara eksplisit sesuai kebutuhan.
Tajuk Otentikasi
Jika penyedia Anda mengharapkan Authorization: Bearer <key> tetapi tidak menggunakan standar API, tetapkan 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: [...]
});Kuncinya diselesaikan untuk setiap permintaan. Permintaan eksplisit Authorization header lebih diutamakan daripada nilai yang dihasilkan.
OAuth Dukungan
Tambahkan autentikasi OAuth/SSO yang terintegrasi dengan /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;
}
}
});Setelah registrasi, pengguna dapat mengautentikasi melalui /login corporate-ai.
OAuthLoginCallback
Objek callbacks menyediakan interaksi UI-netral untuk aliran milik penyedia:
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>;
}OAuthKredensial
Kredensial dipertahankan di ~/.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
}Streaming Khusus API
Untuk penyedia dengan API non-standar, terapkan streamSimple. Pelajari implementasi penyedia yang ada sebelum menulis implementasi Anda sendiri:
Implementasi referensi:
- anthropic.ts - Pesan Antropik API
- mistral.ts - Percakapan Mistral API
- openai-completions.ts - Penyelesaian Obrolan OpenAI
- openai-responses.ts - Respons OpenAI API
- google.ts - AI Generatif Google
- amazon-bedrock.ts - Batuan Dasar AWS
Pola Aliran
Semua penyedia mengikuti pola yang sama:
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;
}Jenis Acara
Dorong acara melalui stream.push() dalam urutan ini:
{ type: "start", partial: output }- Streaming dimulaiPeristiwa konten (dapat diulang, lacak
contentIndexuntuk setiap blok):{ type: "text_start", contentIndex, partial }- Blok teks dimulai{ type: "text_delta", contentIndex, delta, partial }- Potongan teks{ type: "text_end", contentIndex, content, partial }- Blok teks berakhir{ type: "thinking_start", contentIndex, partial }- Pemikiran dimulai{ type: "thinking_delta", contentIndex, delta, partial }- Potongan pemikiran{ type: "thinking_end", contentIndex, content, partial }- Pemikiran berakhir{ type: "toolcall_start", contentIndex, partial }- Panggilan alat dimulai{ type: "toolcall_delta", contentIndex, delta, partial }- Panggilan alat JSON potongan{ type: "toolcall_end", contentIndex, toolCall, partial }- Panggilan alat berakhir
{ type: "done", reason, message }atau{ type: "error", reason, error }- Streaming berakhir
Bidang partial di setiap peristiwa berisi status AssistantMessage saat ini. Perbarui output.content saat Anda menerima data, lalu sertakan output sebagai partial.
Blok Konten
Tambahkan blok konten ke output.content saat blok tersebut tiba:
// 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 });Panggilan Alat
Panggilan alat memerlukan akumulasi JSON dan penguraian:
// 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
});Penggunaan dan Biaya
Perbarui penggunaan dari API respons dan hitung biaya:
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);Kesalahan Meluap Konteks
Ketika permintaan melebihi jendela konteks model, pi dapat pulih secara otomatis dengan memadatkan percakapan dan mencoba lagi. Pemulihan ini hanya dimulai jika pi mengenali kegagalan sebagai luapan.
Deteksi berjalan pada pesan asisten yang diselesaikan:
stopReason === "error"errorMessagecocok dengan salah satu pola luapan pi yang diketahui (lihatpackages/ai/src/utils/overflow.ts)
Jika penyedia Anda mengembalikan kesalahan overflow dengan pesan yang tidak dikenali pi, normalkan kesalahan dari ekstensi yang sama yang mendaftarkan penyedia tersebut. Gunakan pengendali message_end untuk menulis ulang pesan asisten sehingga errorMessage dimulai dengan frasa yang dikenali pi. Penggantian umum context_length_exceeded adalah pilihan paling aman.
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 berjalan sebelum pi melacak pesan asisten untuk pemadatan otomatis, jadi errorMessage yang ditulis ulang itulah yang diperiksa pi. Dengan ini, pi akan:
- Deteksi luapan dari
errorMessage. - Hapus pesan asisten yang gagal dari konteks langsung.
- Jalankan pemadatan.
- Coba lagi permintaan tersebut satu kali.
Jaga penulisan ulang dengan hati-hati:
- Cakupannya ke penyedia Anda (
message.providerdanctx.model?.provider) sehingga kesalahan yang tidak terkait dari penyedia lain tidak tersentuh. - Cocokkan pola khusus penyedia, bukan pola luapan umum pi. Kesalahan penulisan ulang batas kecepatan atau pelambatan (
rate limit,too many requests) akan memicu pemadatan secara salah, bukan jalur percobaan ulang dengan kemunduran normal pi. - Lewati ketika
errorMessagesudah menyertakancontext_length_exceededsehingga handlernya idempoten.
Pendaftaran
Daftarkan fungsi streaming Anda:
pi.registerProvider("my-provider", {
baseUrl: "https://api.example.com",
apiKey: "$MY_API_KEY",
api: "my-custom-api",
models: [...],
streamSimple: streamMyProvider
});Menguji Implementasi Anda
Uji penyedia Anda terhadap rangkaian pengujian yang sama dengan yang digunakan oleh penyedia bawaan. Salin dan sesuaikan file pengujian ini dari packages/ai/test/:
| Tes | Tujuan |
|---|---|
stream.test.ts |
Streaming dasar, keluaran teks |
tokens.test.ts |
Penghitungan dan penggunaan token |
abort.test.ts |
Batalkan penanganan sinyal |
empty.test.ts |
Respons kosong/minimal |
context-overflow.test.ts |
Batas jendela konteks |
image-limits.test.ts |
Penanganan masukan gambar |
unicode-surrogate.test.ts |
Kasus tepi Unicode |
tool-call-without-result.test.ts |
Kasus tepi panggilan alat |
image-tool-result.test.ts |
Gambar dalam hasil alat |
total-tokens.test.ts |
Perhitungan total token |
cross-provider-handoff.test.ts |
Penyerahan konteks antar penyedia |
Jalankan pengujian dengan pasangan penyedia/model Anda untuk memverifikasi kompatibilitas.
Referensi Konfigurasi
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;
};
}Referensi Definisi Model
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 mengirim reasoning: { effort }. deepseek mengirim thinking: { type: "enabled" | "disabled" } dan reasoning_effort saat diaktifkan. together mengirim reasoning: { enabled } dan juga reasoning_effort ketika supportsReasoningEffort diaktifkan. qwen adalah untuk tingkat atas gaya DashScope enable_thinking. Gunakan qwen-chat-template untuk server lokal yang kompatibel dengan Qwen yang membaca chat_template_kwargs.enable_thinking dan memerlukan preserve_thinking. Gunakan chat-template untuk chat_template_kwargs yang dapat dikonfigurasi, misalnya DeepSeek V3.x di belakang vLLM dengan chatTemplateKwargs: { "thinking": { "$var": "thinking.enabled" } }. Gunakan thinkingFormat: "baseten" dengan chatTemplateArgs ketika penyedia mengharapkan nilai peralihan di bawah chat_template_args dan secara opsional mendukung reasoning_effort tingkat atas.
cacheControlFormat: "anthropic" menerapkan penanda gaya Antropis cache_control ke perintah sistem, definisi alat terakhir, dan konten teks pengguna, asisten, atau hasil alat terakhir.