Skip to content

feat(textract): migrate to AWS SDK, add AnalyzeExpense and AnalyzeID#5456

Open
waleedlatif1 wants to merge 6 commits into
stagingfrom
textract-integration-audit
Open

feat(textract): migrate to AWS SDK, add AnalyzeExpense and AnalyzeID#5456
waleedlatif1 wants to merge 6 commits into
stagingfrom
textract-integration-audit

Conversation

@waleedlatif1

Copy link
Copy Markdown
Collaborator

Summary

  • Replace the hand-rolled AWS SigV4 signer in the Textract parse route with the official @aws-sdk/client-textract package, matching how sibling AWS integrations (Secrets Manager, S3, STS) already call AWS
  • Add an Analyze Expense operation (structured invoice/receipt extraction: vendor, totals, line items) via AnalyzeExpense (sync) / StartExpenseAnalysis+GetExpenseAnalysis (async, S3)
  • Add an Analyze Identity Document operation (AnalyzeID) for driver's licenses/passports, with an optional back-of-ID page
  • These fill a real gap: the block's own templates/skills already advertised invoice extraction and ID verification, but no tool backed them
  • Add an operation dropdown to the textract_v2 block; defaults to the existing Analyze Document behavior so already-saved workflows are unaffected
  • Removed 3 dead/unreferenced interfaces from tools/textract/types.ts

Type of Change

  • New feature

Testing

  • Added unit tests for tool body/response construction (all 3 tools) and for the AWS-response-to-output mapping functions in each route (29 tests total)
  • bun run type-check, bun run lint, and bun run check:api-validation all pass

Checklist

  • Code follows project style guidelines
  • Self-reviewed my changes
  • Tests added/updated and passing
  • No new warnings introduced
  • I confirm that I have read and agree to the terms outlined in the Contributor License Agreement (CLA)

- Replace hand-rolled AWS SigV4 signing with @aws-sdk/client-textract, matching sibling AWS integrations (secrets_manager, s3, sts)
- Add Analyze Expense operation (invoice/receipt structured extraction) via AnalyzeExpense/StartExpenseAnalysis+GetExpenseAnalysis
- Add Analyze Identity Document operation (AnalyzeID) with optional back-of-ID page
- Add an operation selector to the textract_v2 block; defaults to the existing Analyze Document behavior for backward compatibility
- Add tests for tool body/response mapping and route-level AWS response normalization
@waleedlatif1 waleedlatif1 requested a review from a team as a code owner July 7, 2026 01:04
@vercel

vercel Bot commented Jul 7, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

1 Skipped Deployment
Project Deployment Actions Updated (UTC)
docs Skipped Skipped Jul 7, 2026 2:07am

Request Review

@cursor

cursor Bot commented Jul 7, 2026

Copy link
Copy Markdown

PR Summary

Medium Risk
Touches document ingestion (SSRF controls, file access) and accepts user-supplied AWS keys; Analyze ID adds PII handling, and the parse-route migration is a large behavioral swap though covered by tests.

Overview
Migrates Textract from hand-rolled SigV4 HTTP calls to @aws-sdk/client-textract, with shared helpers for document resolution (uploads, pinned URL fetch, S3 URI validation), async job polling, and SDK error mapping—including the multi-page PDF hint where async mode exists.

Adds two API surfaces and tools: Analyze Expense (sync AnalyzeExpense / async StartExpenseAnalysis + paginated GetExpenseAnalysis) and Analyze ID (sync AnalyzeID, optional back page), each returning camelCase-normalized payloads.

Extends textract_v2 with an Operation dropdown (defaults to existing analyze-document), routes to textract_parser_v2, textract_analyze_expense, or textract_analyze_id, operation-specific outputs, and back-of-ID inputs for ID verification. Zod contracts and the tool registry are updated; API route count baseline moves to 906.

Reviewed by Cursor Bugbot for commit 55cb4eb. Configure here.

Comment thread apps/sim/blocks/blocks/textract.ts
Comment thread apps/sim/app/api/tools/textract/analyze-expense/route.ts
@greptile-apps

