Compare commits

..

2 Commits

Author SHA1 Message Date
8c72375761
feat: log token usage 2025-02-10 21:07:52 -08:00
a347ff8eb6
feat: use our logging package 2025-02-10 21:00:17 -08:00
14 changed files with 95 additions and 38 deletions

View File

@ -22,6 +22,7 @@
},
"dependencies": {
"@anthropic-ai/sdk": "0.36.3",
"@nhcarrigan/logger": "1.0.0",
"discord.js": "14.18.0",
"fastify": "5.2.1",
"winston": "3.17.0"

8
pnpm-lock.yaml generated
View File

@ -11,6 +11,9 @@ importers:
'@anthropic-ai/sdk':
specifier: 0.36.3
version: 0.36.3
'@nhcarrigan/logger':
specifier: 1.0.0
version: 1.0.0
discord.js:
specifier: 14.18.0
version: 14.18.0
@ -347,6 +350,9 @@ packages:
typescript: '>=5'
vitest: '>=2'
'@nhcarrigan/logger@1.0.0':
resolution: {integrity: sha512-2e19Bie+ZKb6yKPKjhawqsENkhHatYkvBAmFZx9eToOXdOca+CYi51tldRMtejg6e0+4hOOf2bo5zdBQKmH0dw==}
'@nhcarrigan/typescript-config@4.0.0':
resolution: {integrity: sha512-969HVha7A/Sg77fuMwOm6p14a+7C5iE6g55OD71srqwKIgksQl+Ex/hAI/pyzTQFDQ/FBJbpnHlR4Ov25QV/rw==}
engines: {node: '20', pnpm: '9'}
@ -2588,6 +2594,8 @@ snapshots:
- eslint-import-resolver-webpack
- supports-color
'@nhcarrigan/logger@1.0.0': {}
'@nhcarrigan/typescript-config@4.0.0(typescript@5.7.3)':
dependencies:
typescript: 5.7.3

View File

@ -1,2 +1,3 @@
DISCORD_TOKEN="op://Environment Variables - Naomi/Cordelia Taryne/discord_token"
AI_TOKEN="op://Environment Variables - Naomi/Cordelia Taryne/ai_token"
LOG_TOKEN="op://Environment Variables - Naomi/Alert Server/api_auth"

View File

@ -12,7 +12,7 @@ import { proofread } from "./modules/proofread.js";
import { query } from "./modules/query.js";
import { summarise } from "./modules/summarise.js";
import { instantiateServer } from "./server/serve.js";
import { logHandler } from "./utils/logHandler.js";
import { logger } from "./utils/logger.js";
const commands: Record<
string,
@ -41,8 +41,16 @@ client.on(Events.InteractionCreate, (interaction) => {
}
});
client.on(Events.EntitlementCreate, (entitlement) => {
void logger.log("info", `User ${entitlement.userId} has subscribed!`);
});
client.on(Events.EntitlementDelete, (entitlement) => {
void logger.log("info", `User ${entitlement.userId} has unsubscribed... :c`);
});
client.on(Events.ClientReady, () => {
logHandler.info("Bot is ready.");
void logger.log("debug", "Bot is ready.");
});
instantiateServer();

View File

@ -6,6 +6,7 @@
import { MessageFlags, type ChatInputCommandInteraction } from "discord.js";
import { personality } from "../config/personality.js";
import { ai } from "../utils/ai.js";
import { calculateCost } from "../utils/calculateCost.js";
import { isSubscribed } from "../utils/isSubscribed.js";
import type { ImageBlockParam } from "@anthropic-ai/sdk/resources/index.js";
@ -109,4 +110,7 @@ export const alt = async(
response?.text
?? "I'm sorry, I don't have an answer for that. Please try again later.",
);
const { usage } = messages;
await calculateCost(usage, interaction.user.username, "alt-text");
};

View File

