UNPKG

@mastra/core

Version:
434 lines (432 loc) • 15.5 kB
import { convertUint8ArrayToBase64 } from "@ai-sdk/provider-utils-v5"; //#region src/agent/message-list/prompt/data-content.ts /** Converts data content to a base64-encoded string. @param content - Data content to convert. @returns Base64-encoded string. */ function convertDataContentToBase64String(content) { if (typeof content === "string") return content; if (content instanceof ArrayBuffer) return convertUint8ArrayToBase64(new Uint8Array(content)); return convertUint8ArrayToBase64(content); } //#endregion //#region src/agent/signals.ts function isMastraSignalMessage(message) { return message.role === "signal"; } /** * True for a signal DB message created with `transient: true`. * * @mastra/memory keeps a matching local predicate because its peer range includes core versions * without this export. Keep both copies in sync until that peer range can be tightened. * * @experimental Agent signals are experimental and may change in a future release. */ function isTransientSignalMessage(message) { if (message.role !== "signal") return false; const metadata = message.content?.metadata; if (!metadata || typeof metadata !== "object") return false; const signal = metadata.signal; return !!signal && typeof signal === "object" && !Array.isArray(signal) && signal.transient === true; } function normalizeSignalType(input) { if (input.type === "user-message") return { type: "user", tagName: input.tagName ?? "user" }; if (input.type === "system-reminder") return { type: "reactive", tagName: input.tagName ?? "system-reminder" }; if (input.type === "user" || input.type === "state" || input.type === "notification") return { type: input.type, tagName: input.tagName ?? input.type }; if (input.type === "reactive") return { type: "reactive", tagName: input.tagName ?? "system-reminder" }; throw new Error(`Invalid signal type: ${input.type}. Use a supported signal type and set tagName for custom XML tags.`); } function normalizeSignal(signal) { const { type, tagName } = normalizeSignalType(signal); return { ...signal, type, tagName, id: signal.id ?? crypto.randomUUID(), createdAt: signal.createdAt instanceof Date ? signal.createdAt : signal.createdAt ? new Date(signal.createdAt) : /* @__PURE__ */ new Date(), acceptedAt: signal.acceptedAt instanceof Date ? signal.acceptedAt : signal.acceptedAt ? new Date(signal.acceptedAt) : void 0 }; } function escapeXml(value) { return value.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;"); } function escapeXmlAttribute(value) { return escapeXml(value).replaceAll("\"", "&quot;"); } const XML_NAME_PATTERN = /^[A-Za-z_][A-Za-z0-9_.-]*$/; function assertXmlName(name, label) { if (!XML_NAME_PATTERN.test(name)) throw new Error(`Invalid signal XML ${label}: ${name}`); } function signalAttributesToXml(attributes) { if (!attributes) return ""; const serialized = Object.entries(attributes).filter((entry) => entry[1] !== null && entry[1] !== void 0).map(([key, value]) => { assertXmlName(key, "attribute name"); return `${key}="${escapeXmlAttribute(String(value))}"`; }).join(" "); return serialized ? ` ${serialized}` : ""; } function signalToXmlMarkup(signal) { const tagName = signal.tagName ?? normalizeSignalType(signal).tagName; assertXmlName(tagName, "tag name"); const attributesXml = signalAttributesToXml(signal.attributes); if (!signal.contents) return `<${tagName}${attributesXml} />`; return `<${tagName}${attributesXml}>${escapeXml(signal.contents)}</${tagName}>`; } function legacyContentsToSignalContents(value) { if (typeof value === "string") return value; if (Array.isArray(value)) { const parts = []; for (const entry of value) { if (typeof entry === "string") { parts.push({ type: "text", text: entry }); continue; } const decoded = legacyEntryToParts(entry); if (!decoded) return void 0; parts.push(...decoded); } return collapseLegacyParts(parts); } const decoded = legacyEntryToParts(value); return decoded ? collapseLegacyParts(decoded) : void 0; } function legacyEntryToParts(entry) { if (!entry || typeof entry !== "object") return void 0; const record = entry; if (record.role === "user" && "content" in record) { const content = record.content; if (typeof content === "string") return [{ type: "text", text: content }]; if (Array.isArray(content)) { const inner = []; for (const part of content) { const decoded = legacyPartToSignalPart(part); if (!decoded) return void 0; inner.push(decoded); } return inner; } return; } const part = legacyPartToSignalPart(record); return part ? [part] : void 0; } function legacyPartToSignalPart(part) { if (!part || typeof part !== "object") return void 0; const record = part; const providerOptions = record.providerOptions && typeof record.providerOptions === "object" && !Array.isArray(record.providerOptions) ? record.providerOptions : void 0; if (record.type === "text" && typeof record.text === "string") return { type: "text", text: record.text, ...providerOptions ? { providerOptions } : {} }; if (record.type === "file" || record.type === "image") { const data = record.type === "image" ? record.image ?? record.data : record.data; if (typeof data !== "string") return void 0; const mediaType = typeof record.mediaType === "string" ? record.mediaType : typeof record.mimeType === "string" ? record.mimeType : record.type === "image" ? "image/png" : ""; if (!mediaType) return void 0; return { type: "file", data, mediaType, ...typeof record.filename === "string" ? { filename: record.filename } : {}, ...providerOptions ? { providerOptions } : {} }; } } function collapseLegacyParts(parts) { if (parts.length === 0) return void 0; const first = parts[0]; if (parts.length === 1 && first?.type === "text") return first.text; return parts; } function contentsToSignalParts(contents) { if (typeof contents === "string") return [{ type: "text", text: contents }]; return contents.map((part) => { if (part.type === "file") return { type: "file", data: part.data instanceof URL ? part.data.toString() : convertDataContentToBase64String(part.data), mediaType: part.mediaType, ...part.filename ? { filename: part.filename } : {}, ...part.providerOptions ? { providerOptions: part.providerOptions } : {} }; return { type: "text", text: part.text, ...part.providerOptions ? { providerOptions: part.providerOptions } : {} }; }); } function storagePartsToSignalParts(parts) { const out = []; for (const part of parts) { const providerOptions = part.providerMetadata; if (part.type === "text") out.push({ type: "text", text: part.text, ...providerOptions ? { providerOptions } : {} }); else if (part.type === "file" && typeof part.data === "string") { const file = part; out.push({ type: "file", data: file.data, mediaType: typeof file.mimeType === "string" ? file.mimeType : "", ...typeof file.filename === "string" ? { filename: file.filename } : {}, ...providerOptions ? { providerOptions } : {} }); } } return out; } function partsToSignalContents(parts) { if (parts.length === 1 && parts[0]?.type === "text" && !parts[0].providerOptions) return parts[0].text; return parts.map((part) => part.type === "file" ? { type: "file", data: part.data, mediaType: part.mediaType, ...part.filename ? { filename: part.filename } : {}, ...part.providerOptions ? { providerOptions: part.providerOptions } : {} } : { type: "text", text: part.text, ...part.providerOptions ? { providerOptions: part.providerOptions } : {} }); } function hasMeaningfulAttributes(attributes) { if (!attributes) return false; return Object.keys(attributes).some((key) => { const value = attributes[key]; return value !== null && value !== void 0; }); } function injectMarkerInline(signal, parts) { let wrapped = false; const out = []; for (const part of parts) if (!wrapped && part.type === "text") { wrapped = true; out.push({ ...part, text: signalToXmlMarkup({ ...signal, contents: part.text }) }); } else out.push(part); if (!wrapped) { const markerText = signalToXmlMarkup({ type: signal.type, tagName: signal.tagName, attributes: signal.attributes }); out.unshift({ type: "text", text: markerText }); } return out; } function signalToLLMMessage(signal, parts) { const isUserMessage = signal.type === "user"; const hasAttrs = hasMeaningfulAttributes(signal.attributes); const anyPartProviderOptions = parts.some((part) => part.providerOptions); let content; if (isUserMessage && !hasAttrs) content = parts.length === 1 && parts[0]?.type === "text" && !parts[0].providerOptions ? parts[0].text : parts; else if (parts.every((part) => part.type === "text") && !anyPartProviderOptions) content = signalToXmlMarkup({ ...signal, contents: parts.map((part) => part.text).join("\n") }); else content = injectMarkerInline(signal, parts); return { role: "user", content, ...signal.providerOptions ? { providerOptions: signal.providerOptions } : {} }; } function signalToDataPart(signal, parts) { return { type: signal.type === "user" ? "data-user-message" : "data-signal", data: { id: signal.id, type: signal.type, tagName: signal.tagName, contents: partsToSignalContents(parts), createdAt: signal.createdAt.toISOString(), ...signal.acceptedAt ? { acceptedAt: signal.acceptedAt.toISOString() } : {}, ...signal.attributes ? { attributes: signal.attributes } : {}, ...signal.metadata ? { metadata: signal.metadata } : {}, ...signal.providerOptions ? { providerOptions: signal.providerOptions } : {}, ...signal.transient ? { transient: true } : {} }, transient: true }; } function signalToDBMessage(signal, parts, options) { const storageParts = parts.length > 0 ? parts.map((part) => part.type === "file" ? { type: "file", data: part.data, mimeType: part.mediaType, ...part.filename ? { filename: part.filename } : {}, ...part.providerOptions ? { providerMetadata: part.providerOptions } : {} } : { type: "text", text: part.text, ...part.providerOptions ? { providerMetadata: part.providerOptions } : {} }) : [{ type: "text", text: "" }]; return { id: signal.id, role: "signal", createdAt: signal.createdAt, threadId: options?.threadId, resourceId: options?.resourceId, type: signal.tagName, content: { format: 2, parts: storageParts, ...signal.providerOptions ? { providerMetadata: signal.providerOptions } : {}, metadata: { signal: { id: signal.id, type: signal.type, tagName: signal.tagName, createdAt: signal.createdAt.toISOString(), ...signal.acceptedAt ? { acceptedAt: signal.acceptedAt.toISOString() } : {}, ...signal.attributes ? { attributes: signal.attributes } : {}, ...signal.metadata ? { metadata: signal.metadata } : {}, ...signal.transient ? { transient: true } : {} } } } }; } function isCreatedAgentSignal(input) { if (!input || typeof input !== "object" || Array.isArray(input)) return false; return input.__isCreatedSignal === true; } function createSignal(input) { if (input.type === "state" && input.transient !== void 0) throw new Error("state signals cannot be transient"); const signal = normalizeSignal(input); const parts = contentsToSignalParts(signal.contents); const created = { ...signal, __isCreatedSignal: true, toDBMessage: (options) => signalToDBMessage(signal, parts, options), toLLMMessage: () => signalToLLMMessage(signal, parts), toDataPart: () => signalToDataPart(signal, parts) }; if (created.type === "state") { const { transient: _transient, ...stateSignal } = created; return { ...stateSignal, type: created.type }; } return { ...created, type: created.type }; } /** * Resolve delivery option attributes into concrete `attributes` on a signal. * Returns a new signal with the selected branch's `attributes` merged into * top-level `attributes`. * * @experimental */ function resolveDeliveryAttributes(signal, attributes) { if (!attributes || Object.keys(attributes).length === 0) return signal; return createSignal({ ...signal, attributes: { ...signal.attributes, ...attributes } }); } function signalToMessage(signal) { return createSignal(signal).toLLMMessage(); } function signalToMastraDBMessage(signal, options) { return createSignal(signal).toDBMessage(options); } function signalToDataPartFormat(signal) { return createSignal(signal).toDataPart(); } function mastraDBMessageToSignal(message) { const metadataSignal = message.content.metadata?.signal; const signalMetadata = metadataSignal && typeof metadataSignal === "object" && !Array.isArray(metadataSignal) ? metadataSignal : void 0; const type = typeof signalMetadata?.type === "string" ? signalMetadata.type : message.type ?? "user-message"; const tagName = typeof signalMetadata?.tagName === "string" ? signalMetadata.tagName : void 0; const legacyContents = legacyContentsToSignalContents(signalMetadata && "contents" in signalMetadata ? signalMetadata.contents : void 0); const partsContents = partsToSignalContents(storagePartsToSignalParts(message.content.parts)); const contents = legacyContents ?? partsContents; const providerMetadata = message.content.providerMetadata; const base = { id: typeof signalMetadata?.id === "string" ? signalMetadata.id : message.id, createdAt: typeof signalMetadata?.createdAt === "string" ? signalMetadata.createdAt : message.createdAt, acceptedAt: typeof signalMetadata?.acceptedAt === "string" ? signalMetadata.acceptedAt : void 0, attributes: signalMetadata?.attributes && typeof signalMetadata.attributes === "object" && !Array.isArray(signalMetadata.attributes) ? signalMetadata.attributes : void 0, metadata: signalMetadata?.metadata && typeof signalMetadata.metadata === "object" && !Array.isArray(signalMetadata.metadata) ? signalMetadata.metadata : void 0, providerOptions: providerMetadata && typeof providerMetadata === "object" && !Array.isArray(providerMetadata) ? providerMetadata : void 0 }; if (type === "state" && signalMetadata?.transient !== void 0) throw new Error("state signals cannot be transient"); return type === "state" ? createSignal({ ...base, type, tagName, contents }) : createSignal({ ...base, type, tagName, contents, transient: signalMetadata?.transient === true ? true : void 0 }); } function createMessageSignal(input, options) { return createSignal({ ...typeof input === "string" || Array.isArray(input) ? { contents: input } : input, ...options, type: "user", tagName: "user" }); } function dataPartToSignal(part) { if (part.data.type === "state") { if (part.data.transient !== void 0) throw new Error("state signals cannot be transient"); const { transient: _transient, ...data } = part.data; return createSignal({ ...data, type: "state" }); } return createSignal({ ...part.data, type: part.data.type }); } //#endregion export { isMastraSignalMessage as a, resolveDeliveryAttributes as c, signalToMessage as d, signalToXmlMarkup as f, isCreatedAgentSignal as i, signalToDataPartFormat as l, createSignal as n, isTransientSignalMessage as o, convertDataContentToBase64String as p, dataPartToSignal as r, mastraDBMessageToSignal as s, createMessageSignal as t, signalToMastraDBMessage as u }; //# sourceMappingURL=signals-DTzJ08gd.js.map