Repository navigation
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
Codex Review SummaryThis comment shows the latest Codex review activity on this pull request.
ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings. |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: f448de33b0
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| cmd | ||
| .command('list') | ||
| .description('List custom field definitions') | ||
| .action( | ||
| listAction( | ||
| 'custom-field-definitions.list', | ||
| async ({ client, globals }) => { | ||
| const result = await client.customFieldDefinitions.list({ |
There was a problem hiding this comment.
Preserve template listing in the CLI
The SDK now defaults customFieldDefinitions.list() to the account-scoped route and requires scope: 'templates' to access the top-level template route, but this CLI command offers no scope option and always invokes the default. As a result, users who previously ran t49 custom-field-definitions list to obtain template IDs can no longer list templates through the CLI, including when they need a template_id for creation; expose a templates/scope flag and pass it through here.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Fixed. t49 custom-field-definitions list --templates passes scope: 'templates', and without the flag the command still lists the account definitions. A regression test checks both request URLs.
| public async execute<R extends { data?: unknown }>( | ||
| fn: () => Promise<R>, | ||
| ): Promise<NonNullable<R['data']>> { | ||
| const { data } = await fn(); | ||
| return data as T; | ||
| // SAFETY: ErrorMappingInterceptor throws on every non-2xx response, so a | ||
| // call that returns here carries its operation's success body. Only an | ||
| // operation the spec gives no response body resolves to undefined. | ||
| return data as NonNullable<R['data']>; |
There was a problem hiding this comment.
Empty successes get wrong types
Transport.execute removes undefined even when it is the successful result. A call with data: undefined now returns Promise<never>, although it resolves normally. The SDK has bodyless DELETE operations, so this affects supported calls. Preserve undefined or void for bodyless successes and add a type test for that case.
Prompt To Fix With AI
This is a comment left during a code review.
Path: sdks/typescript-sdk/src/client/transport.ts
Line: 73-80
Comment:
**Empty successes get wrong types**
`Transport.execute` removes `undefined` even when it is the successful result. A call with `data: undefined` now returns `Promise<never>`, although it resolves normally. The SDK has bodyless DELETE operations, so this affects supported calls. Preserve `undefined` or `void` for bodyless successes and add a type test for that case.
---
For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.There was a problem hiding this comment.
Fixed. execute now returns the data type of openapi-fetch's success branch, so a 204 DELETE resolves to undefined and GET /me keeps its body type. A type test covers both.
| cmd | ||
| .command('list') | ||
| .description('List custom field definitions') | ||
| .action( |
There was a problem hiding this comment.
Removing --page-size leaves capture-live-fixtures.sh calling custom-field-definitions list --page-size 2 at line 73. With set -euo pipefail, the unknown option stops the script before it finishes. Update the script alongside this change. It also passes removed pagination flags to custom-fields and calls webhook examples without the newly required --event.
Prompt To Fix With AI
This is a comment left during a code review.
Path: sdks/typescript-sdk-cli/src/commands/custom-field-definitions.ts
Line: 57-60
Comment:
**Fixture capture stops early**
Removing `--page-size` leaves `capture-live-fixtures.sh` calling `custom-field-definitions list --page-size 2` at line 73. With `set -euo pipefail`, the unknown option stops the script before it finishes. Update the script alongside this change. It also passes removed pagination flags to `custom-fields` and calls webhook examples without the newly required `--event`.
---
For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.There was a problem hiding this comment.
Fixed in the commits that removed the flags. Against local staging, the old commands exit 2 with a usage error. The new ones exit 0, or exit 6 with the expected 400 for the unknown event.
|
Preview deployment for your docs. Learn more about Mintlify Previews.
|
… field value types
addPartyRole on containers and shipments, containers.update,
parties.create and parties.update, and the setCustomField methods take
types derived from the spec, but none of those types were exported, so
callers could not name what they pass. Export
ContainerAddPartyRoleParams, ShipmentAddPartyRoleParams,
ContainerUpdateAttributes, PartyAttributes, and CustomFieldValue. The
three managers share CustomFieldValue from the models module instead of
each declaring its own copy.
The containers.update test now sends every attribute the spec
documents for PATCH /containers/{id}, and a type test pins the new
exports.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
PATCH /documents/{id} accepts document_type, classification_notes, and
extracted_data_manual. documents.update took documentTypeManual, which
named a field the API never read. It now takes documentType, which
sends document_type, and classificationNotes, which sends
classification_notes and accepts null to clear the notes.
BREAKING CHANGE: documents.update no longer accepts documentTypeManual.
Pass documentType instead.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The API serializes a tracking request's shipment as the polymorphic `tracked_object` relationship. The mapper read `shipment` and `container` relationships that the serializer never sends, so `mapped.shipment` was always null. It now resolves `tracked_object`: the included shipment gives the id, bill of lading, and SCAC, and a shipment that is not included still yields its id. BREAKING CHANGE: mapped tracking requests no longer have a `container` key, and `TrackingRequest` no longer declares it. The API never links a container to a tracking request, so the value was always null. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…r, user The tracking request serializer exposes only the tracked_object, customer, and user relationships. `TrackingRequestInclude` offered `shipment` and `container`, and the API rejects both with 400. BREAKING CHANGE: `TrackingRequestInclude` is now 'tracked_object' | 'customer' | 'user'. Replace `include: ['shipment']` with `include: ['tracked_object']`. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The API answers 401 both for a bad token and for a valid token that the action's policy denies, such as resume_tracking on a shipment that is not stopped. The SDK replaced every 401 message with "Invalid or missing API token", which hid the real reason. AuthenticationError now carries the server's detail and falls back to the generic message only when the response has none. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…h formats
containers.get and containers.update passed no mapper, so `mapped`
returned the raw JSON:API document and `both` returned it twice. With
`defaultFormat: 'mapped'`, `id`, `number`, and `events` were undefined.
Both now map through mapContainer, like containers.list. getDemurrage
and getRailMilestones read raw attributes, so they now request the raw
format and keep working under a mapped default.
BREAKING CHANGE: containers.get, containers.update, and getContainer
return a mapped `Container` for format `mapped`, and `{ raw, mapped }`
with a mapped `Container` for format `both`. Request format `raw` to
keep the JSON:API document. The CLI defaults to format `mapped`, so
`t49 containers get` now prints the mapped `Container` instead of the
JSON:API document. Pass `--format raw` to keep the old output.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
mapShipment built `containers` from the included container resources
only. A shipment fetched without `include=containers` still links its
containers by id, but the mapped shipment showed none. The mapper now
reads `relationships.containers.data`, emits `{ id }` for each linked
container, and adds `number` when the container is included.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
vessels.get, getByImo, futurePositions, and futurePositionsWithCoords
passed no mapper, so format `mapped` returned the raw JSON:API document
and `both` returned it twice. They now map to a `Vessel` (name, IMO,
MMSI, last position, speed, and heading). The future-position methods
map to a `VesselWithFuturePositions`, which adds the estimated
`positions` (latitude, longitude, heading, timestamp, estimated).
BREAKING CHANGE: the vessel methods return `Vessel` or
`VesselWithFuturePositions` for format `mapped`, and `{ raw, mapped }`
with the mapped model for format `both`. Request format `raw` to keep
the JSON:API document. The CLI defaults to format `mapped`, so
`t49 vessels get`, `get-by-imo`, `future-positions`, and
`future-positions-coords` now print the mapped model instead of the
JSON:API document. Pass `--format raw` to keep the old output.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The spec defines no request body for PATCH /shipments/{id}/stop_tracking
or resume_tracking, and the backend reads only the path id. The SDK sent
a JSON:API document through an `as any` cast. Both calls now send the
path id only.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
GET /shipping_lines has no search parameter. The SDK sent `search`
through an `as any` cast, the server ignored it, and
listShippingLines('hapag') returned every line. The SDK now requests
the full list and keeps the lines whose SCAC, alternative SCACs, name,
or short name contain the search text, ignoring case. The filter
applies to the raw document too, so every format agrees.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The spec marks a shipping line's name, short_name, and alternative_scacs as required strings, but the backing columns are nullable. The search filter called toLowerCase on each and spread alternative_scacs, so one line with a null column made listShippingLines(search) throw. The filter now skips null values. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The spec now documents `include` on GET /containers/{id}/custom_fields,
/shipments/{id}/custom_fields, and /tracking_requests/{id}/custom_fields.
The three customFields() methods drop their hand-built executeManual
URLs and call the typed client with `include: 'definition'`. The request
is unchanged.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The spec now documents that GET /custom_field_definitions/{id}/options
is not paginated: the API ignores page[number] and page[size] and
returns every option. The SDK sent those params through a hand-built
executeManual URL. It now calls the typed client without them.
customFieldOptions.list and listCustomFieldOptions take CallOptions, so
passing `page` or `pageSize` is a type error instead of a silently
ignored option. `t49 custom-field-options list` drops --page,
--page-size, and --all for the same reason.
BREAKING CHANGE: customFieldOptions.list and listCustomFieldOptions take
CallOptions; page and pageSize no longer compile. t49
custom-field-options list drops --page, --page-size, and --all.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
GET and POST /custom_field_definitions are the Terminal49 template
routes. For a customer account, list returned the templates (often an
empty list) instead of the account's definitions, and create answered
401. customFieldDefinitions.list and create now call
/accounts/{account_id}/custom_field_definitions. The API scopes that
route to the authenticated account, so the SDK fills account_id from
the client's `accountId` or sends the placeholder `current`.
`list({ scope: 'templates' })` still lists the templates.
BREAKING CHANGE: customFieldDefinitions.list() returns the account's
definitions instead of the Terminal49 templates. Pass
`scope: 'templates'` for the templates.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The spec now documents PATCH /tracking_requests/{id}/cancel, the one
operation the SDK did not cover. trackingRequests.cancel(id) cancels a
request that is still pending or awaiting a manifest. The API returns
an empty object, and 422 for any other status.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The spec now documents that GET /custom_fields and the custom field definition lists are not paginated: the API ignores page[number] and page[size] and returns every row. customFields.list and customFieldDefinitions.list no longer send them, matching customFieldOptions.list. The PaginatedQuery intersections that let the SDK send undeclared params are gone. Both methods now take CallOptions instead of ListOptions, so passing `page` or `pageSize` is a type error instead of a silently ignored option. CustomFieldDefinitionListOptions extends CallOptions. `t49 custom-fields list` and `t49 custom-field-definitions list` drop --page, --page-size, and --all, and the live fixture capture script stops passing --page-size to them. BREAKING CHANGE: customFields.list, listCustomFields, customFieldDefinitions.list, and listCustomFieldDefinitions take CallOptions; page and pageSize no longer compile. t49 custom-fields list and t49 custom-field-definitions list drop --page, --page-size, and --all. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
GET /webhook_notifications/examples answers 400 without an event, and the spec marks the parameter required. The SDK typed it optional and cast any string past the generated enum, so examples() with no event always threw ValidationError: Bad Request. webhookNotifications.examples and getWebhookNotificationExamples now take a required WebhookEvent, exported from the generated paths, and pass it through without a cast. The CLI calls examples(), so webhook-notifications examples makes --event a required option typed as WebhookEvent. The CLI README documents it, and the live fixture capture script gets its 400 envelope from an unknown event instead of a missing one, which the CLI now rejects before calling the API. BREAKING CHANGE: webhookNotifications.examples(event, options?) and getWebhookNotificationExamples(event, options?) require event, typed as the exported WebhookEvent union. Calls without an event, or with a string outside the documented events, no longer compile. The CLI command webhook-notifications examples now requires --event. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The generated container type accepts relationships.pod_terminal with data: null, and containers.get maps such a container with null podTerminal and destinationTerminal. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
trackingRequests.update and updateTrackingRequest took any attributes
and cast the body to any, so `{ ref_number: 'X' }` compiled and the API
answered 200 without changing anything. Both now take
TrackingRequestUpdateAttributes, derived from PATCH /tracking_requests/{id}
(`ref_numbers` only). create and inferNumber also send bodies typed from
their operations instead of casting to any. The wire payloads are
unchanged.
BREAKING CHANGE: trackingRequests.update and updateTrackingRequest accept
only `{ ref_numbers?: string[] }`.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
execute returned `any` by default, so the typed mappers (mapContainerRoute, and mapVessel and mapVesselFuturePositions from earlier in this series) were never checked against their endpoint: handing containers.route() the vessel mapper still compiled. execute now returns the data type of the call's success branch, so each mapper is checked against its route's 200 response, and a bodyless success such as a 204 DELETE resolves to undefined. The redundant `ShippingLineResource` annotation in shippingLines.list() goes away for the same reason. BREAKING CHANGE: Transport.execute no longer takes a result type argument; it infers the result from the request it runs. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
shipments.update and updateShipment took any attributes and cast the
body to any, so `{ tags: 'a,b' }` or `{ ref_number: 'X' }` compiled and
went out unchecked. Both now take ShipmentUpdateAttributes, derived
from PATCH /shipments/{id} (`ref_numbers` and `tags`, each a string
list), the same way tracking request updates are typed. The wire
payload is unchanged.
BREAKING CHANGE: shipments.update and updateShipment accept only
`{ ref_numbers?: string[]; tags?: string[] }`.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
methods.mdx now says what `mapped` returns for containers.get and the vessel methods, lists the includes a tracking request accepts and where its mapped shipment comes from, and adds trackingRequests.cancel. It names the account routes that customFieldDefinitions.list and create call, notes that the custom field lists are not paginated, describes the client-side shippingLines search, marks the webhook notification examples event as required, and documents documents.update with documentType and classificationNotes. Methods without mapping return the JSON:API response unchanged in `mapped` format. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
AuthenticationError covers every HTTP 401, including an action the API refuses for the key, and its message carries the API's detail. AuthorizationError is the HTTP 403 case. The example logs the message instead of assuming the key is invalid. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…Infer createFromInfer resolved the type as the inferred number_type first and the caller's numberType second. infer_number always returns a number_type, so the option was never used. A booking number that infer classified as bill_of_lading was submitted as bill_of_lading, and a retry with numberType after an unrecognized inferred type failed the same way. The caller's numberType now wins over the inferred value. A numberType that is not container, bill_of_lading, booking_number, or booking throws a ValidationError that names it, before any request is sent. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
customFieldDefinitions.list() now reads the account definitions route by default and takes scope: 'templates' for the top-level template route, but custom-field-definitions list had no way to pass the scope. That left the CLI unable to list the templates whose ids custom-field-definitions create needs as template_id. list --templates reads the templates, and list without it still reads the account definitions. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The method reference says it lists every SDK method, but it left out client.me(), which describes the credential, its account, and the gated features it can use. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: f0bba87ab8
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| }), | ||
| ); | ||
| return this.formatResult(raw, options?.format); | ||
| return this.formatResult(raw, options?.format, mapContainer); |
There was a problem hiding this comment.
Preserve linked relationship IDs in mapped container updates
When containers.update() is used with format: 'mapped' (or a mapped default), the PATCH response contains container relationship linkage but no included resources, and mapContainer() resolves relationships only through JsonApiDocument.getRelationship(). Consequently, a container that still belongs to a shipment is returned with shipment: null, and terminal relationships are similarly erased. Preserve the linkage IDs when the related resources are not included, as the updated shipment mapper already does.
Useful? React with 👍 / 👎.
Transport.execute now infers the response body type from the typed openapi-fetch call (#439), so the explicit body type arguments no longer fit its signature. The inferred types are the same TradeIntel* schemas. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Transport.execute now infers the response body type from the typed openapi-fetch call (#439), so the explicit body type arguments no longer fit its signature. The inferred types are the same TradeIntel* schemas. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Lets an agent assign a party (such as the drayage carrier) or set an account custom field on up to 50 containers or shipments, reporting per-record updated, unchanged, failed and not_attempted results. The tools call the SDK methods #433 and #439 added (partyRoles, addPartyRole, removePartyRole, setCustomField, deleteCustomField). The party_role list is parsed in the tool, since the SDK returns it unmapped. set_custom_field accepts an array of option values for multi-select fields and treats the same options in another order as unchanged. A call stops starting new records after 20s, under the 30s /mcp maxDuration, and lists the records it did not reach in not_attempted. The server now lists 16 tools; the protocol smoke scripts and tests expect that count and the two new names. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…415) * docs: add Trade Intelligence endpoints to the API reference Documents the nine /v2/trade_intel endpoints (US import bill-of-lading records from US Customs manifests, January 2022 onward) under a public Trade Intelligence tag: request and response schemas as TradeIntel* components, the 400/401/403/422/429/502/504 error bodies, and endpoint descriptions that carry the data caveats (physical containers, estimated values, partial current month, names split by state or withheld, substring company filter, carrier spelling variants). Adds the reference pages to the API Reference navigation and regenerates the Postman collection. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * feat(sdk): add tradeIntel namespace for trade intelligence client.tradeIntel exposes meta, searchCompanies, companyProfile, searchCommodities, topImporters, trends, breakdown, lookupContainer, and lookupBillOfLading. They return the endpoints' plain JSON bodies with types generated from docs/openapi.json, and surface errors through the existing error types: the 403 "not enabled" body maps to FeatureNotEnabledError, 400 and 422 (including FastAPI detail arrays) to ValidationError. The trade intelligence POSTs are read-only queries, so the retry policy now treats POSTs under /trade_intel/ like GETs: 429, 5xx, and network failures are retried with backoff. Other writes still need an Idempotency-Key. Types are generated with --default-non-nullable=false so request fields with server defaults (limit, months, top) stay optional; no existing type changes. Fixture-based tests cover every method (path, method, body, response passthrough) and the 400/422/403/429/502 paths. Regenerates the SDK reference docs and documents the namespace. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * docs: state the real commodity ordering in trade intelligence lookups Lookups sort commodity lines by bill, then by estimated value within each bill, and the 200-line cap applies to that order. The schemas promised a global highest-value-first order, which a multi-bill response does not give. Found in Codex review. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * docs: mark always-returned trade intelligence response fields required meta, the companies/search results, commodities/search results, trend points, and top importers always carry these fields in production, so the generated SDK types no longer make them optional. Fields that are conditional stay optional: a trend point's partial flag, a ranking's estimated_value without hs4, and a profile's details when found is false. Raised in Codex review on the PR. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * chore(sdk): regenerate types and reference for required response fields Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * docs: restrict trade intelligence month fields to real calendar months since and until in trends and breakdown accepted any two-digit month, so schema-validating clients and generated Postman examples allowed values like 2026-60. Raised in Codex review on the PR. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * feat(mcp): trade intelligence tools (#417) * feat(mcp): add trade intelligence tools Seven read-only tools for US ocean import market data, built on the SDK's tradeIntel namespace: search_importers, get_importer_profile, search_commodities, rank_importers, get_trade_trends, get_trade_breakdown, and get_trade_data_coverage. Record-level container and bill of lading lookups stay in the SDK only. The tool descriptions and server instructions carry the caveats agents got wrong without guidance: search ranks by match not size and splits companies by state, the profile with months: 24 is the way to get one company's trend (the trends company filter is a substring match), trends and breakdowns include the incomplete current month, history starts in January 2022, missing volume can mean withheld names, values are estimates, and volumes don't add across products or companies. Inputs are strict (enums for measure, interval, dimensions, and coast; real YYYY-MM months; bounded limits). A 403 returns a plain "not enabled, contact support@terminal49.com" result instead of an error; 400 shows the API's reason, and 429 and 5xx get plain messages without internals. Responses drop internal fields, round numbers, cap long lists, and flag truncation. Adds unit and server tests, eval cases that pass with or without the feature, the new tools in both store listings, and an Unreleased changelog entry. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * docs(mcp): document the trade intelligence tools Adds a trade intelligence section to the MCP tools reference with each tool's parameters, example questions, the per-account enablement note, and the caveats for reading the numbers. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * fix(mcp): address Codex review of the trade intelligence tools - A high notify-party share is a clue, not proof of a forwarder: large cargo owners can be mostly notify party, so the descriptions and instructions now say to weigh the name and product mix too. - Name and filter inputs accept up to 200 characters, matching the API, so a long company name from search can be reused in later calls. - search_importers reports more_not_shown when it cuts a row's ports, origins, carriers, or products to five, and search_commodities keeps up to 15 common goods and counts the rest in common_goods_not_shown. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * docs(mcp): qualify the notify-party signal and the common goods cap Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * test(mcp): skip trade intelligence evals where the tools aren't deployed Push-time CI runs the live eval against production, which doesn't list tools a PR adds until it deploys. The new cases now skip when the server under test doesn't list the tool, and still run against the PR preview. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * fix(mcp): describe the search window honestly and score gated evals fairly - search_importers results cover the search index's 12-month window, which can end a month or two before the last full month. The period note, min_containers, and the tool description now say so and point to get_importer_profile for the latest months. - Eval cases for account-gated tools score a "not enabled" reply as the allowed outcome it is, so the scorecard and report no longer count it as a contract failure. Adds unit tests for that scorer. Raised in the Codex and Greptile reviews on the PR. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * docs(mcp): spell out TEU and HS, and describe the search window Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * fix(mcp): cap trend series by whole periods and breakdowns by whole levels Trend points arrive ordered by period then group, so cutting the flat series at 500 dropped the newest periods and could split one, while the reported range still claimed them. The series now keeps the most recent whole periods, reports the range actually returned, and counts the earlier periods left out. Breakdowns drop whole deeper levels instead of cutting a parent's children. Raised in Codex review on the PR. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * fix(mcp): describe default windows and min_containers precisely The trends and breakdown defaults run from about two years (or one year) back through the current, incomplete month, inclusive, so they are not a clean 24 or 12 months. Descriptions now say so and tell agents to set since and until for annual totals. min_containers applies to the search window in period, not the last 12 full months. Raised in Codex review on the PR. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * docs(mcp): describe the trends and breakdown default windows precisely Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * fix(mcp): keep a capped trend's range in one period format A capped quarterly or yearly series reported a range mixing a point label (2026-Q1) with a month boundary (2026-06). period now always holds the requested month range, and periods_shown gives the first and last periods actually returned, in the series' own labels. Raised in Codex review on the PR. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * chore: regenerate SDK types, SDK reference docs, and Postman collection Regenerated once on top of main after the rebase (main's SDK resync in Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * docs: state which trade intelligence dimensions can't be combined Lane dimensions (pol, pol_country, container_type) can't be combined with company or product dimensions in one request, and estimated_value isn't available by them; the API answers 400. The MCP tool descriptions said so but the API reference didn't. Raised in Codex review on the PR. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * docs: merge trade intelligence responses into the shared responses block Main's spec now declares components.responses (#438), and the rebase's text merge added the trade intelligence block as a second "responses" key in the same object. JSON parsers keep the last key, but YAML-strict tooling such as openapi-typescript rejects the duplicate. Both sets now live in one block. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * fix(sdk): let tradeIntel calls infer their types from the spec Transport.execute now infers the response body type from the typed openapi-fetch call (#439), so the explicit body type arguments no longer fit its signature. The inferred types are the same TradeIntel* schemas. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * chore: regenerate SDK types, SDK reference docs, and Postman collection Regenerated on top of main after #438 and #439. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * docs: require exact-length trade intelligence state, SCAC and HS4 codes State filters (company_state, dest_state, state) now require two letters, the scac filter four letters, and top importers hs4 four digits, matching the MCP tool schemas. maxLength alone let values like "F" or "123" through. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(sdk): keep the RetryInterceptor doc comment on the class The retry explanation sat above the new ReplayableRequest and RetryRequestParams declarations, so the generated reference lost it. Move it back above RetryInterceptor and say that read-only trade intelligence query POSTs are retried like GETs. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * chore: regenerate SDK reference docs and Postman collection Regenerated after the trade intelligence code patterns and the RetryInterceptor doc comment move. SDK types and MCP filter schemas are unchanged. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * chore: Auto-generate Postman collection from openapi.json [skip ci] * docs(mcp): say estimated_value is not available by lane The API rejects estimated_value with pol, pol_country or container_type as a dimension or filter (docs/openapi.json TradeIntelDimension and TradeIntelFilters). The MCP guide presented estimated_value as generally available, so it now states the restriction for get_trade_trends and get_trade_breakdown and lists the lane combination rules with the other trade intelligence caveats. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(mcp): warn that estimated_value is not available by lane The measure and dimension descriptions for get_trade_trends and get_trade_breakdown now say that estimated_value cannot be used with pol, pol_country or container_type, which the API answers with a 400. A protocol test checks the advertised measure description and the MCP docs section for both tools. Addresses the Codex review on packages/mcp/src/tools/trade-intel.ts:82. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * docs(mcp): count all 21 tools in the README, overview and setup guide The PR registers seven trade intelligence tools, but the MCP README header and table, the TOOLS_OVERVIEW summary and the setup guide's "list the tools" check still said 14. The README table now lists the trade intelligence tools too. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * test(mcp): keep documented tool counts and the README table on tools/list Fails when the README, TOOLS_OVERVIEW or the setup guide state a tool count other than the tools/list length, or when the README tool table misses or adds a tool. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * test(mcp): require the lane-filter case in the estimated_value warning The API rejects estimated_value when pol, pol_country or container_type is used at all, as a dimension or as a filter. The test now fails if the tool description or the docs drop the filter case. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * test(mcp): pin the trade trends and breakdown default windows Without since, the trade-intel service returns the N months ending with until (24 for trends, 12 for breakdown), so since is N - 1 months before until. The test fails while tools/list, docs/mcp/home.mdx or the OpenAPI spec describe the default as "N months ago" or "about a year back". Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * docs(trade-intel): state the trends and breakdown default windows exactly Without since, the API returns the N months ending with until (24 for trends, 12 for breakdown), so since defaults to N - 1 months before until. The since fields said "Defaults to 24/12 months ago", which reads as N + 1 calendar months. The breakdown and trends descriptions and the MCP guide now give the same window. Source: analytics._range in the trade-intel service. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * chore: Auto-generate Postman collection from openapi.json [skip ci] * fix(mcp,sdk): describe the trade default windows as the API computes them get_trade_trends and get_trade_breakdown said "about two years back" and "about a year back, not a clean 12 months". They now say the window is the 24 or 12 months ending with the current, incomplete month, with since 23 or 11 months before until. The SDK JSDoc, regenerated types and SDK reference match the OpenAPI source. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * docs(openapi): match the trade intelligence contract to what the service returns Checked against Terminal49/t49 staging (apps/trade-intel build.py, analytics.py, api.py, index.py): - meta dates (since_month, until_month_exclusive, facts_since_month, partial_month, refreshed_since) and company first_month/last_month are first-of-month dates, YYYY-MM-DD; facts_until_exclusive is the day after the build; mode is full or incremental. - importers/top: states can be null, estimated_value is always sent and is null without hs4, and filters echo port_of_discharge as pod. - companies/profile: until is absent when found is false, and totals.states can be null. - trends periods are always YYYY-MM (the first month of the quarter or year), not YYYY-Qn or YYYY. - a 422 can also be the query cost-cap error, { error }. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(sdk): type trade intelligence nulls and keep retries within maxRetries Regenerated types from the corrected spec, so TradeIntelTopImporter states and estimated_value, profile totals.states and profile until are typed as the service sends them, ranking filters expose pod, and the 422 body includes the cost-cap { error } variant. Type tests in spec-types.test.ts fail against the old types. Fixtures now use the service's real formats (YYYY-MM-DD meta dates, YYYY-MM quarterly periods), and the live smoke test expects facts_since_month as a first-of-month date. RetryInterceptor kept separate retry counters in onError and onResponse, so network failure, network failure, 503, 503, success made five requests with maxRetries 2. Both paths now draw on one per-request budget. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * chore: Auto-generate Postman collection from openapi.json [skip ci] * fix(mcp): read the trade index dates the service actually sends The service sends meta and search-window dates as YYYY-MM-DD, but previousMonth() only accepted YYYY-MM, so get_trade_data_coverage lost last_full_month and search_window.to, and search_importers lost period.to. Index dates are now accepted in either form and shown as YYYY-MM, including first_month and last_month. rank_importers also leaves out estimated_value_usd when the service sends null instead of emitting an undefined key. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
* feat(sdk): party roles and clearing custom fields on containers and shipments Adds partyRoles, addPartyRole, removePartyRole and clearCustomField to the container and shipment managers. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * feat(mcp): set_party_role and set_custom_field tools Lets an agent assign a party (such as the drayage carrier) or set an account custom field on up to 50 containers or shipments, reporting per-record updated, unchanged and failed results. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * docs(mcp): document set_party_role and set_custom_field Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * feat(mcp): set_party_role and set_custom_field tools Lets an agent assign a party (such as the drayage carrier) or set an account custom field on up to 50 containers or shipments, reporting per-record updated, unchanged, failed and not_attempted results. The tools call the SDK methods #433 and #439 added (partyRoles, addPartyRole, removePartyRole, setCustomField, deleteCustomField). The party_role list is parsed in the tool, since the SDK returns it unmapped. set_custom_field accepts an array of option values for multi-select fields and treats the same options in another order as unchanged. A call stops starting new records after 20s, under the 30s /mcp maxDuration, and lists the records it did not reach in not_attempted. The server now lists 16 tools; the protocol smoke scripts and tests expect that count and the two new names. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * docs(mcp): document set_party_role and set_custom_field Adds both tools to the MCP tools reference and TOOLS_OVERVIEW, including multi-select values and not_attempted, and updates the tool count in the setup guide to 16. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * chore: Auto-generate Postman collection from openapi.json [skip ci] * chore: keep main's Postman collection The bot regenerated it because the rebase changed docs/openapi.json relative to the old branch tip, but the spec is identical to main's, and openapi2postmanv2 output differs from run to run. This PR does not change the API, so it carries no collection change. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(mcp): make set_party_role swap single-party roles explicitly and mark record edits destructive A container accepts a second pickup_dray_carrier beside the first, so "add" with a different carrier left two carriers on the record and reported success. Adding one to a shipment deletes the previous carrier server-side, and a second shipment customer hits a unique index. - add now fails a record where a different party already holds pickup_dray_carrier or customer, and names the replace action. - New replace action makes the party the only one in the role: it attaches first, then detaches the others, treats an already-deleted assignment as done, and reports a partial swap. It refuses to replace a shipment customer rather than leave the shipment without one. - set_party_role and set_custom_field now set destructiveHint: true. Per the MCP spec, false means additive-only; detaching a party or overwriting a field value is not. The ChatGPT listing follows. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * docs(mcp): document set_party_role replace and the destructive record edits Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(mcp): report set_party_role swap failures accurately From cross-vendor review of the replace action: - A second shipment customer on add now points to remove-then-add instead of replace, which refuses customers. - A failed detach no longer says the party was attached when the record already had it and no POST was made. - The action description names the customer exception. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * chore: Auto-generate Postman collection from openapi.json [skip ci] * chore: keep main's Postman collection The generator bot regenerated the collection on this branch after the main merge; the output differs from main's only in regenerated IDs. The PR changes no API surface, so it carries main's file unchanged. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * test(mcp): cover record writes still running at the call deadline The 20s budget only stopped workers from starting new records. A read or write already in flight kept the call open, and an SDK request can take 30s plus retries, past the hosted function's 30s limit. These cases fail until applyEach stops waiting at the deadline. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(mcp): stop record writes at the call deadline, not just new starts applyEach now races each record against a 20s deadline. A record whose read was cut off is reported in not_attempted (nothing changed). A record with a write in flight is reported in failed, saying it may or may not have been applied. Every write goes through a save guard, so no write starts after the call has given up on that record. The SDK cannot cancel a request, so the call stops waiting rather than aborting. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * docs(mcp): say what set_party_role and set_custom_field return at the deadline Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(mcp): count sign-in time against the record-write deadline The 20s budget for set_party_role and set_custom_field started inside the executor, after the gateway had resolved the WorkOS token. A slow resolve plus a full batch could still run past the 30s function limit, and the client got nothing back. The gateway now fixes the deadline when the request arrives and passes it to the server, so auth time and record work share one budget. Auth policy, token validation and access are unchanged. A deadline already spent before the call starts touches no record and lists every ID as not_attempted. The text content of these two tools was only the summary line, so text-only clients never saw which records failed, which were not reached, or that a write may have applied. Their text content is now the full result. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * docs(mcp): say the hosted write deadline counts from the request Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * test(mcp): hold set_party_role to the request-wide write deadline The gateway tests only called set_custom_field, so dropping the deadline or the full-result text from set_party_role went unnoticed. This case fails if either is removed. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * docs(mcp): name the credential check in the write deadline and the may-have-applied case Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * test(mcp): isolate the write gateway tests from ambient gateway env A shell with T49_API_TOKEN or T49_MCP_ALLOWED_HOSTS exported sent these tests down the server-token or host-rejection branch. They now clear and restore the same variables api-handler.test.ts clears. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * chore: Auto-generate Postman collection from openapi.json [skip ci] * fix(mcp): warn that a record write may have applied when it fails after dispatch A write that failed after it was sent, for example on a dropped connection, a request timeout or a 5xx, was reported as "The change could not be saved." The write may have been applied, so the record now says so and asks the caller to check it, in the structured result and in the full-result text. Only a 4xx answer counts as a refused write. Which requests the SDK retries is unchanged. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> * fix(mcp): bound the OAuth resolver call by the request deadline The gateway resolves a WorkOS token with a fetch to the Terminal49 backend before the tool call starts. Neither that fetch nor reading its response body had a deadline, so a slow resolver could hold the request past the 30s function limit, even though the write tools already count sign-in time against CALL_BUDGET_MS. The fetch and the body read now share an AbortSignal that fires at the request's deadline. Running past it is an upstream failure, so the client gets the same generic 502 as any other resolver outage. The auth policy and the client-facing errors are unchanged. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> * test(mcp): warm the gateway import before the record-write gateway tests The first test in record-writes-gateway.test.ts timed out at 5s once in a full `vp run test`, and passed on the rerun and in isolation. That test paid for the cold import of api/mcp.ts and the whole server graph: run alone, the first test takes about 870ms cold and 130ms warm, and with every package testing at once the cold import ran past 5s. A beforeAll now imports the gateway once with a 60s hook timeout, so each test re-imports it after vi.resetModules() from the warm transform cache. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> * docs(mcp): say a write that fails without a clear answer may have applied Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> Co-authored-by: Akshay Dodeja <dodeja@gmail.com> Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
Why
#433 merged before its review and live-test fixes landed. Live runs against the latest staging found SDK methods that sent the wrong request, called the wrong route, or returned unmapped data. This PR fixes them on top of #438.
What changed
Scope
sdks/typescript-sdk,docs/sdk, and the SDK nav. It also includes the minimal CLI edits needed to compile, such aswebhook-notifications examples --event.Tradeoffs
Writes are typed from the spec instead of
Record<string, any>. That catches bad payloads at compile time, but callers who passed undocumented attributes will now get a type error.Blast Radius
Commits marked
!break existing call sites. They change mapped results for containers.get/update, vessels, and tracking requests, tracking request includes, the custom field definition routes, document update params, and the webhook notification examples' required event. Each commit has a BREAKING CHANGE footer.Verification
vp run checkandvp run testpass at every commit, and lint and generators are clean at the tip.Base: #438.
🤖 Generated with Claude Code
Need help on this PR? Tag
@codesmith-botwith what you need.The PR appears safe to merge, with non-blocking fixes needed for bodyless result types and the fixture capture script.
Fix with agent prompt
Summary
This PR corrects SDK requests, routes, mapped results, and write types.
tracked_object.documentTypeand can set or clear classification notes.Reviews (1) · Last reviewed commit: "fix(sdk): send the caller's numberType f..." · Reviewed by Greptile