-
Notifications
You must be signed in to change notification settings - Fork 388
Description
I'm using Cloudflare Agents with useAgentChat from @cloudflare/ai-chat/react. It's not clear how to properly authenticate the agent.
Issue 1: useAgentChat—2 methods to change headers
const { messages, sendMessage, status, regenerate, stop, error } =
useAgentChat({
agent,
// should we set the via `headers`
headers: {
Authorization: `Bearer ${token}`,
},
// or
prepareSendMessagesRequest: () => ({
headers: {
Authorization: `Bearer ${token}`,
},
}),
})However, using prepareSendMessagesRequest is not even possible with the code from the README of ai-chat, because this method doesn't add headers, it just overwrites the whole request and therefore messages don't get sent. There's no way of just customizing the headers without overwriting the body.
Issue 2: Where to check the token on the server?
Assuming we set following hook options:
const { messages, sendMessage, status, regenerate, stop, error } =
useAgentChat({
agent,
headers: {
Authorization: `Bearer ${token}`,
},
})In the backend, the token can be checked at 3 multiple locations:
export default {
async fetch(request: Request, env: Env, _ctx: ExecutionContext) {
// (1) should we check request.headers here?
return (
// Route the request to our agent or return 404 if not found
(await routeAgentRequest(request, env, {
onBeforeConnect: (request) => {
// (2) or maybe here?
},
onBeforeRequest: (request) => {
// (3) or here?
},
})) || new Response("Not found", { status: 404 })
)
},
} satisfies ExportedHandler<Env>According to my tests, when just using headers option in the hook (because prepareSendMessagesRequest is unusable), the Authorization header is present in only (3):
| Location | Authorization header |
|---|---|
| In worker fetch body | ❌ |
| onBeforeConnect | ❌ |
| onBeforeRequest | ✅ |
Issue 3: How to pass the token to the Agent's DO?
I need the token to fetch user-specific data before making an LLM request. I couldn't find a way to pass the token from the Worker fetch to the Agent DO itself:
export class QAgent extends AIChatAgent<Env> {
async onChatMessage(
onFinish: StreamTextOnFinishCallback<ToolSet>,
_options?: { abortSignal?: AbortSignal },
) {
// How to access the token here? Or just pass arbitrary data?
const stream = createUIMessageStream({
execute: async ({ writer }) => {
// Clean up incomplete tool calls to prevent API errors
const cleanedMessages = cleanupMessages(this.messages)
const agentStream = (await createAgentUIStream({
agent: qAgent,
uiMessages: cleanedMessages,
options: {
writer,
},
// Type boundary: streamText expects specific tool types, but base class uses ToolSet
// biome-ignore lint/suspicious/noExplicitAny: This is safe because our tools satisfy ToolSet interface (verified by 'satisfies' in tools.ts)
onFinish: onFinish as unknown as UIMessageStreamOnFinishCallback<any>,
})) as AsyncIterableStream<InferUIMessageChunk<QAgentUIMessage>>
writer.merge(agentStream)
},
})
return createUIMessageStreamResponse({ stream })
}
}
export default {
async fetch(request: Request, env: Env, _ctx: ExecutionContext) {
return (
(await routeAgentRequest(request, env, {
// how to pass the data from here?
})) || new Response("Not found", { status: 404 })
)
},
} satisfies ExportedHandler<Env>