Skip to content

feat(adapter-duffel): add Cars API (search → quote → book) v0.7.2#99

Merged
telivity-otaip merged 1 commit into
mainfrom
feat/duffel-cars
May 6, 2026
Merged

feat(adapter-duffel): add Cars API (search → quote → book) v0.7.2#99
telivity-otaip merged 1 commit into
mainfrom
feat/duffel-cars

Conversation

@telivity-otaip
Copy link
Copy Markdown
Collaborator

Summary

Duffel launched Cars as a full API vertical alongside Flights and Stays. This PR extends @otaip/adapter-duffel with the rental-car surface — same Bearer auth, same Duffel-Version: v2, just under the /cars/ namespace. Patterned identically to the Hotelbeds Activities/Transfers extension (#90): one adapter class, new product surface.

What's new on DuffelAdapter

Method Endpoint
searchCars POST /cars/search
quoteCar POST /cars/quotes
bookCar POST /cars/bookings
getCarBooking GET /cars/bookings/{id}
cancelCarBooking POST /cars/bookings/{id}/actions/cancel

Three-step flow (search → quote → book), unlike the two-step flight flow. Search is geo-coordinate based; the brief explicitly defers IATA-to-coordinate conversion (DQ-C8). The adapter accepts only { latitude, longitude, radius? } per location.

The shared request() helper now accepts an optional AbortSignal (pre-flight check; in-flight cancel needs an upstream change to fetchWithRetry, same caveat the Hotelbeds adapter carries).

Files

  • New: cars-types.ts (canonical + wire types — CarRate, CarQuote, CarBookResponse, etc., plus a Money type), cars-mapper.ts (decimal.js-backed wire→canonical mapping), __tests__/cars.test.ts (18 cases), docs/knowledge-base/cars.md.
  • Edit: duffel-adapter.ts (5 methods + request() signal threading), mock-duffel-adapter.ts (full three-step mock flow + 2 car fixtures), index.ts (re-exports), package.json (0.7.1 → 0.7.2).

Domain knowledge

docs/knowledge-base/cars.md captures the vendor brief verbatim and enumerates 8 open DOMAIN_QUESTION markers (DQ-C1..C8) for the gaps the brief leaves open:

  • DQ-C1 — radius upper bound
  • DQ-C2 — pickup_time / dropoff_time timezone
  • DQ-C3 — car.category / car.type enum closure
  • DQ-C4 — privacy_policies consent flow
  • DQ-C5 — Card creation (explicitly out of scope)
  • DQ-C6 — inbound_flight_number format (IATA vs ICAO)
  • DQ-C7 — Refund computation on cancellation
  • DQ-C8 — IATA-to-coordinate conversion (deferred per brief)

Per the constitution's no-invent rule, these are surfaced rather than guessed. The adapter falls back to the safe choice where it must (e.g. unknown payment_type'postpaid' so we never mis-charge) and documents the assumption inline.

Verification

  • pnpm exec vitest run packages/adapters/duffel58 passed / 3 skipped (sandbox-gated).
  • pnpm exec eslint packages/adapters/duffel/src — clean.
  • pnpm --filter @otaip/adapter-duffel typecheck — clean.

Versioning

@otaip/adapter-duffel 0.7.1 → 0.7.2 (single-package patch bump per VERSIONING.md). The workspace-wide v0.7.2 release follows separately, same shape as #92 / #96. The brief asked for a "next minor" bump (0.8.0); flagging that we're sticking with the patch-bump policy unless you want to ratify another exception like the v0.7.0 hotelbeds case — let me know.

Out of scope (intentional)

  • Card creation (POST /payments/cards) — separate Duffel API.
  • IATA → coordinate conversion — DQ-C8.
  • Refund amount computation — DQ-C7.
  • Sandbox integration test — could land in a follow-up; the unit + mock surface validates the full happy path.

🤖 Generated with Claude Code

Duffel Cars is the rental-car vertical alongside Flights and Stays.
Same Bearer auth and Duffel-Version: v2 header as the existing flight
methods, just under the /cars/ namespace.

Five new methods on DuffelAdapter:
- searchCars      POST /cars/search
- quoteCar        POST /cars/quotes
- bookCar         POST /cars/bookings
- getCarBooking   GET  /cars/bookings/{id}
- cancelCarBooking POST /cars/bookings/{id}/actions/cancel

Three-step flow (search → quote → book), unlike the two-step flight
flow. Search is geo-coordinate based; the brief explicitly defers
IATA-to-coordinate conversion to a future iteration (DQ-C8). Adapter
accepts only `{ latitude, longitude, radius? }` per location.

New canonical types in cars-types.ts: CarRate, CarQuote,
CarBookResponse, CarSearchRequest/Result, CarLocation, CarSupplier,
CarDetails (with ACRISS code passed through verbatim), and the open
Money type. Wire types in the same file mirror the Duffel JSON shape
best-effort. Mapper in cars-mapper.ts uses decimal.js for all
amounts, narrows enums where the brief documents them, and falls
back to safe defaults for unknown values (unknown payment_type →
'postpaid' so we never mis-charge).

Shared private request() helper now accepts an optional AbortSignal
in its options bag — pre-flight check throws before dispatch. In-
flight cancel needs an upstream change to fetchWithRetry, same
caveat that exists on the Hotelbeds adapter.

MockDuffelAdapter gains a full mock three-step flow with two car
fixtures (Toyota Corolla compact, VW Tiguan SUV). Synthetic IDs
match the live prefixes (seh_, rae_, qut_, boo_). Mock state is
in-memory and shared across calls so search→quote→book chains
work without the network.

Domain knowledge captured in docs/knowledge-base/cars.md with 8
numbered DOMAIN_QUESTIONs (DQ-C1..C8) for items the brief leaves
open: radius upper bound, time-zone semantics, enum closure,
privacy-policy consent flow, card creation, inbound flight number
format, refund computation, and IATA-to-coordinate conversion. Per
the constitution, these are surfaced rather than guessed.

Tests: 18 new cases across cars.test.ts. Validates body shapes,
Bearer + Duffel-Version headers, /cars/ path routing, mapper
output, abort handling, 429 retry-then-error, payment_type fallback
for unknown values, and a full mock flow round-trip
(search → quote → book → get → cancel).

Bump @otaip/adapter-duffel 0.7.1 → 0.7.2 (single-package patch
bump per VERSIONING.md). Workspace-wide v0.7.2 release follows
separately.

Verification:
- pnpm exec vitest run packages/adapters/duffel — 58 passed / 3 skipped (sandbox-gated).
- pnpm exec eslint packages/adapters/duffel/src — clean.
- TypeScript strict — clean.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@telivity-otaip telivity-otaip merged commit 61812b3 into main May 6, 2026
1 check passed
@telivity-otaip telivity-otaip deleted the feat/duffel-cars branch May 6, 2026 22:09
ntbpy pushed a commit to ntbpy/AI_Agent_otaip that referenced this pull request May 11, 2026
 fix

Patch bump off v0.7.1. Three pieces:

- TelivityAI#99 @otaip/adapter-duffel adds Cars (search → quote → book → get
  → cancel) under /cars/. Same Bearer auth, AbortSignal threading
  on request(). 8 open DOMAIN_QUESTIONs captured in
  docs/knowledge-base/cars.md.
- TelivityAI#97 unbreaks the Release workflow's Count agents step that TelivityAI#93's
  CLI re-export indirection broke. Every push-to-main since TelivityAI#93
  had been failing silently — that's why v0.7.1's tag was created
  but the workspace npm bumps lagged behind for a while.
- TelivityAI#98 switches gh release create to RELEASE_PAT so the
  release: published event propagates to the Publish workflow.
  This is the first release that should auto-publish to npm
  without a manual workflow_dispatch.

Workspace-wide bump: 17 package.json files 0.7.1 → 0.7.2
(@otaip/adapter-duffel was already at 0.7.2 from TelivityAI#99). Going
forward, the next release is v0.7.3.

See CHANGELOG.md for details.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
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