fix(auth): show actionable provider re-auth errors (#450)
This commit is contained in:
@@ -22,6 +22,7 @@ import type { StreamPhase, ToolPopupContent } from './message/types';
|
|||||||
import { deriveMessageRole } from './message/messageRole';
|
import { deriveMessageRole } from './message/messageRole';
|
||||||
import { filterVisibleParts } from './message/partUtils';
|
import { filterVisibleParts } from './message/partUtils';
|
||||||
import { flattenAssistantTextParts } from '@/lib/messages/messageText';
|
import { flattenAssistantTextParts } from '@/lib/messages/messageText';
|
||||||
|
import { isLikelyProviderAuthFailure, PROVIDER_AUTH_FAILURE_MESSAGE } from '@/lib/messages/providerAuthError';
|
||||||
import { FadeInOnReveal } from './message/FadeInOnReveal';
|
import { FadeInOnReveal } from './message/FadeInOnReveal';
|
||||||
import type { TurnGroupingContext } from './hooks/useTurnGrouping';
|
import type { TurnGroupingContext } from './hooks/useTurnGrouping';
|
||||||
|
|
||||||
@@ -646,6 +647,9 @@ const ChatMessage: React.FC<ChatMessageProps> = ({
|
|||||||
if (errorName === 'SessionRetry') {
|
if (errorName === 'SessionRetry') {
|
||||||
return `Opencode failed to send a message. Retry attempt info: \n\`${detail}\``;
|
return `Opencode failed to send a message. Retry attempt info: \n\`${detail}\``;
|
||||||
}
|
}
|
||||||
|
if (isLikelyProviderAuthFailure(detail)) {
|
||||||
|
return PROVIDER_AUTH_FAILURE_MESSAGE;
|
||||||
|
}
|
||||||
return `Opencode failed to send message with error:\n\`${detail}\``;
|
return `Opencode failed to send message with error:\n\`${detail}\``;
|
||||||
}, [isUser, message.info]);
|
}, [isUser, message.info]);
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,34 @@
|
|||||||
|
export const PROVIDER_AUTH_FAILURE_MESSAGE = "Authentication failed for this provider. Please re-authenticate and retry.";
|
||||||
|
|
||||||
|
export const isLikelyProviderAuthFailure = (value: unknown): boolean => {
|
||||||
|
if (typeof value !== "string") {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const detail = value.toLowerCase().trim();
|
||||||
|
if (!detail) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
detail.includes("token refresh failed") ||
|
||||||
|
detail.includes("unauthorized") ||
|
||||||
|
detail.includes("invalid token") ||
|
||||||
|
detail.includes("expired token")
|
||||||
|
) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
const hasOauth = detail.includes("oauth");
|
||||||
|
const hasOauthFailure =
|
||||||
|
detail.includes("failed") || detail.includes("invalid") || detail.includes("expired");
|
||||||
|
if (hasOauth && hasOauthFailure) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
const has401 = /\b401\b/.test(detail);
|
||||||
|
const hasAuthContext =
|
||||||
|
detail.includes("auth") || detail.includes("token") || detail.includes("unauthorized");
|
||||||
|
|
||||||
|
return has401 && hasAuthContext;
|
||||||
|
};
|
||||||
@@ -4,6 +4,7 @@ import { devtools, persist, createJSONStorage } from "zustand/middleware";
|
|||||||
import type { Message, Part } from "@opencode-ai/sdk/v2";
|
import type { Message, Part } from "@opencode-ai/sdk/v2";
|
||||||
import { opencodeClient } from "@/lib/opencode/client";
|
import { opencodeClient } from "@/lib/opencode/client";
|
||||||
import { isExecutionForkMetaText } from "@/lib/messages/executionMeta";
|
import { isExecutionForkMetaText } from "@/lib/messages/executionMeta";
|
||||||
|
import { isLikelyProviderAuthFailure, PROVIDER_AUTH_FAILURE_MESSAGE } from "@/lib/messages/providerAuthError";
|
||||||
import type { SessionMemoryState, MessageStreamLifecycle, AttachedFile } from "./types/sessionTypes";
|
import type { SessionMemoryState, MessageStreamLifecycle, AttachedFile } from "./types/sessionTypes";
|
||||||
import { MEMORY_LIMITS, getMemoryLimits, getBackgroundTrimLimit } from "./types/sessionTypes";
|
import { MEMORY_LIMITS, getMemoryLimits, getBackgroundTrimLimit } from "./types/sessionTypes";
|
||||||
import {
|
import {
|
||||||
@@ -787,6 +788,8 @@ export const useMessageStore = create<MessageStore>()(
|
|||||||
return { abortControllers: nextControllers };
|
return { abortControllers: nextControllers };
|
||||||
});
|
});
|
||||||
return;
|
return;
|
||||||
|
} else if (isLikelyProviderAuthFailure(error.message)) {
|
||||||
|
errorMessage = PROVIDER_AUTH_FAILURE_MESSAGE;
|
||||||
} else if (error.message) {
|
} else if (error.message) {
|
||||||
errorMessage = error.message;
|
errorMessage = error.message;
|
||||||
}
|
}
|
||||||
@@ -814,6 +817,8 @@ export const useMessageStore = create<MessageStore>()(
|
|||||||
errorMessage = "OpenCode is restarting. Please wait a moment and try again.";
|
errorMessage = "OpenCode is restarting. Please wait a moment and try again.";
|
||||||
} else if (error.message?.includes("504") || error.message?.includes("Gateway")) {
|
} else if (error.message?.includes("504") || error.message?.includes("Gateway")) {
|
||||||
errorMessage = "Gateway timeout - your message is being processed. Please wait for response.";
|
errorMessage = "Gateway timeout - your message is being processed. Please wait for response.";
|
||||||
|
} else if (isLikelyProviderAuthFailure(error.message)) {
|
||||||
|
errorMessage = PROVIDER_AUTH_FAILURE_MESSAGE;
|
||||||
} else if (error.message) {
|
} else if (error.message) {
|
||||||
errorMessage = error.message;
|
errorMessage = error.message;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -52,7 +52,9 @@ export const fetchQuota = async () => {
|
|||||||
providerName,
|
providerName,
|
||||||
ok: false,
|
ok: false,
|
||||||
configured: true,
|
configured: true,
|
||||||
error: `API error: ${response.status}`
|
error: response.status === 401
|
||||||
|
? 'Session expired \u2014 please re-authenticate with OpenAI'
|
||||||
|
: `API error: ${response.status}`
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user