import { ToolSet } from 'ai'; import { ArAgentsError } from '@ar-agents/core'; /** * Type definitions for AR shipping operations. * * Carriers (Andreani, OCA, Correo Argentino) have different APIs and * different data shapes; this module normalizes them into a single set of * input/output types so the agent doesn't have to think about which carrier * it's talking to. * * # The 5 core operations (every carrier) * * 1. **cotizar** — quote: dimensions/weight + origin/destination → cost + ETA * 2. **crear** — create a shipment, get a tracking number + label URL * 3. **trackear** — query the status / events of a shipment * 4. **cancelar** — cancel a shipment (when allowed by the carrier) * 5. **listar_sucursales** — find drop-off / pickup branches near a CP * * Some carriers also support pickup scheduling, returns, and bulk operations, * but the 5 above cover 95% of agent flows. */ /** * Carriers this lib normalizes. Add new ones via the adapter pattern. */ type Carrier = "andreani" | "oca" | "correo_argentino"; /** * Direction of a shipment. Most agent flows are "to client" (B2C); * "to seller" is for returns. */ type ShipmentDirection = "to_client" | "to_seller" | "between_branches"; /** * Service level. Each carrier maps these to its own product names internally: * - Andreani: standard → Estándar, express → Urgente, same_day → Mismo Día * - OCA: standard → Encomienda Pactada, express → Express * - Correo: standard → Carta Documento, express → Encomienda Express */ type ServiceLevel = "standard" | "express" | "same_day"; interface Address { /** Recipient/sender full name. */ name: string; /** Optional company / razón social. */ company?: string; /** Street name (e.g., "Av. Cabildo"). */ street: string; /** Street number (e.g., "1234"). String to allow "S/N", "1234-A". */ number: string; /** Apartment / piso / depto / oficina. */ unit?: string; /** Locality / barrio / partido. */ city: string; /** Provincia name OR ISO code OR AFIP code. The lib normalizes. */ state: string | number; /** CPA (Código Postal Argentino) — 4 digits or extended 8-char. */ postalCode: string; /** AR for Argentina. Default. */ country?: "AR" | string; /** Contact phone. Used by carrier for delivery coordination. */ phone?: string; /** Email for tracking notifications (some carriers). */ email?: string; /** Delivery instructions (e.g., "tocar timbre 2 veces"). */ notes?: string; } /** * Package dimensions + weight. Most carriers compute "volumetric weight" * = (length × width × height) / divisor and bill the greater of actual vs * volumetric weight. */ interface PackageInfo { /** Actual weight in kg (e.g., 0.5 for 500g, 12 for 12kg). */ weightKg: number; /** Length in cm. */ lengthCm: number; /** Width in cm. */ widthCm: number; /** Height in cm. */ heightCm: number; /** * Declared value in ARS — for insurance + lost-package compensation. * Most carriers cap their liability at this amount. */ declaredValueArs: number; /** * Optional content description for customs / handling (e.g., "Ropa", "Electrónica"). */ description?: string; /** True if the package is fragile (carrier may apply special handling). */ fragile?: boolean; } interface QuoteInput { origin: Address; destination: Address; packages: PackageInfo[]; /** Service level. Default: standard. */ service?: ServiceLevel; /** * Direction of the shipment. Default: to_client (B2C). */ direction?: ShipmentDirection; } /** * A single quote returned by a carrier. When using `quoteAll()`, you get an * array of these — one per carrier — for comparison. */ interface QuoteOption { carrier: Carrier; service: ServiceLevel; /** Cost in ARS. */ costArs: number; /** Estimated delivery time in business days, lower bound. */ estimatedDaysMin: number; /** Estimated delivery time in business days, upper bound. */ estimatedDaysMax: number; /** Carrier's internal product/service id (opaque, for create_shipment). */ productId?: string; /** Total weight billed (max of actual + volumetric). */ billedWeightKg?: number; /** Raw carrier response for debugging / advanced use. */ raw?: unknown; } interface CreateShipmentInput { origin: Address; destination: Address; packages: PackageInfo[]; service?: ServiceLevel; direction?: ShipmentDirection; /** * Your-system reference (order id, invoice number) — printed on the label * and surfaced in tracking events. Use for reconciliation. */ externalReference?: string; /** * Carrier-specific product id — pass the value from `QuoteOption.productId` * when you want to commit a specific quote. */ productId?: string; } interface ShipmentCreated { carrier: Carrier; /** Carrier's tracking number (the number the recipient sees). */ trackingNumber: string; /** Carrier-internal shipment id (different from trackingNumber for some carriers). */ shipmentId: string; /** PDF label URL. May require auth — use `labelData` for the actual bytes. */ labelUrl?: string; /** Direct base64 PDF when the carrier returns it inline. */ labelDataBase64?: string; /** Cost in ARS. May differ from the quote (rounding, surcharges). */ costArs: number; /** Estimated delivery date in YYYY-MM-DD. */ estimatedDeliveryDate?: string; /** Echo of the input external reference. */ externalReference?: string; raw?: unknown; } /** * Normalized lifecycle states across carriers. Each adapter maps its native * status codes to one of these. */ type TrackingStatus = "label_created" | "in_transit" | "out_for_delivery" | "delivered" | "delivery_failed" | "returned" | "canceled" | "exception" | "unknown"; interface TrackingEvent { /** ISO 8601 timestamp of the event. */ timestamp: string; /** Normalized status. */ status: TrackingStatus; /** Spanish description of what happened (carrier-provided, surface verbatim). */ description: string; /** Location (city, branch) where the event occurred. */ location?: string; } interface TrackingResult { carrier: Carrier; trackingNumber: string; /** The most recent normalized status. Quick-glance summary field. */ currentStatus: TrackingStatus; /** All events, oldest-first. */ events: TrackingEvent[]; /** ISO date when the shipment was delivered, when applicable. */ deliveredAt?: string; /** ETA when not yet delivered. */ estimatedDeliveryDate?: string; raw?: unknown; } interface CancelResult { carrier: Carrier; trackingNumber: string; canceled: boolean; /** Spanish reason if cancellation failed (already in transit, etc.). */ reason?: string; raw?: unknown; } interface Branch { carrier: Carrier; id: string; name: string; address: string; city: string; state: string; postalCode: string; /** Distance from the queried CP in km, when computable. */ distanceKm?: number; /** Opening hours as a free-form string (e.g. "L-V 9-18, S 9-13"). */ openingHours?: string; /** GPS coordinates when available. */ lat?: number; lng?: number; /** Services this branch offers (drop-off, pickup, returns, etc.). */ services?: string[]; } /** * Adapter contract for shipping carriers. Implement to wire any AR carrier * (Andreani, OCA, Correo, your own private courier). * * # Why an adapter? * * Each AR carrier has a wildly different API: Andreani is REST/JSON, * OCA is SOAP/XML legacy, Correo Argentino has yet another shape. * The adapter pattern means callers always work in the normalized * `QuoteOption` / `ShipmentCreated` / `TrackingResult` types, and the * adapter handles translation. * * Two ready-to-use adapters ship: * - `UnconfiguredShippingAdapter` — always-fail, safe to call without setup. * - `MockShippingAdapter` — in-memory deterministic responses, useful for * tests and demos when you don't have carrier credentials yet. * * Real-carrier adapters: `AndreaniAdapter`, `OcaAdapter`, `CorreoAdapter`. */ /** * The adapter interface. All methods may throw — the agent tools wrap * thrown errors into `{ available: false, error }` shapes so the agent * never crashes mid-conversation. */ interface ShippingAdapter { readonly carrier: Carrier; cotizar(input: QuoteInput): Promise; crear(input: CreateShipmentInput): Promise; trackear(trackingNumber: string): Promise; cancelar(trackingNumber: string): Promise; listarSucursales(params: { postalCode: string; limit?: number; }): Promise; } /** * Always-fail adapter. Tools wrap calls with a check; when this is the * adapter, they return `{ available: false, error: }`. */ declare class UnconfiguredShippingAdapter implements ShippingAdapter { readonly carrier: Carrier; constructor(carrier?: Carrier); cotizar(): Promise; crear(): Promise; trackear(): Promise; cancelar(): Promise; listarSucursales(): Promise; } /** * In-memory deterministic mock. Useful for tests + demos. * * Returns synthetic-but-realistic responses: * - cotizar: fixed cost based on weight + ETA based on service level * - crear: random tracking number, label URL is a data: URI * - trackear: lifecycle simulated based on the tracking number * (numbers ending in 0 = label_created, 1-3 = in_transit, etc.) * - cancelar: succeeds for label_created/in_transit, fails otherwise * - listarSucursales: returns 3 mock branches near the requested CP */ declare class MockShippingAdapter implements ShippingAdapter { readonly carrier: Carrier; constructor(carrier?: Carrier); cotizar(input: QuoteInput): Promise; crear(input: CreateShipmentInput): Promise; trackear(trackingNumber: string): Promise; cancelar(trackingNumber: string): Promise; listarSucursales(params: { postalCode: string; limit?: number; }): Promise; } /** * Andreani adapter — wired to Andreani's REST API. * * # Background * * Andreani is the largest private logistics carrier in AR (~50% of B2C * e-commerce shipping share). Has a modern REST API at * https://developers.andreani.com/. * * # Setup * * 1. Register at https://developers.andreani.com/ * 2. Get `clientId` (= `username`) + `clientSecret` (= `password`) for the * sandbox / production environment. * 3. Get your `numeroCliente` from the Andreani commercial team. * 4. Wire the adapter: * ```ts * const andreani = new AndreaniAdapter({ * username: process.env.ANDREANI_USERNAME!, * password: process.env.ANDREANI_PASSWORD!, * clientNumber: process.env.ANDREANI_CLIENT_NUMBER!, * env: "prod", * }); * ``` * * # Auth * * Andreani uses Basic Auth on every request (no token exchange needed). The * adapter constructs the header automatically. * * # Service mapping * * - `standard` → "Estándar" (productId varies by client; default we use a * common code, override via `productIdMap` if your contract differs) * - `express` → "Urgente" * - `same_day` → not generally available; the adapter throws. */ interface AndreaniAdapterOptions { /** Andreani API username. From dev panel. */ username: string; /** Andreani API password. From dev panel. */ password: string; /** * Andreani's `numero de cliente` — assigned by their commercial team. * Required for `crear` (shipment creation). */ clientNumber: string; /** "homo" for sandbox, "prod" for live. Default "prod". */ env?: "homo" | "prod"; /** * Optional override of the productId map. Defaults: standard=140, * express=140Pickup. Override if your Andreani contract uses different * product codes. */ productIdMap?: Partial>; /** Override base URL (testing). */ baseUrl?: string; /** Custom fetch (testing). */ fetchImpl?: typeof fetch; /** Per-request timeout in ms. Default 30s. */ requestTimeoutMs?: number; /** Retries on 5xx + transient errors. Default 1. */ maxRetries?: number; /** Observability hook fired after every request. */ onCall?: (event: { label: string; durationMs: number; httpStatus: number | null; retried: number; success: boolean; }) => void; } declare class AndreaniAdapter implements ShippingAdapter { readonly carrier: "andreani"; private readonly baseUrl; private readonly authHeader; private readonly productMap; private readonly clientNumber; private readonly fetchImpl; private readonly requestTimeoutMs; private readonly maxRetries; private readonly onCall; constructor(opts: AndreaniAdapterOptions); cotizar(input: QuoteInput): Promise; crear(input: CreateShipmentInput): Promise; trackear(trackingNumber: string): Promise; cancelar(trackingNumber: string): Promise; listarSucursales(params: { postalCode: string; limit?: number; }): Promise; private call; private toAndreaniAddress; } /** * OCA adapter — wired to OCA's E-Pak SOAP API. * * # Background * * OCA is the second-largest private courier in AR. Its API is **legacy SOAP** * (E-Pak), with no JSON / REST option. The official docs PDF is at * https://www.oca.com.ar/Solicitudes/wServicios.htm * * # Status — v0.1 * * v0.1 ships a **HTTP-based stub** for the operations OCA exposes via their * "Tarifador" REST endpoint (https://www.oca.com.ar/api/v1/Tarifador/...). * For the full SOAP shipment-creation flow (`Ingreso_OR`), use the OCA * official PHP/.NET libraries until v0.2 ships a complete TS port. * * For most agent flows, this is enough: cotizar + listar_sucursales work, * crear/trackear/cancelar throw `ShippingNotSupportedError` with a clear * message pointing to the OCA dashboard. * * # Setup * * 1. Get a `cuit` + `operativa` (operativa code) from OCA's commercial team. * 2. Sign up at https://www.oca.com.ar for the API access. */ interface OcaAdapterOptions { /** OCA CUIT (your CUIT registered with OCA). */ cuit: string; /** OCA "operativa" code — the contract id assigned by OCA. */ operativa: string; /** Override base URL (testing). */ baseUrl?: string; /** Custom fetch (testing). */ fetchImpl?: typeof fetch; /** Per-request timeout in ms. Default 30s. */ requestTimeoutMs?: number; /** Retries on 5xx + transient errors. Default 1. */ maxRetries?: number; /** Observability hook. */ onCall?: (event: { label: string; durationMs: number; httpStatus: number | null; retried: number; success: boolean; }) => void; } declare class OcaAdapter implements ShippingAdapter { readonly carrier: "oca"; private readonly baseUrl; private readonly cuit; private readonly operativa; private readonly fetchImpl; private readonly requestTimeoutMs; private readonly maxRetries; private readonly onCall; constructor(opts: OcaAdapterOptions); cotizar(input: QuoteInput): Promise; crear(_input: CreateShipmentInput): Promise; trackear(_trackingNumber: string): Promise; cancelar(_trackingNumber: string): Promise; listarSucursales(params: { postalCode: string; limit?: number; }): Promise; private call; } /** * Correo Argentino adapter — wired to Correo's public REST endpoints. * * # Background * * Correo Argentino is the state-owned national postal service, the only * carrier that reaches every CPA in the country (including remote areas * private couriers won't service). Has a "Mi Correo Empresas" portal at * https://www.correoargentino.com.ar/empresas with a basic REST API. * * # Status — v0.1 * * v0.1 implements `cotizar` (their public Tarifador endpoint) and * `trackear` (the public seguimiento endpoint that doesn't need auth). * Shipment creation and cancellation require a corporate contract + * the SAU portal — those throw `ShippingNotSupportedError` for v0.1. * * # Setup * * For v0.1 (cotizar + trackear): no setup required, both endpoints are * public. For shipment creation: contact the Correo Argentino Empresas * commercial team to get a corporate contract + portal credentials. */ interface CorreoAdapterOptions { /** Override base URL (testing). */ baseUrl?: string; /** Custom fetch (testing). */ fetchImpl?: typeof fetch; /** Per-request timeout in ms. Default 30s. */ requestTimeoutMs?: number; /** Retries on 5xx + transient errors. Default 1. */ maxRetries?: number; /** Observability hook. */ onCall?: (event: { label: string; durationMs: number; httpStatus: number | null; retried: number; success: boolean; }) => void; } declare class CorreoAdapter implements ShippingAdapter { readonly carrier: "correo_argentino"; private readonly baseUrl; private readonly fetchImpl; private readonly requestTimeoutMs; private readonly maxRetries; private readonly onCall; constructor(opts?: CorreoAdapterOptions); cotizar(input: QuoteInput): Promise; crear(_input: CreateShipmentInput): Promise; trackear(trackingNumber: string): Promise; cancelar(_trackingNumber: string): Promise; listarSucursales(params: { postalCode: string; limit?: number; }): Promise; private call; } interface ShippingToolsOptions { /** * Shipping adapters keyed by carrier. Pass at least one. When a tool is * called and the requested carrier isn't configured, it returns * `{ available: false, error }` instead of crashing. * * @example * ```ts * shippingTools({ * adapters: { * andreani: new AndreaniAdapter({ ... }), * correo_argentino: new CorreoAdapter(), * }, * defaultCarrier: "andreani", * }); * ``` */ adapters?: Partial>; /** * Default carrier when the agent doesn't specify one. Saves the agent * from having to remember which carriers are wired. */ defaultCarrier?: Carrier; /** * Override the agent-facing tool descriptions. */ descriptions?: Partial>; } type ShippingToolName = "cotizar_envio" | "cotizar_envio_todos" | "crear_envio" | "trackear_envio" | "cancelar_envio" | "listar_sucursales"; declare function shippingTools(options?: ShippingToolsOptions): ToolSet; declare function unconfiguredShippingTools(): ToolSet; /** * AR provincial codes — used by shipping carriers and AFIP / ARCA forms. * * Two parallel encoding schemes exist: * - **AFIP / IGJ codes**: 1-digit "código provincia" (1=BS AS, 2=CABA, etc.) * - **ISO 3166-2 codes**: "AR-X" two-letter (AR-B = Buenos Aires, AR-C = CABA…) * * Carriers use either or both; this module exposes both forms so the * adapters can map to whatever shape the API needs. */ interface Provincia { /** ISO 3166-2 letter code (e.g. "B" for Buenos Aires). */ iso: string; /** AFIP / IGJ numeric code. */ afipCode: number; /** Full Spanish name. */ name: string; /** Common short name / alias for fuzzy lookup. */ aliases?: string[]; } declare const PROVINCIAS: Provincia[]; /** * Resolve a provincia by name (fuzzy, accent-insensitive), ISO code, or * AFIP numeric code. Returns null on no match. * * @example * lookupProvincia("CABA") // → CABA entry * lookupProvincia("córdoba") // → Córdoba entry * lookupProvincia("B") // → Buenos Aires entry * lookupProvincia(8) // → La Pampa entry */ declare function lookupProvincia(input: string | number): Provincia | null; /** * Validate an Argentine postal code. AR uses CPA (Código Postal Argentino): * 4-digit legacy OR 8-character extended (1 letter + 4 digits + 3 letters). * * @example * isValidCPA("1842") // true (Monte Grande, BA) * isValidCPA("B1842ZAB") // true (extended CPA) * isValidCPA("00000") // false */ declare function isValidCPA(cp: string): boolean; /** * Errors emitted by `@ar-agents/shipping` adapters and helpers. */ type ShippingErrorCode = "shipping_not_configured" | "shipping_invalid_input" | "shipping_carrier_error" | "shipping_not_supported" | "shipping_unknown_error"; declare class ShippingError extends ArAgentsError { readonly code: ShippingErrorCode; readonly details?: unknown; constructor(code: ShippingErrorCode, message: string, details?: unknown); } /** * Thrown when a tool is called against an adapter that hasn't been * configured. Surface the message — it tells the user how to enable. */ declare class ShippingNotConfiguredError extends ShippingError { carrier: Carrier; constructor(carrier: Carrier); } /** * Thrown when an operation isn't supported by a specific carrier (e.g. * cancelar after pickup is OCA-only). */ declare class ShippingNotSupportedError extends ShippingError { carrier: Carrier; operation: string; constructor(carrier: Carrier, operation: string); } /** * Thrown when the carrier's API returns a structured error. */ declare class ShippingCarrierError extends ShippingError { carrier: Carrier; httpStatus?: number | undefined; carrierErrorCode?: string | undefined; constructor(carrier: Carrier, message: string, httpStatus?: number | undefined, carrierErrorCode?: string | undefined, details?: unknown); } export { type Address, AndreaniAdapter, type AndreaniAdapterOptions, type Branch, type CancelResult, type Carrier, CorreoAdapter, type CorreoAdapterOptions, type CreateShipmentInput, MockShippingAdapter, OcaAdapter, type OcaAdapterOptions, PROVINCIAS, type PackageInfo, type Provincia, type QuoteInput, type QuoteOption, type ServiceLevel, type ShipmentCreated, type ShipmentDirection, type ShippingAdapter, ShippingCarrierError, ShippingError, type ShippingErrorCode, ShippingNotConfiguredError, ShippingNotSupportedError, type ShippingToolName, type ShippingToolsOptions, type TrackingEvent, type TrackingResult, type TrackingStatus, UnconfiguredShippingAdapter, isValidCPA, lookupProvincia, shippingTools, unconfiguredShippingTools };