jeopi-ai 16.2.20 → 16.2.22

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -2,6 +2,12 @@
2
2
 
3
3
  ## [Unreleased]
4
4
 
5
+ ## [16.2.21] - 2026-07-02
6
+
7
+ ### Fixed
8
+
9
+ - Fixed Cloud Code Assist `Individual quota reached` 429 responses to render as concise quota-exhausted messages with the affected model, account, reset delay, and `/login` recovery guidance instead of dumping the raw Google error JSON. The shared retry-hint parser now also understands Cloud Code Assist `Resets in …` text and `quotaResetDelay` metadata.
10
+
5
11
  ## [16.2.18] - 2026-07-02
6
12
 
7
13
  ### Fixed
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "type": "module",
3
3
  "name": "jeopi-ai",
4
- "version": "16.2.20",
4
+ "version": "16.2.22",
5
5
  "description": "Unified LLM API with automatic model discovery and provider configuration",
6
6
  "homepage": "https://github.com/akillness/jeopi",
7
7
  "author": "Can Boluk",
@@ -38,9 +38,9 @@
38
38
  },
39
39
  "dependencies": {
40
40
  "@bufbuild/protobuf": "^2.12.0",
41
- "jeopi-catalog": "16.2.20",
42
- "jeopi-utils": "16.2.20",
43
- "jeopi-wire": "16.2.20",
41
+ "jeopi-catalog": "16.2.22",
42
+ "jeopi-utils": "16.2.22",
43
+ "jeopi-wire": "16.2.22",
44
44
  "arktype": "^2.2.0",
45
45
  "zod": "^4"
46
46
  },
