import { generateText, tool, ModelMessage, stepCountIs } from 'ai';
import { z } from 'zod';
import { exa } from './utils';
export const generateResponse = async (
messages: ModelMessage[],
updateStatus?: (status: string) => void,
) => {
const { text } = await generateText({
model: 'anthropic/claude-sonnet-4-20250514',
system: `You are a Slack bot assistant. Keep your responses concise and to the point.
- Do not tag users.
- Current date is: ${new Date().toISOString().split('T')[0]}
- Always include sources in your final response if you use web search.`,
messages,
stopWhen: stepCountIs(10),
tools: {
getWeather: tool({
description: 'Get the current weather at a location',
inputSchema: z.object({
latitude: z.number(),
longitude: z.number(),
city: z.string(),
}),
execute: async ({ latitude, longitude, city }) => {
updateStatus?.(`is getting weather for ${city}...`);
const response = await fetch(
`https://api.open-meteo.com/v1/forecast?latitude=${latitude}&longitude=${longitude}¤t=temperature_2m,weathercode,relativehumidity_2m&timezone=auto`,
);
const weatherData = await response.json();
return {
temperature: weatherData.current.temperature_2m,
weatherCode: weatherData.current.weathercode,
humidity: weatherData.current.relativehumidity_2m,
city,
};
},
}),
searchWeb: tool({
description: 'Use this to search the web for information',
inputSchema: z.object({
query: z.string(),
specificDomain: z
.string()
.nullable()
.describe(
'a domain to search if the user specifies e.g. bbc.com',
),
}),
execute: async ({ query, specificDomain }) => {
updateStatus?.(`is searching the web for ${query}...`);
const { results } = await exa.searchAndContents(query, {
livecrawl: 'always',
numResults: 3,
includeDomains: specificDomain ? [specificDomain] : undefined,
});
return {
results: results.map(result => ({
title: result.title,
url: result.url,
snippet: result.text.slice(0, 1000),
})),
};
},
}),
},
});
// Convert markdown to Slack mrkdwn format
return text.replace(/\[(.*?)\]\((.*?)\)/g, '<$2|$1>').replace(/\*\*/g, '*');
};