Skip to content

fix(sdk): TypeScript SDK request/response parity + missing /api prefix (#15528, #16495) - #16497

Merged
mrveiss merged 5 commits into
mainfrom
issue-15528-sdk-ts-parity
Sep 13, 2026
Merged

mrveiss merged 5 commits into
mainfrom
issue-15528-sdk-ts-parity

Conversation

@mrveiss

@mrveiss mrveiss commented Sep 12, 2026

Copy link
Copy Markdown
Owner

Thinking Path

The Python SDK's request/response shapes were already fixed against the real
routes; the TypeScript package still sent the old, wrong ones (#15528 lists
sessions.list's positional limit/offset, knowledge.search's GET+query-
params call, analytics.usage/performance's ignored period argument, and
several invented response fields, among others). Fixing the request shapes
required rewriting types.ts against the real backend models rather than the
SDK's own prior (wrong) assumptions, since the two had diverged together.

While tracing every resource method against the real routes, agents.ts's
getConfig/updateConfig turned out to call a bare /agent/config with no
agent id at all -- confirmed against autobot-backend/api/agent_config.py's
actual route table that per-agent config lives under
/agent_config/agents/{agent_id}/..., and that no PUT route exists for
updateConfig on either mount. updateConfig is removed; setModel/
setEnabled are added to reach parity with the Python SDK's already-correct
surface.

Also discovered mid-work: every TypeScript SDK request was missing the /api
prefix entirely, so no request built by this package could reach a real
backend regardless of path or body correctness. This is the same defect
#15053 already found and fixed once in the Python package, filed here as its
own issue (#16495) for traceability and fixed in this PR since shipping
#15528 without it would leave the SDK still fully non-functional.

This checker cannot execute TypeScript, so verification is a new static-
analysis guard (repo_tests/sdk_ts_request_contract_test.py) rather than a
runtime test: it text-scans each resource method's source for its HTTP verb,
path template and sent field names, and checks both that the source matches
its own pinned table and that the table matches the real backend route
table -- reusing the same route_query_params/route_request_bodies
oracle fixtures the existing Python SDK guards already use, so there is only
one source of truth for what the backend actually accepts.

What Changed

  • libs/autobot-sdk-ts/src/client.ts — added API_PREFIX/apiPath(),
    mirroring the Python SDK's api_path(); buildUrl() now applies it (bug(sdk): every TypeScript SDK request omits the /api prefix — 404 against a real backend #16495).
  • libs/autobot-sdk-ts/src/types.ts — rewritten to match the backend's real
    response shapes for sessions, chat messages, agent health/config, knowledge
    entries/stats/search, and analytics usage/performance.
  • libs/autobot-sdk-ts/src/resources/sessions.ts — list(scope?, teamId?),
    get(sessionId, page?, perPage?).
  • libs/autobot-sdk-ts/src/resources/knowledge.ts — search() is now a POST
    with a JSON body; getEntries() is cursor-paginated, not offset-paginated.
  • libs/autobot-sdk-ts/src/resources/analytics.ts — usage()/performance()
    take no arguments and hit the routes' real paths.
  • libs/autobot-sdk-ts/src/resources/agents.ts — health()'s path gained its
    missing /agent segment; getConfig(agentId) now targets the per-agent
    route; updateConfig() removed (no backend route); setModel/setEnabled
    added.
  • libs/autobot-sdk-ts/src/index.ts — export list extended with the new
    nested types.
  • libs/autobot-sdk-ts/tests/live/backend.test.ts — updated one call site for
    sessions.list()'s new signature.
  • repo_tests/sdk_ts_request_contract_test.py (new) — static contract guard
    for the TS SDK, pinned against the same backend-derived oracle the Python
    SDK guards use.
  • docs/sdk/README.md — feature-coverage table and defect notes brought
    current.
  • changelog/unreleased/15528-sdk-ts-parity.md (new).

Verification

  • repo_tests/sdk_ts_request_contract_test.py: ran via the repo's pre-push
    hook (this checker does not execute product code directly) — passed.
  • black, isort, flake8 --max-line-length=120, ruff check, and
    python3 -c "import ast; ast.parse(...)" all pass clean on the new guard
    file.
  • Every .ts change was manually traced against the actual backend route
    decorators (autobot-backend/api/agent_config.py,
    autobot-backend/api/knowledge*.py, autobot-backend/api/analytics.py,
    autobot-backend/api/chat_sessions.py) rather than executed, since this
    checker has no TypeScript runtime available.
  • Pre-commit (black/isort/flake8/hardcoded-values/detect-secrets/etc.) is
    green on the full diff.

Closes #15528
Closes #16495

Model Used

Claude Sonnet 5

🤖 Generated with Claude Code

…#15528, #16495)

sessions.list sends scope/team_id, knowledge.search is a POST with a JSON
body, analytics.usage/performance take no arguments, and several response
types now match the flat documents or DataResponse envelopes the backend
actually returns instead of invented shapes. agents.updateConfig is
removed -- no PUT route for it exists on either mount -- and setModel/
setEnabled are added to match the Python SDK.

Every request was also missing the /api prefix entirely (#16495), the
same defect #15053 already found once in the Python package; buildUrl
now applies it the same way Python's api_path() does.

repo_tests/sdk_ts_request_contract_test.py is a new static-analysis guard
that pins both the TS source's own requests and the real backend route
table (reusing sdk_request_url_test.py's oracle fixtures) since this
checker cannot execute TypeScript.
@mrveiss mrveiss added this to the v0.10.0 milestone Sep 12, 2026
@mrveiss mrveiss added bug Something isn't working priority: medium frontend area: api-contract Wave 2 · cluster C — Frontend↔backend API contract labels Sep 12, 2026
@mrveiss mrveiss self-assigned this Sep 12, 2026
@coderabbitai

coderabbitai Bot commented Sep 12, 2026 •

Copy link
Copy Markdown
Contributor

Important

  • 🔍 Trigger review

This repository does not receive automatic reviews because it has fewer than 10 stars.

⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Advanced

Run ID: f6973860-315a-493c-a0a4-d7b281f831c4


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@github-actions

Copy link
Copy Markdown
Contributor

Notice: 29 open PRs — past the runaway threshold (25)

There is no PR queue limit, and this is not a request to defer this PR. Work proceeds one issue at a time without a cap on open PRs; review capacity is the constraint.

This notice only means the count is high enough to be worth a glance for a runaway — something opening PRs in a loop, or a merge pipeline that has stalled so nothing is draining.

Currently open:

If the queue is draining normally, ignore this. Otherwise:

  1. Check whether CI is dispatching at all — see the ci-dispatch-watchdog status on these PRs
  2. Merge the ones whose CI has finished and review has passed: gh pr merge <number> --squash --delete-branch
  3. Look for a loop opening near-identical PRs

Warn-only runaway detector — .github/workflows/pr-queue-gate.yml. It never blocks a merge.

@github-actions

Copy link
Copy Markdown
Contributor

✅ SSOT Configuration Compliance: Passing

🎉 No new hardcoded values of either class — ssot and other both block.

Known backlog in pipeline-scripts/hardcoded_values_baseline.txt is suppressed and tracked in #14371.

@mrveiss

mrveiss commented Sep 12, 2026

Copy link
Copy Markdown
Owner Author

Independent review: changes requested.

Severity Where Evidence Fix
HIGH repo_tests/sdk_ts_request_contract_test.py / libs/autobot-sdk-ts/src/client.ts #16495's AC2 says: "A guard test proves it (mock fetch, assert the actual request URL carries /api/...)." The new guard is a text scan of each method's relative path literal. It never reads client.ts and never exercises apiPath(), API_PREFIX or buildUrl(), and no test mocks fetch. Nothing proves the prefix reaches the wire, yet the PR closes #16495 Add a Jest test in libs/autobot-sdk-ts/tests/ that mocks global.fetch, calls a resource method, and asserts the captured URL starts with /api. Or, if the guard stays static, parse client.ts and assert every call routes through apiPath()
MEDIUM test_the_table_covers_every_resource_file_that_exists It reads with Path.glob rather than tools.lint._scan_helpers.tracked_paths. It also asserts pinned <= on_disk, which only checks that named files exist, so a new resource file with unpinned methods passes. Together with the hardcoded 4-file parametrize list, the guard can't catch the drift it's named for Enumerate with tracked_paths(repo_root(), "libs/autobot-sdk-ts/src/resources/*.ts"), and assert that the files on disk are a subset of the pinned ones, or equal to them

Verified correct:

  • Every sessions, knowledge, analytics and agents mapping matches its backend route and Pydantic schema.
  • updateConfig is correctly removed: no PUT route exists.
  • The setModel/setEnabled bodies match AgentModelUpdate and enable/disable.
  • The README's known-defects note is correctly removed.

Non-blocking:

  • The changelog fragment doesn't mention the removed agents.updateConfig or getConfig's new required agentId, which are the most visible breaking changes.
  • The oracle check ignores the route's media type, which the Python sibling asserts.
  • knowledge.search drops category, claiming the route has none, but SearchRequest.category exists. That mirrors the same wrong claim in the Python SDK, and is filed separately against both.

… the file-coverage guard (#15528, #16495)

HIGH: the static guard text-scanned each method's relative path
literal but never touched client.ts, so nothing proved the /api
prefix actually reaches a request -- exactly what #16495's AC2 asks
for. Added libs/autobot-sdk-ts/tests/apiPrefix.test.ts: mocks
global.fetch, calls one method per resource plus setEnabled's
runtime-chosen segment, and asserts the captured URL starts with
/api (plus one case proving apiPath() doesn't double-prefix a path
that already carries it).

MEDIUM: test_the_table_covers_every_resource_file_that_exists used
Path.glob and asserted pinned <= on_disk, so a new resource file
with unpinned methods would pass silently, and the parametrize list
for the completeness test was a hardcoded 4 files. Both now derive
from tools.lint._scan_helpers.tracked_paths, and the coverage
assertion is on_disk == pinned in both directions.

Also added, since it was sitting right next to the fields check the
oracle already gave media type for: every POST/PUT row now asserts
the route publishes application/json, not just that the field names
match -- the same defect class #15527 fixed on the route side
(a Form field beside a dict body publishes as
x-www-form-urlencoded, which no JSON body can satisfy). Verified
statically against agent.py's current execute_command that #15527's
fix already makes this hold before adding the assertion.

Non-blocking items: changelog fragment now names agents.getConfig's
new required agentId and the removed updateConfig. The category-field
gap in knowledge.search was filed separately (#16543, already open)
and isn't touched here.
@mrveiss

mrveiss commented Sep 12, 2026

Copy link
Copy Markdown
Owner Author

Both blocking items addressed, pushed at abed6aa:

  • HIGH: added libs/autobot-sdk-ts/tests/apiPrefix.test.ts — mocks global.fetch, calls one method per resource (sessions/knowledge/agents/analytics) plus setEnabled's runtime-chosen trailing segment, and asserts the captured URL starts with /api. One more case proves apiPath() doesn't double-prefix a path that already carries it.
  • MEDIUM: test_the_table_covers_every_resource_file_that_exists and the completeness test's parametrize list now both derive from tools.lint._scan_helpers.tracked_paths, not Path.glob or a hardcoded 4-file list. The coverage assertion is now on_disk == pinned (was pinned <= on_disk), so an unpinned new resource file fails instead of passing silently.

Also added a media-type assertion next to the existing field-name check (every POST/PUT row now asserts the route publishes application/json, the same defect class #15527 fixed) — flagged non-blocking but small enough to fold in here. Changelog fragment now names getConfig's new required agentId and the removed updateConfig. Left the category gap alone since #16543 already tracks it for both SDKs.

Re-ran the guard via the pre-push hook (this checker has no Node runtime, so the new Jest file couldn't be exercised locally the same way -- it's real for the first time in CI): pytest: all relevant tests pass.

@mrveiss

mrveiss commented Sep 12, 2026

Copy link
Copy Markdown
Owner Author

Delta 6a9f103d8..abed6aa4d: verified, approved.

… this package's ESM preset, and record the new TS-tree glob dependency

NPM Package Tests failed with "ReferenceError: jest is not defined" on
every test in apiPrefix.test.ts. describe/test/expect are ambient
globals under ts-jest/presets/default-esm + --experimental-vm-modules,
but the jest mock-utility object is not -- it has to come from
@jest/globals explicitly, unlike the CJS preset every other file in
this package predates. Fixed by importing it and typing the mock as
ReturnType<typeof jest.fn> instead of the CJS-only jest.Mock.

Separately, python-suite shard 8/12 failed
glob_declared_reads_15900_test.py: sdk_ts_request_contract_test.py's
new tracked_paths(_REPO, "libs/autobot-sdk-ts/src/resources/*.ts") is
a glob into a tree the python path-filter does not cover, and #15900's
guard requires that dependency be written down rather than silently
invisible to the filter. Added the entry to GLOB_DECLARED_UNCOVERED.
@mrveiss

mrveiss commented Sep 12, 2026

Copy link
Copy Markdown
Owner Author

Two red-CI issues found and fixed at 406c76d:

  • NPM Package Tests: apiPrefix.test.ts failed everywhere with ReferenceError: jest is not defined. This package's jest config uses the ESM preset (ts-jest/presets/default-esm + --experimental-vm-modules); under that preset describe/test/expect are ambient globals but the jest mock-utility object is not — it needs an explicit import { jest } from "@jest/globals", which no prior file in this package needed since none used jest.fn() before. Fixed, and retyped the mock as ReturnType<typeof jest.fn> instead of the CJS-only jest.Mock.
  • python-suite shard 8/12: glob_declared_reads_15900_test.py failed because the new tracked_paths(_REPO, "libs/autobot-sdk-ts/src/resources/*.ts") call in the guard is a glob into a tree the python path-filter doesn't cover, and guard(ci): the filter-coverage checker ignores glob-declared reads, so its green over-claims #15900's guard requires that be recorded rather than silently invisible. Added the entry to GLOB_DECLARED_UNCOVERED.

Re-ran both via the pre-push hook where possible (the glob-coverage one for real; the Jest fix can't run locally here, no Node runtime — CI is its first real execution).

@mrveiss

mrveiss commented Sep 12, 2026

Copy link
Copy Markdown
Owner Author

Delta abed6aa4d..406c76d08: verified, still approved. Two files, +12/−1:

… an inferred one (#16495)

The ESM-globals fix in the last commit cleared "jest is not defined",
but NPM Package Tests then failed to even compile: TS2345, because
jest.fn() called with no type argument infers Mock<never>, so
mockResolvedValue(...) rejected the real response object outright.

Spelled out the call signature -- jest.fn<(...args: unknown[]) =>
Promise<FakeFetchResponse>>(impl) -- and passed the implementation
alongside it instead of chaining mockResolvedValue, so there is one
generic instantiation to satisfy rather than an inferred one meeting
a separate call. .mock.calls[0]'s cast to [string] is a widen from
unknown[], not a narrow from a concrete tuple, for the same reason.
@mrveiss

mrveiss commented Sep 12, 2026

Copy link
Copy Markdown
Owner Author

Follow-up fix at c60eb2d: the ESM-globals fix cleared the ReferenceError, but then NPM Package Tests failed to compile -- TS2345, because jest.fn() called with no type argument infers Mock<never>, so mockResolvedValue(...) rejected the real response object. Spelled out the call signature explicitly (jest.fn<(...args: unknown[]) => Promise<FakeFetchResponse>>(impl)) and passed the implementation alongside it instead of chaining mockResolvedValue separately, so there's one generic instantiation to satisfy instead of an inferred one meeting a separate call.

@mrveiss

mrveiss commented Sep 12, 2026

Copy link
Copy Markdown
Owner Author

Delta 406c76d08..c60eb2d50: still approved. Across the net diffs against main, the only file whose change differs is libs/autobot-sdk-ts/tests/apiPrefix.test.ts. There, c60eb2d50 gives the fetch mock an explicit jest.fn<(...args: unknown[]) => Promise<FakeFetchResponse>> signature, with the implementation passed inline. The earlier bare jest.fn() inferred Mock<never>, which is why it failed with TS2345. This is a test-typing change only; the SDK sources are identical to what I approved. cc3514448 in the range is main's commit, brought in by a merge. I did not re-check CI here.

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

Labels

area: api-contract Wave 2 · cluster C — Frontend↔backend API contract bug Something isn't working frontend priority: medium

Projects

None yet

1 participant