Benutzerdefiniert Providers
Extensions kann benutzerdefinierte Modellanbieter über pi.registerProvider() registrieren. Dies ermöglicht:
- Proxys – Leiten Sie Anfragen über Unternehmens-Proxys oder API Gateways weiter
- Benutzerdefinierte Endpunkte – Verwenden Sie selbstgehostete oder private Modellbereitstellungen
- OAuth/SSO – Authentifizierungsflüsse für Unternehmensanbieter hinzufügen
- Benutzerdefinierte APIs – Implementieren Sie Streaming für nicht standardmäßige LLM APIs
Beispiel Extensions
Sehen Sie sich diese vollständigen Anbieterbeispiele an:
Inhaltsverzeichnis
- 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
Kurzreferenz
Extensions kann entweder ein vollständiges Pi-AI Provider registrieren oder das alte Provider-Config-Formular verwenden. Bevorzugen Sie einen Komplettanbieter, wenn benutzerdefiniertes Authentifizierungs-, Filter-, Aktualisierungs- oder Streaming-Verhalten erforderlich ist. Pi erstellt models.json Überschreibungen über registrierten nativen Anbietern.
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
}
]
});
}Die Erweiterungsfabrik kann auch async sein. Für die dynamische Modellerkennung holen und registrieren Sie Modelle in der Fabrik statt session_start. pi wartet auf die Factory, bevor der Startvorgang fortgesetzt wird, sodass der Anbieter während des interaktiven Startvorgangs und für pi --list-models verfügbar ist.
Vorhandenen Anbieter überschreiben
Der einfachste Anwendungsfall: Einen bestehenden Anbieter über einen Proxy umleiten.
// 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
}
});Wenn nur baseUrl und/oder headers bereitgestellt werden (kein models), bleiben alle vorhandenen Modelle für diesen Anbieter mit dem neuen Endpunkt erhalten.
Neuen Anbieter registrieren
Um einen völlig neuen Anbieter hinzuzufügen, geben Sie models zusammen mit der erforderlichen Konfiguration an.
Wenn die Modellliste von einem Remote-Endpunkt stammt, verwenden Sie eine asynchrone Erweiterungsfactory:
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,
})),
});
}Dadurch werden die abgerufenen Modelle registriert, bevor der Startvorgang abgeschlossen ist.
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
}
]
});Wenn models bereitgestellt wird, ersetzt es alle vorhandenen Modelle für diesen Anbieter.
apiKey und benutzerdefinierte Header-Werte verwenden dieselbe Konfigurationswertsyntax wie models.json: !command führt beim Start einen Befehl für den gesamten Wert aus, $ENV_VAR und ${ENV_VAR} interpolieren Umgebungsvariablen, $ gibt ein Literal ``apiKeyund benutzerdefinierte Header-Werte verwenden dieselbe Konfigurationswertsyntax wiemodels.json: !commandführt beim Start einen Befehl für den gesamten Wert aus,$ENV_VARund${ENV_VAR}interpolieren Umgebungsvariablen,$gibt ein Literal aus und$!gibt ein Literal aus!`.
Anbieter abmelden
Mit pi.unregisterProvider(name) können Sie einen Anbieter entfernen, der zuvor über pi.registerProvider(name,...) registriert wurde:
// 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");Durch die Aufhebung der Registrierung werden die dynamischen Modelle, API key Fallback, OAuth Anbieterregistrierung und benutzerdefinierte Stream-Handler-Registrierungen dieses Anbieters entfernt. Alle integrierten Modelle oder Anbieterverhalten, die überschrieben wurden, werden wiederhergestellt.
Anrufe, die nach der ersten Ladephase der Erweiterung getätigt werden, werden sofort angewendet, sodass kein /reload erforderlich ist.
API Typen
Das Feld api bestimmt, welche Streaming-Implementierung verwendet wird:
| API | Verwendung für |
|---|---|
anthropic-messages |
Anthropic Claude API und kompatible |
openai-completions |
OpenAI-Chat-Abschlüsse API und kompatible |
openai-responses |
OpenAI-Antworten API |
azure-openai-responses |
Azure OpenAI-Antworten API |
openai-codex-responses |
OpenAI-Codex-Antworten API |
mistral-conversations |
Native Mistral Chat Completions-Streaming |
google-generative-ai |
Generative KI von Google API |
google-vertex |
Google Vertex AI API |
bedrock-converse-stream |
Amazon Bedrock Converse API |
Die meisten OpenAI-kompatiblen Anbieter arbeiten mit openai-completions. Verwenden Sie die Modellebene thinkingLevelMap für modellspezifische Denkebenen und compat für Anbieter-Eigenheiten. Die Ebenen xhigh und max sind optional, erfordern Nicht-Null-Karteneinträge und können durch nicht unterstützte Lücken getrennt sein:
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
}
}]Verwenden Sie openrouter für reasoning: { effort }-Steuerelemente im OpenRouter-Stil. Verwenden Sie together für reasoning: { enabled }-Steuerelemente im Together-Stil. mit supportsReasoningEffort sendet es auch reasoning_effort. Verwenden Sie qwen-chat-template für lokale Qwen-kompatible Server, die chat_template_kwargs.enable_thinking lesen und preserve_thinking benötigen.
Verwenden Sie cacheControlFormat: "anthropic" für OpenAI-kompatible Anbieter, die Eingabeaufforderungs-Caching im Anthropic-Stil über cache_control für die Systemeingabeaufforderung, die letzte Tooldefinition und den Textinhalt des letzten Benutzers, Assistenten oder Toolergebnisses verfügbar machen.
Für Anthropic-kompatible Anbieter, die api: "anthropic-messages" verwenden, setzen Sie compat.forceAdaptiveThinking: true für Modelle oder Anbieter, deren Upstream-Modell adaptives Denken erfordert (thinking.type: "adaptive" plus output_config.effort). Integrierte adaptive Claude-Modelle stellen dies automatisch ein. Legen Sie compat.allowEmptySignature: true nur für Anbieter fest, die leere Denksignaturen aussenden und bei der Wiedergabe signature: "" erwarten.
Migrationshinweis: Mistral ist von
openai-completionsaufmistral-conversationsumgezogen. Verwenden Siemistral-conversationsfür native Mistral-Modelle. Wenn Sie Mistral-kompatible/benutzerdefinierte Endpunkte absichtlich überopenai-completionsweiterleiten, legen Sie diecompat-Flags explizit nach Bedarf fest.
Auth-Header
Wenn Ihr Anbieter Authorization: Bearer <key> erwartet, aber keinen Standard API verwendet, legen Sie authHeader: true fest:
pi.registerProvider("custom-api", {
baseUrl: "https://api.example.com",
apiKey: "$MY_API_KEY",
authHeader: true, // adds Authorization: Bearer header
api: "openai-completions",
models: [...]
});Der Schlüssel wird für jede Anfrage aufgelöst. Ein expliziter Anforderungsheader Authorization hat Vorrang vor dem generierten Wert.
OAuth Unterstützung
Fügen Sie die OAuth/SSO-Authentifizierung hinzu, die in /login integriert ist:
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;
}
}
});Nach der Registrierung können sich Benutzer über /login corporate-ai authentifizieren.
OAuthLoginCallbacks
Das callbacks-Objekt stellt UI-neutrale Interaktionen für den anbietereigenen Flow bereit:
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>;
}OAuthAnmeldeinformationen
Anmeldeinformationen bleiben in ~/.pi/agent/auth.json erhalten:
interface OAuthCredentials {
refresh: string; // Refresh token (for refreshToken())
access: string; // Access token (returned by getApiKey())
expires: number; // Expiration timestamp in milliseconds
}Benutzerdefiniertes Streaming API
Implementieren Sie für Anbieter mit nicht standardmäßigen APIs streamSimple. Studieren Sie die vorhandenen Anbieterimplementierungen, bevor Sie Ihre eigene schreiben:
Referenzimplementierungen:
- anthropic.ts – Anthropische Botschaften API
- mistral.ts – Mistral-Gespräche API
- openai-completions.ts – OpenAI-Chat-Abschlüsse
- openai-responses.ts – OpenAI-Antworten API
- google.ts – Google Generative AI
- amazon-bedrock.ts – AWS-Grundgestein
Stream-Muster
Alle Anbieter folgen dem gleichen Muster:
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;
}Ereignistypen
Push-Ereignisse über stream.push() in dieser Reihenfolge:
{ type: "start", partial: output }– Stream gestartetInhaltsereignisse (wiederholbar, Spur
contentIndexfür jeden Block):{ type: "text_start", contentIndex, partial }– Textblock gestartet{ type: "text_delta", contentIndex, delta, partial }– Textblock{ type: "text_end", contentIndex, content, partial }– Textblock beendet{ type: "thinking_start", contentIndex, partial }- Das Nachdenken hat begonnen{ type: "thinking_delta", contentIndex, delta, partial }– Denkblock{ type: "thinking_end", contentIndex, content, partial }– Das Denken ist beendet{ type: "toolcall_start", contentIndex, partial }– Werkzeugaufruf gestartet{ type: "toolcall_delta", contentIndex, delta, partial }– Werkzeugaufruf JSON Chunk{ type: "toolcall_end", contentIndex, toolCall, partial }– Werkzeugaufruf beendet
{ type: "done", reason, message }oder{ type: "error", reason, error }– Stream beendet
Das partial-Feld in jedem Ereignis enthält den aktuellen AssistantMessage-Status. Aktualisieren Sie output.content, sobald Sie Daten erhalten, und fügen Sie dann output als partial hinzu.
Inhaltsblöcke
Fügen Sie Inhaltsblöcke zu output.content hinzu, sobald sie eintreffen:
// 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 });Werkzeugaufrufe
Toolaufrufe erfordern das Sammeln von JSON und das Parsen:
// 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
});Nutzung und Kosten
Aktualisieren Sie die Nutzung von API Antwort und berechnen Sie die Kosten:
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);Kontextüberlauffehler
Wenn eine Anfrage das Kontextfenster des Modells überschreitet, kann Pi automatisch wiederhergestellt werden, indem die Konversation komprimiert und erneut versucht wird. Diese Wiederherstellung setzt nur dann ein, wenn Pi den Fehler als Überlauf erkennt.
Die Erkennung erfolgt anhand der finalisierten Assistentennachricht:
stopReason === "error"errorMessageentspricht einem der bekannten Überlaufmuster von pi (siehepackages/ai/src/utils/overflow.ts)
Wenn Ihr Anbieter Überlauffehler mit einer Meldung zurückgibt, die pi nicht erkennt, normalisieren Sie den Fehler über dieselbe Erweiterung, die den Anbieter registriert. Verwenden Sie einen message_end-Handler, um die Assistentennachricht so umzuschreiben, dass ihre errorMessage mit einer Phrase beginnt, die pi erkennt. Der generische Fallback context_length_exceeded ist die sicherste Wahl.
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 wird ausgeführt, bevor Pi die Assistentenmeldung für die automatische Komprimierung verfolgt, sodass Pi das umgeschriebene errorMessage prüft. Wenn dies eingerichtet ist, wird pi:
- Erkennen Sie den Überlauf von
errorMessage. - Löschen Sie die fehlgeschlagene Assistentennachricht aus dem Live-Kontext.
- Führen Sie die Komprimierung aus.
- Wiederholen Sie die Anfrage einmal.
Bewahren Sie die Umschreibung sorgfältig auf:
- Ordnen Sie es Ihrem Provider zu (
message.providerundctx.model?.provider), sodass unabhängige Fehler von anderen Providern unberührt bleiben. - Entspricht einem anbieterspezifischen Muster, nicht den generischen Überlaufmustern von pi. Das Umschreiben von Ratenbegrenzungs- oder Drosselungsfehlern (
rate limit,too many requests) würde fälschlicherweise eine Komprimierung anstelle des normalen Pi-Wiederholungspfads mit Backoff auslösen. - Überspringen, wenn
errorMessagebereitscontext_length_exceededenthält, sodass der Handler idempotent ist.
Anmeldung
Registrieren Sie Ihre Stream-Funktion:
pi.registerProvider("my-provider", {
baseUrl: "https://api.example.com",
apiKey: "$MY_API_KEY",
api: "my-custom-api",
models: [...],
streamSimple: streamMyProvider
});Testen Sie Ihre Implementierung
Testen Sie Ihren Anbieter anhand derselben Testsuiten, die auch von integrierten Anbietern verwendet werden. Kopieren Sie diese Testdateien von packages/ai/test/ und passen Sie sie an:
| Prüfen | Zweck |
|---|---|
stream.test.ts |
Grundlegendes Streaming, Textausgabe |
tokens.test.ts |
Token-Zählung und -Nutzung |
abort.test.ts |
AbortSignal-Behandlung |
empty.test.ts |
Leere/minimale Antworten |
context-overflow.test.ts |
Grenzen des Kontextfensters |
image-limits.test.ts |
Handhabung der Bildeingabe |
unicode-surrogate.test.ts |
Unicode-Randfälle |
tool-call-without-result.test.ts |
Randfälle von Werkzeugaufrufen |
image-tool-result.test.ts |
Bilder in Tool-Ergebnissen |
total-tokens.test.ts |
Gesamt-Token-Berechnung |
cross-provider-handoff.test.ts |
Kontextübergabe zwischen Anbietern |
Führen Sie Tests mit Ihren Anbieter-/Modellpaaren durch, um die Kompatibilität zu überprüfen.
Konfigurationsreferenz
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;
};
}Modelldefinitionsreferenz
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 sendet reasoning: { effort }. deepseek sendet thinking: { type: "enabled" | "disabled" } und reasoning_effort, wenn aktiviert. together sendet reasoning: { enabled } und auch reasoning_effort, wenn supportsReasoningEffort aktiviert ist. qwen steht für die oberste Ebene im DashScope-Stil enable_thinking. Verwenden Sie qwen-chat-template für lokale Qwen-kompatible Server, die chat_template_kwargs.enable_thinking lesen und preserve_thinking benötigen. Verwenden Sie chat-template für konfigurierbare chat_template_kwargs, zum Beispiel DeepSeek V3.x hinter vLLM mit chatTemplateKwargs: { "thinking": { "$var": "thinking.enabled" } }. Verwenden Sie thinkingFormat: "baseten" mit chatTemplateArgs, wenn der Anbieter Umschaltwerte unter chat_template_args erwartet und optional reasoning_effort der obersten Ebene unterstützt.
cacheControlFormat: "anthropic" wendet cache_control-Markierungen im Anthropic-Stil auf die Systemeingabeaufforderung, die letzte Werkzeugdefinition und den Textinhalt des letzten Benutzers, Assistenten oder Werkzeugergebnisses an.