101 lines
2.6 KiB
TypeScript
101 lines
2.6 KiB
TypeScript
import { openai } from "@ai-sdk/openai";
|
|
import { generateObject } from "ai";
|
|
import * as z from "zod";
|
|
|
|
import { Credits } from "../credits/utils";
|
|
|
|
import { MODELS, PROMPTS } from "./constants";
|
|
|
|
import type {
|
|
ChatMessagePartPayload,
|
|
ChatMessageOptionsPayload,
|
|
} from "./schema";
|
|
import type { Message, Part, ChatMessage, ChatMessagePart } from "./types";
|
|
import type { AnthropicProviderOptions } from "@ai-sdk/anthropic";
|
|
import type { OpenAIResponsesProviderOptions } from "@ai-sdk/openai";
|
|
import type { XaiProviderOptions } from "@ai-sdk/xai";
|
|
|
|
export const generateChatName = async (content: string) => {
|
|
const { object } = await generateObject({
|
|
model: openai.responses("gpt-4.1-mini"),
|
|
schema: z.object({
|
|
name: z.string().min(1),
|
|
}),
|
|
system: PROMPTS.CHAT_NAME,
|
|
prompt: `User message: ${content}`,
|
|
});
|
|
|
|
return object.name;
|
|
};
|
|
|
|
export const getProviderOptions = (options: ChatMessageOptionsPayload) => {
|
|
const model = MODELS.find((model) => model.id === options.model);
|
|
const reasoning = !!model?.reason && !!options.reason;
|
|
|
|
return {
|
|
anthropic: {
|
|
thinking: {
|
|
type: reasoning ? "enabled" : "disabled",
|
|
budgetTokens: 1200,
|
|
},
|
|
} satisfies AnthropicProviderOptions,
|
|
openai: {
|
|
...(reasoning
|
|
? { reasoningEffort: "medium", reasoningSummary: "detailed" }
|
|
: {}),
|
|
textVerbosity: "medium",
|
|
} satisfies OpenAIResponsesProviderOptions,
|
|
xai: {
|
|
...(reasoning ? { reasoningEffort: "low" } : {}),
|
|
} satisfies XaiProviderOptions,
|
|
};
|
|
};
|
|
|
|
export const getCreditsDeduction = (
|
|
options: ChatMessageOptionsPayload,
|
|
parts?: ChatMessagePartPayload[],
|
|
) => {
|
|
const model = MODELS.find((model) => model.id === options.model);
|
|
|
|
const searchDeduction = options.search
|
|
? Credits.COST.DEFAULT
|
|
: Credits.COST.FREE;
|
|
const reasoningDeduction =
|
|
options.reason && model?.reason ? Credits.COST.DEFAULT : Credits.COST.FREE;
|
|
|
|
const attachments = parts?.filter((part) => part.type === "file");
|
|
const attachmentDeduction = (attachments?.length ?? 0) * Credits.COST.DEFAULT;
|
|
|
|
return (
|
|
Credits.COST.DEFAULT +
|
|
searchDeduction +
|
|
reasoningDeduction +
|
|
attachmentDeduction
|
|
);
|
|
};
|
|
|
|
export const toChatMessagePart = ({
|
|
type,
|
|
details,
|
|
}: Part): ChatMessagePart | null => {
|
|
if (typeof details !== "object" || details === null) {
|
|
return null;
|
|
}
|
|
|
|
return {
|
|
type,
|
|
...details,
|
|
} as ChatMessagePart;
|
|
};
|
|
|
|
export const toChatMessage = (
|
|
message: Message & {
|
|
parts?: Part[];
|
|
},
|
|
): ChatMessage => {
|
|
return {
|
|
...message,
|
|
parts: message.parts?.map(toChatMessagePart).filter(Boolean) ?? [],
|
|
};
|
|
};
|