mirror of
https://github.com/bilawalsidhu/gods-eye-view.git
synced 2026-11-06 03:14:00 +00:00
fix(voice): keep a stopped round's actions and follow-up memory
A reply stopped partway through a round now keeps the actions that ran, answering the calls it never reached as skipped, so the history stays paired for both providers. A failed typed turn no longer resets the pointer, referents and follow-up memory, since its conversation continues. The server now tells a rejected key (401, or Gemini's 400 with reason API_KEY_INVALID, matched exactly), denied access (403) and an account out of credit (OpenAI's 429 insufficient_quota) apart; each cools down for a minute and gets its own message in the chat.
This commit is contained in:
1 parent
9d4d7b3ee0
commit
0a8d824124
19 files changed
+238
-51
No files matched your search
@@ -3293,5 +3293,7 @@
|
||||
"That request needed too many steps. Changes already made to the map stay in place.": "تطلّب هذا الطلب خطوات كثيرة جدًا. تبقى التغييرات التي أُجريت على الخريطة كما هي.",
|
||||
"That request needed too many steps. Try a simpler request.": "تطلّب هذا الطلب خطوات كثيرة جدًا. جرّب طلبًا أبسط.",
|
||||
"The provider rejected the API key. Check it in Provider Settings.": "رفض المزوّد مفتاح API. تحقّق منه في إعدادات المزوّد.",
|
||||
"Too many chat requests. Try again in {seconds} s.": "طلبات دردشة كثيرة جدًا. حاول مجددًا بعد {seconds} ث."
|
||||
"Too many chat requests. Try again in {seconds} s.": "طلبات دردشة كثيرة جدًا. حاول مجددًا بعد {seconds} ث.",
|
||||
"The provider refused access for this key. Check the key's project, region or permissions.": "رفض المزوّد الوصول لهذا المفتاح. تحقّق من مشروع المفتاح أو منطقته أو أذوناته.",
|
||||
"The provider account is out of credit. Check its billing, or choose another AI Provider in Settings.": "نفد رصيد حساب المزوّد. تحقّق من الفوترة، أو اختر مزوّد ذكاء اصطناعي آخر في الإعدادات."
|
||||
}
|
||||
@@ -3013,5 +3013,7 @@
|
||||
"That request needed too many steps. Changes already made to the map stay in place.": "Diese Anfrage brauchte zu viele Schritte. Bereits vorgenommene Kartenänderungen bleiben erhalten.",
|
||||
"That request needed too many steps. Try a simpler request.": "Diese Anfrage brauchte zu viele Schritte. Versuche eine einfachere Anfrage.",
|
||||
"The provider rejected the API key. Check it in Provider Settings.": "Der Anbieter hat den API-Schlüssel abgelehnt. Prüfe ihn in den Anbieter-Einstellungen.",
|
||||
"Too many chat requests. Try again in {seconds} s.": "Zu viele Chat-Anfragen. In {seconds} s erneut versuchen."
|
||||
"Too many chat requests. Try again in {seconds} s.": "Zu viele Chat-Anfragen. In {seconds} s erneut versuchen.",
|
||||
"The provider refused access for this key. Check the key's project, region or permissions.": "Der Anbieter hat den Zugriff für diesen Schlüssel verweigert. Prüfe Projekt, Region oder Berechtigungen des Schlüssels.",
|
||||
"The provider account is out of credit. Check its billing, or choose another AI Provider in Settings.": "Das Anbieterkonto hat kein Guthaben mehr. Prüfe die Abrechnung oder wähle in den Einstellungen einen anderen KI-Anbieter."
|
||||
}
|
||||
@@ -3083,5 +3083,7 @@
|
||||
"That request needed too many steps. Changes already made to the map stay in place.": "Esa solicitud necesitó demasiados pasos. Los cambios ya hechos en el mapa se mantienen.",
|
||||
"That request needed too many steps. Try a simpler request.": "Esa solicitud necesitó demasiados pasos. Prueba una solicitud más sencilla.",
|
||||
"The provider rejected the API key. Check it in Provider Settings.": "El proveedor rechazó la clave de API. Revísala en Ajustes del proveedor.",
|
||||
"Too many chat requests. Try again in {seconds} s.": "Demasiadas solicitudes de chat. Vuelve a intentarlo en {seconds} s."
|
||||
"Too many chat requests. Try again in {seconds} s.": "Demasiadas solicitudes de chat. Vuelve a intentarlo en {seconds} s.",
|
||||
"The provider refused access for this key. Check the key's project, region or permissions.": "El proveedor denegó el acceso a esta clave. Revisa el proyecto, la región o los permisos de la clave.",
|
||||
"The provider account is out of credit. Check its billing, or choose another AI Provider in Settings.": "La cuenta del proveedor no tiene crédito. Revisa la facturación o elige otro proveedor de IA en Ajustes."
|
||||
}
|
||||
@@ -3083,5 +3083,7 @@
|
||||
"That request needed too many steps. Changes already made to the map stay in place.": "Cette demande a nécessité trop d’étapes. Les modifications déjà apportées à la carte sont conservées.",
|
||||
"That request needed too many steps. Try a simpler request.": "Cette demande a nécessité trop d’étapes. Essayez une demande plus simple.",
|
||||
"The provider rejected the API key. Check it in Provider Settings.": "Le fournisseur a refusé la clé d’API. Vérifiez-la dans les paramètres du fournisseur.",
|
||||
"Too many chat requests. Try again in {seconds} s.": "Trop de requêtes de chat. Réessayez dans {seconds} s."
|
||||
"Too many chat requests. Try again in {seconds} s.": "Trop de requêtes de chat. Réessayez dans {seconds} s.",
|
||||
"The provider refused access for this key. Check the key's project, region or permissions.": "Le fournisseur a refusé l’accès pour cette clé. Vérifiez le projet, la région ou les autorisations de la clé.",
|
||||
"The provider account is out of credit. Check its billing, or choose another AI Provider in Settings.": "Le compte du fournisseur n’a plus de crédit. Vérifiez la facturation ou choisissez un autre fournisseur d’IA dans les paramètres."
|
||||
}
|
||||
@@ -3083,5 +3083,7 @@
|
||||
"That request needed too many steps. Changes already made to the map stay in place.": "Questa richiesta ha richiesto troppi passaggi. Le modifiche già fatte alla mappa restano.",
|
||||
"That request needed too many steps. Try a simpler request.": "Questa richiesta ha richiesto troppi passaggi. Prova una richiesta più semplice.",
|
||||
"The provider rejected the API key. Check it in Provider Settings.": "Il provider ha rifiutato la chiave API. Controllala nelle impostazioni del provider.",
|
||||
"Too many chat requests. Try again in {seconds} s.": "Troppe richieste di chat. Riprova tra {seconds} s."
|
||||
"Too many chat requests. Try again in {seconds} s.": "Troppe richieste di chat. Riprova tra {seconds} s.",
|
||||
"The provider refused access for this key. Check the key's project, region or permissions.": "Il provider ha negato l’accesso per questa chiave. Controlla progetto, regione o autorizzazioni della chiave.",
|
||||
"The provider account is out of credit. Check its billing, or choose another AI Provider in Settings.": "L’account del provider ha esaurito il credito. Controlla la fatturazione o scegli un altro provider IA nelle impostazioni."
|
||||
}
|
||||
@@ -2943,5 +2943,7 @@
|
||||
"That request needed too many steps. Changes already made to the map stay in place.": "このリクエストは手順が多すぎました。地図に加えた変更はそのまま残ります。",
|
||||
"That request needed too many steps. Try a simpler request.": "このリクエストは手順が多すぎました。より簡単なリクエストを試してください。",
|
||||
"The provider rejected the API key. Check it in Provider Settings.": "プロバイダーが API キーを拒否しました。プロバイダー設定で確認してください。",
|
||||
"Too many chat requests. Try again in {seconds} s.": "チャットのリクエストが多すぎます。{seconds} 秒後にもう一度お試しください。"
|
||||
"Too many chat requests. Try again in {seconds} s.": "チャットのリクエストが多すぎます。{seconds} 秒後にもう一度お試しください。",
|
||||
"The provider refused access for this key. Check the key's project, region or permissions.": "プロバイダーがこのキーへのアクセスを拒否しました。キーのプロジェクト、地域、権限を確認してください。",
|
||||
"The provider account is out of credit. Check its billing, or choose another AI Provider in Settings.": "プロバイダーのアカウントのクレジットが不足しています。請求を確認するか、設定で別の AI プロバイダーを選んでください。"
|
||||
}
|
||||
@@ -3083,5 +3083,7 @@
|
||||
"That request needed too many steps. Changes already made to the map stay in place.": "Esse pedido precisou de etapas demais. As alterações já feitas no mapa continuam.",
|
||||
"That request needed too many steps. Try a simpler request.": "Esse pedido precisou de etapas demais. Tente um pedido mais simples.",
|
||||
"The provider rejected the API key. Check it in Provider Settings.": "O provedor recusou a chave de API. Verifique-a nas configurações do provedor.",
|
||||
"Too many chat requests. Try again in {seconds} s.": "Pedidos de chat demais. Tente de novo em {seconds} s."
|
||||
"Too many chat requests. Try again in {seconds} s.": "Pedidos de chat demais. Tente de novo em {seconds} s.",
|
||||
"The provider refused access for this key. Check the key's project, region or permissions.": "O provedor negou o acesso para esta chave. Verifique o projeto, a região ou as permissões da chave.",
|
||||
"The provider account is out of credit. Check its billing, or choose another AI Provider in Settings.": "A conta do provedor está sem crédito. Verifique o faturamento ou escolha outro provedor de IA nas configurações."
|
||||
}
|
||||
@@ -3153,5 +3153,7 @@
|
||||
"That request needed too many steps. Changes already made to the map stay in place.": "Этот запрос потребовал слишком много шагов. Уже внесённые изменения карты сохранятся.",
|
||||
"That request needed too many steps. Try a simpler request.": "Этот запрос потребовал слишком много шагов. Попробуйте запрос попроще.",
|
||||
"The provider rejected the API key. Check it in Provider Settings.": "Провайдер отклонил ключ API. Проверьте его в настройках провайдера.",
|
||||
"Too many chat requests. Try again in {seconds} s.": "Слишком много запросов чата. Повторите через {seconds} с."
|
||||
"Too many chat requests. Try again in {seconds} s.": "Слишком много запросов чата. Повторите через {seconds} с.",
|
||||
"The provider refused access for this key. Check the key's project, region or permissions.": "Провайдер запретил доступ для этого ключа. Проверьте проект, регион или разрешения ключа.",
|
||||
"The provider account is out of credit. Check its billing, or choose another AI Provider in Settings.": "На счёте провайдера закончились средства. Проверьте оплату или выберите другого провайдера ИИ в настройках."
|
||||
}
|
||||
@@ -3153,5 +3153,7 @@
|
||||
"That request needed too many steps. Changes already made to the map stay in place.": "Цей запит потребував забагато кроків. Уже внесені зміни на мапі залишаються.",
|
||||
"That request needed too many steps. Try a simpler request.": "Цей запит потребував забагато кроків. Спробуйте простіший запит.",
|
||||
"The provider rejected the API key. Check it in Provider Settings.": "Провайдер відхилив ключ API. Перевірте його в налаштуваннях провайдера.",
|
||||
"Too many chat requests. Try again in {seconds} s.": "Забагато запитів до чату. Спробуйте ще раз через {seconds} с."
|
||||
"Too many chat requests. Try again in {seconds} s.": "Забагато запитів до чату. Спробуйте ще раз через {seconds} с.",
|
||||
"The provider refused access for this key. Check the key's project, region or permissions.": "Провайдер заборонив доступ для цього ключа. Перевірте проєкт, регіон або дозволи ключа.",
|
||||
"The provider account is out of credit. Check its billing, or choose another AI Provider in Settings.": "На рахунку провайдера закінчилися кошти. Перевірте оплату або виберіть іншого провайдера ШІ в налаштуваннях."
|
||||
}
|
||||
@@ -2943,5 +2943,7 @@
|
||||
"That request needed too many steps. Changes already made to the map stay in place.": "此请求所需步骤过多。已对地图做出的更改会保留。",
|
||||
"That request needed too many steps. Try a simpler request.": "此请求所需步骤过多。请尝试更简单的请求。",
|
||||
"The provider rejected the API key. Check it in Provider Settings.": "提供商拒绝了 API 密钥。请在提供商设置中检查。",
|
||||
"Too many chat requests. Try again in {seconds} s.": "聊天请求过多。请在 {seconds} 秒后重试。"
|
||||
"Too many chat requests. Try again in {seconds} s.": "聊天请求过多。请在 {seconds} 秒后重试。",
|
||||
"The provider refused access for this key. Check the key's project, region or permissions.": "提供商拒绝了此密钥的访问。请检查该密钥的项目、地区或权限。",
|
||||
"The provider account is out of credit. Check its billing, or choose another AI Provider in Settings.": "提供商账户余额不足。请检查账单,或在设置中选择其他 AI 提供商。"
|
||||
}
|
||||
@@ -2943,5 +2943,7 @@
|
||||
"That request needed too many steps. Changes already made to the map stay in place.": "此請求所需步驟過多。已對地圖做出的變更會保留。",
|
||||
"That request needed too many steps. Try a simpler request.": "此請求所需步驟過多。請嘗試更簡單的請求。",
|
||||
"The provider rejected the API key. Check it in Provider Settings.": "供應商拒絕了 API 金鑰。請在供應商設定中檢查。",
|
||||
"Too many chat requests. Try again in {seconds} s.": "聊天請求過多。請在 {seconds} 秒後重試。"
|
||||
"Too many chat requests. Try again in {seconds} s.": "聊天請求過多。請在 {seconds} 秒後重試。",
|
||||
"The provider refused access for this key. Check the key's project, region or permissions.": "供應商拒絕了此金鑰的存取。請檢查該金鑰的專案、地區或權限。",
|
||||
"The provider account is out of credit. Check its billing, or choose another AI Provider in Settings.": "供應商帳戶餘額不足。請檢查帳單,或在設定中選擇其他 AI 供應商。"
|
||||
}
|
||||
@@ -442,6 +442,43 @@ for (const [name, provider] of Object.entries(providers)) {
|
||||
await plain.send();
|
||||
assert.equal(upstream, 3);
|
||||
});
|
||||
test(`${name} a 403 or an out-of-credit 429 cools down with its own code`, async (t) => {
|
||||
for (const [status, body, code] of [
|
||||
[
|
||||
403,
|
||||
{ error: { code: 'unsupported_country_region_territory' } },
|
||||
'ACCESS_DENIED',
|
||||
],
|
||||
[429, { error: { code: 'insufficient_quota' } }, 'QUOTA_EXCEEDED'],
|
||||
]) {
|
||||
let upstream = 0;
|
||||
const f = await fixture(t, provider, async () => {
|
||||
upstream++;
|
||||
return Response.json(body, { status });
|
||||
});
|
||||
const first = await f.send();
|
||||
assert.equal(first.status, 502);
|
||||
assert.equal((await first.json()).code, code);
|
||||
const cooling = await f.send();
|
||||
assert.equal((await cooling.json()).code, code);
|
||||
assert.equal(upstream, 1, `${code} is not retried at once`);
|
||||
}
|
||||
});
|
||||
test(`${name} only an exact invalid-key reason marks a 400 as a rejected key`, async (t) => {
|
||||
let upstream = 0;
|
||||
const f = await fixture(t, provider, async () => {
|
||||
upstream++;
|
||||
return Response.json(
|
||||
{ error: { code: 400, message: 'Field API_KEY_INVALID is unknown' } },
|
||||
{ status: 400 },
|
||||
);
|
||||
});
|
||||
const reply = await f.send();
|
||||
assert.equal(reply.status, 502);
|
||||
assert.equal((await reply.json()).code, undefined);
|
||||
await f.send();
|
||||
assert.equal(upstream, 2);
|
||||
});
|
||||
test(`${name} throttled replies say they are rate limited`, async (t) => {
|
||||
const local = await fixture(t, provider, undefined, {
|
||||
resolveRateLimit: () => '1',
|
||||
|
||||
@@ -472,6 +472,24 @@ test('native connecting phases retain output observation; idle starts before fir
|
||||
assert.equal(adapter.stops, 1);
|
||||
});
|
||||
|
||||
test('a failed typed turn keeps follow-up memory; a stop or voice error clears it', async (t) => {
|
||||
let resets = 0;
|
||||
const runner = async () => ({ ok: true });
|
||||
runner.resetConversation = () => resets++;
|
||||
const f = mounted(t, { runner });
|
||||
const adapter = f.instances[0];
|
||||
adapter.getStartOptions = () => ({ textOnly: true });
|
||||
await f.commands.session.start({ textOnly: true });
|
||||
adapter.hooks.emit({ type: 'state', state: 'error', detail: 'Busy' });
|
||||
assert.equal(resets, 0, 'the typed conversation continues');
|
||||
f.commands.session.stop();
|
||||
assert.equal(resets, 1);
|
||||
adapter.getStartOptions = () => ({});
|
||||
await f.commands.session.start();
|
||||
adapter.hooks.emit({ type: 'state', state: 'error', detail: 'Lost' });
|
||||
assert.equal(resets, 2);
|
||||
});
|
||||
|
||||
test('the voice inactivity timeout never ends a typed chat session', async (t) => {
|
||||
const f = mounted(t, { native: true });
|
||||
const adapter = f.instances[0];
|
||||
|
||||
@@ -166,7 +166,12 @@ export function createVoiceCommands({
|
||||
const reticle = pointer ? createReticle(pointer) : null;
|
||||
const updatePointer = session.subscribe((event) => {
|
||||
reticle?.handle(event);
|
||||
if (event.type === 'state' && ['idle', 'error'].includes(event.state)) {
|
||||
// A failed typed turn ends only that reply; its conversation continues.
|
||||
if (
|
||||
event.type === 'state' &&
|
||||
(event.state === 'idle' ||
|
||||
(event.state === 'error' && !adapter.getStartOptions?.().textOnly))
|
||||
) {
|
||||
pointer?.clear();
|
||||
referents?.clear();
|
||||
// Conversation memory ("those", "the last answer") ends with the
|
||||
|
||||
+48
-23
@@ -20,6 +20,7 @@ const HISTORY_BYTES = 180000;
|
||||
const REQUEST_BYTES = 512 * 1024;
|
||||
// Results from earlier turns keep their outcome and say line, not their records.
|
||||
const COMPACT_RESULT_BYTES = 1500;
|
||||
const NOT_RUN = { ok: false, skipped: true, reason: 'reply-stopped' };
|
||||
const PARTIAL_NOTE =
|
||||
'This reply stopped before it finished. The map actions above already ran.';
|
||||
|
||||
@@ -159,15 +160,29 @@ export function createTextChat({
|
||||
// A turn that stops after some map actions ran keeps them, closed by a
|
||||
// note, so the next message knows they happened.
|
||||
let settled = 0;
|
||||
// The round in progress: its calls, and the results of those that ran.
|
||||
let openRound = null;
|
||||
partial = {
|
||||
owner,
|
||||
keep() {
|
||||
if (settled)
|
||||
history = [
|
||||
...past,
|
||||
...contents.slice(0, settled),
|
||||
protocol.note(PARTIAL_NOTE),
|
||||
];
|
||||
const ran = openRound?.pairs.length ? contents.length : settled;
|
||||
if (!ran) return;
|
||||
// Calls that never ran are answered as skipped, so every call in the
|
||||
// kept history stays paired with a result.
|
||||
const closing = openRound?.pairs.length
|
||||
? protocol.results(
|
||||
openRound.calls.map(
|
||||
(call, index) =>
|
||||
openRound.pairs[index] ?? { call, result: NOT_RUN },
|
||||
),
|
||||
)
|
||||
: [];
|
||||
history = [
|
||||
...past,
|
||||
...contents.slice(0, ran),
|
||||
...closing,
|
||||
protocol.note(PARTIAL_NOTE),
|
||||
];
|
||||
},
|
||||
};
|
||||
const encode = () =>
|
||||
@@ -279,6 +294,7 @@ export function createTextChat({
|
||||
totalCalls += calls.length;
|
||||
if (totalCalls > 16) throw tooManySteps('Tool limit');
|
||||
const pairs = [];
|
||||
openRound = { calls, pairs };
|
||||
const invalid = new Map(
|
||||
(data.invalidCalls || []).map((item) => [item.index, item.error]),
|
||||
);
|
||||
@@ -387,6 +403,7 @@ export function createTextChat({
|
||||
}
|
||||
contents.push(...protocol.results(pairs));
|
||||
settled = contents.length;
|
||||
openRound = null;
|
||||
}
|
||||
throw tooManySteps('Round limit');
|
||||
} catch (error) {
|
||||
@@ -404,23 +421,31 @@ export function createTextChat({
|
||||
? t(
|
||||
'The provider rejected the API key. Check it in Provider Settings.',
|
||||
)
|
||||
: error?.code === 'RATE_LIMITED' || error?.status === 429
|
||||
? t('Too many chat requests. Try again in {seconds} s.', {
|
||||
seconds: Math.min(error.retryAfter || 60, 3600),
|
||||
})
|
||||
: error?.status === 504 || owner.signal.aborted
|
||||
? t('Connection timed out. Try again.')
|
||||
: error?.incomplete
|
||||
? t('Reply interrupted. You can try again.')
|
||||
: error?.tooManySteps
|
||||
? applied
|
||||
? t(
|
||||
'That request needed too many steps. Changes already made to the map stay in place.',
|
||||
)
|
||||
: t(
|
||||
'That request needed too many steps. Try a simpler request.',
|
||||
)
|
||||
: t('Could not connect. Try again.'),
|
||||
: error?.code === 'ACCESS_DENIED'
|
||||
? t(
|
||||
"The provider refused access for this key. Check the key's project, region or permissions.",
|
||||
)
|
||||
: error?.code === 'QUOTA_EXCEEDED'
|
||||
? t(
|
||||
'The provider account is out of credit. Check its billing, or choose another AI Provider in Settings.',
|
||||
)
|
||||
: error?.code === 'RATE_LIMITED' || error?.status === 429
|
||||
? t('Too many chat requests. Try again in {seconds} s.', {
|
||||
seconds: Math.min(error.retryAfter || 60, 3600),
|
||||
})
|
||||
: error?.status === 504 || owner.signal.aborted
|
||||
? t('Connection timed out. Try again.')
|
||||
: error?.incomplete
|
||||
? t('Reply interrupted. You can try again.')
|
||||
: error?.tooManySteps
|
||||
? applied
|
||||
? t(
|
||||
'That request needed too many steps. Changes already made to the map stay in place.',
|
||||
)
|
||||
: t(
|
||||
'That request needed too many steps. Try a simpler request.',
|
||||
)
|
||||
: t('Could not connect. Try again.'),
|
||||
);
|
||||
} finally {
|
||||
recordUsage(null);
|
||||
|
||||
@@ -136,6 +136,45 @@ for (const provider of ['openai', 'gemini']) {
|
||||
h.chat.stop();
|
||||
});
|
||||
|
||||
test(`${provider}: Stop during the second action keeps the first, pairing the rest as skipped`, async () => {
|
||||
let runs = 0;
|
||||
const h = harness(
|
||||
provider,
|
||||
(n) => (n === 1 ? w.calls(2) : w.final('Done')),
|
||||
{
|
||||
runAction: () => (++runs === 1 ? { ok: true } : new Promise(() => {})),
|
||||
},
|
||||
);
|
||||
h.chat.start();
|
||||
h.chat.sendText('Do both');
|
||||
await settle();
|
||||
assert.equal(runs, 2);
|
||||
assert.equal(h.chat.cancelTurn(), true);
|
||||
h.chat.sendText('Continue');
|
||||
await settle();
|
||||
const items = w.items(h.requests[1]);
|
||||
assert.deepEqual(w.userTexts(items), ['Do both', 'Continue']);
|
||||
const results =
|
||||
provider === 'openai'
|
||||
? items
|
||||
.filter((item) => item.role === 'tool')
|
||||
.map((item) => JSON.parse(item.content))
|
||||
: items.flatMap((item) =>
|
||||
item.parts
|
||||
.filter((part) => part.functionResponse)
|
||||
.map((part) => part.functionResponse.response),
|
||||
);
|
||||
assert.equal(results.length, 2);
|
||||
assert.equal(results[0].ok, true);
|
||||
assert.equal(results[1].skipped, true);
|
||||
const note = items.at(-2);
|
||||
assert.match(
|
||||
provider === 'openai' ? note.content : note.parts[0].text,
|
||||
/map actions above already ran/,
|
||||
);
|
||||
h.chat.stop();
|
||||
});
|
||||
|
||||
test(`${provider}: cancelTurn aborts only the reply in progress`, async () => {
|
||||
let toolSignal;
|
||||
const h = harness(
|
||||
@@ -210,6 +249,24 @@ for (const provider of ['openai', 'gemini']) {
|
||||
() =>
|
||||
t('The provider rejected the API key. Check it in Provider Settings.'),
|
||||
],
|
||||
[
|
||||
502,
|
||||
{ error: 'Refused', code: 'ACCESS_DENIED' },
|
||||
{},
|
||||
() =>
|
||||
t(
|
||||
"The provider refused access for this key. Check the key's project, region or permissions.",
|
||||
),
|
||||
],
|
||||
[
|
||||
502,
|
||||
{ error: 'Refused', code: 'QUOTA_EXCEEDED' },
|
||||
{},
|
||||
() =>
|
||||
t(
|
||||
'The provider account is out of credit. Check its billing, or choose another AI Provider in Settings.',
|
||||
),
|
||||
],
|
||||
[
|
||||
429,
|
||||
{ error: 'Refused', code: 'RATE_LIMITED' },
|
||||
|
||||
Reference in new issue
Block a user