import type { Message } from "@langchain/langgraph-sdk"; import { normalizeSearchResults } from "@/core/messages/search-results"; import type { Skill, SkillDisplayConfig } from "@/core/skills/type"; import { normalizeDisplaySources, type DisplaySourceItem, } from "./display-adapter"; export type RuntimeDisplaySource = DisplaySourceItem & { index: number; skillName: string; mode: SkillDisplayConfig["mode"]; tableColumns?: SkillDisplayConfig["tableColumns"]; }; type RuntimeDisplaySourceDraft = Omit; export type SkillDisplayMap = Map; type RuntimeToolCall = { id?: string; name: string; args?: Record; }; export function buildSkillDisplayMap(skills: Skill[]): SkillDisplayMap { const map = new Map(); for (const skill of skills) { const display = skill.display; if (!display?.enabled || display.mode === "none") { continue; } map.set(skill.name, display); } return map; } export function findDisplayConfigForTool( toolName: string, displays: SkillDisplayMap, toolArgs?: Record, ): { skillName: string; display: SkillDisplayConfig } | null { const exact = displays.get(toolName); if (exact) { return { skillName: toolName, display: exact }; } const normalizedTool = normalizeName(toolName); for (const [skillName, display] of displays) { const normalizedSkill = normalizeName(skillName); if ( normalizedTool === normalizedSkill || normalizedTool.endsWith(normalizedSkill) || normalizedTool.includes(normalizedSkill) ) { return { skillName, display }; } } // bash/python skill invocations don't carry the skill name in the tool name — // the path lives inside the command/args (e.g. `python3 skills/custom/demo/scripts/run.py`). // Scan stringy args for the `skills/{public,custom}//` shape so display // configs still resolve. Mirrors the backend `_SKILL_PATH_RE` in // tool_metrics_middleware.py. if (toolArgs && canInferSkillFromToolArgs(toolName)) { const skillFromArgs = extractSkillNameFromArgs(toolArgs); if (skillFromArgs) { const direct = displays.get(skillFromArgs); if (direct) { return { skillName: skillFromArgs, display: direct }; } } } return null; } const SKILL_PATH_RE = /[\\/]skills[\\/](?:public|custom)[\\/]([^\\/]+)(?:[\\/]|$)/; const TOOL_ARG_SKILL_MATCH_EXCLUDES = new Set([ "read_file", "readfile", "ls", "write_file", "writefile", "str_replace", "strreplace", "web_fetch", "webfetch", ]); function canInferSkillFromToolArgs(toolName: string): boolean { const normalized = normalizeName(toolName); if ( TOOL_ARG_SKILL_MATCH_EXCLUDES.has(toolName) || TOOL_ARG_SKILL_MATCH_EXCLUDES.has(normalized) ) { return false; } return ( normalized.includes("bash") || normalized.includes("shell") || normalized.includes("python") || normalized.includes("execute") || normalized.includes("terminal") ); } function extractSkillNameFromArgs(args: Record): string | undefined { for (const value of Object.values(args)) { if (typeof value !== "string") continue; const match = SKILL_PATH_RE.exec(value); if (match) { return match[1]; } } return undefined; } export function collectRuntimeDisplaySources( messages: Message[], displays: SkillDisplayMap, options: { afterMessageId?: string; beforeMessageId?: string; includeBeforeMessage?: boolean; includeModes?: SkillDisplayConfig["mode"][]; } = {}, ): RuntimeDisplaySource[] { const start = options.afterMessageId === undefined ? 0 : Math.max( messages.findIndex((message) => message.id === options.afterMessageId) + 1, 0, ); const limit = options.beforeMessageId === undefined ? messages.length : Math.max( messages.findIndex((message) => message.id === options.beforeMessageId) + (options.includeBeforeMessage ? 1 : 0), 0, ); const allowedModes = options.includeModes ? new Set(options.includeModes) : null; const sources: RuntimeDisplaySource[] = []; let citationIndex = 0; const seenCitationKeys = new Map(); const consumedToolMessageIds = new Set(); const consumedToolMessages = new Set(); for (let index = start; index < limit; index++) { const message = messages[index]; if (!message || message.type !== "ai") { continue; } for (const toolCall of extractRuntimeToolCalls(message)) { const toolMatch = findDisplayConfigForTool( toolCall.name, displays, toolCall.args, ); const toolMessage = findToolResultMessage( toolCall, messages, index, consumedToolMessageIds, consumedToolMessages, ); if (!toolMessage) { continue; } const result = parseToolMessagePayload(toolMessage); if (result === undefined) { continue; } if (toolMessage.id) { consumedToolMessageIds.add(toolMessage.id); } consumedToolMessages.add(toolMessage); if (isDisplaySamplePayload(result)) { continue; } const match = toolMatch; const effectiveMode = match?.display.mode ?? "citation"; if (allowedModes && !allowedModes.has(effectiveMode)) { continue; } const normalized = match ? normalizeDisplaySources(result, match.display) : shouldUseDefaultCitationMode( toolCall.name, result, toolCall.args, ) ? normalizeDefaultDisplaySources(result) : []; appendDisplaySources( sources, normalized.map((item) => ({ ...item, skillName: match?.skillName ?? toolCall.name, mode: item.displayMode ?? effectiveMode, tableColumns: item.tableColumns ?? match?.display.tableColumns, })), seenCitationKeys, () => ++citationIndex, ); } } for (let index = start; index < limit; index++) { const message = messages[index]; if ( !message || message.type !== "tool" || consumedToolMessages.has(message) || consumedToolMessageIds.has(message.id ?? "") ) { continue; } const result = parseToolMessagePayload(message); if (result === undefined || isDisplaySamplePayload(result)) { continue; } const toolName = extractToolMessageName(message); const match = findDisplayConfigForPayload(toolName, result, displays); const effectiveMode = match?.display.mode ?? "citation"; if (allowedModes && !allowedModes.has(effectiveMode)) { continue; } const normalized = match ? normalizeDisplaySources(result, match.display) : shouldUseDefaultCitationMode(toolName, result) || normalizeDefaultDisplaySources(result).length > 0 ? normalizeDefaultDisplaySources(result) : []; appendDisplaySources( sources, normalized.map((item) => ({ ...item, skillName: match?.skillName ?? toolName, mode: item.displayMode ?? effectiveMode, tableColumns: item.tableColumns ?? match?.display.tableColumns, })), seenCitationKeys, () => ++citationIndex, ); } return sources; } function appendDisplaySources( target: RuntimeDisplaySource[], drafts: RuntimeDisplaySourceDraft[], seenCitationKeys: Map, nextCitationIndex: () => number, ) { for (const draft of drafts) { if (draft.mode === "citation") { const source = { ...draft, index: nextCitationIndex(), }; const key = citationSourceKey(draft); if (key) { seenCitationKeys.set(`${key}:${source.index}`, source); } target.push(source); continue; } target.push({ ...draft, index: nextCitationIndex(), }); } } export function reindexRuntimeDisplaySources( sources: RuntimeDisplaySource[], globalSources: RuntimeDisplaySource[], ): RuntimeDisplaySource[] { if (sources.length === 0 || globalSources.length === 0) { return sources; } const globalByKey = new Map( globalSources.map((source) => [citationSourceKey(source), source]), ); return sources.map((source) => { const global = globalByKey.get(citationSourceKey(source)); return global ? { ...source, index: global.index, } : source; }); } function citationSourceKey(source: RuntimeDisplaySourceDraft): string { const raw = source.raw ?? {}; const id = source.id || source.url || stringifyMaybe(raw.recUuid) || stringifyMaybe(raw.uuid) || stringifyMaybe(raw.docId) || stringifyMaybe(raw.id); if (id) { return normalizeCitationKey(String(id)); } const fallback = [ source.skillName, source.title, source.source, source.time, source.content, source.snippet, ] .filter(Boolean) .join("|"); return normalizeCitationKey(fallback) || normalizeCitationKey(JSON.stringify(raw).slice(0, 300)); } function extractRuntimeToolCalls(message: Message): RuntimeToolCall[] { const calls: RuntimeToolCall[] = []; const direct = (message as { tool_calls?: unknown }).tool_calls; if (Array.isArray(direct)) { for (const raw of direct) { const call = normalizeRuntimeToolCall(raw); if (call) { calls.push(call); } } } const kwargs = (message as { additional_kwargs?: Record }) .additional_kwargs; const extra = kwargs?.tool_calls; if (Array.isArray(extra)) { for (const raw of extra) { const call = normalizeRuntimeToolCall(raw); if (call && !calls.some((item) => item.id && item.id === call.id)) { calls.push(call); } } } const chunks = (message as { tool_call_chunks?: unknown }).tool_call_chunks; if (Array.isArray(chunks)) { for (const raw of chunks) { const call = normalizeRuntimeToolCall(raw); if (call && !calls.some((item) => item.id && item.id === call.id)) { calls.push(call); } } } return calls; } function normalizeRuntimeToolCall(raw: unknown): RuntimeToolCall | null { if (!isRecord(raw)) { return null; } const fn = isRecord(raw.function) ? raw.function : null; const name = stringifyMaybe(raw.name) ?? stringifyMaybe(fn?.name) ?? stringifyMaybe(raw.tool_name); if (!name) { return null; } return { id: stringifyMaybe(raw.id) ?? stringifyMaybe(raw.tool_call_id) ?? stringifyMaybe(raw.call_id), name, args: normalizeToolArgs(raw.args ?? raw.arguments ?? fn?.arguments), }; } function normalizeToolArgs(value: unknown): Record | undefined { if (isRecord(value)) { return value; } if (typeof value === "string") { try { const parsed = JSON.parse(value); return isRecord(parsed) ? parsed : undefined; } catch { return { command: value }; } } return undefined; } export function findDisplayConfigForPayload( toolName: string, _payload: unknown, displays: SkillDisplayMap, ): { skillName: string; display: SkillDisplayConfig } | null { return findDisplayConfigForTool(toolName, displays); } export function isUnconfiguredSkillInvocation( toolName: string, displays: SkillDisplayMap | undefined, toolArgs?: Record, ): boolean { if (!toolArgs || !canInferSkillFromToolArgs(toolName)) { return false; } const skillName = extractSkillNameFromArgs(toolArgs); return !!skillName && !displays?.has(skillName); } export function isDisplaySamplePayload(payload: unknown): boolean { if (!isRecord(payload)) { return false; } if (payload.__displaySample === true || payload.displaySample === true) { return true; } if (payload.query === "sample query") { return true; } const items = getArrayByPath(payload, "results") ?? getArrayByPath(payload, "data") ?? getArrayByPath(payload, "items"); const first = items?.find((item): item is Record => isRecord(item)); if (!first) { return false; } const text = Object.values(first) .filter((value) => typeof value === "string") .join("\n") .toLowerCase(); return ( /^sample-/i.test(String(first.id ?? "")) || text.includes("mock result") || text.includes("knowledge result one") ); } function getArrayByPath(payload: unknown, path: string): unknown[] | null { const value = path.split(".").reduce((acc, key) => { if (acc && typeof acc === "object") { return (acc as Record)[key]; } return undefined; }, payload); return Array.isArray(value) ? value : null; } export function shouldUseDefaultCitationMode( toolName: string, payload: unknown, toolArgs?: Record, ): boolean { const normalizedName = normalizeName(toolName); const isSearchLikeTool = normalizedName.includes("search") || normalizedName.includes("retriev") || normalizedName.includes("knowledge") || normalizedName.includes("query"); const isSkillInvocation = !!( toolArgs && canInferSkillFromToolArgs(toolName) && extractSkillNameFromArgs(toolArgs) ); if (!isSearchLikeTool && !isSkillInvocation) { return false; } return normalizeDefaultDisplaySources(payload).length > 0; } export function normalizeDefaultDisplaySources(payload: unknown): DisplaySourceItem[] { const normalized = normalizeSearchResults( typeof payload === "string" || isRecord(payload) ? (payload as string | Record) : undefined, ); return normalized.map((item, index) => ({ id: item.recUuid ?? item.uuid ?? item.docId ?? item.id ?? item.url, title: item.title ?? item.source ?? `结果 ${index + 1}`, type: "result", snippet: item.snippet ?? item.content ?? "", content: item.content ?? item.snippet, source: item.source, author: item.author, time: item.published_date ?? item.publishedDate ?? item.date, url: item.url, raw: item as unknown as Record, })); } export function applyInlineCitations( content: string, sources: RuntimeDisplaySource[], ): string { if (!content || sources.length === 0) { return content; } const referenceSources = sources.filter((source) => source.mode !== "none"); if (referenceSources.length === 0) { return content; } const byIndex = new Map(referenceSources.map((source) => [source.index, source])); const byUrl = new Map(); const byTitle = new Map(); for (const source of referenceSources) { if (source.url) { byUrl.set(source.url, source); } if (source.title) { byTitle.set(source.title.trim(), source); } } const normalized = normalizeExistingCitationLinks( content, byIndex, byUrl, byTitle, referenceSources, ); const markdownLinkPattern = /!?\[(?:[^\[\]]|\[[^\]]*\])*\]\([^)]+\)/g; let cursor = 0; let next = ""; let replaced = false; for (const match of normalized.matchAll(markdownLinkPattern)) { const start = match.index ?? 0; const replacedSlice = replaceCitationTokens( normalized.slice(cursor, start), byIndex, referenceSources, ); if (replacedSlice !== normalized.slice(cursor, start)) { replaced = true; } next += replacedSlice; next += match[0]; cursor = start + match[0].length; } const tail = replaceCitationTokens(normalized.slice(cursor), byIndex, referenceSources); if (tail !== normalized.slice(cursor)) { replaced = true; } next += tail; if (replaced || hasCitationMarkdown(next)) { return next; } return appendCitationSummary(next, referenceSources); } function replaceCitationTokens( content: string, byIndex: Map, orderedSources: RuntimeDisplaySource[], ): string { const normalized = normalizeLooseCitationTokens(content, byIndex, orderedSources); const withDoubleBracketCitations = normalized .replace(/\[\[(\d+)\]\]/g, (match, rawIndex: string) => citationMarkdown(match, Number(rawIndex), byIndex, orderedSources), ); return replaceBracketCitationTokens( withDoubleBracketCitations, byIndex, orderedSources, ); } function replaceBracketCitationTokens( content: string, byIndex: Map, orderedSources: RuntimeDisplaySource[], ): string { const tokenPattern = /\\?\[(\d{1,3})\\?\](?!\()/g; let cursor = 0; let next = ""; for (const match of content.matchAll(tokenPattern)) { const start = match.index ?? 0; const previous = start > 0 ? content[start - 1] : ""; if (previous === ":" || previous === "[" || previous === "!") { continue; } next += content.slice(cursor, start); next += citationMarkdown(match[0], Number(match[1]), byIndex, orderedSources); cursor = start + match[0].length; } next += content.slice(cursor); return next; } function normalizeLooseCitationTokens( content: string, byIndex: Map, orderedSources: RuntimeDisplaySource[], ): string { const isValidIndex = (index: number) => Number.isInteger(index) && index > 0 && (byIndex.has(index) || !!orderedSources[index - 1]); const toCitationRun = (raw: string) => { const parts = raw.match(/\\?\[\d{1,3}\\?\]|\d{1,3}/g) ?? []; const indexes = parts.map((part) => Number(part.replace(/\D/g, ""))); if (indexes.length === 0 || indexes.some((index) => !isValidIndex(index))) { return raw; } return indexes.map((index) => `[${index}]`).join(""); }; let next = content.replace( /(^|[^\d\[\]\\])((?:\d{1,3}|\\?\[\d{1,3}\\?\])(?:\\?\[\d{1,3}\\?\]|\d{1,3})+)/g, (match, prefix: string, rawRun: string) => { if (!rawRun.includes("[")) { return match; } return `${prefix}${toCitationRun(rawRun)}`; }, ); next = next.replace( /(^|[\s\uFF0C\u3002\uFF1B\uFF1A\u3001,!?!?)])(\d{1,3})(?=([\s\uFF0C\u3002\uFF1B\uFF1A\u3001,!?!?)]|$))/g, (match, prefix: string, rawIndex: string) => { const index = Number(rawIndex); if (!isValidIndex(index)) { return match; } return `${prefix}[${index}]`; }, ); return next; } function normalizeExistingCitationLinks( content: string, byIndex: Map, byUrl: Map, byTitle: Map, orderedSources: RuntimeDisplaySource[], ): string { // Resolve every `[citation:LABEL](URL)` link — whether LABEL is a number // ("[1]", "1") or a free-form title — to a numbered reference link that // CitationLink renders as a badge and scrolls to the right-side panel. return content.replace( /\[citation:([^\]]+)\]\(([^)]+)\)(?:\([^)]+\))*/g, (match, rawLabel: string, rawUrl: string) => { const label = rawLabel.trim(); const url = rawUrl.trim(); const numberMatch = label.match(/^\[?(\d+)\]?$/); if (numberMatch) { const index = Number(numberMatch[1]); const source = byIndex.get(index) ?? orderedSources[index - 1]; if (source) { return citationLinkMarkdown(source); } } const sourceByUrl = byUrl.get(url); if (sourceByUrl) { return citationLinkMarkdown(sourceByUrl); } const sourceByTitle = byTitle.get(label); if (sourceByTitle) { return citationLinkMarkdown(sourceByTitle); } return match; }, ); } function citationMarkdown( fallback: string, index: number, byIndex: Map, orderedSources: RuntimeDisplaySource[], ): string { const source = byIndex.get(index) ?? orderedSources[index - 1]; if (!source) { return fallback; } return citationLinkMarkdown(source); } function hasCitationMarkdown(content: string): boolean { return /\[citation:(?:\[[^\]]+\]|\d+)\]\(#reference-[^)]+\)/.test(content); } function appendCitationSummary( content: string, citationSources: RuntimeDisplaySource[], ): string { const citations = citationSources .slice(0, 5) .map((source) => citationLinkMarkdown(source)) .join(" "); if (!citations) { return content; } const trimmed = content.trimEnd(); return `${trimmed}${trimmed ? "\n\n" : ""}参考来源:${citations}`; } function citationLinkMarkdown(source: RuntimeDisplaySource): string { return `[citation:${source.index}](#reference-${source.index})`; } function normalizeName(name: string): string { return name.toLowerCase().replace(/[^a-z0-9]+/g, ""); } function normalizeCitationKey(value: string): string { return value .toLowerCase() .normalize("NFKC") .replace(/\s+/g, " ") .trim(); } function isRecord(value: unknown): value is Record { return !!value && typeof value === "object" && !Array.isArray(value); } function stringifyMaybe(value: unknown): string | undefined { return typeof value === "string" && value.trim() ? value : undefined; } function findToolResultMessage( toolCall: RuntimeToolCall, messages: Message[], toolCallMessageIndex = -1, consumedToolMessageIds?: Set, consumedToolMessages?: Set, ): Message | undefined { const byId = findToolMessageById(toolCall.id, messages); if ( byId && !consumedToolMessages?.has(byId) && (!byId.id || !consumedToolMessageIds?.has(byId.id)) ) { return byId; } return findToolMessageByName( toolCall, messages, toolCallMessageIndex, consumedToolMessageIds, consumedToolMessages, ); } function parseToolMessagePayload(toolMessage: Message): unknown { const structured = extractStructuredToolPayload(toolMessage.content); if (structured !== undefined) { return structured; } const content = extractToolText(toolMessage.content); if (!content) { return undefined; } const parsed = parseJSONLikeText(content); if (parsed !== undefined) { return parsed; } return content; } function extractToolMessageName(message: Message): string { return ( stringifyMaybe((message as { name?: unknown }).name) ?? stringifyMaybe( (message as { additional_kwargs?: Record }).additional_kwargs ?.name, ) ?? "tool" ); } function findToolMessageById( toolCallId: string | undefined, messages: Message[], ): Message | undefined { if (!toolCallId) { return undefined; } return messages.find((message) => { if (message.type !== "tool") { return false; } const candidates = [ (message as { tool_call_id?: unknown }).tool_call_id, (message as { id?: unknown }).id, (message as { additional_kwargs?: Record }).additional_kwargs ?.tool_call_id, (message as { additional_kwargs?: Record }).additional_kwargs ?.id, ]; return candidates.some((candidate) => candidate === toolCallId); }); } function findToolMessageByName( toolCall: RuntimeToolCall, messages: Message[], toolCallMessageIndex: number, consumedToolMessageIds?: Set, consumedToolMessages?: Set, ): Message | undefined { const start = Math.max(toolCallMessageIndex + 1, 0); for (let index = start; index < messages.length; index++) { const message = messages[index]; if (!message) { continue; } if (index > start && (message.type === "human" || message.type === "ai")) { break; } if (message.type !== "tool") { continue; } if (message.id && consumedToolMessageIds?.has(message.id)) { continue; } if (consumedToolMessages?.has(message)) { continue; } const name = stringifyMaybe((message as { name?: unknown }).name) ?? stringifyMaybe( (message as { additional_kwargs?: Record }).additional_kwargs ?.name, ); const messageToolCallId = stringifyMaybe( (message as { tool_call_id?: unknown }).tool_call_id, ); if (messageToolCallId && toolCall.id && messageToolCallId !== toolCall.id) { continue; } if (name && normalizeName(name) !== normalizeName(toolCall.name)) { continue; } const payload = extractStructuredToolPayload(message.content); if (payload !== undefined) { return message; } const text = extractToolText(message.content); if (text && parseJSONLikeText(text) !== undefined) { return message; } } return undefined; } function extractStructuredToolPayload(content: unknown): unknown { if (isRecord(content)) { return unwrapToolPayloadObject(content); } if (Array.isArray(content)) { for (const part of content) { if (!isRecord(part)) { continue; } if (isRecord(part.json)) { return unwrapToolPayloadObject(part.json); } const unwrapped = unwrapToolPayloadObject(part); if (unwrapped !== undefined) { return unwrapped; } } } return undefined; } function unwrapToolPayloadObject(value: Record): unknown { if ( Array.isArray(value.results) || Array.isArray(value.data) || Array.isArray(value.items) ) { return value; } for (const key of ["result", "output", "stdout", "stderr", "content", "text"]) { const nested = value[key]; if (typeof nested === "string") { const parsed = parseJSONLikeText(nested); if (parsed !== undefined) { return parsed; } } if (isRecord(nested)) { const unwrapped = unwrapToolPayloadObject(nested); if (unwrapped !== undefined) { return unwrapped; } } } return undefined; } function parseJSONLikeText(text: string): unknown { const trimmed = text.trim(); if (!trimmed) { return undefined; } try { return JSON.parse(trimmed) as unknown; } catch { return extractJSONFromText(trimmed) ?? extractSearchPayloadFromText(trimmed); } } function extractJSONFromText(text: string): unknown { const candidates: Array<[number, number]> = []; const firstObj = text.indexOf("{"); if (firstObj !== -1) candidates.push([firstObj, text.lastIndexOf("}")]); const firstArr = text.indexOf("["); if (firstArr !== -1) candidates.push([firstArr, text.lastIndexOf("]")]); candidates.sort(([a], [b]) => a - b); for (const [start, end] of candidates) { if (start < 0 || end <= start) continue; const slice = text.slice(start, end + 1); try { return JSON.parse(slice); } catch { // try the next candidate } } return undefined; } function extractSearchPayloadFromText(text: string): unknown { if (!text.includes('"results"') || !text.includes('"recUuid"')) { return undefined; } const results: Record[] = []; const objectPattern = /\{\s*"index"\s*:\s*\d+[\s\S]*?\n\s*\}/g; for (const match of text.matchAll(objectPattern)) { try { const item = JSON.parse(match[0]) as unknown; if (isRecord(item) && item.recUuid) { results.push(item); } } catch { const item = extractSearchItemFromText(match[0]); if (item) { results.push(item); } } } if (results.length === 0) { return undefined; } return { success: true, query: extractStringField(text, "query") ?? "", count: results.length, source: extractStringField(text, "source") ?? "tool-output", results, }; } function extractSearchItemFromText(text: string): Record | null { const recUuid = extractStringField(text, "recUuid"); if (!recUuid) { return null; } const score = extractNumberField(text, "relevance_score"); return { index: extractNumberField(text, "index") ?? undefined, recUuid, relevance_score: score, title: extractStringField(text, "title") ?? recUuid, source_category: extractStringField(text, "source_category") ?? "", source: extractStringField(text, "source") ?? "", publish_time: extractStringField(text, "publish_time") ?? "", content_preview: extractStringField(text, "content_preview") ?? "", content_length: extractNumberField(text, "content_length") ?? undefined, }; } function extractStringField(text: string, field: string): string | undefined { const pattern = new RegExp(`"${escapeRegExp(field)}"\\s*:\\s*"((?:\\\\.|[^"\\\\])*)"`); const match = pattern.exec(text); if (!match) { return undefined; } try { return JSON.parse(`"${match[1]}"`) as string; } catch { return match[1]; } } function extractNumberField(text: string, field: string): number | undefined { const pattern = new RegExp(`"${escapeRegExp(field)}"\\s*:\\s*(-?\\d+(?:\\.\\d+)?)`); const match = pattern.exec(text); if (!match) { return undefined; } const value = Number(match[1]); return Number.isFinite(value) ? value : undefined; } function escapeRegExp(value: string): string { return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); } function extractToolText(content: unknown): string { if (typeof content === "string") { return content.trim(); } if (Array.isArray(content)) { return content .map((part) => { if (typeof part === "string") { return part; } if (part && typeof part === "object" && "text" in part) { const text = (part as { text?: unknown }).text; return typeof text === "string" ? text : ""; } return ""; }) .join("\n") .trim(); } return ""; }