@tanstack/ai
Version:
Type-safe TypeScript AI SDK for streaming chat, tool calling, agents, structured outputs, and multimodal generation.
76 lines (75 loc) • 2.49 kB
JavaScript
//#region src/utilities/sanitize-unicode.ts
/** Remove lone UTF-16 surrogates. Keep valid pairs unchanged. */
function sanitizeUnicode(text) {
return text.replace(/[\uD800-\uDBFF](?![\uDC00-\uDFFF])|(?<![\uD800-\uDBFF])[\uDC00-\uDFFF]/g, "");
}
/** Sanitize decoded argument strings. Keep original JSON bytes when nothing changes. */
function sanitizeJsonArguments(argumentsJson) {
try {
JSON.parse(argumentsJson);
return argumentsJson.replace(/"(?:\\.|[^"\\])*"/g, (token) => {
const decoded = JSON.parse(token);
if (typeof decoded !== "string") return token;
const clean = sanitizeUnicode(decoded);
return clean === decoded ? token : JSON.stringify(clean);
});
} catch {
return argumentsJson;
}
}
/**
* Clean string content that has a block order map. Each text block is
* cleaned on its own, so the map lengths still match the content.
*/
function sanitizeOrderedContent(content, order) {
let offset = 0;
let clean = "";
const blockOrder = [];
for (const block of order) {
if (block.type !== "text") {
blockOrder.push(block);
continue;
}
const text = sanitizeUnicode(content.slice(offset, offset + block.length));
offset += block.length;
clean += text;
if (text.length > 0) blockOrder.push({
...block,
length: text.length
});
}
return {
content: clean + sanitizeUnicode(content.slice(offset)),
blockOrder
};
}
/**
* Remove lone surrogates from the text `chat()` sends to a provider. A lone
* surrogate is invalid UTF-8 / JSON on the wire, so providers reject the
* whole request.
*/
function sanitizeProviderRequest(messages, systemPrompts) {
return {
messages: messages.map((message) => ({
...message,
...typeof message.content === "string" ? message.blockOrder ? sanitizeOrderedContent(message.content, message.blockOrder) : { content: sanitizeUnicode(message.content) } : { content: message.content?.map((part) => part.type === "text" ? {
...part,
content: sanitizeUnicode(part.content)
} : part) ?? null },
...message.toolCalls && { toolCalls: message.toolCalls.map((toolCall) => ({
...toolCall,
function: {
...toolCall.function,
arguments: sanitizeJsonArguments(toolCall.function.arguments)
}
})) }
})),
systemPrompts: systemPrompts?.map((prompt) => typeof prompt === "string" ? sanitizeUnicode(prompt) : {
...prompt,
content: sanitizeUnicode(prompt.content)
})
};
}
//#endregion
export { sanitizeProviderRequest };
//# sourceMappingURL=sanitize-unicode.js.map