Skip to content

fix: expose conversionId in convert --async and add --timeout flag #22

@rarce

Description

@rarce

Description

When docutray convert --async starts polling, it never prints the conversion_id. If polling times out, the user has no way to recover the result later. Additionally, the polling timeout is hardcoded at 300s which is insufficient for large documents (e.g., 70 pages in multi_prompt mode).

Bundles TODO items #8 (expose conversionId) and #10 (configurable timeout).

Motivation

When a conversion times out after 5 minutes of polling, the user loses all ability to check the result because the conversion_id was never shown. The only recourse is to resubmit the document. For large documents that legitimately take longer than 300s, users need a way to extend the timeout.

Acceptance Criteria

  • conversion_id is printed to stderr immediately after runAsync returns (before polling starts)
  • Each polling status update includes the conversion_id
  • TTY format: ⟳ Conversion cmnp36ska005d01tm5gxakfdl: PROCESSING
  • JSON format: {"conversionId":"cmnp36ska005d01tm5gxakfdl","status":"PROCESSING"}
  • On timeout error, the error message includes the conversion_id
  • New --timeout <seconds> flag controls the polling timeout (default: 300)
  • The --timeout value is passed to the SDK's .wait() method

Technical Approach

Expose conversionId (src/commands/convert.ts)

The SDK's runAsync returns a ConversionStatus with conversion_id. Currently at line 52:

const status = await client.convert.runAsync(params)
// status.conversion_id is available but never used

Changes:

  1. Print conversion_id to stderr right after runAsync (line 52)
  2. Include conversion_id in the onStatus callback output (lines 54-59)
  3. Wrap the .wait() call in a try/catch to enrich timeout errors with the conversion_id

Add --timeout flag

Add a --timeout flag (in seconds) and pass it to the SDK's .wait() options:

const result = await status.wait({
  timeout: flags.timeout * 1000, // convert to ms
  onStatus(s) { /* ... */ },
})

Affected Components

  • src/commands/convert.ts (lines 51-62): Add --timeout flag, print conversion_id, enrich status and error output
  • test/commands/convert.test.ts: Add tests for conversion_id output and --timeout flag

Implementation Checklist

  • Add --timeout flag definition (default: 300, description mentions seconds)
  • Print conversion_id to stderr immediately after runAsync
  • Include conversion_id in each onStatus output (both TTY and JSON)
  • Catch timeout errors and enrich with conversion_id + recovery hint
  • Pass timeout (in ms) to SDK .wait() method
  • Add test: conversion_id appears in stderr output
  • Add test: --timeout flag is passed to .wait()
  • Add test: timeout error includes conversion_id

Testing Strategy

// Mock runAsync to return a status with conversion_id
const mockWait = vi.fn().mockResolvedValue({ status: 'SUCCESS', data: {} })
const mockRunAsync = vi.fn().mockResolvedValue({
  conversion_id: 'conv_abc123',
  status: 'ENQUEUED',
  wait: mockWait,
})

await Convert.run(['file.pdf', '-t', 'invoice', '--async'])

// Verify conversion_id printed to stderr
expect(stderrSpy).toHaveBeenCalledWith(
  expect.stringContaining('conv_abc123')
)

// Verify timeout passed to wait
await Convert.run(['file.pdf', '-t', 'invoice', '--async', '--timeout', '600'])
expect(mockWait).toHaveBeenCalledWith(
  expect.objectContaining({ timeout: 600000 })
)

Dependencies

None — uses existing SDK runAsync and .wait() capabilities.

Considerations

  • The --timeout flag uses seconds (not ms) for user-friendliness, converted to ms before passing to SDK
  • The conversion_id is printed to stderr (not stdout) to avoid polluting the JSON result on stdout
  • A future docutray convert status <conversionId> subcommand (out of scope) could complement this by allowing manual status checks

Definition of Done

  • Implementation complete
  • Tests added/updated
  • All acceptance criteria met
  • npm run build passes
  • npm run test passes

🤖 Generated with Claude Code

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't workingdxDeveloper experience improvementspriority:highHigh priority

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions