export type { SerializedToolExecuteResult, ToolExecuteResult, ToolResultExecutionMetadata, ToolResultMetadata, ToolResultMetadataInput, ToolResultListMetadata, ToolResultListAccessor, ToolResultTargetMetadata, ToolResultTargetAccessor, } from './tool-result-types'; export type { EmailDeliverability, EmailMxClass, EmailStatus, EmailStatusValue, EmailStatusVerdict, } from './email-status'; import { buildEmailStatus, type EmailStatusExtractorConfig, } from './email-status'; import { JOB_CHANGE_STATUS_VALUES, type JobChangeGetterValue, type JobChangeStatus, } from './extractor-targets'; import type { SerializedToolExecuteResult, ToolExecuteResult, ToolResultExecutionMetadata, ToolResultExtractorDescriptor, ToolResultListAccessor, ToolResultListMetadata, ToolResultEnvelope, ToolResultMetadataInput, ToolResultTargetAccessor, ToolResultTargetMetadata, } from './tool-result-types'; import { createDeferredPlayDataset, createPlayDataset, isSerializedPlayDataset, residentPlayDatasetRows, trimSerializedPlayDatasetPreview, type PlayDataset, type SerializedPlayDataset, } from '../plays/dataset'; import { normalizeTableNamespace, sha256Hex } from '../plays/row-identity'; import { listNameFromDeclaredPath } from './tool-result-paths'; import { legacyRawFromToolResponseRawV2, providerMetaFromToolResponseRawV2, type ToolResponseView, } from './tool-response-contract'; type PathSegment = string | number | '*'; const SERIALIZED_TOOL_EXECUTE_RESULT_V1_KIND = 'deepline.tool_execute_result.v1'; const SERIALIZED_TOOL_EXECUTE_RESULT_V2_KIND = 'deepline.tool_execute_result.v2'; const SERIALIZED_TOOL_RESULT_LIST_PREVIEW_LIMIT = 5; const LIVE_TOOL_RESULT_LIST_DATASET_REGISTRY_LIMIT = 256; const SERIALIZED_TOOL_LIST_ROWS = Symbol('deepline.serialized_tool_list_rows'); const liveToolResultListDatasets = new Map< string, PlayDataset> >(); /** * Keys that describe a field rather than carry it. * * These sit next to the real value under a similar name, so a substring match * on the target name reaches them as soon as the real field is absent. * `phone_status: "unfound"` is not a phone number and `email_type: "work"` is * not an email address. */ const DESCRIPTOR_SUFFIX = /_(status|type|score|count|id|verified|valid|confidence|quality|source)$/i; /** A company-scoped key is never the person-scoped answer. */ const COMPANY_SCOPED = /company/i; type TargetFallbackRule = { /** Key names that may carry this target's value. */ match: readonly RegExp[]; /** Key names that must never be treated as this target's value. */ reject?: readonly RegExp[]; }; const TARGET_FALLBACK_KEYS: Record = { email: { match: [/^email$/i, /^address$/i, /email/i], reject: [DESCRIPTOR_SUFFIX], }, phone: { match: [/^phone$/i, /mobile/i, /phone/i, /telephone/i], reject: [DESCRIPTOR_SUFFIX], }, linkedin: { match: [/^linkedin_url$/i, /^linkedin$/i, /linkedin/i], reject: [DESCRIPTOR_SUFFIX, COMPANY_SCOPED], }, company_linkedin_url: { match: [/company.*linkedin/i, /linkedin.*company/i], reject: [DESCRIPTOR_SUFFIX], }, company_domain: { match: [/^company_domain$/i, /company.*domain/i], reject: [DESCRIPTOR_SUFFIX], }, company_name: { match: [/^company_name$/i, /company.*name/i], reject: [DESCRIPTOR_SUFFIX], }, domain: { match: [/^domain$/i, /company_domain/i, /domain/i], reject: [DESCRIPTOR_SUFFIX], }, // Status targets legitimately want the `_status` keys the suffix rule // rejects elsewhere, so they carry no rejections. status: { match: [/^email_status$/i, /^status$/i] }, email_status: { match: [/^email_status$/i, /^status$/i] }, }; function isRecord(value: unknown): value is Record { return Boolean(value) && typeof value === 'object' && !Array.isArray(value); } type V2ToolExecuteOutput = { raw: unknown; rawV2?: unknown; view?: ToolResponseView; meta?: Record; }; function parseV2ToolExecuteOutput( data: Record, ): V2ToolExecuteOutput | null { const toolResponse = data.toolResponse; if (isRecord(toolResponse)) { const rawV2 = Object.prototype.hasOwnProperty.call(toolResponse, 'rawV2') ? toolResponse.rawV2 : undefined; const view = toolResponse.view === 'data' || toolResponse.view === 'rawV2' ? toolResponse.view : undefined; const providerMeta = providerMetaFromToolResponseRawV2( rawV2, view ?? 'rawV2', ); const responseMeta = isRecord(toolResponse.responseMeta) ? toolResponse.responseMeta : undefined; const meta = { ...(isRecord(toolResponse.meta) ? toolResponse.meta : {}), ...(providerMeta ?? {}), ...(responseMeta ?? {}), }; const raw = Object.prototype.hasOwnProperty.call(toolResponse, 'raw') ? toolResponse.raw : rawV2 === undefined ? null : legacyRawFromToolResponseRawV2(rawV2, view ?? 'rawV2', responseMeta); return { raw, ...(rawV2 !== undefined ? { rawV2 } : {}), ...(view ? { view } : {}), ...(Object.keys(meta).length > 0 ? { meta } : {}), }; } return null; } function adaptV2ExecuteResponseToToolResult(data: Record): { output?: V2ToolExecuteOutput; result: unknown; } { const output = parseV2ToolExecuteOutput(data); if (!output) { return { result: data.result ?? data }; } return { output, result: { data: output.raw, ...(output.meta ? { meta: output.meta } : {}), }, }; } /** * Parsed view of a raw `/api/v2/integrations/:toolId/execute` JSON body. * * This is the single runtime-side seam from a V2 execute response to the * inputs of {@link createToolExecuteResult}. The CJS runtime must go through * {@link parseToolExecuteResponse}; the intermediate envelope shapes * (`toolResponse` adaptation, `_metadata.tool` parsing, status derivation) * are private to this module. * * Boundary note: provider-specific redaction (wiza/apify/bettercontact billing * fields) happens server-side in * `src/lib/integrations/execute-result-normalization.ts` BEFORE the response * leaves the API. This module only ever sees the already-redacted public * response — do not move redaction here, it would leak provider internals * into the shared runtime bundles. */ export type ParsedToolExecuteResponse = { status: string; jobId?: string; meta?: Record; toolResponse?: { raw?: unknown; rawV2?: unknown; view?: 'data' | 'rawV2'; meta?: Record; responseMeta?: Record; }; /** Legacy `{ data, meta }` envelope consumed by createToolExecuteResult. */ result: unknown; /** Tool extractor metadata parsed from `_metadata.tool`, if present. */ metadata: ToolResultMetadataInput | null; }; export function parseToolExecuteResponse( toolId: string, body: Record, ): ParsedToolExecuteResponse { const { result } = adaptV2ExecuteResponseToToolResult(body); const status = typeof body.status === 'string' ? body.status : result == null ? 'no_result' : 'completed'; return { status, jobId: typeof body.job_id === 'string' ? body.job_id : undefined, meta: isRecord(body.meta) ? body.meta : undefined, toolResponse: (() => { const output = parseV2ToolExecuteOutput(body); return output ? { raw: output.raw, ...(output.rawV2 !== undefined ? { rawV2: output.rawV2 } : {}), ...(output.view ? { view: output.view } : {}), ...(output.meta ? { meta: output.meta } : {}), } : undefined; })(), result, metadata: parseExecuteToolMetadata(toolId, body), }; } function parseExecuteToolMetadata( toolId: string, data: Record, ): ToolResultMetadataInput | null { const metadata = data._metadata; if (!metadata || typeof metadata !== 'object' || Array.isArray(metadata)) { return null; } const tool = (metadata as Record).tool; if (!tool || typeof tool !== 'object' || Array.isArray(tool)) return null; const record = tool as Record; const metadataToolId = typeof record.toolId === 'string' && record.toolId.trim() ? record.toolId : toolId; const readGetters = (value: unknown): Record => { if (!value || typeof value !== 'object' || Array.isArray(value)) return {}; return Object.fromEntries( Object.entries(value as Record).flatMap( ([key, paths]) => { if (!Array.isArray(paths)) return []; const normalized = paths.filter( (path): path is string => typeof path === 'string' && path.trim().length > 0, ); return normalized.length > 0 ? [[key, normalized]] : []; }, ), ); }; const readExtractors = ( value: unknown, ): ToolResultMetadataInput['extractors'] => { if (!value || typeof value !== 'object' || Array.isArray(value)) return {}; return Object.fromEntries( Object.entries(value as Record).flatMap( ([key, entry]) => { if (!entry || typeof entry !== 'object' || Array.isArray(entry)) return []; const recordEntry = entry as Record; if (!Array.isArray(recordEntry.paths)) return []; const paths = recordEntry.paths.filter( (path): path is string => typeof path === 'string' && path.trim().length > 0, ); if (paths.length === 0) return []; const overrides = Array.isArray(recordEntry.overrides) ? recordEntry.overrides.flatMap((override) => { if ( !override || typeof override !== 'object' || Array.isArray(override) ) { return []; } const overrideRecord = override as Record; const overridePaths = Array.isArray(overrideRecord.paths) ? overrideRecord.paths.filter( (path): path is string => typeof path === 'string' && path.trim().length > 0, ) : []; if (overridePaths.length === 0) return []; const readOverridePrimitive = ( candidate: unknown, ): string | number | boolean | null | undefined => { if (candidate === null) return null; if (typeof candidate === 'string') return candidate; if (typeof candidate === 'number') return candidate; if (typeof candidate === 'boolean') return candidate; return undefined; }; const value = readOverridePrimitive(overrideRecord.value); if (value === undefined) { return []; } const equals: string | number | boolean | null = readOverridePrimitive(overrideRecord.equals) ?? true; return [{ paths: overridePaths, equals, value }]; }) : []; const emailStatus = recordEntry.emailStatus && typeof recordEntry.emailStatus === 'object' && !Array.isArray(recordEntry.emailStatus) ? (recordEntry.emailStatus as EmailStatusExtractorConfig) : undefined; return [ [ key, { paths, ...(Array.isArray(recordEntry.transforms) ? { transforms: recordEntry.transforms.filter( (transform): transform is string => typeof transform === 'string' && transform.trim().length > 0, ), } : {}), ...(Array.isArray(recordEntry.enum) ? { enum: recordEntry.enum.filter( (value): value is string => typeof value === 'string' && value.trim().length > 0, ), } : {}), ...(overrides.length > 0 ? { overrides } : {}), ...(emailStatus ? { emailStatus } : {}), }, ], ]; }, ), ); }; const listExtractorPaths = Array.isArray(record.listExtractorPaths) ? record.listExtractorPaths.filter( (path): path is string => typeof path === 'string' && path.trim().length > 0, ) : []; return { toolId: metadataToolId, extractors: readExtractors(record.extractors), targetGetters: readGetters(record.targetGetters), listExtractorPaths, listIdentityGetters: readGetters(record.listIdentityGetters), }; } function toV2RawToolOutputPath(path: string): string { const normalized = String(path || '') .trim() .replace(/^\./, ''); if (!normalized) return 'toolResponse.raw'; if ( normalized === 'toolResponse.raw' || normalized.startsWith('toolResponse.raw.') ) { return normalized; } const rawPath = normalized .replace(/^result\.data\.?/, '') .replace(/^result(?:\.|$)/, '') .replace(/^data\.?/, '') .replace(/^\./, ''); return rawPath ? `toolResponse.raw.${rawPath}` : 'toolResponse.raw'; } function isMeaningfulValue(value: unknown): boolean { if (value == null) return false; if (typeof value === 'string') return value.trim().length > 0; if (Array.isArray(value)) return value.length > 0; if (typeof value === 'object') return Object.keys(value).length > 0; return true; } function parsePath(path: string): PathSegment[] { const segments: PathSegment[] = []; for (const rawPart of path.split('.').filter(Boolean)) { const bracketPattern = /([^\[\]]+)|\[(\d+|\*)\]/g; let matched = false; for (const match of rawPart.matchAll(bracketPattern)) { matched = true; if (match[1]) { segments.push(match[1]); } else if (match[2] === '*') { segments.push('*'); } else if (match[2]) { segments.push(Number(match[2])); } } if (!matched) { segments.push(rawPart); } } return segments; } function pathToString(segments: readonly PathSegment[]): string { return segments .map((segment, index) => typeof segment === 'number' ? `[${segment}]` : segment === '*' ? '[*]' : index === 0 ? segment : `.${segment}`, ) .join(''); } function valuesAtSegments( current: unknown, segments: readonly PathSegment[], path: readonly PathSegment[] = [], ): Array<{ value: unknown; path: string }> { if (segments.length === 0) { return [{ value: current, path: pathToString(path) }]; } const [segment, ...rest] = segments; if (segment === '*') { if (!Array.isArray(current)) return []; return current.flatMap((entry, index) => valuesAtSegments(entry, rest, [...path, index]), ); } if (typeof segment === 'number') { if (!Array.isArray(current)) return []; return valuesAtSegments(current[segment], rest, [...path, segment]); } if (!isRecord(current)) return []; const directMatches = valuesAtSegments(current[segment], rest, [ ...path, segment, ]); if (directMatches.length > 0 || typeof segment !== 'string') { return directMatches; } { for (let end = segments.length; end > 1; end -= 1) { const literalSegments = segments.slice(0, end); if (!literalSegments.every((entry) => typeof entry === 'string')) { continue; } const literalKey = literalSegments.join('.'); if (!Object.prototype.hasOwnProperty.call(current, literalKey)) { continue; } return valuesAtSegments(current[literalKey], segments.slice(end), [ ...path, literalKey, ]); } } return directMatches; } function getValuesAtPath(root: unknown, path: string): unknown[] { return valuesAtSegments(root, parsePath(path)).map((entry) => entry.value); } function toResultEnvelope(value: unknown): ToolResultEnvelope { if (isRecord(value) && 'data' in value) { const envelope: ToolResultEnvelope = { data: value.data }; if ('meta' in value) envelope.meta = value.meta as Record; return envelope; } return { data: value }; } function normalizeResultPath(path: string): string { const trimmed = String(path || '') .trim() .replace(/^\./, ''); if (!trimmed) return ''; return toV2RawToolOutputPath(trimmed); } function toV2RawToolOutputPathPreservingProviderData(path: string): string { const normalized = String(path || '') .trim() .replace(/^\./, ''); if (!normalized) return 'toolResponse.raw'; if ( normalized === 'toolResponse.raw' || normalized.startsWith('toolResponse.raw.') ) { return normalized; } const rawPath = normalized.replace(/^result(?:\.|$)/, '').replace(/^\./, ''); return rawPath ? `toolResponse.raw.${rawPath}` : 'toolResponse.raw'; } function candidateResultPaths(path: string): string[] { const candidates = [ normalizeResultPath(path), toV2RawToolOutputPathPreservingProviderData(path), ]; return candidates.filter( (candidate, index, all) => candidate.length > 0 && all.indexOf(candidate) === index, ); } function normalizeRelativePath(path: string): string { return String(path || '') .trim() .replace(/^result\./, '') .replace(/^\./, ''); } function getFirstMeaningfulValueAtPath( root: unknown, path: string, ): ToolResultTargetMetadata | null { for (const entry of valuesAtSegments(root, parsePath(path))) { if (isMeaningfulValue(entry.value)) { return { value: entry.value, path: entry.path }; } } return null; } function getAtPath(root: unknown, path: string): unknown { const segments = parsePath(path); if (segments.includes('*')) { return getValuesAtPath(root, path).filter(isMeaningfulValue); } let current = root; for (const segment of segments) { if (typeof segment === 'number') { if (!Array.isArray(current)) return undefined; current = current[segment]; continue; } if (!isRecord(current)) return undefined; current = current[segment]; } return current; } function normalizeString(value: unknown): string | null { if (typeof value === 'string') { const trimmed = value.trim(); return trimmed ? trimmed : null; } if (typeof value === 'number' && Number.isFinite(value)) { return String(value); } return null; } function normalizeRows(value: unknown): Record[] | null { if (!Array.isArray(value)) return null; return value.map((entry) => (isRecord(entry) ? entry : { value: entry })); } function findFirstTargetByPath( result: unknown, paths: readonly string[] | undefined, ): ToolResultTargetMetadata | null { for (const path of paths ?? []) { for (const candidate of candidateResultPaths(path)) { const match = getFirstMeaningfulValueAtPath(result, candidate); if (match) return match; } } return null; } function firstValueForPaths( result: unknown, paths: readonly string[] | undefined, ): ToolResultTargetMetadata | null { return findFirstTargetByPath(result, paths); } function buildEmailStatusTarget( result: unknown, descriptor: ToolResultExtractorDescriptor, ): ToolResultTargetMetadata | null { const config = descriptor.emailStatus; if (!config) return null; const values: Record = {}; const pathSets: Record = { rawStatus: config.rawStatus, rawScore: config.rawScore, valid: config.valid, deliverability: config.deliverability, catchAll: config.catchAll, mxProvider: config.mxProvider, mxRecord: config.mxRecord, fraudScore: config.fraudScore, disposable: config.disposable, roleBased: config.roleBased, freeEmail: config.freeEmail, abuse: config.abuse, spamtrap: config.spamtrap, suspect: config.suspect, }; let firstPath: string | null = null; for (const [name, paths] of Object.entries(pathSets)) { const match = firstValueForPaths(result, paths); if (!match) continue; values[name] = match.value; firstPath ??= match.path; } if (!firstPath) return null; return { path: firstPath, value: buildEmailStatus({ config, values }), }; } function findFirstTargetByKey( result: unknown, target: string, depth = 0, path: PathSegment[] = [], ): ToolResultTargetMetadata | null { if (depth > 6) return null; if (Array.isArray(result)) { for (let index = 0; index < result.length; index += 1) { const found = findFirstTargetByKey(result[index], target, depth + 1, [ ...path, index, ]); if (found) return found; } return null; } if (!isRecord(result)) return null; const rule: TargetFallbackRule = TARGET_FALLBACK_KEYS[target] ?? { match: [new RegExp(`^${target}$`, 'i')], }; for (const [key, value] of Object.entries(result)) { if (rule.reject?.some((pattern) => pattern.test(key))) continue; if ( rule.match.some((pattern) => pattern.test(key)) && isMeaningfulValue(value) ) { return { value, path: pathToString([...path, key]) }; } } for (const [key, value] of Object.entries(result)) { const found = findFirstTargetByKey(value, target, depth + 1, [ ...path, key, ]); if (found) return found; } return null; } function normalizeEmailStatus(value: unknown): unknown { const normalized = normalizeString(value) ?.toLowerCase() .replace(/[\s-]+/g, '_'); if (!normalized) return 'unknown'; if (['deliverable', 'verified', 'ok', 'true'].includes(normalized)) return 'valid'; if (['undeliverable', 'bad', 'false', 'failed'].includes(normalized)) return 'invalid'; if ( ['accept_all', 'acceptall', 'catchall', 'valid_catch_all'].includes( normalized, ) ) { return 'catch_all'; } return normalized; } function normalizePhoneStatus(value: unknown): unknown { if (typeof value === 'boolean') return value ? 'valid' : 'invalid'; const normalized = normalizeString(value) ?.toLowerCase() .replace(/[\s-]+/g, '_'); if (!normalized) return 'unknown'; if (['verified', 'ok', 'true', 'active'].includes(normalized)) return 'valid'; if ( ['bad', 'false', 'failed', 'inactive', 'disconnected'].includes(normalized) ) { return 'invalid'; } return normalized; } function normalizeJobChangeStatus(value: unknown): unknown { if (typeof value === 'boolean') return value ? 'moved' : 'no_change'; const normalized = normalizeString(value) ?.toLowerCase() .replace(/[\s-]+/g, '_'); if (!normalized) return 'unknown'; if (['true', 'yes', 'moved', 'changed', 'new_company'].includes(normalized)) { return 'moved'; } if (['false', 'no', 'same', 'no_change'].includes(normalized)) return 'no_change'; if (['left', 'left_company'].includes(normalized)) return 'left_company'; if ((JOB_CHANGE_STATUS_VALUES as readonly string[]).includes(normalized)) { return normalized; } return 'unknown'; } function firstExperienceDate(value: unknown): string | null { if (!Array.isArray(value)) return null; for (const entry of value) { if (!isRecord(entry)) continue; const date = normalizeString( entry.start_date ?? entry.started_at ?? entry.startDate, ); if (date) return date; } return null; } function normalizeJobChange(value: unknown): JobChangeGetterValue { const record = isRecord(value) ? value : {}; const nested = isRecord(record.job_change) ? record.job_change : record; const output = isRecord(nested.output) ? nested.output : nested; const person = isRecord(output.person) ? output.person : {}; const status = normalizeJobChangeStatus( output.status ?? output.job_change_status ?? output.job_changed ?? output.changed, ) as JobChangeStatus; const moved = status === 'moved'; return { status, date: moved ? (normalizeString( output.date ?? output.job_change_date ?? output.change_date ?? output.changed_at, ) ?? firstExperienceDate(person.experiences)) : null, new_company: moved ? normalizeString( output.new_company ?? output.current_company ?? person.company_name ?? person.current_company, ) : null, new_title: moved ? normalizeString( output.new_title ?? output.current_title ?? person.title ?? person.headline, ) : null, }; } function applyExtractorTransforms( value: unknown, descriptor: ToolResultExtractorDescriptor, ): unknown { return (descriptor.transforms ?? []).reduce((current, transform) => { if (transform.endsWith('emailStatus')) return normalizeEmailStatus(current); if (transform.endsWith('phoneStatus')) return normalizePhoneStatus(current); if (transform === 'jobChange') return normalizeJobChange(current); if (transform === 'jobChangeStatus') return normalizeJobChangeStatus(current); return current; }, value); } function coerceToEnum( value: unknown, descriptor: ToolResultExtractorDescriptor, ): unknown { if (!descriptor.enum?.length) return value; const normalized = normalizeString(value); if (!normalized) return value; return descriptor.enum.includes(normalized) ? normalized : value; } function resolveListRows( result: unknown, listExtractorPaths: readonly string[] | undefined, ): Record[] }> { const lists: Record< string, { path: string; rows: Record[] } > = {}; for (const rawPath of listExtractorPaths ?? []) { const listName = listNameFromDeclaredPath(rawPath); if (!listName) continue; const path = normalizeResultPath(rawPath); if (!path) continue; const candidates = [...candidateResultPaths(rawPath)].filter( (candidate, index, all) => candidate && all.indexOf(candidate) === index, ); let resolved: { path: string; rows: Record[] } | null = null; let emptyMatch: { path: string; rows: Record[] } | null = null; for (const candidate of candidates) { const candidateRows = normalizeRows(getAtPath(result, candidate)); if (!candidateRows) { continue; } if (candidateRows.length > 0) { resolved = { path: candidate, rows: candidateRows }; break; } emptyMatch ??= { path: candidate, rows: candidateRows }; } resolved ??= emptyMatch; if (!resolved) continue; const existing = lists[listName]; if (existing?.rows.length && resolved.rows.length === 0) { continue; } lists[listName] = resolved; } return lists; } function deriveListKeys(input: { listPath: string; rows: readonly Record[]; targetGetters?: Record; listIdentityGetters?: Record; }): Record { const keys: Record = {}; for (const [target, paths] of Object.entries( input.listIdentityGetters ?? {}, )) { const firstPath = paths .map((rawPath) => normalizeRelativePath(rawPath)) .find(Boolean); if (firstPath) { keys[target] = firstPath; } } if (Object.keys(keys).length > 0) { return keys; } const listPrefix = input.listPath.replace(/\[\d+\]$/, ''); for (const [target, paths] of Object.entries(input.targetGetters ?? {})) { for (const rawPath of paths) { const path = String(rawPath || '') .trim() .replace(/^\./, ''); if (!path) continue; const firstRow = input.rows[0]; if (firstRow && Object.prototype.hasOwnProperty.call(firstRow, path)) { keys[target] = path; break; } for (const resultPath of candidateResultPaths(path)) { const directPrefix = `${listPrefix}.`; if (resultPath.startsWith(directPrefix)) { keys[target] = resultPath .slice(directPrefix.length) .replace(/^\[\d+\]\.?/, ''); break; } const indexedPrefix = `${listPrefix}[0].`; if (resultPath.startsWith(indexedPrefix)) { keys[target] = resultPath.slice(indexedPrefix.length); break; } const wildcardPrefix = `${listPrefix}[*].`; if (resultPath.startsWith(wildcardPrefix)) { keys[target] = resultPath.slice(wildcardPrefix.length); break; } const dottedIndexPrefix = `${listPrefix}.0.`; if (resultPath.startsWith(dottedIndexPrefix)) { keys[target] = resultPath.slice(dottedIndexPrefix.length); break; } } if (keys[target]) break; } } if (Object.keys(keys).length === 0 && input.rows[0]) { for (const key of Object.keys(input.rows[0])) { keys[key] = key; } } return keys; } function buildTargets( result: unknown, extractors?: Record, targetGetters?: Record, ): Record { const targets: Record = {}; const declaredTargetPaths = new Map(); const semanticStatusTargets = new Set(); for (const [target, descriptor] of Object.entries(extractors ?? {})) { // Normal scalar getters are total: a declared but absent value reads null. // Semantic status getters stay absent when their provider status did not // resolve; manufacturing an "unknown" status would blur no signal with an // observed provider verdict. const isSemanticStatus = descriptor.emailStatus !== undefined || descriptor.transforms?.some((transform) => /(emailStatus|phoneStatus|jobChange(?:Status)?)$/.test(transform), ) === true; if (isSemanticStatus) semanticStatusTargets.add(target); else declaredTargetPaths.set(target, descriptor.paths); const emailStatusTarget = buildEmailStatusTarget(result, descriptor); if (emailStatusTarget) { targets[target] = emailStatusTarget; continue; } const fromExtractor = findFirstTargetByPath(result, descriptor.paths); if (!fromExtractor) continue; const transformed = coerceToEnum( applyExtractorTransforms(fromExtractor.value, descriptor), descriptor, ); const override = findExtractorOverride(result, descriptor); targets[target] = { path: fromExtractor.path, value: override?.value ?? transformed, }; } const metadataTargets = new Set(Object.keys(targetGetters ?? {})); for (const target of metadataTargets) { if (targets[target] || semanticStatusTargets.has(target)) continue; declaredTargetPaths.set( target, targetGetters?.[target] ?? declaredTargetPaths.get(target) ?? [], ); const fromMetadata = findFirstTargetByPath(result, targetGetters?.[target]); if (fromMetadata) { targets[target] = fromMetadata; continue; } // Declared paths are routinely incomplete against the shape a provider // actually returns (zerobounce declares `result.data.email` and answers // with `address`), so the key scan stays as the rescue. It is bounded by // each target's `reject` rules: the scan runs precisely when the provider // returned no value, which is when a same-named neighbour is most likely // to be a different field. Without those rules a real Wiza miss resolved // `phone` to `phone_status: "unfound"` and `linkedin` to // `company_linkedin`, reporting a company page as the person's profile. const fallback = findFirstTargetByKey(result, target); if (fallback) { targets[target] = fallback; } } if (metadataTargets.size === 0) { for (const target of ['email', 'phone', 'linkedin', 'domain', 'status']) { const found = findFirstTargetByKey(result, target); if (found) targets[target] = found; } } for (const [target, paths] of declaredTargetPaths) { if (targets[target]) continue; targets[target] = { value: null, path: paths.flatMap(candidateResultPaths).find(Boolean) ?? `toolResponse.raw.${target}`, }; } return targets; } function findExtractorOverride( result: unknown, descriptor: ToolResultExtractorDescriptor, ): { value: string | number | boolean | null } | null { for (const override of descriptor.overrides ?? []) { const expected = Object.prototype.hasOwnProperty.call(override, 'equals') ? override.equals : true; for (const path of override.paths) { const match = findFirstTargetByPath(result, [path]); if (!match) continue; if (match.value === expected) { return { value: override.value }; } } } return null; } function buildLists( resolved: Record[] }>, metadata: ToolResultMetadataInput, ): Record { const lists: Record = {}; for (const [name, list] of Object.entries(resolved)) { lists[name] = { path: list.path, count: list.rows.length, keys: deriveListKeys({ listPath: list.path, rows: list.rows, targetGetters: metadata.targetGetters, listIdentityGetters: metadata.listIdentityGetters, }), }; } return lists; } function buildExtractedAccessors( targets: Record, ): Record { return Object.fromEntries( Object.entries(targets).map(([target, metadata]) => { const accessor = { path: metadata.path } as ToolResultTargetAccessor; Object.defineProperties(accessor, { value: { value: metadata.value, enumerable: false, }, get: { value() { return metadata.value ?? null; }, enumerable: false, }, }); return [target, accessor]; }), ); } function serializedTargetValuesFromResult( value: ToolExecuteResult, ): Record | undefined { const targets = value._metadata.targets; return Object.keys(targets).length > 0 ? targets : undefined; } function applySerializedTargetValues( result: ToolExecuteResult, targetValues: Record | undefined, ): ToolExecuteResult { if (!targetValues || Object.keys(targetValues).length === 0) return result; result._metadata.targets = targetValues; result.extractedValues = buildExtractedAccessors(targetValues); return result; } function buildListAccessors( resolved: Record[] }>, lists: Record, toolId: string, executionDiscriminator: string, ): Record { return Object.fromEntries( Object.entries(lists).map(([name, metadata]) => { const rows = resolved[name]?.rows ?? []; const datasetDiscriminator = `${executionDiscriminator}:${listRowsFingerprint(rows)}`; const dataset = createPlayDataset(rows, { kind: 'csv', sourceLabel: metadata.path, tableNamespace: listTableNamespace( toolId, name, metadata.path, datasetDiscriminator, ), datasetId: `tool-list:${sha256Hex( `${toolId}:${metadata.path}:${datasetDiscriminator}`, )}`, }); const accessor = { path: metadata.path, count: metadata.count, keys: metadata.keys, } as ToolResultListAccessor; Object.defineProperty(accessor, 'get', { value() { return dataset; }, enumerable: false, }); return [name, accessor]; }), ); } function listRowsFingerprint(rows: readonly Record[]): string { try { return sha256Hex( JSON.stringify( { count: rows.length, rows, }, (_key, value) => (typeof value === 'bigint' ? value.toString() : value), ), ).slice(0, 12); } catch { return sha256Hex(String(rows.length)).slice(0, 12); } } function listTableNamespace( toolId: string, name: string, path: string, discriminator: string, ): string { const raw = `${toolId}_${name || path || 'rows'}_${sha256Hex(discriminator).slice(0, 10)}`; try { return normalizeTableNamespace(raw); } catch { const hash = sha256Hex(raw).slice(0, 10); const leaf = name || path.split('.').filter(Boolean).at(-1) || 'rows'; let prefix = 'rows'; try { prefix = normalizeTableNamespace(leaf).slice(0, 52) || 'rows'; } catch { prefix = 'rows'; } return normalizeTableNamespace(`${prefix}_${hash}`); } } export function createToolExecuteResult(input: { status: string; jobId?: string; result: TResult; metadata: ToolResultMetadataInput; execution: ToolResultExecutionMetadata; meta?: Record; response?: { rawV2?: unknown; view?: 'data' | 'rawV2'; meta?: Record; }; }): ToolExecuteResult { const result = toResultEnvelope(input.result); const resultRoot = { toolResponse: { raw: result.data, ...(input.response && 'rawV2' in input.response ? { rawV2: input.response.rawV2 } : {}), ...(input.response?.view ? { view: input.response.view } : {}), ...(result.meta ? { meta: result.meta } : {}), }, }; const targets = buildTargets( resultRoot, input.metadata.extractors, input.metadata.targetGetters, ); const resolvedLists = resolveListRows( resultRoot, input.metadata.listExtractorPaths, ); const lists = buildLists(resolvedLists, input.metadata); const metadata = { toolId: input.metadata.toolId, execution: input.execution, targets, listExtractorPaths: [...(input.metadata.listExtractorPaths ?? [])], ...(input.metadata.extractors ? { extractors: input.metadata.extractors } : {}), lists, }; const toolResponse = { raw: result.data, ...(input.response && 'rawV2' in input.response ? { rawV2: input.response.rawV2 } : {}), ...(input.response?.view ? { view: input.response.view } : {}), ...(result.meta ? { meta: result.meta } : {}), }; const extractedValues = buildExtractedAccessors(targets); const extractedLists = buildListAccessors( resolvedLists, lists, input.metadata.toolId, input.jobId ?? input.execution.cacheKey ?? 'inline', ); const wrapper = { status: input.status, ...(input.jobId ? { job_id: input.jobId } : {}), ...(input.meta ? { meta: input.meta } : {}), toolResponse, extractedValues, extractedLists, } as ToolExecuteResult; Object.defineProperties(wrapper, { toolOutput: { value: toolResponse, enumerable: false, }, }); Object.defineProperty(wrapper, '_metadata', { value: metadata, enumerable: false, }); return wrapper; } export function isToolExecuteResult( value: unknown, ): value is ToolExecuteResult { return ( isRecord(value) && typeof value.status === 'string' && '_metadata' in value && isRecord(value._metadata) && 'toolResponse' in value && 'toolOutput' in value ); } function resultRootOf(result: ToolExecuteResult): unknown { return { toolResponse: result.toolOutput }; } /** * Read a single value out of a tool result — by declared getter name, then by * selector paths, then by key. Used by SDK play helpers (`extractValue`). */ export function readValue( result: ToolExecuteResult, selector: readonly string[] | string, ): unknown { if (typeof selector === 'string') { const declared = result.extractedValues[selector]?.get(); if (declared != null) return declared; } const root = resultRootOf(result); const paths = Array.isArray(selector) ? selector : [selector]; const byPath = findFirstTargetByPath(root, paths); if (byPath) return byPath.value; if (typeof selector === 'string') { const byKey = findFirstTargetByKey(root, selector); if (byKey) return byKey.value; } return null; } /** * Read array rows out of a tool result — by selector paths, else the first * declared list accessor. Companion to {@link readValue} for `extractList`. */ export function readList( result: ToolExecuteResult, selector?: readonly string[] | string, ): PlayDataset> | Record[] { if (selector) { const paths = Array.isArray(selector) ? selector : [selector]; const declaredList = findDeclaredListAccessor(result, paths); if (declaredList && preservedSerializedListRows(result)) { return declaredList.get(); } const found = findFirstTargetByPath(resultRootOf(result), paths)?.value; const rows = normalizeRows(found); if (rows) { if ( declaredList && typeof declaredList.count === 'number' && declaredList.count > rows.length ) { return declaredList.get(); } return rows; } if (declaredList) return declaredList.get(); } return Object.values(result.extractedLists)[0]?.get() ?? []; } export function attachToolResultListDataset>( result: ToolExecuteResult, input: { name: string; path: string; dataset: PlayDataset; count: number; keys?: Record; }, ): ToolExecuteResult { const existing = result._metadata.lists[input.name]; result._metadata.lists[input.name] = { path: input.path, count: input.count, keys: input.keys ?? existing?.keys ?? {}, }; const declaredPaths = result._metadata.listExtractorPaths ?? []; const inputCandidates = new Set(candidateResultPaths(input.path)); const equivalentDeclaration = declaredPaths.find((path) => candidateResultPaths(path).some((candidate) => inputCandidates.has(candidate), ), ); result._metadata.listExtractorPaths = equivalentDeclaration ? [...declaredPaths] : [ ...declaredPaths.filter( (path) => listNameFromDeclaredPath(path) !== input.name, ), input.path, ]; const accessor = { path: input.path, count: input.count, keys: result._metadata.lists[input.name].keys, } as ToolResultListAccessor; Object.defineProperty(accessor, 'get', { value() { return input.dataset; }, enumerable: false, }); result.extractedLists[input.name] = accessor; const serialized = input.dataset.toJSON(); let root: unknown = { toolResponse: { raw: result.toolResponse.raw } }; const candidates = [...candidateResultPaths(input.path)].filter( (candidate, index, all) => all.indexOf(candidate) === index, ); for (const candidate of candidates) { if (!Array.isArray(getAtPath(root, candidate))) continue; root = replaceAtPath(root, candidate, serialized.preview); break; } if ( isRecord(root) && isRecord(root.toolResponse) && Object.prototype.hasOwnProperty.call(root.toolResponse, 'raw') ) { result.toolResponse.raw = root.toolResponse.raw; result.toolOutput.raw = root.toolResponse.raw; } return result; } function normalizeListPathForMatch(path: string): string { return path.replace(/\[(?:\*|\d+)\]$/g, ''); } function findDeclaredListAccessor( result: ToolExecuteResult, paths: readonly string[], ): ToolResultListAccessor | undefined { const candidates = new Set( paths.flatMap((path) => candidateResultPaths(path).flatMap((candidate) => [ candidate, normalizeListPathForMatch(candidate), ]), ), ); return Object.values(result.extractedLists).find((list) => candidates.has(normalizeListPathForMatch(list.path)), ); } function replaceAtPath( root: unknown, path: string, replacement: unknown, ): unknown { const segments = parsePath(path); if (segments.length === 0 || segments.includes('*')) return root; const replace = (current: unknown, index: number): unknown => { if (index >= segments.length) return replacement; const segment = segments[index]!; if (typeof segment === 'number') { if (!Array.isArray(current)) return current; const copy = [...current]; copy[segment] = replace(copy[segment], index + 1); return copy; } if (!isRecord(current)) return current; return { ...current, [segment]: replace(current[segment], index + 1), }; }; return replace(root, 0); } function serializedListDatasetsFromResult( value: ToolExecuteResult, ): Record>> | undefined { const entries = Object.entries(value.extractedLists).flatMap( ([name, accessor]) => { const dataset = accessor.get(); const serialized = dataset.toJSON(); if (!isSerializedPlayDataset>(serialized)) { return []; } rememberLiveToolResultListDataset(serialized.datasetId, dataset); return [ [ name, trimSerializedPlayDatasetPreview( serialized, SERIALIZED_TOOL_RESULT_LIST_PREVIEW_LIMIT, ), ], ] as const; }, ); return entries.length > 0 ? Object.fromEntries(entries) : undefined; } function rememberLiveToolResultListDataset( datasetId: string, dataset: PlayDataset>, ): void { if (!datasetId) return; liveToolResultListDatasets.delete(datasetId); liveToolResultListDatasets.set(datasetId, dataset); while ( liveToolResultListDatasets.size > LIVE_TOOL_RESULT_LIST_DATASET_REGISTRY_LIMIT ) { const oldest = liveToolResultListDatasets.keys().next().value; if (typeof oldest !== 'string') break; liveToolResultListDatasets.delete(oldest); } } function readLiveToolResultListDataset( serialized: SerializedPlayDataset>, ): PlayDataset> | null { const dataset = liveToolResultListDatasets.get(serialized.datasetId); if (!dataset) return null; const current = dataset.toJSON(); if (current.count !== serialized.count) return null; return dataset; } function serializedListRowsFromRaw(input: { raw: unknown; metadata: ToolResultMetadataInput; }): Record>> | undefined { const resolved = resolveListRows( { toolResponse: { raw: input.raw } }, input.metadata.listExtractorPaths, ); const entries = Object.entries(resolved).map(([name, list]) => [ name, list.rows, ]); return entries.length > 0 ? Object.fromEntries(entries) : undefined; } function preservedSerializedListRows( value: ToolExecuteResult, ): Record>> | undefined { const rows = ( value as ToolExecuteResult & { [SERIALIZED_TOOL_LIST_ROWS]?: | Record>> | undefined; } )[SERIALIZED_TOOL_LIST_ROWS]; return rows && Object.keys(rows).length > 0 ? rows : undefined; } function serializedListRowsFromResult(input: { value: ToolExecuteResult; metadata: ToolResultMetadataInput; listDatasets: | Record>> | undefined; }): Record>> | undefined { const preserved = preservedSerializedListRows(input.value); const rawRows = serializedListRowsFromRaw({ raw: input.value.toolResponse.raw, metadata: input.metadata, }); const names = new Set([ ...Object.keys(input.listDatasets ?? {}), ...Object.keys(preserved ?? {}), ...Object.keys(rawRows ?? {}), ]); const entries = [...names].flatMap((name) => { const dataset = input.listDatasets?.[name]; const liveDataset = input.value.extractedLists[name]?.get(); const residentRows = liveDataset ? residentPlayDatasetRows(liveDataset) : null; if (residentRows && (!dataset || residentRows.length >= dataset.count)) { return [ [name, [...residentRows] as Array>] as const, ]; } const preservedRows = preserved?.[name]; if (preservedRows && (!dataset || preservedRows.length >= dataset.count)) { return [[name, preservedRows]] as const; } const rows = rawRows?.[name]; if (rows && (!dataset || rows.length >= dataset.count)) { return [[name, rows]] as const; } return []; }); return entries.length > 0 ? Object.fromEntries(entries) : undefined; } function serializedRawWithListPreviews(input: { raw: unknown; metadata: ToolResultMetadataInput; listDatasets: | Record>> | undefined; }): unknown { if (!input.listDatasets) return input.raw; let root: unknown = { toolResponse: { raw: input.raw } }; const datasetsBySource = new Map( Object.values(input.listDatasets).flatMap((dataset) => dataset.sourceLabel ? [[dataset.sourceLabel, dataset]] : [], ), ); for (const rawPath of input.metadata.listExtractorPaths ?? []) { const candidates = [...candidateResultPaths(rawPath)].filter( (candidate, index, all) => all.indexOf(candidate) === index, ); const dataset = candidates .map((candidate) => datasetsBySource.get(candidate)) .find(Boolean) ?? Object.values(input.listDatasets)[0]; if (!dataset) continue; for (const candidate of candidates) { if (!Array.isArray(getAtPath(root, candidate))) continue; root = replaceAtPath(root, candidate, dataset.preview); break; } } return isRecord(root) && isRecord(root.toolResponse) && Object.prototype.hasOwnProperty.call(root.toolResponse, 'raw') ? root.toolResponse.raw : input.raw; } function rawWithSerializedListRows(input: { raw: unknown; lists: Record; listDatasets: | Record>> | undefined; listRows: Record>> | undefined; }): unknown { if (!input.listRows || Object.keys(input.listRows).length === 0) { return input.raw; } let root: unknown = { toolResponse: { raw: input.raw } }; for (const [name, rows] of Object.entries(input.listRows)) { const sourcePath = input.listDatasets?.[name]?.sourceLabel ?? input.lists[name]?.path ?? name; const candidates = [...candidateResultPaths(sourcePath)].filter( (candidate, index, all) => all.indexOf(candidate) === index, ); for (const candidate of candidates) { if (!Array.isArray(getAtPath(root, candidate))) continue; root = replaceAtPath(root, candidate, rows); break; } } return isRecord(root) && isRecord(root.toolResponse) && Object.prototype.hasOwnProperty.call(root.toolResponse, 'raw') ? root.toolResponse.raw : input.raw; } function createDatasetFromSerializedToolList( serialized: SerializedPlayDataset>, rows?: Array>, ): PlayDataset> { const preview = serialized.preview; const sourceRows = rows ?? preview; const isPartialPreview = rows === undefined && serialized.count > preview.length; const partialPreviewError = (): Error => new Error( `Serialized tool list ${serialized.datasetId} only carries ${preview.length} preview row(s) for ${serialized.count} total row(s). ` + 'Return the Dataset Handle from play code or export rows through runs export for full data.', ); const collectPreview = (limit?: number): Record[] => { if (limit === undefined && isPartialPreview) { throw partialPreviewError(); } const requested = limit === undefined ? sourceRows.length : Math.max(0, Math.floor(limit)); if (requested > preview.length && isPartialPreview) { throw partialPreviewError(); } return sourceRows.slice(0, requested); }; return createDeferredPlayDataset({ datasetKind: serialized.datasetKind, datasetId: serialized.datasetId, count: serialized.count, backing: serialized.backing, previewRows: preview, residentRows: isPartialPreview ? null : sourceRows, sourceLabel: serialized.sourceLabel ?? null, tableNamespace: serialized.tableNamespace ?? null, workProgress: serialized._metadata?.workProgress, resolvers: { count: async () => serialized.count, peek: async (limit) => collectPreview(limit), materialize: async (limit) => collectPreview(limit), iterate: () => ({ async *[Symbol.asyncIterator]() { if (isPartialPreview) { throw partialPreviewError(); } yield* sourceRows; }, }) as AsyncIterable>, }, }); } function applySerializedListDatasets( result: ToolExecuteResult, listDatasets: | Record>> | undefined, listRows: Record>> | undefined, ): ToolExecuteResult { if (!listDatasets) return result; if (listRows && Object.keys(listRows).length > 0) { result.toolResponse.raw = rawWithSerializedListRows({ raw: result.toolResponse.raw, lists: result._metadata.lists, listDatasets, listRows, }); if (Object.prototype.hasOwnProperty.call(result.toolResponse, 'rawV2')) { const view = result.toolResponse.view ?? 'rawV2'; if (view === 'data' && isRecord(result.toolResponse.rawV2)) { result.toolResponse.rawV2 = { ...result.toolResponse.rawV2, data: result.toolResponse.raw, }; } else { result.toolResponse.rawV2 = result.toolResponse.raw; } } Object.defineProperty(result, SERIALIZED_TOOL_LIST_ROWS, { value: listRows, enumerable: false, configurable: false, writable: false, }); } for (const [name, serialized] of Object.entries(listDatasets)) { if (!isSerializedPlayDataset>(serialized)) { continue; } const dataset = readLiveToolResultListDataset(serialized) ?? createDatasetFromSerializedToolList(serialized, listRows?.[name]); const existingMetadata = result._metadata.lists[name]; result._metadata.lists[name] = { path: serialized.sourceLabel ?? existingMetadata?.path ?? name, count: serialized.count, keys: existingMetadata?.keys ?? {}, }; const accessor = { path: result._metadata.lists[name].path, count: serialized.count, keys: result._metadata.lists[name].keys, } as ToolResultListAccessor; Object.defineProperty(accessor, 'get', { value() { return dataset; }, enumerable: false, }); result.extractedLists[name] = accessor; } return result; } function metadataInputFromToolExecuteResult( value: ToolExecuteResult, ): ToolResultMetadataInput { return { toolId: value._metadata.toolId, ...(value._metadata.extractors ? { extractors: value._metadata.extractors } : {}), targetGetters: Object.fromEntries( Object.entries(value._metadata.targets).map(([target, info]) => [ target, [info.path], ]), ), listExtractorPaths: value._metadata.listExtractorPaths ?? Object.values(value._metadata.lists).map((list) => list.path), listIdentityGetters: Object.fromEntries( Object.values(value._metadata.lists) .flatMap((list) => Object.entries(list.keys)) .map(([target, path]) => [target, [path]]), ), }; } function sameJsonValue(left: unknown, right: unknown): boolean { if (left === right) return true; try { return JSON.stringify(left) === JSON.stringify(right); } catch { return false; } } function responseMetaOutsideRawV2(input: { rawV2: unknown; view: 'data' | 'rawV2'; meta: Record | undefined; }): Record | undefined { if (!input.meta) return undefined; const providerMeta = input.view === 'data' && isRecord(input.rawV2) && isRecord(input.rawV2.meta) ? input.rawV2.meta : undefined; if (!providerMeta) return input.meta; const responseMeta = Object.fromEntries( Object.entries(input.meta).filter( ([key, value]) => !Object.prototype.hasOwnProperty.call(providerMeta, key) || !sameJsonValue(providerMeta[key], value), ), ); return Object.keys(responseMeta).length > 0 ? responseMeta : undefined; } function rawV2WithSerializedRawPreview(input: { rawV2: unknown; view: 'data' | 'rawV2'; raw: unknown; }): unknown { if (input.view === 'data' && isRecord(input.rawV2)) { return { ...input.rawV2, data: input.raw }; } return input.raw; } export function serializeToolExecuteResult( value: ToolExecuteResult, ): SerializedToolExecuteResult { const metadata = metadataInputFromToolExecuteResult(value); const listDatasets = serializedListDatasetsFromResult(value); const listRows = serializedListRowsFromResult({ value, listDatasets, metadata, }); const targetValues = serializedTargetValuesFromResult(value); const serializedRaw = serializedRawWithListPreviews({ raw: value.toolResponse.raw, metadata, listDatasets, }); if (Object.prototype.hasOwnProperty.call(value.toolResponse, 'rawV2')) { const view = value.toolResponse.view ?? 'rawV2'; const rawV2 = rawV2WithSerializedRawPreview({ rawV2: value.toolResponse.rawV2, view, raw: view === 'data' ? serializedRaw : serializedRawWithListPreviews({ raw: value.toolResponse.rawV2, metadata, listDatasets, }), }); const responseMeta = responseMetaOutsideRawV2({ rawV2: value.toolResponse.rawV2, view, meta: value.toolResponse.meta, }); return { __kind: SERIALIZED_TOOL_EXECUTE_RESULT_V2_KIND, status: value.status, ...(typeof value.job_id === 'string' ? { job_id: value.job_id } : {}), ...(isRecord(value.meta) ? { meta: value.meta } : {}), toolResponse: { rawV2, view, ...(responseMeta ? { responseMeta } : {}), }, ...(listDatasets ? { listDatasets } : {}), ...(listRows ? { listRows } : {}), ...(targetValues ? { targetValues } : {}), metadata, execution: value._metadata.execution, }; } return { __kind: SERIALIZED_TOOL_EXECUTE_RESULT_V1_KIND, status: value.status, ...(typeof value.job_id === 'string' ? { job_id: value.job_id } : {}), ...(isRecord(value.meta) ? { meta: value.meta } : {}), toolResponse: { raw: serializedRaw, ...(value.toolResponse.meta ? { meta: value.toolResponse.meta } : {}), }, ...(listDatasets ? { listDatasets } : {}), ...(listRows ? { listRows } : {}), ...(targetValues ? { targetValues } : {}), metadata, execution: value._metadata.execution, }; } export function isSerializedToolExecuteResult( value: unknown, ): value is SerializedToolExecuteResult { return ( isRecord(value) && (value.__kind === SERIALIZED_TOOL_EXECUTE_RESULT_V1_KIND || value.__kind === SERIALIZED_TOOL_EXECUTE_RESULT_V2_KIND) && typeof value.status === 'string' && isRecord(value.toolResponse) && isRecord(value.metadata) && isRecord(value.execution) ); } export function deserializeToolExecuteResult( value: SerializedToolExecuteResult, ): ToolExecuteResult { if (value.__kind === SERIALIZED_TOOL_EXECUTE_RESULT_V2_KIND) { const view = value.toolResponse.view; const rawV2 = value.toolResponse.rawV2; const providerMeta = view === 'data' && isRecord(rawV2) && isRecord(rawV2.meta) ? rawV2.meta : undefined; const meta = { ...(providerMeta ?? {}), ...(value.toolResponse.responseMeta ?? {}), }; const raw = legacyRawFromToolResponseRawV2( rawV2, view, value.toolResponse.responseMeta, ); return applySerializedListDatasets( applySerializedTargetValues( createToolExecuteResult({ status: value.status, jobId: value.job_id, result: { data: raw, ...(Object.keys(meta).length > 0 ? { meta } : {}), }, response: { rawV2, view, ...(Object.keys(meta).length > 0 ? { meta } : {}), }, metadata: value.metadata, execution: value.execution, meta: value.meta, }), value.targetValues, ), value.listDatasets, value.listRows, ); } return applySerializedListDatasets( applySerializedTargetValues( createToolExecuteResult({ status: value.status, jobId: value.job_id, result: { data: value.toolResponse.raw, ...(value.toolResponse.meta ? { meta: value.toolResponse.meta } : {}), }, metadata: value.metadata, execution: value.execution, meta: value.meta, }), value.targetValues, ), value.listDatasets, value.listRows, ); } export function cloneToolExecuteResultWithExecution( value: ToolExecuteResult, execution: ToolResultExecutionMetadata, ): ToolExecuteResult { return deserializeToolExecuteResult({ ...serializeToolExecuteResult(value), execution, }) as ToolExecuteResult; }