feat(danisman): profil onayı kredi harcamaz; yapay zekâ sağlayıcı adresi .env'den (AI_BASE_URL/AI_API_KEY)
- Bilal kararı (26 Eyl 2026): 'kaydedeyim mi?' sorusuna kısa evet/hayır cevabı bedelsiz. Sunucu son danışman mesajında onay ibaresini arar, mesaj ≤40 karakter ve evet/hayır kalıbıyla başlıyorsa kredi düşmez, ledger'a yazılmaz, iade yolu atlanır; X-Kredi-Dusuldu başlığıyla istemci bakiyeyi buna göre günceller. Gerçek soru içeren 'evet ama …' bedava geçmez. - Bilal kararı: model Türkiye'de barındırılacak. client.ts baseURL ve anahtar AI_BASE_URL/AI_API_KEY'den okunur (OPENROUTER_* geriye uyumlu); sağlayıcı değişince kod değil .env değişir, /gizlilik aynı commit'te. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
@@ -1,9 +1,29 @@
|
||||
import OpenAI from "openai";
|
||||
|
||||
// Tüm AI akışı OpenRouter üzerinden tek kapıdan gider (kullanıcı kararı,
|
||||
// 7 Ağu 2026 — önceki Anthropic/Gemini/lokal sağlayıcı seçimi kaldırıldı).
|
||||
// Model .env'den değiştirilebilir; rapor ve sohbet istenirse ayrışabilir.
|
||||
// Tüm AI akışı OpenAI uyumlu TEK kapıdan gider. Varsayılan OpenRouter
|
||||
// (7 Ağu 2026 kararı); 26 Eyl 2026 kararıyla model Türkiye'de barındırılacak:
|
||||
// sağlayıcı değişince kod değil .env değişir — AI_BASE_URL + AI_API_KEY
|
||||
// (OpenRouter için eski OPENROUTER_* adları da çalışır). Sağlayıcı değişirse
|
||||
// /gizlilik "Hizmet sağlayıcılar" bölümü aynı commit'te güncellenir.
|
||||
const VARSAYILAN_MODEL = "deepseek/deepseek-v4-flash";
|
||||
const VARSAYILAN_BASE_URL = "https://openrouter.ai/api/v1";
|
||||
|
||||
function baseUrl(): string {
|
||||
return process.env.AI_BASE_URL?.trim() || VARSAYILAN_BASE_URL;
|
||||
}
|
||||
|
||||
function apiKey(): string | undefined {
|
||||
return process.env.AI_API_KEY?.trim() || process.env.OPENROUTER_API_KEY?.trim();
|
||||
}
|
||||
|
||||
/** OpenRouter dışı bir sağlayıcı tanımlı mı (gizlilik metni ve log için). */
|
||||
export function saglayiciAdi(): string {
|
||||
try {
|
||||
return new URL(baseUrl()).hostname;
|
||||
} catch {
|
||||
return "bilinmiyor";
|
||||
}
|
||||
}
|
||||
|
||||
export function raporModeli(): string {
|
||||
return (
|
||||
@@ -23,20 +43,20 @@ export function sohbetModeli(): string {
|
||||
|
||||
/** API anahtarı tanımlı mı (mock moduna düşülecek mi)? */
|
||||
export function anahtarVar(): boolean {
|
||||
return Boolean(process.env.OPENROUTER_API_KEY);
|
||||
return Boolean(apiKey());
|
||||
}
|
||||
|
||||
const globalForAi = globalThis as unknown as { __openrouter?: OpenAI };
|
||||
|
||||
export function getOpenRouter(): OpenAI {
|
||||
const apiKey = process.env.OPENROUTER_API_KEY;
|
||||
if (!apiKey) {
|
||||
const key = apiKey();
|
||||
if (!key) {
|
||||
throw new Error("AI_KEY_MISSING");
|
||||
}
|
||||
if (!globalForAi.__openrouter) {
|
||||
globalForAi.__openrouter = new OpenAI({
|
||||
baseURL: "https://openrouter.ai/api/v1",
|
||||
apiKey,
|
||||
baseURL: baseUrl(),
|
||||
apiKey: key,
|
||||
// Tek retry otoritesi cagri.ts'teki geciciHatadaTekrarla
|
||||
maxRetries: 0,
|
||||
// OpenRouter panelindeki kullanım dökümünde uygulama atfı (opsiyonel)
|
||||
|
||||
@@ -137,6 +137,33 @@ export async function spendCreditForMessage(opts: {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Kredi DÜŞMEDEN kullanıcı mesajı kaydeder — yalnız profil onayı ("kaydedeyim
|
||||
* mi?" → "evet") için (Bilal kararı, 26 Eyl 2026: onay ücretsiz). Ledger'a
|
||||
* satır yazılmaz; clientMessageId çakışırsa DUPLICATE.
|
||||
*/
|
||||
export async function bedelsizMesajKaydet(opts: {
|
||||
userId: string;
|
||||
content: string;
|
||||
clientMessageId: string;
|
||||
}): Promise<SpendResult & { messageId?: string }> {
|
||||
const messageId = newId();
|
||||
try {
|
||||
await appDb.insert(chatMessages).values({
|
||||
id: messageId,
|
||||
userId: opts.userId,
|
||||
role: "user",
|
||||
content: opts.content,
|
||||
clientMessageId: opts.clientMessageId,
|
||||
createdAt: new Date(),
|
||||
});
|
||||
return { ok: true, messageId };
|
||||
} catch (err) {
|
||||
if (uniqueIhlaliMi(err)) return { ok: false, error: "DUPLICATE" };
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
|
||||
/** `paketTekil` ile çağrılan tanımlamada kullanıcı zaten paketliyse fırlatılır. */
|
||||
class ZatenPaketli extends Error {}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user