Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 23 additions & 5 deletions hooks/useVercelChat.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import { DEFAULT_MODEL } from "@/lib/consts";
import { usePaymentProvider } from "@/providers/PaymentProvider";
import useArtistFilesForMentions from "@/hooks/useArtistFilesForMentions";
import type { KnowledgeBaseEntry } from "@/lib/supabase/getArtistKnowledge";
import { usePrivy } from "@privy-io/react-auth";
import { useChatTransport } from "./useChatTransport";
import { useAccessToken } from "./useAccessToken";
import { TextAttachment } from "@/types/textAttachment";
Expand Down Expand Up @@ -60,6 +61,7 @@ export function useVercelChat({
availableModels[0]?.id ?? "",
);
const { refetchCredits } = usePaymentProvider();
const { getAccessToken } = usePrivy();
const { transport, headers } = useChatTransport();
const accessToken = useAccessToken();

Expand Down Expand Up @@ -195,7 +197,7 @@ export function useVercelChat({
},
});

const handleSubmit = (e: React.FormEvent<HTMLFormElement>) => {
const handleSubmit = async (e: React.FormEvent<HTMLFormElement>) => {
e.preventDefault();

// Combine all attachments
Expand Down Expand Up @@ -234,7 +236,16 @@ export function useVercelChat({
files: nonAudioAttachments.length > 0 ? nonAudioAttachments : undefined,
};

sendMessage(payload, chatRequestOptions);
// Get a fresh token at send time so expired tokens are never used
const freshToken = await getAccessToken();
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Mark send state before awaiting access token

Awaiting getAccessToken() before sendMessage() leaves the form in a sendable state while token refresh is in progress; when refresh takes noticeable time (the exact expiry case this patch targets), users can press Enter/click submit again and enqueue duplicate copies of the same prompt, causing duplicate model runs and credit usage. This path previously submitted immediately, so the new await introduces a regression unless resubmission is blocked before the token call.

Useful? React with 👍 / 👎.

const requestOptions = freshToken
? {
...chatRequestOptions,
headers: { Authorization: `Bearer ${freshToken}` },
}
: chatRequestOptions;

sendMessage(payload, requestOptions);
Comment on lines +239 to +248
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Headers are replaced instead of merged, potentially losing other headers.

When freshToken exists, the code overwrites headers entirely rather than merging with existing headers from chatRequestOptions. If chatRequestOptions.headers contains additional headers (now or in the future), they will be silently dropped.

🔧 Proposed fix to merge headers
     // Get a fresh token at send time so expired tokens are never used
     const freshToken = await getAccessToken();
     const requestOptions = freshToken
       ? {
           ...chatRequestOptions,
-          headers: { Authorization: `Bearer ${freshToken}` },
+          headers: { ...chatRequestOptions.headers, Authorization: `Bearer ${freshToken}` },
         }
       : chatRequestOptions;
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
// Get a fresh token at send time so expired tokens are never used
const freshToken = await getAccessToken();
const requestOptions = freshToken
? {
...chatRequestOptions,
headers: { Authorization: `Bearer ${freshToken}` },
}
: chatRequestOptions;
sendMessage(payload, requestOptions);
// Get a fresh token at send time so expired tokens are never used
const freshToken = await getAccessToken();
const requestOptions = freshToken
? {
...chatRequestOptions,
headers: { ...(chatRequestOptions.headers || {}), Authorization: `Bearer ${freshToken}` },
}
: chatRequestOptions;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@hooks/useVercelChat.ts` around lines 239 - 248, The code overwrites
chatRequestOptions.headers when freshToken exists, dropping any existing
headers; fix by creating requestOptions from chatRequestOptions (or an empty
object) and set headers to a new object that merges existing
chatRequestOptions.headers with the Authorization header (so Authorization wins
but other headers are preserved), do not mutate chatRequestOptions, and then
pass this merged requestOptions into sendMessage; reference getAccessToken,
chatRequestOptions, freshToken, and sendMessage when locating the change.

setInput("");
};

Expand Down Expand Up @@ -298,7 +309,7 @@ export function useVercelChat({
const messageContent = input;

// Submit the message
handleSubmit(event);
await handleSubmit(event);

if (!roomId) {
// Optimistically append a temporary conversation so it appears in Recent Chats
Expand All @@ -311,9 +322,16 @@ export function useVercelChat({
const handleSendQueryMessages = useCallback(
async (initialMessage: UIMessage) => {
silentlyUpdateUrl();
sendMessage(initialMessage, chatRequestOptions);
const freshToken = await getAccessToken();
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Avoid re-triggering initial send during token refresh

The initial-message path now waits for getAccessToken() before calling sendMessage(), which creates a window where status is still ready and messages are unchanged; if a rerender updates dependencies (for example model initialization updates chatRequestOptions), the effect can invoke this callback again and send the initial prompt twice. This duplicate-send condition is specific to slower token retrieval and was not present when sendMessage() happened synchronously.

Useful? React with 👍 / 👎.

const requestOptions = freshToken
? {
...chatRequestOptions,
headers: { Authorization: `Bearer ${freshToken}` },
}
: chatRequestOptions;
sendMessage(initialMessage, requestOptions);
Comment on lines +325 to +332
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Same headers merge issue applies here.

Consistent with the earlier comment on handleSubmit, this should also spread existing headers to avoid losing them.

🔧 Proposed fix
       const freshToken = await getAccessToken();
       const requestOptions = freshToken
         ? {
             ...chatRequestOptions,
-            headers: { Authorization: `Bearer ${freshToken}` },
+            headers: { ...chatRequestOptions.headers, Authorization: `Bearer ${freshToken}` },
           }
         : chatRequestOptions;
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const freshToken = await getAccessToken();
const requestOptions = freshToken
? {
...chatRequestOptions,
headers: { Authorization: `Bearer ${freshToken}` },
}
: chatRequestOptions;
sendMessage(initialMessage, requestOptions);
const freshToken = await getAccessToken();
const requestOptions = freshToken
? {
...chatRequestOptions,
headers: { ...chatRequestOptions.headers, Authorization: `Bearer ${freshToken}` },
}
: chatRequestOptions;
sendMessage(initialMessage, requestOptions);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@hooks/useVercelChat.ts` around lines 325 - 332, The current token-injection
before calling sendMessage overwrites any existing headers on
chatRequestOptions; update the merge so headers = {
...(chatRequestOptions.headers || {}), Authorization: `Bearer ${freshToken}` }
when building requestOptions (inside the getAccessToken branch) so existing
headers are preserved—apply this change where getAccessToken,
chatRequestOptions, initialMessage and sendMessage are used.

},
[silentlyUpdateUrl, sendMessage, chatRequestOptions],
[silentlyUpdateUrl, sendMessage, chatRequestOptions, getAccessToken],
);

useEffect(() => {
Expand Down
Loading