@maximai/maxim-js
Version:
Maxim AI JS SDK. Visit https://getmaxim.ai for more info.
387 lines • 18.5 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.parsePromptMessagesV3 = parsePromptMessagesV3;
exports.processToolResultsFromPromptV3 = processToolResultsFromPromptV3;
exports.convertDoGenerateResultToChatCompletionResultV3 = convertDoGenerateResultToChatCompletionResultV3;
exports.processStreamV3 = processStreamV3;
const utils_1 = require("../utils");
const uuid_1 = require("uuid");
/**
* Converts a LanguageModelV3Prompt into an array of CompletionRequest or ChatCompletionMessage objects.
*
* This function transforms the structured prompt format used by the Vercel AI SDK v3 into the message format expected by downstream consumers, handling system, user, assistant, and tool roles.
* It also extracts file attachments from file messages.
*
* @param prompt - The prompt to be parsed, consisting of structured message parts.
* @returns An object containing parsed messages and extracted file attachments.
* @throws If an unsupported user message type is encountered.
*/
function parsePromptMessagesV3(prompt) {
const attachments = [];
const promptMessages = prompt
.map((promptMsg) => {
var _a, _b, _c, _d, _e;
switch (promptMsg.role) {
case "system": {
return [
{
role: "system",
content: promptMsg.content,
},
];
}
case "user": {
const contentItems = [];
for (const msg of promptMsg.content) {
switch (msg.type) {
case "text":
contentItems.push({
type: "text",
text: msg.text,
});
break;
case "file": {
// Handle different data types: Uint8Array, string (base64), or URL
let attachment;
// Check if data is a URL object
if (msg.data instanceof URL || (typeof msg.data === "object" && msg.data !== null && "href" in msg.data)) {
// If it's a URL, create a URL attachment
const urlString = msg.data instanceof URL ? msg.data.toString() : msg.data.href;
attachment = {
id: (0, uuid_1.v4)(),
type: "url",
url: urlString,
mimeType: msg.mediaType,
tags: { attachedTo: "input" },
};
}
else if (typeof msg.data === "string") {
// Convert base64 string to Buffer
let fileData;
// If it's a base64 string, decode it
if (msg.data.startsWith("data:")) {
const match = msg.data.match(/^data:([^;]+);base64,(.+)$/);
if (match) {
const base64Data = match[2];
fileData = Buffer.from(base64Data, "base64");
}
else {
// Assume it's already base64 without the data URI prefix
fileData = Buffer.from(msg.data, "base64");
}
}
else {
// Assume it's base64 encoded
fileData = Buffer.from(msg.data, "base64");
}
// Extract file extension from mediaType if possible
const mediaTypeParts = ((_a = msg.mediaType) === null || _a === void 0 ? void 0 : _a.split("/")) || [];
const extension = ((_b = mediaTypeParts[1]) === null || _b === void 0 ? void 0 : _b.split(";")[0]) || "bin";
// Create fileData attachment
attachment = {
id: (0, uuid_1.v4)(),
type: "fileData",
data: fileData,
mimeType: msg.mediaType,
name: `file.${extension}`,
tags: { attachedTo: "input" },
};
}
else {
// It's a Uint8Array, convert to Buffer
const fileData = Buffer.from(msg.data);
// Extract file extension from mediaType if possible
const mediaTypeParts = ((_c = msg.mediaType) === null || _c === void 0 ? void 0 : _c.split("/")) || [];
const extension = ((_d = mediaTypeParts[1]) === null || _d === void 0 ? void 0 : _d.split(";")[0]) || "bin";
// Create fileData attachment
attachment = {
id: (0, uuid_1.v4)(),
type: "fileData",
data: fileData,
mimeType: msg.mediaType,
name: `file.${extension}`,
tags: { attachedTo: "input" },
};
}
attachments.push(attachment);
// Don't include file content in the message - it's now an attachment
// The parseAttachmentsFromMessages function will handle image_url types,
// but we've already extracted the file data, so we skip adding it to content
break;
}
default:
throw new Error(`Unsupported user message type: ${msg}`);
}
}
// Only create user message if there's content (text or other non-file items)
if (contentItems.length > 0) {
return [
{
role: "user",
content: contentItems,
},
];
}
else {
// If all content was files, return empty string content
return [
{
role: "user",
content: "",
},
];
}
}
case "assistant": {
const assistantText = promptMsg.content.find((msg) => msg.type === "text");
const assistantToolCalls = promptMsg.content.filter((msg) => msg.type === "tool-call");
return [
{
role: "assistant",
content: (_e = assistantText === null || assistantText === void 0 ? void 0 : assistantText.text) !== null && _e !== void 0 ? _e : null,
tool_calls: assistantToolCalls.map((tool) => ({
id: tool.toolCallId,
type: "function",
function: {
name: tool.toolName,
arguments: JSON.stringify(tool.input),
},
})),
},
];
}
case "tool": {
const toolCalls = promptMsg.content.filter((part) => part.type === "tool-result");
return [
...toolCalls.map((tool) => ({
role: "tool",
tool_call_id: tool.toolCallId,
content: (0, utils_1.parseToolResultOutput)(tool.output),
})),
];
}
}
})
.flat();
return { messages: promptMessages, attachments };
}
/**
* Processes tool results from the raw prompt and logs them to Maxim.
* Calls toolCallError for error-type results (error-text, error-json) and toolCallResult for successes.
*
* @param prompt - The raw LanguageModelV3 prompt containing tool results
* @param logger - The MaximLogger instance for logging tool results/errors
*/
function processToolResultsFromPromptV3(prompt, logger) {
for (const promptMsg of prompt) {
if (promptMsg.role !== "tool")
continue;
for (const part of promptMsg.content) {
if (part.type !== "tool-result")
continue;
const toolCallId = part.toolCallId;
const output = part.output;
const isError = output.type === "error-text" || output.type === "error-json";
if (isError) {
const errorInfo = (0, utils_1.extractErrorInfo)(output.value);
logger.toolCallError(toolCallId, errorInfo);
}
else {
const content = (0, utils_1.parseToolResultOutput)(output);
logger.toolCallResult(toolCallId, content);
}
}
}
}
/**
* Converts a doGenerate result object into a ChatCompletionResult format.
*
* This function adapts the result of a language model generation v3 (including token usage, model info, and choices) into the standardized ChatCompletionResult structure expected by downstream consumers.
*
* @param result - The result object from a generation call, including usage, response, and content fields.
* @returns The formatted chat completion result, including id, model, choices, and token usage.
*/
function convertDoGenerateResultToChatCompletionResultV3(result) {
var _a, _b, _c, _d, _e, _f;
return {
id: (0, uuid_1.v4)(),
object: "chat_completion",
created: Math.floor(Date.now() / 1000),
model: (_b = (_a = result.response) === null || _a === void 0 ? void 0 : _a.modelId) !== null && _b !== void 0 ? _b : "unknown",
choices: result.content.map((content, index) => {
switch (content.type) {
case "text":
return {
index,
message: {
content: content.text,
role: "assistant",
},
finish_reason: result.finishReason.unified,
logprobs: null,
};
case "file":
return {
index,
message: {
content: content.data,
role: "assistant",
},
finish_reason: result.finishReason.unified,
logprobs: null,
};
case "tool-call":
return {
index,
logprobs: null,
message: {
content: null,
role: "assistant",
tool_calls: [
{
id: content.toolCallId,
type: "function",
function: {
name: content.toolName,
arguments: content.input,
},
},
],
},
finish_reason: result.finishReason.unified,
};
case "tool-result":
return {
index,
logprobs: null,
message: {
content: typeof content.result === "string" ? content.result : JSON.stringify(content.result),
role: "assistant",
},
finish_reason: result.finishReason.unified,
};
case "source":
return {
index,
logprobs: null,
message: {
content: content.sourceType === "url" ? content.url : content.title,
role: "assistant",
},
finish_reason: result.finishReason.unified,
};
default:
return {
index,
logprobs: null,
message: {
content: JSON.stringify(content),
role: "assistant",
},
finish_reason: result.finishReason.unified,
};
}
}),
usage: {
prompt_tokens: (_c = result.usage.inputTokens.total) !== null && _c !== void 0 ? _c : 0,
completion_tokens: (_d = result.usage.outputTokens.total) !== null && _d !== void 0 ? _d : 0,
total_tokens: ((_e = result.usage.inputTokens.total) !== null && _e !== void 0 ? _e : 0) + ((_f = result.usage.outputTokens.total) !== null && _f !== void 0 ? _f : 0),
},
};
}
/**
* Processes a stream of language model output chunks and logs the result to Maxim tracing.
*
* This function aggregates streamed output parts, constructs a chat completion result, and finalizes the generation, span, and trace as appropriate. It also handles errors and ensures proper cleanup of tracing resources.
*
* @param chunks - The array of streamed output parts from the language model.
* @param span - The Maxim tracing span associated with this generation.
* @param trace - The Maxim tracing trace associated with this generation.
* @param generation - The Maxim generation object to log the result to.
* @param model - The model identifier used for this generation.
* @param maximMetadata - Optional Maxim metadata for advanced tracing.
*/
function processStreamV3(chunks, span, trace, generation, model, maximMetadata) {
var _a, _b, _c, _d, _e, _f, _g, _h, _j;
try {
const result = processChunksV3(chunks);
generation.result({
id: (0, uuid_1.v4)(),
object: "chat_completion",
created: Math.floor(Date.now() / 1000),
model: model,
choices: [
{
index: 0,
message: {
tool_calls: result.toolCalls.map((toolCall) => ({
id: toolCall.toolCallId,
type: toolCall.type,
function: {
name: toolCall.toolName,
arguments: toolCall.input,
},
})),
content: result.text,
role: "assistant",
},
finish_reason: (_a = result.finishReason) !== null && _a !== void 0 ? _a : "stop",
logprobs: null,
},
],
usage: {
prompt_tokens: (_c = (_b = result.usage) === null || _b === void 0 ? void 0 : _b.promptTokens) !== null && _c !== void 0 ? _c : 0,
completion_tokens: (_e = (_d = result.usage) === null || _d === void 0 ? void 0 : _d.completionTokens) !== null && _e !== void 0 ? _e : 0,
total_tokens: ((_g = (_f = result.usage) === null || _f === void 0 ? void 0 : _f.promptTokens) !== null && _g !== void 0 ? _g : 0) + ((_j = (_h = result.usage) === null || _h === void 0 ? void 0 : _h.completionTokens) !== null && _j !== void 0 ? _j : 0),
},
});
generation.end();
}
catch (error) {
generation.error({
message: error.message,
});
console.error("[Maxim SDK] Logging failed:", error);
}
finally {
span.end();
// Note: Trace ending is now handled by the wrapper to support tool-call sequences
}
}
/**
* Processes an array of streamed language model output chunks into a structured result.
*
* This function aggregates text, tool calls, token usage, and finish reason from the provided stream parts, returning a single object summarizing the output of the language model stream.
*
* @param chunks - The array of streamed output parts from the language model.
* @returns An object containing the aggregated text, tool calls, token usage, and finish reason.
*/
function processChunksV3(chunks) {
var _a, _b;
let text = "";
const toolCalls = {};
let usage = undefined;
let finishReason = undefined;
for (const chunk of chunks) {
switch (chunk.type) {
case "text-delta":
text += chunk.delta;
break;
case "tool-call":
toolCalls[chunk.toolCallId] = chunk;
break;
case "tool-result":
text += typeof chunk.result === "string" ? chunk.result : JSON.stringify(chunk.result);
break;
case "finish":
usage = {
promptTokens: (_a = chunk.usage.inputTokens.total) !== null && _a !== void 0 ? _a : 0,
completionTokens: (_b = chunk.usage.outputTokens.total) !== null && _b !== void 0 ? _b : 0,
};
finishReason = chunk.finishReason.unified;
break;
}
}
return { text, toolCalls: Object.values(toolCalls), usage, finishReason };
}
//# sourceMappingURL=utils.js.map