Konfiguration, Anpassung, Plattform-Einrichtung und API-Referenzen für Pi.

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

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-completions auf mistral-conversations umgezogen. Verwenden Sie mistral-conversations für native Mistral-Modelle. Wenn Sie Mistral-kompatible/benutzerdefinierte Endpunkte absichtlich über openai-completions weiterleiten, legen Sie die compat-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:

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:

  1. { type: "start", partial: output } – Stream gestartet

  2. Inhaltsereignisse (wiederholbar, Spur contentIndex fü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
  3. { 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:

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:

  1. Erkennen Sie den Überlauf von errorMessage.
  2. Löschen Sie die fehlgeschlagene Assistentennachricht aus dem Live-Kontext.
  3. Führen Sie die Komprimierung aus.
  4. Wiederholen Sie die Anfrage einmal.

Bewahren Sie die Umschreibung sorgfältig auf:

  • Ordnen Sie es Ihrem Provider zu (message.provider und ctx.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 errorMessage bereits context_length_exceeded enthä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.