Vercel ai gateway (#7)

* feat: use vercel ai gateway

* chore: update UI
This commit is contained in:
2025-10-11 12:15:30 +02:00
committed by GitHub
parent ff84b75847
commit 7c9635ba83
14 changed files with 2348 additions and 1809 deletions

54
utils/aiGatewayClient.ts Normal file
View File

@@ -0,0 +1,54 @@
import { generateText } from 'ai';
interface AiGatewayResponse {
text: string;
tokensUsed: number;
}
export async function queryAiGateway(
text: string,
model: string
): Promise<AiGatewayResponse> {
const requestId = Math.random().toString(36).substring(7);
const startTime = Date.now();
console.info(`[AI-${requestId}] Starting Vercel Gateway AI request`, {
promptLength: text.length,
model,
timestamp: new Date().toISOString()
});
try {
const response = await generateText({
model,
prompt: text
});
const duration = Date.now() - startTime;
const tokensUsed = response.usage?.totalTokens || 0;
console.info(
`[AI-${requestId}] Vercel Gateway AI response received in ${duration}ms`,
{
responseLength: response.text.length,
tokensUsed,
usage: response.usage
}
);
return {
text: response.text,
tokensUsed
};
} catch (error) {
const duration = Date.now() - startTime;
console.error(
`[AI-${requestId}] Vercel Gateway AI error after ${duration}ms:`,
{
error: error instanceof Error ? error.message : String(error),
promptLength: text.length
}
);
throw new Error(`Vercel Gateway AI error: ${JSON.stringify(error)}.`);
}
}

View File

@@ -1,43 +0,0 @@
import Anthropic from '@anthropic-ai/sdk';
interface AnthropicResponse {
text: string;
tokensUsed: number;
}
export async function getMessage(text: string): Promise<AnthropicResponse> {
const anthropic = new Anthropic({
apiKey: process.env.ANTHROPIC_API_KEY
});
console.info('Anthropic request with text: ', text);
const response = await anthropic.messages.create({
model: 'claude-3-5-sonnet-20241022',
max_tokens: 2048,
messages: [{ role: 'user', content: text }]
});
console.info('Anthropic response: ', response);
try {
const data = response.content as [{ type: string; text: string }];
const tokensUsed =
(response.usage?.input_tokens || 0) +
(response.usage?.output_tokens || 0);
console.info('Token usage:', {
input_tokens: response.usage?.input_tokens,
output_tokens: response.usage?.output_tokens,
total: tokensUsed
});
return {
text: data[0].text,
tokensUsed
};
} catch (error) {
throw new Error(`Anthropic client error: ${JSON.stringify(error)}.`);
}
}

View File

@@ -1,18 +1,27 @@
import { getMessage } from '@utils/anthropicClient';
import { queryAiGateway } from '@utils/aiGatewayClient';
import { ShortcutsResponse } from '../types';
import { dbOperations } from '@utils/db';
export async function anthropicCommand(
parameters: Record<string, string> | undefined
): Promise<ShortcutsResponse> {
const commandId = Math.random().toString(36).substring(7);
const startTime = Date.now();
let question = '';
let response = '';
let success = false;
let errorMessage: string | undefined;
let tokensUsed: number | undefined;
console.info(`[CMD-${commandId}] Anthropic command started`, {
hasParameters: !!parameters,
timestamp: new Date().toISOString()
});
try {
if (!parameters || !parameters['question']) {
console.warn(`[CMD-${commandId}] Missing question parameter`);
errorMessage = 'Need to provide a question.';
return {
success: false,
@@ -21,16 +30,35 @@ export async function anthropicCommand(
}
question = parameters['question'];
console.info(`[CMD-${commandId}] Processing question`, {
questionLength: question.length,
question:
question.substring(0, 100) + (question.length > 100 ? '...' : '')
});
const prompt =
'I want to know ' +
question +
'. Structure the response in a manner suitable for spoken communication.';
const anthropicResponse = await getMessage(prompt);
const anthropicResponse = await queryAiGateway(
prompt,
'anthropic/claude-sonnet-4.5'
);
response = anthropicResponse.text;
tokensUsed = anthropicResponse.tokensUsed;
success = true;
const duration = Date.now() - startTime;
console.info(
`[CMD-${commandId}] Anthropic command completed in ${duration}ms`,
{
responseLength: response.length,
tokensUsed,
success: true
}
);
return {
success: true,
message: response,
@@ -39,7 +67,11 @@ export async function anthropicCommand(
}
};
} catch (error) {
console.error('Anthropic command error:', error);
const duration = Date.now() - startTime;
console.error(
`[CMD-${commandId}] Anthropic command failed after ${duration}ms:`,
error
);
success = false;
errorMessage = error instanceof Error ? error.message : 'Unknown error';
response = 'Sorry. There was a problem with Anthropic.';
@@ -51,6 +83,7 @@ export async function anthropicCommand(
} finally {
if (question) {
try {
console.info(`[CMD-${commandId}] Saving query to database`);
await dbOperations.saveAnthropicQuery({
question,
response,
@@ -58,8 +91,12 @@ export async function anthropicCommand(
errorMessage,
tokensUsed
});
console.info(`[CMD-${commandId}] Query saved to database successfully`);
} catch (error) {
console.error('Failed to log query to database:', error);
console.error(
`[CMD-${commandId}] Failed to log query to database:`,
error
);
}
}
}

View File

@@ -2,7 +2,7 @@ import { z } from 'zod';
export const RequestSchema = z.object({
command: z.string(),
parameters: z.record(z.string()).optional(),
parameters: z.record(z.string(), z.string()).optional(),
apiKey: z.string()
});