v0.6.95: data enrichment block, nullable workflow description fix#4786
Conversation
* feat(enrichment): workflow Enrichment block + /api/tools/enrichment/run Add a generic Enrichment workflow block that runs a code-defined enrichment (Work Email, Phone Number, Company Domain, Company Info, …) and returns its outputs — usable in workflows, not just tables. - New internal endpoint POST /api/tools/enrichment/run (checkInternalAuth + contract) runs the same runEnrichment provider cascade; injects the workspace's hosted/BYOK key via executeTool. - New tool enrichment_run posts to it and surfaces hosted-key cost on the output so the workflow logging session bills it. - New block blocks/blocks/enrichment.ts generated from the enrichment registry: operation dropdown = enrichments, per-enrichment conditional inputs, union of conditional outputs. New registry entries appear automatically. - EnrichmentRunContext.tableId/rowId made optional (workflow path has no row). - Register tool + block; bump api-validation route baseline; add EnrichmentIcon and generated docs page. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * chore: re-trigger CI * refactor(enrichment): share mapFieldType helper between block and tool * fix(enrichment): reserved output keys (matched/provider) win over enrichment outputs --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
The latest updates on your projects. Learn more about Vercel for GitHub. |
PR SummaryMedium Risk Overview Also allows Reviewed by Cursor Bugbot for commit 066cd70. Bugbot is set up for automated code reviews on this repo. Configure here. |
Greptile SummaryThis PR introduces a new Data Enrichment workflow block that lets users run registry enrichments (Work Email, Phone Number, Company Domain, Company Info) directly in a workflow, using the same provider cascade and BYOK/hosted-key injection that table enrichments use. It also makes workflow
Confidence Score: 4/5Safe to merge; the enrichment block wires a new internal-only route through the existing provider cascade with proper auth, and the nullable-description fix is a one-line schema correction. The core cascade logic, auth, Zod contracts, and block generation are all solid. Two minor issues exist: apps/sim/tools/enrichment/run.ts — Important Files Changed
Sequence DiagramsequenceDiagram
participant UI as Workflow UI
participant Executor as Workflow Executor
participant Route as /api/tools/enrichment/run
participant Runner as runEnrichment()
participant Provider as External Provider Tool
UI->>Executor: Run workflow (enrichment block)
Executor->>Route: "POST {enrichmentId, inputs, workspaceId}"
Route->>Route: checkInternalAuth()
Route->>Route: Zod validate body
Route->>Route: getEnrichment(enrichmentId)
Route->>Runner: "runEnrichment(enrichment, inputs, {workspaceId})"
loop Provider cascade
Runner->>Runner: buildParams(inputs) — null? skip
Runner->>Provider: executeTool(toolId, params)
Provider-->>Runner: response
Runner->>Runner: mapOutput(response) + hasResult?
alt hit
Runner-->>Route: "{result, cost, provider}"
else miss / 404
Runner->>Runner: try next provider
end
end
Runner-->>Route: "{result:{}, cost, error, provider:null}"
Route-->>Executor: "{matched, result, cost, error, provider}"
Executor->>Executor: transformResponse
Executor-->>UI: Block output
Reviews (1): Last reviewed commit: "fix(schema) Make workflow description nu..." | Re-trigger Greptile |
| body: (params: EnrichmentRunParams & { _context?: { workspaceId?: string } }) => ({ | ||
| enrichmentId: params.enrichmentId, | ||
| inputs: params.inputs ?? {}, | ||
| workspaceId: params._context?.workspaceId, | ||
| }), |
There was a problem hiding this comment.
Missing
workspaceId causes a confusing validation error. When _context?.workspaceId is undefined, JSON.stringify silently drops the key, and the server-side Zod contract rejects the request with "workspaceId is required" — which surfaces to the user as a generic enrichment failure rather than a meaningful block error. A fallback empty string would still be rejected by the z.string().min(1) guard, but the request would at least include the field so the error message is clearer.
| body: (params: EnrichmentRunParams & { _context?: { workspaceId?: string } }) => ({ | |
| enrichmentId: params.enrichmentId, | |
| inputs: params.inputs ?? {}, | |
| workspaceId: params._context?.workspaceId, | |
| }), | |
| body: (params: EnrichmentRunParams & { _context?: { workspaceId?: string } }) => ({ | |
| enrichmentId: params.enrichmentId, | |
| inputs: params.inputs ?? {}, | |
| workspaceId: params._context?.workspaceId ?? '', | |
| }), |
There was a problem hiding this comment.
workspace id should always be passed so this should never occur.
| logger.info('Enrichment block run', { | ||
| enrichmentId, | ||
| matched: Object.keys(result).length > 0, | ||
| provider, | ||
| }) | ||
| return NextResponse.json({ | ||
| matched: Object.keys(result).length > 0, |
There was a problem hiding this comment.
The
matched flag is computed from Object.keys(result).length > 0, but the cascade's hasResult guard (in run.ts) requires at least one non-null, non-empty value before accepting a provider result. The two checks are currently equivalent in practice (the cascade guarantees result is either {} or has at least one non-empty value), but aligning the route to the same guard makes the intent explicit and prevents drift if the cascade logic changes.
| logger.info('Enrichment block run', { | |
| enrichmentId, | |
| matched: Object.keys(result).length > 0, | |
| provider, | |
| }) | |
| return NextResponse.json({ | |
| matched: Object.keys(result).length > 0, | |
| const matched = Object.values(result).some((v) => v !== undefined && v !== null && v !== '') | |
| logger.info('Enrichment block run', { | |
| enrichmentId, | |
| matched, | |
| provider, | |
| }) | |
| return NextResponse.json({ | |
| matched, |
Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!
🤖 Generated with Claude Code