feat(rah-light): remove chat API routes
- Deleted app/api/rah/chat/route.ts - Deleted app/api/rah/usage/route.ts - Deleted empty app/api/rah/ directory - RAHChat.tsx references removed in Story 7 Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.5
parent
92e48a2175
commit
d9c0ba89fe
@@ -1,465 +0,0 @@
|
|||||||
import { NextRequest } from 'next/server';
|
|
||||||
import { streamText, convertToModelMessages } from 'ai';
|
|
||||||
import { createAnthropic } from '@ai-sdk/anthropic';
|
|
||||||
import { createOpenAI } from '@ai-sdk/openai';
|
|
||||||
import { getHelperTools, getDefaultToolNamesForRole } from '@/tools/infrastructure/registry';
|
|
||||||
import { buildSystemPromptBlocks } from '@/services/helpers/contextBuilder';
|
|
||||||
import { helperLogger } from '@/services/helpers/logger';
|
|
||||||
import { withChatLogging } from '@/services/chat/middleware';
|
|
||||||
import { AgentRegistry } from '@/services/agents/registry';
|
|
||||||
import { calculateCost } from '@/services/analytics/pricing';
|
|
||||||
import { UsageData } from '@/types/analytics';
|
|
||||||
import type { CacheStats } from '@/types/prompts';
|
|
||||||
import { randomUUID } from 'crypto';
|
|
||||||
import { RequestContext } from '@/services/context/requestContext';
|
|
||||||
import { isLocalMode } from '@/config/runtime';
|
|
||||||
|
|
||||||
export const maxDuration = 900; // 15 minutes (for workflows)
|
|
||||||
|
|
||||||
if (isLocalMode()) {
|
|
||||||
// TODO: add any special local-mode setup if needed later
|
|
||||||
}
|
|
||||||
|
|
||||||
const ANTHROPIC_MODEL_MAP: Record<string, string> = {
|
|
||||||
'claude-sonnet-4.5': 'claude-sonnet-4-5-20250929',
|
|
||||||
'claude-3-5-sonnet': 'claude-3-5-sonnet-20241022',
|
|
||||||
};
|
|
||||||
|
|
||||||
type ApiKeyOverrides = {
|
|
||||||
openai?: string;
|
|
||||||
anthropic?: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
function resolveModel(modelId: string, apiKeys?: ApiKeyOverrides) {
|
|
||||||
if (modelId.startsWith('anthropic/')) {
|
|
||||||
const rawName = modelId.split('/')[1];
|
|
||||||
const mapped = ANTHROPIC_MODEL_MAP[rawName] || rawName;
|
|
||||||
|
|
||||||
const orchestratorKey =
|
|
||||||
apiKeys?.anthropic ||
|
|
||||||
process.env.RAH_ORCHESTRATOR_ANTHROPIC_API_KEY ||
|
|
||||||
process.env.ANTHROPIC_API_KEY;
|
|
||||||
if (!orchestratorKey) {
|
|
||||||
throw new Error('RAH_ORCHESTRATOR_ANTHROPIC_API_KEY (or ANTHROPIC_API_KEY) is not set.');
|
|
||||||
}
|
|
||||||
const provider = createAnthropic({
|
|
||||||
apiKey: orchestratorKey,
|
|
||||||
headers: {
|
|
||||||
'anthropic-beta': 'prompt-caching-2024-07-31',
|
|
||||||
},
|
|
||||||
});
|
|
||||||
return provider(mapped);
|
|
||||||
}
|
|
||||||
if (modelId.startsWith('openai/')) {
|
|
||||||
const name = modelId.split('/')[1];
|
|
||||||
|
|
||||||
const delegateKey =
|
|
||||||
apiKeys?.openai ||
|
|
||||||
process.env.RAH_DELEGATE_OPENAI_API_KEY ||
|
|
||||||
process.env.OPENAI_API_KEY;
|
|
||||||
if (!delegateKey) {
|
|
||||||
throw new Error('RAH_DELEGATE_OPENAI_API_KEY (or OPENAI_API_KEY) is not set.');
|
|
||||||
}
|
|
||||||
const provider = createOpenAI({ apiKey: delegateKey });
|
|
||||||
return provider(name);
|
|
||||||
}
|
|
||||||
throw new Error(`Unsupported model id: ${modelId}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Global cache stats storage for monitoring
|
|
||||||
declare global {
|
|
||||||
// eslint-disable-next-line no-var
|
|
||||||
var lastCacheStats: CacheStats | undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
type AnthropicUsageLike = {
|
|
||||||
inputTokens?: number;
|
|
||||||
outputTokens?: number;
|
|
||||||
promptTokens?: number;
|
|
||||||
completionTokens?: number;
|
|
||||||
cacheCreationInputTokens?: number;
|
|
||||||
cachedInputTokens?: number;
|
|
||||||
totalTokens?: number;
|
|
||||||
cache_write_input_tokens?: number;
|
|
||||||
cache_read_input_tokens?: number;
|
|
||||||
cacheWriteInputTokens?: number;
|
|
||||||
cacheReadInputTokens?: number;
|
|
||||||
};
|
|
||||||
|
|
||||||
function normaliseUsage(entry: AnthropicUsageLike | undefined | null) {
|
|
||||||
if (!entry) {
|
|
||||||
return { input: 0, output: 0, cacheWrite: 0, cacheRead: 0 };
|
|
||||||
}
|
|
||||||
|
|
||||||
const toNumber = (value: unknown): number => {
|
|
||||||
if (typeof value === 'number' && Number.isFinite(value)) return value;
|
|
||||||
if (typeof value === 'string' && value.trim().length > 0) {
|
|
||||||
const parsed = Number(value);
|
|
||||||
return Number.isFinite(parsed) ? parsed : 0;
|
|
||||||
}
|
|
||||||
return 0;
|
|
||||||
};
|
|
||||||
|
|
||||||
const input = toNumber(entry.inputTokens ?? entry.promptTokens ?? (entry as any).input_tokens ?? 0);
|
|
||||||
const output = toNumber(entry.outputTokens ?? entry.completionTokens ?? (entry as any).output_tokens ?? 0);
|
|
||||||
const cacheWrite = toNumber(
|
|
||||||
entry.cacheCreationInputTokens ??
|
|
||||||
(entry as any).cache_creation_input_tokens ??
|
|
||||||
entry.cacheWriteInputTokens ??
|
|
||||||
entry.cache_write_input_tokens ??
|
|
||||||
0
|
|
||||||
);
|
|
||||||
const cacheRead = toNumber(
|
|
||||||
entry.cachedInputTokens ??
|
|
||||||
entry.cacheReadInputTokens ??
|
|
||||||
(entry as any).cache_read_input_tokens ??
|
|
||||||
0
|
|
||||||
);
|
|
||||||
|
|
||||||
return { input, output, cacheWrite, cacheRead };
|
|
||||||
}
|
|
||||||
|
|
||||||
function aggregateAnthropicUsage(usage: AnthropicUsageLike | undefined, providerMetadata: any) {
|
|
||||||
const totals = { inputTokens: 0, outputTokens: 0, cacheWriteTokens: 0, cacheReadTokens: 0 };
|
|
||||||
let dataSources = 0;
|
|
||||||
|
|
||||||
const add = (entry: AnthropicUsageLike | undefined | null) => {
|
|
||||||
if (!entry) return;
|
|
||||||
const { input, output, cacheWrite, cacheRead } = normaliseUsage(entry);
|
|
||||||
if (input || output || cacheWrite || cacheRead) {
|
|
||||||
totals.inputTokens += input;
|
|
||||||
totals.outputTokens += output;
|
|
||||||
totals.cacheWriteTokens += cacheWrite;
|
|
||||||
totals.cacheReadTokens += cacheRead;
|
|
||||||
dataSources += 1;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const anthropicMeta = providerMetadata?.anthropic;
|
|
||||||
|
|
||||||
if (anthropicMeta) {
|
|
||||||
if (Array.isArray(anthropicMeta.requests)) {
|
|
||||||
anthropicMeta.requests.forEach((req: any) => {
|
|
||||||
add(req?.usage ?? req);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (anthropicMeta.response?.usage) {
|
|
||||||
add(anthropicMeta.response.usage);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (anthropicMeta.usage && dataSources === 0) {
|
|
||||||
add(anthropicMeta.usage);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (dataSources === 0) {
|
|
||||||
add(usage);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Fallback to usage totals if metadata did not provide cache info
|
|
||||||
if (totals.cacheReadTokens === 0 && usage?.cachedInputTokens) {
|
|
||||||
totals.cacheReadTokens = normaliseUsage(usage).cacheRead;
|
|
||||||
}
|
|
||||||
if (totals.cacheWriteTokens === 0 && usage?.cacheCreationInputTokens) {
|
|
||||||
totals.cacheWriteTokens = normaliseUsage(usage).cacheWrite;
|
|
||||||
}
|
|
||||||
|
|
||||||
return totals;
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function POST(request: NextRequest) {
|
|
||||||
let helperKey = 'ra-h';
|
|
||||||
try {
|
|
||||||
const {
|
|
||||||
messages = [],
|
|
||||||
openTabs = [],
|
|
||||||
activeTabId = null,
|
|
||||||
currentView = 'nodes',
|
|
||||||
sessionId,
|
|
||||||
traceId,
|
|
||||||
mode: requestedMode = 'easy',
|
|
||||||
apiKeys: rawApiKeys
|
|
||||||
} = await request.json();
|
|
||||||
const apiKeys: ApiKeyOverrides | undefined = rawApiKeys
|
|
||||||
? {
|
|
||||||
openai: typeof rawApiKeys.openai === 'string' && rawApiKeys.openai.trim().length > 0
|
|
||||||
? rawApiKeys.openai.trim()
|
|
||||||
: undefined,
|
|
||||||
anthropic: typeof rawApiKeys.anthropic === 'string' && rawApiKeys.anthropic.trim().length > 0
|
|
||||||
? rawApiKeys.anthropic.trim()
|
|
||||||
: undefined,
|
|
||||||
}
|
|
||||||
: undefined;
|
|
||||||
|
|
||||||
const mode: 'easy' | 'hard' = requestedMode === 'hard' ? 'hard' : 'easy';
|
|
||||||
helperKey = mode === 'hard' ? 'ra-h' : 'ra-h-easy';
|
|
||||||
|
|
||||||
const conversationTraceId = traceId || randomUUID();
|
|
||||||
|
|
||||||
RequestContext.set({
|
|
||||||
traceId: conversationTraceId,
|
|
||||||
openTabs,
|
|
||||||
activeTabId,
|
|
||||||
mode,
|
|
||||||
apiKeys,
|
|
||||||
});
|
|
||||||
|
|
||||||
// Filter messages to only valid roles
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
||||||
const sanitizedMessages = messages.filter((msg: any) =>
|
|
||||||
msg && ['user', 'assistant', 'system'].includes(msg.role)
|
|
||||||
);
|
|
||||||
|
|
||||||
const helperConfig = await AgentRegistry.orchestratorForMode(mode);
|
|
||||||
if (!helperConfig) {
|
|
||||||
throw new Error(`No orchestrator definition found for mode '${mode}'`);
|
|
||||||
}
|
|
||||||
|
|
||||||
helperKey = helperConfig.key || helperKey;
|
|
||||||
|
|
||||||
helperLogger.logUserMessage(helperKey, messages, openTabs, activeTabId);
|
|
||||||
|
|
||||||
const { blocks: systemBlocks, cacheHit } = await buildSystemPromptBlocks(
|
|
||||||
{ nodes: openTabs, activeNodeId: activeTabId },
|
|
||||||
helperKey
|
|
||||||
);
|
|
||||||
|
|
||||||
const systemPromptPreview = systemBlocks.map(b => b.text).join('\n').substring(0, 200) + '...';
|
|
||||||
helperLogger.logSystemPrompt(helperKey, systemPromptPreview, cacheHit);
|
|
||||||
|
|
||||||
console.log('🔧 [Prompt Caching] System blocks structure:', {
|
|
||||||
helperKey,
|
|
||||||
totalBlocks: systemBlocks.length,
|
|
||||||
cachedBlocks: systemBlocks.filter(b => b.cache_control).length,
|
|
||||||
blockLengths: systemBlocks.map((b, i) => ({
|
|
||||||
index: i,
|
|
||||||
length: b.text.length,
|
|
||||||
cached: !!b.cache_control
|
|
||||||
}))
|
|
||||||
});
|
|
||||||
|
|
||||||
const toolNames = helperConfig.availableTools?.length
|
|
||||||
? helperConfig.availableTools
|
|
||||||
: getDefaultToolNamesForRole(helperConfig.role);
|
|
||||||
const tools = getHelperTools(toolNames);
|
|
||||||
const modelId = helperConfig.model || 'anthropic/claude-sonnet-4.5';
|
|
||||||
const model = resolveModel(modelId, apiKeys);
|
|
||||||
|
|
||||||
const rawModelId = modelId.split('/')[1] || modelId;
|
|
||||||
const fullModelId = ANTHROPIC_MODEL_MAP[rawModelId] || rawModelId;
|
|
||||||
|
|
||||||
const isAnthropicModel = modelId.startsWith('anthropic/');
|
|
||||||
const isOpenAIModel = modelId.startsWith('openai/');
|
|
||||||
|
|
||||||
const toolsUsedInSession: string[] = [];
|
|
||||||
|
|
||||||
// Convert system blocks to messages with providerOptions for caching
|
|
||||||
const systemMessages = systemBlocks.map((block) => ({
|
|
||||||
role: 'system' as const,
|
|
||||||
content: block.text,
|
|
||||||
...(isAnthropicModel && block.cache_control ? {
|
|
||||||
providerOptions: {
|
|
||||||
anthropic: { cacheControl: block.cache_control }
|
|
||||||
}
|
|
||||||
} : {})
|
|
||||||
}));
|
|
||||||
|
|
||||||
const coreMessages = await convertToModelMessages(sanitizedMessages);
|
|
||||||
const allMessages = [...systemMessages, ...coreMessages];
|
|
||||||
|
|
||||||
// Debug logging (can be removed in production)
|
|
||||||
if (process.env.DEBUG_CACHE === 'true') {
|
|
||||||
console.log('🔍 [Debug] System messages with cache control:');
|
|
||||||
systemMessages.forEach((msg, i) => {
|
|
||||||
console.log(` Block ${i}:`, {
|
|
||||||
hasContent: !!msg.content,
|
|
||||||
contentLength: msg.content?.length || 0,
|
|
||||||
hasProviderOptions: !!msg.providerOptions,
|
|
||||||
cacheControl: msg.providerOptions?.anthropic?.cacheControl
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
const streamConfig = {
|
|
||||||
model,
|
|
||||||
messages: allMessages,
|
|
||||||
tools,
|
|
||||||
stopWhen: [],
|
|
||||||
maxSteps: 10,
|
|
||||||
...(isOpenAIModel ? { reasoning: { effort: 'light' as const } } : {}),
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
||||||
onToolCall: ({ toolCall }: any) => {
|
|
||||||
helperLogger.logToolCall(helperKey, toolCall.toolName, toolCall.args);
|
|
||||||
if (!toolsUsedInSession.includes(toolCall.toolName)) {
|
|
||||||
toolsUsedInSession.push(toolCall.toolName);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
||||||
onToolResult: ({ toolCall, result }: any) => {
|
|
||||||
helperLogger.logToolResult(helperKey, toolCall.toolName, result);
|
|
||||||
},
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
||||||
onFinish: (result: any) => {
|
|
||||||
helperLogger.logAssistantResponse(helperKey, result.text);
|
|
||||||
|
|
||||||
// Debug logging (can be removed in production)
|
|
||||||
if (process.env.DEBUG_CACHE === 'true') {
|
|
||||||
console.log('🔍 [Debug] Full onFinish result keys:', Object.keys(result));
|
|
||||||
console.log('🔍 [Debug] Raw usage object:', JSON.stringify(result.usage, null, 2));
|
|
||||||
console.log('🔍 [Debug] Provider metadata:', JSON.stringify(result.providerMetadata, null, 2));
|
|
||||||
console.log('🔍 [Debug] Steps array length:', result.steps?.length || 0);
|
|
||||||
if (result.steps && result.steps.length > 0) {
|
|
||||||
result.steps.forEach((step: any, i: number) => {
|
|
||||||
console.log(`🔍 [Debug] Step ${i} usage:`, JSON.stringify(step.usage, null, 2));
|
|
||||||
console.log(`🔍 [Debug] Step ${i} providerMetadata:`, JSON.stringify(step.providerMetadata, null, 2));
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const aggregatedUsage = isAnthropicModel
|
|
||||||
? (() => {
|
|
||||||
// Aggregate across ALL steps, not just final result
|
|
||||||
const totals = { inputTokens: 0, outputTokens: 0, cacheWriteTokens: 0, cacheReadTokens: 0 };
|
|
||||||
|
|
||||||
if (result.steps && Array.isArray(result.steps)) {
|
|
||||||
result.steps.forEach((step: any) => {
|
|
||||||
const stepUsage = aggregateAnthropicUsage(step.usage, step.providerMetadata);
|
|
||||||
totals.inputTokens += stepUsage.inputTokens;
|
|
||||||
totals.outputTokens += stepUsage.outputTokens;
|
|
||||||
// Cache write only happens once (in first step), so take MAX not SUM
|
|
||||||
totals.cacheWriteTokens = Math.max(totals.cacheWriteTokens, stepUsage.cacheWriteTokens);
|
|
||||||
totals.cacheReadTokens += stepUsage.cacheReadTokens;
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
// Fallback to result-level usage if no steps
|
|
||||||
const resultUsage = aggregateAnthropicUsage(result.usage, result.providerMetadata);
|
|
||||||
totals.inputTokens = resultUsage.inputTokens;
|
|
||||||
totals.outputTokens = resultUsage.outputTokens;
|
|
||||||
totals.cacheWriteTokens = resultUsage.cacheWriteTokens;
|
|
||||||
totals.cacheReadTokens = resultUsage.cacheReadTokens;
|
|
||||||
}
|
|
||||||
|
|
||||||
return totals;
|
|
||||||
})()
|
|
||||||
: {
|
|
||||||
inputTokens: Number(result.usage?.inputTokens ?? result.usage?.promptTokens ?? 0),
|
|
||||||
outputTokens: Number(result.usage?.outputTokens ?? result.usage?.completionTokens ?? 0),
|
|
||||||
cacheWriteTokens: 0,
|
|
||||||
cacheReadTokens: 0,
|
|
||||||
};
|
|
||||||
|
|
||||||
const regular = aggregatedUsage.inputTokens || 0;
|
|
||||||
const cacheWrite = aggregatedUsage.cacheWriteTokens || 0;
|
|
||||||
const cacheRead = aggregatedUsage.cacheReadTokens || 0;
|
|
||||||
const outputTokens = aggregatedUsage.outputTokens || 0;
|
|
||||||
const total = regular + cacheWrite + cacheRead;
|
|
||||||
|
|
||||||
if (regular || cacheWrite || cacheRead || outputTokens) {
|
|
||||||
const savingsPercentage = total > 0 && cacheRead > 0 ? Math.round((cacheRead / total) * 100) : 0;
|
|
||||||
|
|
||||||
if (isAnthropicModel) {
|
|
||||||
const cacheStats: CacheStats = {
|
|
||||||
cacheCreationInputTokens: cacheWrite,
|
|
||||||
cacheReadInputTokens: cacheRead,
|
|
||||||
inputTokens: regular,
|
|
||||||
outputTokens,
|
|
||||||
savingsPercentage
|
|
||||||
};
|
|
||||||
|
|
||||||
console.log('\n📦 ═══════════════════════════════════════');
|
|
||||||
console.log('📦 ANTHROPIC PROMPT CACHE STATISTICS');
|
|
||||||
console.log('📦 ═══════════════════════════════════════');
|
|
||||||
console.log(`📦 Cache Write: ${cacheWrite.toLocaleString()} tokens ${cacheWrite > 0 ? '(NEW CACHE CREATED ✨)' : ''}`);
|
|
||||||
console.log(`📦 Cache Read: ${cacheRead.toLocaleString()} tokens ${cacheRead > 0 ? '(CACHE HIT 🎯)' : '(CACHE MISS ❌)'}`);
|
|
||||||
console.log(`📦 Regular: ${regular.toLocaleString()} tokens`);
|
|
||||||
console.log(`📦 Total Input: ${total.toLocaleString()} tokens`);
|
|
||||||
console.log(`📦 Output: ${cacheStats.outputTokens.toLocaleString()} tokens`);
|
|
||||||
if (cacheRead > 0) {
|
|
||||||
const costSavings = Math.round(((cacheWrite * 1.25 + regular * 1.0 + cacheRead * 0.1) / (total * 1.0)) * 100);
|
|
||||||
console.log(`📦 💰 Savings: ${savingsPercentage}% tokens, ~${100 - costSavings}% cost`);
|
|
||||||
}
|
|
||||||
console.log('📦 ═══════════════════════════════════════\n');
|
|
||||||
|
|
||||||
global.lastCacheStats = cacheStats;
|
|
||||||
}
|
|
||||||
|
|
||||||
const costResult = calculateCost({
|
|
||||||
inputTokens: regular,
|
|
||||||
outputTokens,
|
|
||||||
cacheWriteTokens: cacheWrite,
|
|
||||||
cacheReadTokens: cacheRead,
|
|
||||||
modelId: fullModelId,
|
|
||||||
});
|
|
||||||
|
|
||||||
usageData = {
|
|
||||||
inputTokens: regular,
|
|
||||||
outputTokens,
|
|
||||||
totalTokens: costResult.totalTokens,
|
|
||||||
cacheWriteTokens: cacheWrite,
|
|
||||||
cacheReadTokens: cacheRead,
|
|
||||||
cacheHit: cacheRead > 0,
|
|
||||||
cacheSavingsPct: savingsPercentage,
|
|
||||||
estimatedCostUsd: costResult.totalCostUsd,
|
|
||||||
modelUsed: fullModelId,
|
|
||||||
provider: isAnthropicModel ? 'anthropic' : 'openai',
|
|
||||||
toolsUsed: toolsUsedInSession.length > 0 ? toolsUsedInSession : undefined,
|
|
||||||
toolCallsCount: toolsUsedInSession.length > 0 ? toolsUsedInSession.length : undefined,
|
|
||||||
traceId: conversationTraceId,
|
|
||||||
workflowKey: currentContext.workflowKey,
|
|
||||||
workflowNodeId: currentContext.workflowNodeId,
|
|
||||||
mode,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
let usageData: UsageData | undefined;
|
|
||||||
|
|
||||||
const systemMessageText = systemBlocks.map(b => b.text).join('\n\n');
|
|
||||||
const currentContext = RequestContext.get();
|
|
||||||
|
|
||||||
const chatMetadata = {
|
|
||||||
helperName: helperKey,
|
|
||||||
openTabs,
|
|
||||||
activeTabId,
|
|
||||||
currentView,
|
|
||||||
sessionId: sessionId || `session_${Date.now()}`,
|
|
||||||
agentType: 'orchestrator' as const,
|
|
||||||
traceId: conversationTraceId,
|
|
||||||
mode,
|
|
||||||
modelUsed: fullModelId,
|
|
||||||
systemMessage: systemMessageText,
|
|
||||||
workflowKey: currentContext.workflowKey,
|
|
||||||
workflowNodeId: currentContext.workflowNodeId,
|
|
||||||
get usageData() {
|
|
||||||
return usageData;
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
const result = await streamText(withChatLogging(streamConfig, chatMetadata, messages));
|
|
||||||
|
|
||||||
return result.toUIMessageStreamResponse();
|
|
||||||
} catch (error) {
|
|
||||||
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
||||||
const errorStack = error instanceof Error ? error.stack : undefined;
|
|
||||||
|
|
||||||
console.error(`❌ [${helperKey}] Route error:`, {
|
|
||||||
error: errorMessage,
|
|
||||||
stack: errorStack,
|
|
||||||
timestamp: new Date().toISOString()
|
|
||||||
});
|
|
||||||
|
|
||||||
helperLogger.logError(helperKey, error instanceof Error ? error : String(error));
|
|
||||||
return new Response(
|
|
||||||
JSON.stringify({
|
|
||||||
error: errorMessage,
|
|
||||||
details: 'Check server logs for full error details'
|
|
||||||
}),
|
|
||||||
{
|
|
||||||
status: 500,
|
|
||||||
headers: { 'Content-Type': 'application/json' }
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,28 +0,0 @@
|
|||||||
import { NextRequest } from 'next/server';
|
|
||||||
import { getSQLiteClient } from '@/services/database/sqlite-client';
|
|
||||||
|
|
||||||
export async function GET(request: NextRequest) {
|
|
||||||
const sessionId = request.nextUrl.searchParams.get('sessionId');
|
|
||||||
|
|
||||||
if (!sessionId) {
|
|
||||||
return Response.json({ inputTokens: 0 });
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
const sqlite = getSQLiteClient();
|
|
||||||
const row = sqlite.prepare(`
|
|
||||||
SELECT json_extract(metadata, '$.input_tokens') as input_tokens
|
|
||||||
FROM chats
|
|
||||||
WHERE thread_id LIKE ?
|
|
||||||
ORDER BY created_at DESC
|
|
||||||
LIMIT 1
|
|
||||||
`).get(`%${sessionId}%`) as { input_tokens: number | null } | undefined;
|
|
||||||
|
|
||||||
return Response.json({
|
|
||||||
inputTokens: row?.input_tokens ?? 0
|
|
||||||
});
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Usage fetch error:', error);
|
|
||||||
return Response.json({ inputTokens: 0 });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Reference in New Issue
Block a user