greptile-apps Bot commented Jul 7, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR migrates the Textract parse route from a hand-rolled AWS SigV4 signer to the official @aws-sdk/client-textract package, and adds two new operations — Analyze Expense (invoices/receipts) and Analyze Identity Document (driver's licenses/passports) — behind a new operation dropdown on the textract_v2 block. Shared utilities (document resolution, S3 URI parsing, async job polling, error mapping) are extracted into a shared.ts module consumed by all three routes.

  • AWS SDK migration for the parse route, with shared polling, document-download, and error-mapping helpers extracted to shared.ts.
  • New analyze-expense and analyze-id routes (sync and async for expense, sync-only for ID) with matching tool definitions, block subBlocks, Zod contracts, and normalization functions.
  • A documentFieldCondition helper is introduced so that front-document upload fields stay visible for analyze_id even when a stale processingMode: 'async' value persists from a previous operation, addressing the pre-existing state isolation gap.

Confidence Score: 5/5

Safe to merge — all three operation paths are well-tested, the shared polling/error-mapping utilities are extracted cleanly, and the block UI correctly handles the stale-processingMode case for analyze_id via documentFieldCondition.

The migration from hand-rolled SigV4 to the official AWS SDK is straightforward and the new routes follow the same patterns as sibling integrations. The stale processingMode issue from the previous review is addressed. No data-correctness or auth issues were found.

No files require special attention.

Important Files Changed

Filename Overview
apps/sim/app/api/tools/textract/shared.ts New shared module consolidating document resolution, S3 URI validation, async job polling, and SDK error mapping — logic extracted cleanly from the old parse route; no issues found.
apps/sim/app/api/tools/textract/analyze-expense/route.ts New AnalyzeExpense route handling both sync (file bytes) and async (S3 polling) paths; normalization functions are correct and well-tested.
apps/sim/app/api/tools/textract/analyze-id/route.ts New AnalyzeID route (sync-only); missing maxDuration export present in sibling routes, though it's intentional since the route has no async polling loop.
apps/sim/blocks/blocks/textract.ts Adds operation dropdown, documentFieldCondition helper to fix stale-processingMode visibility for analyze_id, and correct s3Uri visibility guard; all paths handled correctly.
apps/sim/lib/api/contracts/tools/media/document-parse.ts New Zod schemas and route contracts for analyze-expense and analyze-id; superRefine cross-field validations are correct.
apps/sim/tools/textract/types.ts Removed three unused raw-response interfaces and added typed input/output interfaces for the two new operations; no issues.

Sequence Diagram

%%{init: {'theme': 'neutral'}}%%
sequenceDiagram
    participant UI as Block UI
    participant Tool as Tool Body
    participant Route as API Route
    participant Shared as shared.ts
    participant AWS as AWS Textract

    UI->>Tool: params (operation, processingMode, file/s3Uri)
    Tool->>Route: "POST /api/tools/textract/{operation}"

    alt analyze_document / analyze_expense — sync
        Route->>Shared: "resolveDocumentInput(file | filePath)"
        Shared-->>Route: "{ bytes, isPdf }"
        Route->>AWS: "Analyze*Command({ Document: { Bytes } })"
        AWS-->>Route: response
        Route-->>UI: "{ success, output }"
    end

    alt analyze_document / analyze_expense — async (S3)
        Route->>Shared: parseS3Uri(s3Uri)
        Shared-->>Route: "{ bucket, key }"
        Route->>AWS: "Start*AnalysisCommand({ S3Object })"
        AWS-->>Route: "{ JobId }"
        Route->>Shared: pollTextractJob(getPage, mergePage)
        loop Poll every 5s until SUCCEEDED / FAILED / timeout
            Shared->>AWS: "Get*Command({ JobId, NextToken? })"
            AWS-->>Shared: "{ JobStatus, data, NextToken? }"
        end
        Shared-->>Route: merged result
        Route-->>UI: "{ success, output }"
    end

    alt analyze_id — sync only
        Route->>Shared: resolveDocumentInput(front)
        Shared-->>Route: front bytes
        opt back of ID provided
            Route->>Shared: resolveDocumentInput(back)
            Shared-->>Route: back bytes
        end
        Route->>AWS: "AnalyzeIDCommand({ DocumentPages })"
        AWS-->>Route: "{ IdentityDocuments }"
        Route-->>UI: "{ success, output }"
    end
Loading
%%{init: {'theme': 'base', 'themeVariables': {"darkMode": true, "background": "#0d1117", "primaryColor": "#21262d", "primaryTextColor": "#e6edf3", "primaryBorderColor": "#8b949e", "lineColor": "#8b949e", "textColor": "#e6edf3", "edgeLabelBackground": "#161b22", "actorBkg": "#21262d", "actorBorder": "#8b949e", "actorTextColor": "#e6edf3", "actorLineColor": "#8b949e", "signalColor": "#8b949e", "signalTextColor": "#e6edf3", "noteBkgColor": "#373320", "noteBorderColor": "#d4a72c", "noteTextColor": "#f0e6c0", "labelBoxBkgColor": "#21262d", "labelBoxBorderColor": "#8b949e", "labelTextColor": "#e6edf3", "loopTextColor": "#e6edf3", "activationBkgColor": "#30363d", "activationBorderColor": "#8b949e"}}}%%
sequenceDiagram
    participant UI as Block UI
    participant Tool as Tool Body
    participant Route as API Route
    participant Shared as shared.ts
    participant AWS as AWS Textract

    UI->>Tool: params (operation, processingMode, file/s3Uri)
    Tool->>Route: "POST /api/tools/textract/{operation}"

    alt analyze_document / analyze_expense — sync
        Route->>Shared: "resolveDocumentInput(file | filePath)"
        Shared-->>Route: "{ bytes, isPdf }"
        Route->>AWS: "Analyze*Command({ Document: { Bytes } })"
        AWS-->>Route: response
        Route-->>UI: "{ success, output }"
    end

    alt analyze_document / analyze_expense — async (S3)
        Route->>Shared: parseS3Uri(s3Uri)
        Shared-->>Route: "{ bucket, key }"
        Route->>AWS: "Start*AnalysisCommand({ S3Object })"
        AWS-->>Route: "{ JobId }"
        Route->>Shared: pollTextractJob(getPage, mergePage)
        loop Poll every 5s until SUCCEEDED / FAILED / timeout
            Shared->>AWS: "Get*Command({ JobId, NextToken? })"
            AWS-->>Shared: "{ JobStatus, data, NextToken? }"
        end
        Shared-->>Route: merged result
        Route-->>UI: "{ success, output }"
    end

    alt analyze_id — sync only
        Route->>Shared: resolveDocumentInput(front)
        Shared-->>Route: front bytes
        opt back of ID provided
            Route->>Shared: resolveDocumentInput(back)
            Shared-->>Route: back bytes
        end
        Route->>AWS: "AnalyzeIDCommand({ DocumentPages })"
        AWS-->>Route: "{ IdentityDocuments }"
        Route-->>UI: "{ success, output }"
    end
Loading

Reviews (5): Last reviewed commit: "chore(textract): drop redundant inline c..." | Re-trigger Greptile

Comment thread apps/sim/app/api/tools/textract/shared.ts Outdated
Comment thread apps/sim/app/api/tools/textract/analyze-expense/route.ts
- textract_analyze_expense/textract_analyze_id now fall back to filePath/filePathBack when the document input is a URL string rather than an uploaded file object, so advanced "File reference" URL inputs actually reach the API (Cursor Bugbot)
- mapTextractSdkError defaults to 500 (not 400) when the AWS SDK error has no HTTP status, since that implies a server-side/network failure rather than a bad request (Greptile)
@waleedlatif1

Copy link
Copy Markdown
Collaborator Author

@greptile

@waleedlatif1

Copy link
Copy Markdown
Collaborator Author

@cursor review

Comment thread apps/sim/blocks/blocks/textract.ts
- Front-document fields (fileUpload/fileReference) are shared across all 3 operations; gate them with a values-aware condition so switching to Analyze Identity Document keeps them visible even if a stale processingMode='async' is left over from a previous operation
- S3 URI field now also requires operation !== 'analyze_id', since that operation never supports S3 input
@waleedlatif1

Copy link
Copy Markdown
Collaborator Author

@greptile

@waleedlatif1

Copy link
Copy Markdown
Collaborator Author

@cursor review

Comment thread apps/sim/app/api/tools/textract/parse/route.ts
pollTextractJob's merge callbacks spread only the latest page, dropping
any field (DocumentMetadata, model version) the first page had but a
follow-up NextToken page omits. Merge accumulated first so later pages
only override fields they actually return.
@waleedlatif1

Copy link
Copy Markdown
Collaborator Author

@greptile

@waleedlatif1

Copy link
Copy Markdown
Collaborator Author

@cursor review

Comment thread apps/sim/app/api/tools/textract/shared.ts Outdated
…ch failures

fetchDocumentBytes hardcoded 400 for any non-OK response from a document
URL, masking transient 5xx failures from the document host as client
errors and blocking tool-execution retries. Use the actual response
status instead.
@waleedlatif1

Copy link
Copy Markdown
Collaborator Author

@greptile

@waleedlatif1

Copy link
Copy Markdown
Collaborator Author

@cursor review

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ Bugbot reviewed your changes and found no new issues!

Comment @cursor review or bugbot run to trigger another review on this PR

Reviewed by Cursor Bugbot for commit dd92247. Configure here.

@waleedlatif1

Copy link
Copy Markdown
Collaborator Author

@greptile

@waleedlatif1

Copy link
Copy Markdown
Collaborator Author

@cursor review

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant