Pi için yapılandırma, özelleştirme, platform kurulumu ve API referansları.

Özel Providers

Extensions özel model sağlayıcılarını pi.registerProvider() aracılığıyla kaydedebilir. Bu şunları sağlar:

  • Proxy'ler - İstekleri kurumsal proxy'ler veya API ağ geçitleri aracılığıyla yönlendirin
  • Özel uç noktalar - Şirket içinde barındırılan veya özel model dağıtımlarını kullanın
  • OAuth/SSO - Kurumsal sağlayıcılar için kimlik doğrulama akışları ekleyin
  • Özel API'ler - Standart olmayan LLM API'ler için akış uygulayın

Örnek Extensions

Bu eksiksiz sağlayıcı örneklerine bakın:

İçindekiler

Hızlı Referans

Extensions tam bir pi-ai Provider kaydedebilir veya eski sağlayıcı yapılandırma formunu kullanabilir. Özel kimlik doğrulama, filtreleme, yenileme veya akış davranışı gerektiğinde eksiksiz bir sağlayıcıyı tercih edin. Pi, kayıtlı yerel sağlayıcıların üzerindeki models.json geçersiz kılmaları oluşturur.

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
      }
    ]
  });
}

Uzantı fabrikası da async olabilir. Dinamik model keşfi için, modelleri session_start yerine fabrikaya getirin ve kaydedin. pi, başlatma devam etmeden önce fabrikayı bekler, böylece sağlayıcı etkileşimli başlatma sırasında ve pi --list-models'ye hazır olur.

Mevcut Sağlayıcıyı Geçersiz Kıl

En basit kullanım durumu: Mevcut bir sağlayıcıyı bir proxy aracılığıyla yeniden yönlendirmek.

// 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
  }
});

Yalnızca baseUrl ve/veya headers sağlandığında (models yok), o sağlayıcı için mevcut tüm modeller yeni uç noktayla korunur.

Yeni Sağlayıcıyı Kaydedin

Tamamen yeni bir sağlayıcı eklemek için gerekli yapılandırmayla birlikte models belirtin.

Model listesi uzak bir uç noktadan geliyorsa eşzamansız bir uzantı fabrikası kullanın:

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,
    })),
  });
}

Bu, getirilen modelleri başlatma tamamlanmadan önce kaydeder.

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
    }
  ]
});

models sağlandığında, o sağlayıcı için mevcut tüm modellerin yerini alır.

apiKey ve özel başlık değerleri, models.json ile aynı yapılandırma değeri sözdizimini kullanır: !command başlangıçta tüm değer için bir komut yürütür, $ENV_VAR ve ${ENV_VAR} ortam değişkenlerini enterpolasyona tabi tutar, $ bir değişmez değer ``apiKeyve özel başlık değerleri,models.jsonile aynı yapılandırma değeri sözdizimini kullanır:!commandbaşlangıçta tüm değer için bir komut yürütür,$ENV_VARve${ENV_VAR}ortam değişkenlerini enterpolasyona tabi tutar,$bir değişmez değer ve$!bir değişmez değer yayar!`.

Sağlayıcının Kaydını İptal Et

Daha önce pi.registerProvider(name,...) aracılığıyla kaydedilen bir sağlayıcıyı kaldırmak için pi.unregisterProvider(name) tuşunu kullanın:

// 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");

Kayıt silme işlemi, sağlayıcının dinamik modellerini, API key geri dönüşünü, OAuth sağlayıcı kaydını ve özel akış işleyicisi kayıtlarını kaldırır. Geçersiz kılınan tüm yerleşik modeller veya sağlayıcı davranışları geri yüklenir.

İlk dahili yükleme aşamasından sonra yapılan çağrılar hemen uygulanır, dolayısıyla /reload gerekli değildir.

API Türler

api alanı hangi akış uygulamasının kullanılacağını belirler:

API Şunun için kullanın:
anthropic-messages Antropik Claude API ve uyumlular
openai-completions OpenAI Sohbet Tamamlamaları API ve uyumlular
openai-responses OpenAI Yanıtları API
azure-openai-responses Azure OpenAI Yanıtları API
openai-codex-responses OpenAI Kodeksi Yanıtları API
mistral-conversations Yerel Mistral Sohbet Tamamlamaları akışı
google-generative-ai Google Üretken Yapay Zeka API
google-vertex Google Vertex AI API
bedrock-converse-stream Amazon Bedrock Converse API

Çoğu OpenAI uyumlu sağlayıcı openai-completions ile çalışır. Modele özgü düşünme düzeyleri için model düzeyi thinkingLevelMap'yi ve sağlayıcı tuhaflıkları için compat'yi kullanın. xhigh ve max düzeyleri isteğe bağlıdır, boş olmayan harita girişleri gerektirir ve desteklenmeyen deliklerle ayrılabilir:

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
  }
}]

OpenRouter tarzı reasoning: { effort } kontrolleri için openrouter kullanın. Birlikte tarzı reasoning: { enabled } kontrolleri için together kullanın; supportsReasoningEffort ile reasoning_effort'yi de gönderir. chat_template_kwargs.enable_thinking okuyan ve preserve_thinking'ye ihtiyaç duyan yerel Qwen uyumlu sunucular için qwen-chat-template kullanın. Sistem isteminde, son araç tanımında ve son kullanıcı, asistan veya araç sonucu metin içeriğinde cache_control aracılığıyla Antropik tarzda bilgi istemi önbelleğe almayı ortaya çıkaran OpenAI uyumlu sağlayıcılar için cacheControlFormat: "anthropic" kullanın.

api: "anthropic-messages" kullanan Antropik uyumlu sağlayıcılar için, yukarı akış modeli uyarlamalı düşünme gerektiren modellere veya sağlayıcılara (thinking.type: "adaptive" artı output_config.effort) compat.forceAdaptiveThinking: true değerini ayarlayın. Yerleşik uyarlanabilir Claude modelleri bunu otomatik olarak ayarlar. compat.allowEmptySignature: true'yi yalnızca boş düşünme imzaları yayan ve tekrar oynatıldığında signature: "" bekleyen sağlayıcılar için ayarlayın.

Geçiş notu: Mistral openai-completions'den mistral-conversations'ye taşındı. Yerel Mistral modelleri için mistral-conversations kullanın. Mistral uyumlu/özel uç noktaları kasıtlı olarak openai-completions üzerinden yönlendiriyorsanız, compat işaretlerini gerektiği gibi açıkça ayarlayın.

Kimlik Doğrulama Başlığı

Sağlayıcınız Authorization: Bearer <key> bekliyor ancak standart API kullanmıyorsa authHeader: true değerini ayarlayın:

pi.registerProvider("custom-api", {
  baseUrl: "https://api.example.com",
  apiKey: "$MY_API_KEY",
  authHeader: true,  // adds Authorization: Bearer header
  api: "openai-completions",
  models: [...]
});

Anahtar her istek için çözümlenir. Açık bir istek Authorization başlığı, oluşturulan değere göre önceliklidir.

OAuth Destek

/login ile entegre olan OAuth/SSO kimlik doğrulamasını ekleyin:

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;
    }
  }
});

Kayıt olduktan sonra kullanıcılar /login corporate-ai aracılığıyla kimlik doğrulaması yapabilir.

OAuthOturum AçmaGeri Aramalar

callbacks nesnesi, sağlayıcının sahip olduğu akış için kullanıcı arayüzünden bağımsız etkileşimler sağlar:

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>;
}

OAuthKimlik Bilgileri

Kimlik bilgileri ~/.pi/agent/auth.json'de kalıcıdır:

interface OAuthCredentials {
  refresh: string;   // Refresh token (for refreshToken())
  access: string;    // Access token (returned by getApiKey())
  expires: number;   // Expiration timestamp in milliseconds
}

Özel Yayın API

Standart olmayan API'lere sahip sağlayıcılar için streamSimple'yi uygulayın. Kendi uygulamanızı yazmadan önce mevcut sağlayıcı uygulamalarını inceleyin:

Referans uygulamalar:

Akış Deseni

Tüm sağlayıcılar aynı modeli izler:

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;
}

Etkinlik Türleri

Olayları stream.push() aracılığıyla şu sırayla aktarın:

  1. { type: "start", partial: output } - Yayın başladı

  2. İçerik etkinlikleri (tekrarlanabilir, her blok için contentIndex parça):

    • { type: "text_start", contentIndex, partial } - Metin bloğu başlatıldı
    • { type: "text_delta", contentIndex, delta, partial } - Metin öbeği
    • { type: "text_end", contentIndex, content, partial } - Metin bloğu sona erdi
    • { type: "thinking_start", contentIndex, partial } - Düşünmeye başlandı
    • { type: "thinking_delta", contentIndex, delta, partial } - Düşünme öbeği
    • { type: "thinking_end", contentIndex, content, partial } - Düşünme sona erdi
    • { type: "toolcall_start", contentIndex, partial } - Araç çağrısı başlatıldı
    • { type: "toolcall_delta", contentIndex, delta, partial } - Araç çağrısı JSON öbeği
    • { type: "toolcall_end", contentIndex, toolCall, partial } - Araç çağrısı sona erdi
  3. { type: "done", reason, message } veya { type: "error", reason, error } - Yayın sona erdi

Her etkinlikteki partial alanı mevcut AssistantMessage durumunu içerir. Verileri aldıkça output.content'yi güncelleyin, ardından partial olarak output'yi ekleyin.

İçerik Blokları

output.content'ye ulaştıkça içerik blokları ekleyin:

// 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 });

Araç Çağrıları

Araç çağrıları JSON biriktirmeyi ve ayrıştırmayı gerektirir:

// 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
});

Kullanım ve Maliyet

API yanıtından kullanımı güncelleyin ve maliyeti hesaplayın:

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);

Bağlam Taşması Hataları

Bir istek modelin bağlam penceresini aştığında pi, konuşmayı sıkıştırıp yeniden deneyerek otomatik olarak kurtarılabilir. Bu kurtarma yalnızca Pi'nin arızayı bir taşma olarak algılaması durumunda devreye girer.

Algılama, sonlandırılan asistan mesajı üzerinde çalışır:

Sağlayıcınız pi'nin tanımadığı bir mesajla taşma hataları döndürürse, sağlayıcıyı kaydeden aynı uzantıdan hatayı normalleştirin. Asistan mesajını, errorMessage pi'nin tanıdığı bir ifadeyle başlayacak şekilde yeniden yazmak için bir message_end işleyicisi kullanın. Genel geri dönüş context_length_exceeded en güvenli seçimdir.

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, pi'nin otomatik sıkıştırma için asistan mesajını izlemesinden önce çalışır, dolayısıyla yeniden yazılan errorMessage, pi'nin kontrol ettiği şeydir. Bunu yerine getirdiğimizde pi şunları yapacaktır:

  1. errorMessage'den taşmayı tespit edin.
  2. Başarısız olan asistan mesajını canlı bağlamdan bırakın.
  3. Sıkıştırmayı çalıştırın.
  4. İsteği bir kez yeniden deneyin.

Yeniden yazmayı dikkatli bir şekilde koruyun:

  • Bunu sağlayıcınızın kapsamına alın (message.provider ve ctx.model?.provider), böylece diğer sağlayıcılardan gelen ilgisiz hatalara dokunulmaz.
  • Pi'nin genel taşma modellerini değil, sağlayıcıya özel bir modeli eşleştirin. Hız sınırı veya azaltma hatalarının (rate limit, too many requests) yeniden yazılması, pi'nin normal geri çekme ile yeniden deneme yolu yerine yanlışlıkla sıkıştırmayı tetikler.
  • errorMessage zaten context_length_exceeded içerdiğinde, işleyicinin önemsiz olması durumunda atlayın.

Kayıt

Akış işlevinizi kaydedin:

pi.registerProvider("my-provider", {
  baseUrl: "https://api.example.com",
  apiKey: "$MY_API_KEY",
  api: "my-custom-api",
  models: [...],
  streamSimple: streamMyProvider
});

Uygulamanızı Test Etme

Sağlayıcınızı, yerleşik sağlayıcılar tarafından kullanılan aynı test paketleriyle karşılaştırarak test edin. Bu test dosyalarını packages/ai/test/ adresinden kopyalayıp uyarlayın:

Test Amaç
stream.test.ts Temel akış, metin çıkışı
tokens.test.ts Jeton sayımı ve kullanımı
abort.test.ts AbortSinyal yönetimi
empty.test.ts Boş/minimum yanıtlar
context-overflow.test.ts Bağlam penceresi sınırları
image-limits.test.ts Görüntü girişi yönetimi
unicode-surrogate.test.ts Unicode uç durumları
tool-call-without-result.test.ts Araç çağrısı uç durumları
image-tool-result.test.ts Araç sonuçlarındaki resimler
total-tokens.test.ts Toplam jeton hesaplaması
cross-provider-handoff.test.ts Sağlayıcılar arasında bağlam aktarımı

Uyumluluğu doğrulamak için sağlayıcınız/model çiftlerinizle testler yapın.

Yapılandırma Referansı

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;
  };
}

Model Tanımı Referansı

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 reasoning: { effort } gönderir. deepseek etkinleştirildiğinde thinking: { type: "enabled" | "disabled" } ve reasoning_effort gönderir. together, reasoning: { enabled }'yi ve ayrıca supportsReasoningEffort etkinleştirildiğinde reasoning_effort'yi gönderir. qwen DashScope tarzı üst düzey enable_thinking içindir. chat_template_kwargs.enable_thinking okuyan ve preserve_thinking'ye ihtiyaç duyan yerel Qwen uyumlu sunucular için qwen-chat-template kullanın. Yapılandırılabilir chat_template_kwargs için chat-template kullanın, örneğin chatTemplateKwargs: { "thinking": { "$var": "thinking.enabled" } } ile vLLM'nin arkasında DeepSeek V3.x. Sağlayıcı chat_template_args'nin altında geçiş değerleri beklediğinde ve isteğe bağlı olarak üst düzey reasoning_effort'yi desteklediğinde thinkingFormat: "baseten"'yi chatTemplateArgs ile birlikte kullanın. cacheControlFormat: "anthropic" sistem istemine, son araç tanımına ve son kullanıcı, asistan veya araç sonucu metin içeriğine Antropik stil cache_control işaretleyicileri uygular.