@code-yeongyu/senpi-ai 2026.10.5 → 2026.10.6

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.
@@ -67,6 +67,7 @@ export declare function isRecoverableLength(message: AssistantMessage, desiredMa
67
67
  */
68
68
  export declare function getOverflowPatterns(): RegExp[];
69
69
  export declare function isCursorPayloadResourceExhausted(message: {
70
+ provider?: string;
70
71
  stopReason?: string;
71
72
  errorMessage?: string;
72
73
  usage?: {
@@ -83,6 +84,7 @@ export declare function isCursorPayloadResourceExhausted(message: {
83
84
  * context window.
84
85
  */
85
86
  export declare function isCursorQuotaResourceExhausted(message: {
87
+ provider?: string;
86
88
  stopReason?: string;
87
89
  errorMessage?: string;
88
90
  usage?: {
@@ -94,6 +96,7 @@ export declare function isCursorQuotaResourceExhausted(message: {
94
96
  };
95
97
  }, contextWindow: number): boolean;
96
98
  export declare function isCursorZeroTokenResourceExhausted(message: {
99
+ provider?: string;
97
100
  stopReason?: string;
98
101
  errorMessage?: string;
99
102
  usage?: {
@@ -215,6 +215,15 @@ export function isRecoverableLength(message, desiredMaxOutput) {
215
215
  export function getOverflowPatterns() {
216
216
  return [...OVERFLOW_PATTERNS];
217
217
  }
218
+ const CURSOR_PROVIDERS = new Set(["cursor", "cursor-cli-oauth"]);
219
+ /**
220
+ * The zero-token / quota `resource_exhausted` signatures below describe Cursor's backend only.
221
+ * Other providers send `resource_exhausted` for their own rate and usage limits (senpi#2660), and
222
+ * reading those as a Cursor payload overflow or re-mint skips the fallback chain.
223
+ */
224
+ function isCursorProviderMessage(message) {
225
+ return message.provider === undefined || CURSOR_PROVIDERS.has(message.provider);
226
+ }
218
227
  function cursorZeroTokenCount(message) {
219
228
  const usage = message.usage;
220
229
  if (!usage)
@@ -222,6 +231,8 @@ function cursorZeroTokenCount(message) {
222
231
  return (usage.totalTokens || (usage.input ?? 0) + (usage.output ?? 0) + (usage.cacheRead ?? 0) + (usage.cacheWrite ?? 0));
223
232
  }
224
233
  export function isCursorPayloadResourceExhausted(message, _estimateTokens) {
234
+ if (!isCursorProviderMessage(message))
235
+ return false;
225
236
  if (message.stopReason !== "error" || !/resource.?exhausted/i.test(message.errorMessage || "")) {
226
237
  return false;
227
238
  }
@@ -234,13 +245,16 @@ export function isCursorPayloadResourceExhausted(message, _estimateTokens) {
234
245
  */
235
246
  export function isCursorQuotaResourceExhausted(message, contextWindow) {
236
247
  const tokens = cursorZeroTokenCount(message);
237
- return (message.stopReason === "error" &&
248
+ return (isCursorProviderMessage(message) &&
249
+ message.stopReason === "error" &&
238
250
  RESOURCE_EXHAUSTED_PATTERN.test(message.errorMessage || "") &&
239
251
  contextWindow > 0 &&
240
252
  tokens > 0 &&
241
253
  tokens < contextWindow * 0.5);
242
254
  }
243
255
  export function isCursorZeroTokenResourceExhausted(message) {
256
+ if (!isCursorProviderMessage(message))
257
+ return false;
244
258
  if (message.stopReason !== "error" || !/resource.?exhausted/i.test(message.errorMessage || "")) {
245
259
  return false;
246
260
  }
@@ -6,7 +6,7 @@
6
6
  * 2. retry-after header (strict integer delta-seconds `^\d+$`, else HTTP-date)
7
7
  * 3. x-ratelimit-reset{,-requests,-tokens} (epoch seconds, max-wins)
8
8
  * 4. JSON retryDelay strings (ms/s/m units, recursive, max-wins; numeric rejected)
9
- * 5. body prose (ms-marker, seconds-marker, relative, resets-at ISO8601)
9
+ * 5. body prose (ms-marker, seconds-marker, relative, resets-in, resets-at ISO8601)
10
10
  *
11
11
  * Eligibility gate: status 429 OR body marks rate-limit error.
12
12
  * Explicit zero = 0 (beats lower-precedence positives).
@@ -6,7 +6,7 @@
6
6
  * 2. retry-after header (strict integer delta-seconds `^\d+$`, else HTTP-date)
7
7
  * 3. x-ratelimit-reset{,-requests,-tokens} (epoch seconds, max-wins)
8
8
  * 4. JSON retryDelay strings (ms/s/m units, recursive, max-wins; numeric rejected)
9
- * 5. body prose (ms-marker, seconds-marker, relative, resets-at ISO8601)
9
+ * 5. body prose (ms-marker, seconds-marker, relative, resets-in, resets-at ISO8601)
10
10
  *
11
11
  * Eligibility gate: status 429 OR body marks rate-limit error.
12
12
  * Explicit zero = 0 (beats lower-precedence positives).
@@ -132,6 +132,10 @@ function fromBodyProse(bodyText, nowMs) {
132
132
  return Number.parseInt(m[1], 10) * 1000;
133
133
  // relative prose: try again | retry | wait after ... [in] N <unit>
134
134
  m = bodyText.match(/(?:try again|retry|wait after).*?(?:in\s+)?(\d+(?:\.\d+)?)\s*(ms|s|sec|seconds?|m|min|minutes?)\b/i);
135
+ if (m)
136
+ return convertUnit(Number.parseFloat(m[1]), m[2]);
137
+ // limit prose: [limit will] reset(s) in N <unit>
138
+ m = bodyText.match(/\bresets?\s+in\s+(\d+(?:\.\d+)?)\s*(ms|s|sec|seconds?|m|min|minutes?)\b/i);
135
139
  if (m)
136
140
  return convertUnit(Number.parseFloat(m[1]), m[2]);
137
141
  // resets at <ISO8601-with-tz>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@code-yeongyu/senpi-ai",
3
- "version": "2026.10.5",
3
+ "version": "2026.10.6",
4
4
  "description": "Unified LLM API with automatic model discovery and provider configuration",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -88,7 +88,7 @@
88
88
  "dependencies": {
89
89
  "@anthropic-ai/sdk": "0.127.0",
90
90
  "@aws-sdk/client-bedrock-runtime": "3.1136.0",
91
- "@earendil-works/pi-telemetry": "npm:@code-yeongyu/senpi-telemetry@2026.10.5",
91
+ "@earendil-works/pi-telemetry": "npm:@code-yeongyu/senpi-telemetry@2026.10.6",
92
92
  "@google/genai": "2.23.0",
93
93
  "@smithy/node-http-handler": "4.12.1",
94
94
  "http-proxy-agent": "9.1.0",