fix(portable): Gemini keys, Telegram pairing UI, and config durability
Improve first-run and channel setup for non-technical users: detect newer Gemini key formats, pin Node 22.22.3, add Control Panel Telegram approve flow, and keep channels/models when config is rewritten. Persist uclaw wizard state via uclaw-meta.json so restarts skip language/persona prompts. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -27,10 +27,12 @@ const PROVIDERS = [
|
||||
{
|
||||
id: 'google',
|
||||
label: 'Google Gemini',
|
||||
prefixes: ['AIza'],
|
||||
// Google AI Studio now issues Auth keys (AQ.Ab…) alongside legacy Standard keys (AIzaSy…).
|
||||
prefixes: ['AQ.', 'AIza'],
|
||||
baseUrl: 'https://generativelanguage.googleapis.com/v1beta/openai',
|
||||
model: 'gemini-2.5-flash',
|
||||
models: ['gemini-2.5-flash', 'gemini-2.5-pro'],
|
||||
// New AI Studio accounts often cannot use 2.5-flash yet (404). Try 2.0 first.
|
||||
model: 'gemini-2.0-flash',
|
||||
models: ['gemini-2.0-flash', 'gemini-flash-latest', 'gemini-2.5-flash', 'gemini-2.5-flash-lite', 'gemini-2.5-pro'],
|
||||
},
|
||||
{
|
||||
id: 'groq',
|
||||
@@ -75,7 +77,7 @@ export function detectProvider(rawKey) {
|
||||
export function classifyFailure({ status, code } = {}) {
|
||||
if (code === 'ENOTFOUND' || code === 'EAI_AGAIN' || code === 'ECONNREFUSED') return 'key.err_offline';
|
||||
if (code === 'ETIMEDOUT' || code === 'UND_ERR_CONNECT_TIMEOUT' || code === 'ABORT_ERR') return 'key.err_timeout';
|
||||
if (status === 401 || status === 403) return 'key.err_rejected';
|
||||
if (status === 400 || status === 401 || status === 403) return 'key.err_rejected';
|
||||
if (status === 429) return 'key.err_quota';
|
||||
if (status === 404) return 'key.err_model';
|
||||
if (typeof status === 'number' && status >= 500) return 'key.err_provider_down';
|
||||
@@ -85,3 +87,32 @@ export function classifyFailure({ status, code } = {}) {
|
||||
export function listProviders() {
|
||||
return PROVIDERS.map(({ id, label, baseUrl, model, models }) => ({ id, label, baseUrl, model, models }));
|
||||
}
|
||||
|
||||
// Google rotates model availability per account. When the first choice 404s, try the rest.
|
||||
export function modelsToTry(provider, requestedModel) {
|
||||
const primary = String(requestedModel || provider?.model || '').trim();
|
||||
if (provider?.id === 'google') {
|
||||
return [...new Set([primary, provider.model, ...(provider.models || [])].filter(Boolean))];
|
||||
}
|
||||
return primary ? [primary] : [];
|
||||
}
|
||||
|
||||
// AQ auth keys only work on the OpenAI-compatible surface. Ask Google which models
|
||||
// this key can actually see instead of guessing from a static list.
|
||||
export async function discoverGoogleOpenAiModels(baseUrl, apiKey) {
|
||||
const root = String(baseUrl || '').replace(/\/+$/, '');
|
||||
if (!root || !apiKey) return [];
|
||||
try {
|
||||
const res = await fetch(`${root}/models`, {
|
||||
headers: { Authorization: `Bearer ${apiKey}` },
|
||||
});
|
||||
if (!res.ok) return [];
|
||||
const body = await res.json();
|
||||
const rows = body.data || body.models || [];
|
||||
return rows
|
||||
.map((m) => String(m.id || m.name || '').replace(/^models\//, ''))
|
||||
.filter(Boolean);
|
||||
} catch {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user