@@ -100,7 +100,7 @@ export function calculateRateLimitBackoffMs(reason: RateLimitReason): number {
100
100
 
101
101
  /** Detect usage/quota limit errors in error messages (persistent, requires credential switch). */
102
102
  const USAGE_LIMIT_PATTERN =
103
- /usage.?limit|usage_limit_reached|usage_not_included|limit_reached|quota.?exceeded|quota.?reached|resource.?exhausted|exhausted your capacity|quota will reset|insufficient.?(?:balance|quota)/i;
103
+ /usage.?limit|usage_limit_reached|usage_not_included|limit_reached|quota.?exceeded|quota.?reached|quota.?exhaust(ed|ion)?|resource.?exhausted|exhausted your capacity|quota will reset|insufficient.?(?:balance|quota)/i;
104
104
 
105
105
  /**
106
106
  * HTTP status codes that, absent richer body classification, represent an
@@ -423,6 +423,99 @@ export function shouldRefreshGeminiCliCredentials(
423
423
  const skewMs = isAntigravity ? ANTIGRAVITY_REFRESH_SKEW_MS : GOOGLE_GEMINI_REFRESH_SKEW_MS;
424
424
  return nowMs + skewMs >= expiresAt;
425
425
  }
426
+ interface CloudCodeAssistQuotaDetails {
427
+ message?: string;
428
+ model?: string;
429
+ quotaResetDelay?: string;
430
+ quotaResetTimeStamp?: string;
431
+ retryDelay?: string;
432
+ }
433
+
434
+ function recordOf(value: unknown): Record<string, unknown> | undefined {
435
+ return value && typeof value === "object" && !Array.isArray(value) ? (value as Record<string, unknown>) : undefined;
436
+ }
437
+
438
+ function stringField(record: Record<string, unknown> | undefined, key: string): string | undefined {
439
+ const value = record?.[key];
440
+ return typeof value === "string" && value.length > 0 ? value : undefined;
441
+ }
442
+
443
+ function extractCloudCodeAssistQuotaDetails(errorBody: string): CloudCodeAssistQuotaDetails | undefined {
444
+ if (!/\bquota\b|QUOTA_EXHAUSTED|RESOURCE_EXHAUSTED/i.test(errorBody)) return undefined;
445
+ const jsonStart = errorBody.indexOf("{");
446
+ if (jsonStart === -1) return undefined;
447
+
448
+ try {
449
+ const jsonText = errorBody.slice(jsonStart).replace(/\r?\n/g, " ");
450
+ const parsed = JSON.parse(jsonText);
451
+ const root = recordOf(parsed);
452
+ const error = recordOf(root?.error);
453
+ if (!error) return undefined;
454
+
455
+ const message = stringField(error, "message");
456
+ const status = stringField(error, "status");
457
+ const details = Array.isArray(error.details) ? error.details : [];
458
+
459
+ let isQuotaExhausted =
460
+ status === "RESOURCE_EXHAUSTED" && message !== undefined && /\bquota\b|exhausted/i.test(message);
461
+ let model: string | undefined;
462
+ let quotaResetDelay: string | undefined;
463
+ let quotaResetTimeStamp: string | undefined;
464
+ let retryDelay: string | undefined;
465
+
466
+ for (const rawDetail of details) {
467
+ const detail = recordOf(rawDetail);
468
+ if (!detail) continue;
469
+ const reason = stringField(detail, "reason");
470
+ if (reason === "QUOTA_EXHAUSTED") {
471
+ isQuotaExhausted = true;
472
+ }
473
+ retryDelay ??= stringField(detail, "retryDelay");
474
+ const metadata = recordOf(detail.metadata);
475
+ model ??= stringField(metadata, "model");
476
+ quotaResetDelay ??= stringField(metadata, "quotaResetDelay");
477
+ quotaResetTimeStamp ??=
478
+ stringField(metadata, "quotaResetTimeStamp") ?? stringField(metadata, "quotaResetTimestamp");
479
+ }
480
+
481
+ if (!isQuotaExhausted) return undefined;
482
+ return { message, model, quotaResetDelay, quotaResetTimeStamp, retryDelay };
483
+ } catch {
484
+ return undefined;
485
+ }
486
+ }
487
+
488
+ function sentence(text: string): string {
489
+ return /[.!?]$/.test(text) ? text : `${text}.`;
490
+ }
491
+
492
+ function formatCloudCodeAssistQuotaMessage(errorBody: string, email?: string): string | undefined {
493
+ const details = extractCloudCodeAssistQuotaDetails(errorBody);
494
+ if (!details) return undefined;
495
+
496
+ const target = details.model ? ` for ${details.model}` : "";
497
+ const account = email ? ` on ${email}` : "";
498
+ const parts = [`Cloud Code Assist quota exhausted${target}${account}.`];
499
+
500
+ const providerSummary = details.message
501
+ ?.replace(/\s+/g, " ")
502
+ .trim()
503
+ .split(/\s+Resets?\s+in\s+/i)[0];
504
+ if (providerSummary) {
505
+ parts.push(sentence(providerSummary));
506
+ }
507
+
508
+ const resetDelay = details.quotaResetDelay ?? details.retryDelay;
509
+ if (resetDelay) {
510
+ const resetTime = details.quotaResetTimeStamp ? ` (${details.quotaResetTimeStamp})` : "";
511
+ parts.push(`Your quota will reset after ${resetDelay}${resetTime}.`);
512
+ } else if (details.quotaResetTimeStamp) {
513
+ parts.push(`Your quota resets at ${details.quotaResetTimeStamp}.`);
514
+ }
515
+
516
+ parts.push("Add another Google account with /login, switch models/providers, or wait for the reset.");
517
+ return parts.join(" ");
518
+ }
426
519
 
427
520
  interface CloudCodeAssistRequest {
428
521
  project: string;
@@ -759,7 +852,11 @@ export const streamGoogleGeminiCli: StreamFunction<"google-gemini-cli"> = (
759
852
  event => options?.onSseEvent?.({ event: event.event, data: event.data, raw: [...event.raw] }, model),
760
853
  )) {
761
854
  if (chunk.error) {
762
- const detail = chunk.error.message || chunk.error.status || "unknown error";
855
+ const quotaDetail = formatCloudCodeAssistQuotaMessage(
856
+ JSON.stringify({ error: chunk.error }),
857
+ parsedCredentials.email,
858
+ );
859
+ const detail = quotaDetail ?? chunk.error.message ?? chunk.error.status ?? "unknown error";
763
860
  const message = `Cloud Code Assist stream error: ${detail}`;
764
861
  throw typeof chunk.error.code === "number" && chunk.error.code >= 400
765
862
  ? new AIError.GeminiCliApiError(message, chunk.error.code)
@@ -949,14 +1046,17 @@ export const streamGoogleGeminiCli: StreamFunction<"google-gemini-cli"> = (
949
1046
  }
950
1047
  }
951
1048
  const errorText = await response.text();
952
- const validationUrl = extractGoogleValidationUrl(errorText);
953
- const errorMessage = validationUrl
954
- ? formatGoogleValidationRequiredMessage(
955
- validationUrl,
956
- "retry your request",
957
- parsedCredentials.email,
958
- )
959
- : errorText;
1049
+ const quotaMessage = formatCloudCodeAssistQuotaMessage(errorText, parsedCredentials.email);
1050
+ const validationUrl = quotaMessage ? undefined : extractGoogleValidationUrl(errorText);
1051
+ const errorMessage =
1052
+ quotaMessage ??
1053
+ (validationUrl
1054
+ ? formatGoogleValidationRequiredMessage(
1055
+ validationUrl,
1056
+ "retry your request",
1057
+ parsedCredentials.email,
1058
+ )
1059
+ : errorText);
960
1060
  throw new AIError.GeminiCliApiError(
961
1061
  `Cloud Code Assist API error (${response.status}): ${errorMessage}`,
962
1062
  response.status,
@@ -993,8 +1093,10 @@ export const streamGoogleGeminiCli: StreamFunction<"google-gemini-cli"> = (
993
1093
 
994
1094
  if (!currentResponse.ok) {
995
1095
  const retryErrorText = await currentResponse.text();
1096
+ const retryErrorMessage =
1097
+ formatCloudCodeAssistQuotaMessage(retryErrorText, parsedCredentials.email) ?? retryErrorText;
996
1098
  throw new AIError.GeminiCliApiError(
997
- `Cloud Code Assist API error (${currentResponse.status}): ${retryErrorText}`,
1099
+ `Cloud Code Assist API error (${currentResponse.status}): ${retryErrorMessage}`,
998
1100
  currentResponse.status,
999
1101
  { headers: currentResponse.headers },
1000
1102
  );