@ -6,6 +6,7 @@
import { MessageFlags, type ChatInputCommandInteraction } from "discord.js";
import { personality } from "../config/personality.js";
import { ai } from "../utils/ai.js";
import { calculateCost } from "../utils/calculateCost.js";
import { isSubscribed } from "../utils/isSubscribed.js";
/**
@ -40,4 +41,7 @@ export const evaluate = async(
response?.text
?? "I'm sorry, I don't have an answer for that. Please try again later.",
);
const { usage } = messages;
await calculateCost(usage, interaction.user.username, "evaluate");
};

View File

@ -6,6 +6,7 @@
import { MessageFlags, type ChatInputCommandInteraction } from "discord.js";
import { personality } from "../config/personality.js";
import { ai } from "../utils/ai.js";
import { calculateCost } from "../utils/calculateCost.js";
import { isSubscribed } from "../utils/isSubscribed.js";
/**
@ -41,4 +42,7 @@ export const mood = async(
response?.text
?? "I'm sorry, I don't have an answer for that. Please try again later.",
);
const { usage } = messages;
await calculateCost(usage, interaction.user.username, "mood");
};

View File

@ -6,6 +6,7 @@
import { MessageFlags, type ChatInputCommandInteraction } from "discord.js";
import { personality } from "../config/personality.js";
import { ai } from "../utils/ai.js";
import { calculateCost } from "../utils/calculateCost.js";
import { isSubscribed } from "../utils/isSubscribed.js";
/**
@ -41,4 +42,7 @@ export const proofread = async(
response?.text
?? "I'm sorry, I don't have an answer for that. Please try again later.",
);
const { usage } = messages;
await calculateCost(usage, interaction.user.username, "proofread");
};

View File

@ -6,6 +6,7 @@
import { MessageFlags, type ChatInputCommandInteraction } from "discord.js";
import { personality } from "../config/personality.js";
import { ai } from "../utils/ai.js";
import { calculateCost } from "../utils/calculateCost.js";
import { isSubscribed } from "../utils/isSubscribed.js";
/**
@ -41,4 +42,7 @@ export const query = async(
response?.text
?? "I'm sorry, I don't have an answer for that. Please try again later.",
);
const { usage } = messages;
await calculateCost(usage, interaction.user.username, "query");
};

View File

@ -6,6 +6,7 @@
import { MessageFlags, type ChatInputCommandInteraction } from "discord.js";
import { personality } from "../config/personality.js";
import { ai } from "../utils/ai.js";
import { calculateCost } from "../utils/calculateCost.js";
import { isSubscribed } from "../utils/isSubscribed.js";
/**
@ -41,4 +42,7 @@ export const summarise = async(
response?.text
?? "I'm sorry, I don't have an answer for that. Please try again later.",
);
const { usage } = messages;
await calculateCost(usage, interaction.user.username, "summarise");
};

View File

@ -5,7 +5,7 @@
*/
import fastify from "fastify";
import { logHandler } from "../utils/logHandler.js";
import { logger } from "../utils/logger.js";
const html = `<!DOCTYPE html>
<html>
@ -60,12 +60,16 @@ export const instantiateServer = (): void => {
server.listen({ port: 5002 }, (error) => {
if (error) {
logHandler.error(error);
void logger.error("instantiate server", error);
return;
}
logHandler.info("Server listening on port 5002.");
void logger.log("debug", "Server listening on port 5002.");
});
} catch (error) {
logHandler.error(error);
if (error instanceof Error) {
void logger.error("instantiate server", error);
return;
}
void logger.error("instantiate server", new Error("Unknown error"));
}
};

View File

@ -0,0 +1,30 @@
/**
* @copyright nhcarrigan
* @license Naomi's Public License
* @author Naomi Carrigan
*/
import { logger } from "./logger.js";
import type { Usage } from "@anthropic-ai/sdk/resources/index.js";
/**
* Calculates the cost of a command run by a user, and sends to
* our logging service.
* @param usage -- The usage payload from Anthropic.
* @param uuid -- The Discord ID of the user who ran the command.
* @param command -- The command that was run.
*/
export const calculateCost = async(
usage: Usage,
uuid: string,
command: string,
): Promise<void> => {
const inputCost = usage.input_tokens * (3 / 1_000_000);
const outputCost = usage.output_tokens * (15 / 1_000_000);
const totalCost = inputCost + outputCost;
await logger.log(
"info",
`User ${uuid} ran \`${command}\` which accepted ${usage.input_tokens.toString()} and generated ${usage.output_tokens.toString()}.
Total cost: ${totalCost.toLocaleString("en-GB", { currency: "USD", style: "currency" })}`,
);
};

View File

@ -1,31 +0,0 @@
/**
* @copyright nhcarrigan
* @license Naomi's Public License
* @author Naomi Carrigan
*/
import { createLogger, format, transports, config } from "winston";
const { combine, timestamp, colorize, printf } = format;
/**
* Standard log handler, using winston to wrap and format
* messages. Call with `logHandler.log(level, message)`.
* @param {string} level - The log level to use.
* @param {string} message - The message to log.
*/
export const logHandler = createLogger({
exitOnError: false,
format: combine(
timestamp({
format: "YYYY-MM-DD HH:mm:ss",
}),
colorize(),
printf((info) => {
// eslint-disable-next-line @typescript-eslint/restrict-template-expressions -- Winston properties...
return `${info.level}: ${info.timestamp}: ${info.message}`;
}),
),
level: "silly",
levels: config.npm.levels,
transports: [ new transports.Console() ],
});

12
src/utils/logger.ts Normal file
View File

@ -0,0 +1,12 @@
/**
* @copyright nhcarrigan
* @license Naomi's Public License
* @author Naomi Carrigan
*/
import { Logger } from "@nhcarrigan/logger";
export const logger = new Logger(
"Cordelia Taryne",
process.env.LOG_TOKEN ?? "",
);