UNPKG

@tanstack/ai

Version:

Type-safe TypeScript AI SDK for streaming chat, tool calling, agents, structured outputs, and multimodal generation.

95 lines (90 loc) • 2.98 kB
import type { SystemPrompt } from '../system-prompts' import type { ModelMessage, ModelMessageBlock } from '../types' /** Remove lone UTF-16 surrogates. Keep valid pairs unchanged. */ function sanitizeUnicode(text: string): string { 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: string): string { try { JSON.parse(argumentsJson) return argumentsJson.replace(/"(?:\\.|[^"\\])*"/g, (token) => { const decoded: unknown = 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: string, order: Array<ModelMessageBlock>, ): { content: string; blockOrder: Array<ModelMessageBlock> } { let offset = 0 let clean = '' const blockOrder: Array<ModelMessageBlock> = [] 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. */ export function sanitizeProviderRequest( messages: Array<ModelMessage>, systemPrompts: Array<SystemPrompt> | undefined, ) { 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) }, ), } }