From 98aefb0b121543d97a787caf2d484e726a0d068a Mon Sep 17 00:00:00 2001 From: Naomi Carrigan Date: Tue, 23 Dec 2025 14:37:26 -0800 Subject: [PATCH] feat: include cost in announcements --- server/src/modules/generateAnnouncements.ts | 17 +++++++---- server/src/routes/announcement.ts | 5 ++-- server/src/utils/getAiCost.ts | 31 +++++++++++++++++++++ 3 files changed, 45 insertions(+), 8 deletions(-) create mode 100644 server/src/utils/getAiCost.ts diff --git a/server/src/modules/generateAnnouncements.ts b/server/src/modules/generateAnnouncements.ts index d15f852..32bb17e 100644 --- a/server/src/modules/generateAnnouncements.ts +++ b/server/src/modules/generateAnnouncements.ts @@ -10,8 +10,8 @@ import { announcementJsonSchema, announcementSystemMessage, } from "../config/announcements.js"; -import type { AnnouncementResponse } - from "../interfaces/announcementResponse.js"; +import { getAiCost } from "../utils/getAiCost.js"; +import type { AnnouncementResponse } from "../interfaces/announcementResponse.js"; /** * Generates announcements for all platforms using AI. @@ -20,7 +20,7 @@ import type { AnnouncementResponse } */ export const generateAnnouncements = async( content: string, -): Promise => { +): Promise<{ cost: string; response: AnnouncementResponse } | null> => { if (process.env.ANTHROPIC_KEY === undefined) { return null; } @@ -46,12 +46,17 @@ export const generateAnnouncements = async( }, system: announcementSystemMessage, }); - const text = response.content.find((m) => { + const { usage, content: responseContent } = response; + const text = responseContent.find((m) => { return m.type === "text"; }); if (text?.text === undefined) { return null; } - // eslint-disable-next-line @typescript-eslint/consistent-type-assertions -- Being lazy. - return JSON.parse(text.text) as AnnouncementResponse; + + return { + cost: getAiCost(usage), + // eslint-disable-next-line @typescript-eslint/consistent-type-assertions -- Being lazy. + response: JSON.parse(text.text) as AnnouncementResponse, + }; }; diff --git a/server/src/routes/announcement.ts b/server/src/routes/announcement.ts index 2dbb403..5a4fd0f 100644 --- a/server/src/routes/announcement.ts +++ b/server/src/routes/announcement.ts @@ -86,7 +86,7 @@ export const announcementRoutes: FastifyPluginAsync = async(server) => { }); } - const { bluesky, discord, reddit, twitter } = announcement; + const { bluesky, discord, reddit, twitter } = announcement.response; const { title: discordTitle, content: discordContent } = discord; const { title: redditTitle, content: redditContent } = reddit; @@ -111,8 +111,9 @@ export const announcementRoutes: FastifyPluginAsync = async(server) => { const blueskyPost = await announceOnBluesky(bluesky); const twitterPost = await announceOnTwitter(twitter); return await reply.status(201).send({ + cost: announcement.cost, message: `Announcement processed. Discord: ${discordPost}, Reddit: ${redditPost}, Bluesky: ${blueskyPost}, Twitter: ${twitterPost}`, - rawPost: announcement, + rawPost: announcement.response, }); }, ); diff --git a/server/src/utils/getAiCost.ts b/server/src/utils/getAiCost.ts new file mode 100644 index 0000000..265a38b --- /dev/null +++ b/server/src/utils/getAiCost.ts @@ -0,0 +1,31 @@ +/** + * @copyright nhcarrigan + * @license Naomi's Public License + * @author Naomi Carrigan + */ + +import type { BetaUsage } from "@anthropic-ai/sdk/resources/beta.js"; + +/** + * Calculates the cost of an AI response. + * @param usage - The usage payload from Anthropic. + * @returns A description of the cost of the AI response. + */ +export const getAiCost = (usage: BetaUsage): string => { + const { input_tokens: inputTokens, output_tokens: outputTokens } = usage; + const costPerInputToken = 5 / 1_000_000; + const costPerOutputToken = 25 / 1_000_000; + const inputCost = inputTokens * costPerInputToken; + const outputCost = outputTokens * costPerOutputToken; + const totalCost = inputCost + outputCost; + return `Input cost: ${inputCost.toLocaleString("en-GB", { + currency: "USD", + style: "currency", + })} Output cost: ${outputCost.toLocaleString("en-GB", { + currency: "USD", + style: "currency", + })} Total cost: ${totalCost.toLocaleString("en-GB", { + currency: "USD", + style: "currency", + })}`; +};