feat(sasquatch-pro): crew agent sends Pro credentials too

useCrewAgentChat now signs the same NIP-98 auth + attaches the viewer's Pro
receipt (when they have one), so the proxy's Pro gate lets Pro users through the
gateway models; everyone else still falls back to the keyless provider.
This commit is contained in:
SkateSpots 2026-09-27 14:27:02 +07:00
parent ccc04cc172
commit 8d272a9c76

View file

@ -1,6 +1,9 @@
import { useState, useCallback } from 'react'; import { useState, useCallback } from 'react';
import { useCVMClient } from '@/hooks/useCVMClient'; import { useCVMClient } from '@/hooks/useCVMClient';
import { callSasquatchAI, parseToolCall, type ChatTurn } from '@/lib/sasquatchBrain'; import { callSasquatchAI, parseToolCall, type ChatTurn } from '@/lib/sasquatchBrain';
import { useCurrentUser } from '@/hooks/useCurrentUser';
import { useSasquatchPro } from '@/hooks/useSasquatchPro';
import { buildProAuthHeaders } from '@/lib/proAuth';
export interface CrewAgentMessage { export interface CrewAgentMessage {
id: string; id: string;
@ -166,6 +169,8 @@ Rules:
*/ */
export function useCrewAgentChat(ctx: CrewContext) { export function useCrewAgentChat(ctx: CrewContext) {
const cvm = useCVMClient(); const cvm = useCVMClient();
const { user } = useCurrentUser();
const { isPro, receiptEvent } = useSasquatchPro();
const [messages, setMessages] = useState<CrewAgentMessage[]>([]); const [messages, setMessages] = useState<CrewAgentMessage[]>([]);
const [isThinking, setIsThinking] = useState(false); const [isThinking, setIsThinking] = useState(false);
@ -206,12 +211,21 @@ export function useCrewAgentChat(ctx: CrewContext) {
append({ role: 'user', text: q }); append({ role: 'user', text: q });
try { try {
// Pass the viewer's identity + Pro pass so the proxy's gate (when on)
// lets Pro users through; others fall back to the keyless provider.
const authHeaders = isPro
? await buildProAuthHeaders({
pubkey: user?.pubkey,
signEvent: user ? (e) => user.signer.signEvent(e) : undefined,
receiptEvent,
})
: {};
const system = buildCrewSystemPrompt(ctx); const system = buildCrewSystemPrompt(ctx);
// First pass — AI answers directly from crew memory or emits a tool call. // First pass — AI answers directly from crew memory or emits a tool call.
const firstPass: ChatTurn[] = [ const firstPass: ChatTurn[] = [
{ role: 'user', content: `${system}\n\n---\n\nMember: ${q}` }, { role: 'user', content: `${system}\n\n---\n\nMember: ${q}` },
]; ];
const aiText = await callSasquatchAI(firstPass); const aiText = await callSasquatchAI(firstPass, 400, undefined, authHeaders);
const toolCall = parseToolCall(aiText); const toolCall = parseToolCall(aiText);
if (!toolCall) { if (!toolCall) {
@ -246,7 +260,7 @@ ${JSON.stringify(toolResult, null, 2)}
- If count > 0: a short intro line — the app shows the results as cards below your message - If count > 0: a short intro line — the app shows the results as cards below your message
- Keep it short and skater-friendly. Never invent data.`, - Keep it short and skater-friendly. Never invent data.`,
}, },
]); ], 400, undefined, authHeaders);
append({ append({
role: 'agent', role: 'agent',
@ -263,7 +277,7 @@ ${JSON.stringify(toolResult, null, 2)}
setIsThinking(false); setIsThinking(false);
} }
}, },
[ctx, isThinking, append, runTool], [ctx, isThinking, append, runTool, isPro, receiptEvent, user],
); );
return { messages, isThinking, send }; return { messages, isThinking, send };