Merge a6f2057ab3 into 4b5514a259
This commit is contained in:
commit
dcfd17501c
@ -7,6 +7,7 @@ Status: beta.
|
||||
|
||||
### Changes
|
||||
- Rebrand: rename the npm package/CLI to `moltbot`, add a `moltbot` compatibility shim, and move extensions to the `@moltbot/*` scope.
|
||||
- Gateway: add user-friendly error messages for API rate limits (429) and payment issues (402). (#2202) Thanks @shamsulalam1114.
|
||||
- Commands: group /help and /commands output with Telegram paging. (#2504) Thanks @hougangdev.
|
||||
- macOS: limit project-local `node_modules/.bin` PATH preference to debug builds (reduce PATH hijacking risk).
|
||||
- macOS: finish Moltbot app rename for macOS sources, bundle identifiers, and shared kit paths. (#2844) Thanks @fal3.
|
||||
|
||||
@ -299,6 +299,23 @@ export function formatAssistantErrorText(
|
||||
return "The AI service is temporarily overloaded. Please try again in a moment.";
|
||||
}
|
||||
|
||||
// Handle rate limit errors (429) with user-friendly messaging
|
||||
if (isRateLimitErrorMessage(raw)) {
|
||||
const retryAfter = extractRetryAfterSeconds(raw);
|
||||
const retryHint = retryAfter
|
||||
? ` Please try again in ${formatRetryDuration(retryAfter)}.`
|
||||
: " Please try again in a few minutes.";
|
||||
return `⚠️ Rate limit reached.${retryHint} You may need to upgrade your API plan or wait for the limit to reset.`;
|
||||
}
|
||||
|
||||
// Handle billing/payment errors (402)
|
||||
if (isBillingErrorMessage(raw)) {
|
||||
return (
|
||||
"⚠️ API quota or payment issue detected. " +
|
||||
"Please check your account billing and credits at your provider's dashboard."
|
||||
);
|
||||
}
|
||||
|
||||
if (isLikelyHttpErrorText(raw) || isRawApiErrorPayload(raw)) {
|
||||
return formatRawAssistantErrorForUi(raw);
|
||||
}
|
||||
@ -516,3 +533,39 @@ export function isFailoverAssistantError(msg: AssistantMessage | undefined): boo
|
||||
if (!msg || msg.stopReason !== "error") return false;
|
||||
return isFailoverErrorMessage(msg.errorMessage ?? "");
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract retry-after duration from error message (in seconds).
|
||||
* Checks for Retry-After header or x-ratelimit-reset in the error payload.
|
||||
*/
|
||||
function extractRetryAfterSeconds(raw: string): number | null {
|
||||
if (!raw) return null;
|
||||
|
||||
// Look for Retry-After header value (seconds)
|
||||
const retryAfterMatch = raw.match(/retry[_-]?after[:\s]+(\d+)/i);
|
||||
if (retryAfterMatch?.[1]) {
|
||||
return Number.parseInt(retryAfterMatch[1], 10);
|
||||
}
|
||||
|
||||
// Look for x-ratelimit-reset (Unix timestamp)
|
||||
const resetMatch = raw.match(/x[_-]?ratelimit[_-]?reset[:\s]+(\d+)/i);
|
||||
if (resetMatch?.[1]) {
|
||||
const resetTime = Number.parseInt(resetMatch[1], 10);
|
||||
const now = Math.floor(Date.now() / 1000);
|
||||
const seconds = resetTime - now;
|
||||
return seconds > 0 ? seconds : null;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Format retry duration in human-readable form.
|
||||
*/
|
||||
function formatRetryDuration(seconds: number): string {
|
||||
if (seconds < 60) return `${seconds} second${seconds !== 1 ? "s" : ""}`;
|
||||
const minutes = Math.ceil(seconds / 60);
|
||||
if (minutes < 60) return `${minutes} minute${minutes !== 1 ? "s" : ""}`;
|
||||
const hours = Math.ceil(minutes / 60);
|
||||
return `${hours} hour${hours !== 1 ? "s" : ""}`;
|
||||
}
|
||||
|
||||
@ -38,6 +38,7 @@ export async function sanitizeSessionMessagesImages(
|
||||
allowBase64Only?: boolean;
|
||||
includeCamelCase?: boolean;
|
||||
};
|
||||
maxBytes?: number;
|
||||
},
|
||||
): Promise<AgentMessage[]> {
|
||||
const sanitizeMode = options?.sanitizeMode ?? "full";
|
||||
@ -62,6 +63,7 @@ export async function sanitizeSessionMessagesImages(
|
||||
const nextContent = (await sanitizeContentBlocksImages(
|
||||
content as ContentBlock[],
|
||||
label,
|
||||
{ maxBytes: options?.maxBytes },
|
||||
)) as unknown as typeof toolMsg.content;
|
||||
out.push({ ...toolMsg, content: nextContent });
|
||||
continue;
|
||||
@ -74,6 +76,7 @@ export async function sanitizeSessionMessagesImages(
|
||||
const nextContent = (await sanitizeContentBlocksImages(
|
||||
content as unknown as ContentBlock[],
|
||||
label,
|
||||
{ maxBytes: options?.maxBytes },
|
||||
)) as unknown as typeof userMsg.content;
|
||||
out.push({ ...userMsg, content: nextContent });
|
||||
continue;
|
||||
@ -88,6 +91,7 @@ export async function sanitizeSessionMessagesImages(
|
||||
const nextContent = (await sanitizeContentBlocksImages(
|
||||
content as unknown as ContentBlock[],
|
||||
label,
|
||||
{ maxBytes: options?.maxBytes },
|
||||
)) as unknown as typeof assistantMsg.content;
|
||||
out.push({ ...assistantMsg, content: nextContent });
|
||||
} else {
|
||||
@ -101,6 +105,7 @@ export async function sanitizeSessionMessagesImages(
|
||||
const nextContent = (await sanitizeContentBlocksImages(
|
||||
content as unknown as ContentBlock[],
|
||||
label,
|
||||
{ maxBytes: options?.maxBytes },
|
||||
)) as unknown as typeof assistantMsg.content;
|
||||
out.push({ ...assistantMsg, content: nextContent });
|
||||
continue;
|
||||
@ -118,6 +123,7 @@ export async function sanitizeSessionMessagesImages(
|
||||
const finalContent = (await sanitizeContentBlocksImages(
|
||||
filteredContent as unknown as ContentBlock[],
|
||||
label,
|
||||
{ maxBytes: options?.maxBytes },
|
||||
)) as unknown as typeof assistantMsg.content;
|
||||
if (finalContent.length === 0) {
|
||||
continue;
|
||||
|
||||
@ -313,6 +313,7 @@ export async function sanitizeSessionHistory(params: {
|
||||
sessionManager: SessionManager;
|
||||
sessionId: string;
|
||||
policy?: TranscriptPolicy;
|
||||
maxBytes?: number;
|
||||
}): Promise<AgentMessage[]> {
|
||||
// Keep docs/reference/transcript-hygiene.md in sync with any logic changes here.
|
||||
const policy =
|
||||
@ -328,6 +329,7 @@ export async function sanitizeSessionHistory(params: {
|
||||
toolCallIdMode: policy.toolCallIdMode,
|
||||
preserveSignatures: policy.preserveSignatures,
|
||||
sanitizeThoughtSignatures: policy.sanitizeThoughtSignatures,
|
||||
maxBytes: params.maxBytes,
|
||||
});
|
||||
const sanitizedThinking = policy.normalizeAntigravityThinkingBlocks
|
||||
? sanitizeAntigravityThinkingBlocks(sanitizedImages)
|
||||
|
||||
@ -523,6 +523,7 @@ export async function runEmbeddedAttempt(
|
||||
sessionManager,
|
||||
sessionId: params.sessionId,
|
||||
policy: transcriptPolicy,
|
||||
maxBytes: params.config?.tools?.media?.image?.maxBytes,
|
||||
});
|
||||
cacheTrace?.recordStage("session:sanitized", { messages: prior });
|
||||
const validatedGemini = transcriptPolicy.validateGeminiTurns
|
||||
@ -743,7 +744,7 @@ export async function runEmbeddedAttempt(
|
||||
model: params.model,
|
||||
existingImages: params.images,
|
||||
historyMessages: activeSession.messages,
|
||||
maxBytes: MAX_IMAGE_BYTES,
|
||||
maxBytes: params.config?.tools?.media?.image?.maxBytes ?? MAX_IMAGE_BYTES,
|
||||
// Enforce sandbox path restrictions when sandbox is enabled
|
||||
sandboxRoot: sandbox?.enabled ? sandbox.workspaceDir : undefined,
|
||||
});
|
||||
|
||||
Loading…
Reference in New Issue
Block a user