// Tiny dispatcher for tool-call payload renderers. // // Hosts compose matchers (predicate + render) and a fallback. The result is a // `renderPayload` function compatible with ``. // // Pure — no React, no DOM. The matchers themselves return ReactNode. import type { ReactNode } from 'react'; import type { ChatToolCall } from '../types'; import type { ToolPayloadKind } from '../messages/ToolCalls'; export interface ToolPayloadMatcher { /** Cheap predicate. First match wins. */ match: (value: unknown, kind: ToolPayloadKind, call: ChatToolCall) => boolean; render: (value: unknown, kind: ToolPayloadKind, call: ChatToolCall) => ReactNode; } export type ToolPayloadFallback = ( value: unknown, kind: ToolPayloadKind, call: ChatToolCall, ) => ReactNode; export function dispatchToolPayload( matchers: ToolPayloadMatcher[], fallback: ToolPayloadFallback, ): ToolPayloadFallback { return (value, kind, call) => { for (const m of matchers) { if (m.match(value, kind, call)) return m.render(value, kind, call); } return fallback(value, kind, call); }; } // ---- Common matcher predicates ------------------------------------------- // Re-export as building blocks. Hosts can use directly or compose. export function isPlainObject(v: unknown): v is Record { return v !== null && typeof v === 'object' && !Array.isArray(v); } export function isLatLng(v: unknown): v is { lat: number; lng: number } { return isPlainObject(v) && typeof v.lat === 'number' && typeof v.lng === 'number'; } export function isGeoJSONFeatureCollection(v: unknown): v is { type: 'FeatureCollection'; features: unknown[] } { return isPlainObject(v) && v.type === 'FeatureCollection' && Array.isArray(v.features); } export function isStringValue(v: unknown): v is string { return typeof v === 'string'; }