Skip to content

fix(helpers): make X-AIMock-Strict header parsing case-insensitive and whitespace-tolerant - #408

Merged
jpr5 merged 2 commits into
CopilotKit:mainfrom
Ayush7614:fix/strict-header-case-insensitive
Sep 9, 2026
Merged

fix(helpers): make X-AIMock-Strict header parsing case-insensitive and whitespace-tolerant#408
jpr5 merged 2 commits into
CopilotKit:mainfrom
Ayush7614:fix/strict-header-case-insensitive

Conversation

@Ayush7614

Copy link
Copy Markdown
Contributor

Clients, proxies, and shell snippets often emit X-AIMock-Strict: True, TRUE, or true with surrounding whitespace. The previous resolveStrictMode compared literally against "true"/"false"/"1"/"0", so those variants silently fell back to the server default.

On a --strict false server that meant X-AIMock-Strict: True stayed at 404 instead of 503; on a --strict true server X-AIMock-Strict: False stayed strict. The same header also gates reasoning suppression, so the mismatch could surface as flaky reasoning assertions.

Change:

  • Trim surrounding whitespace and lower-case the header value before comparing
  • "true"/"TRUE"/" True " / "1" / " 1 " → strict on
  • "false"/"FALSE"/" false " / "0" / " 0 " → strict off
  • Unrecognised values still fall back to serverDefault

Verified:

  • pnpm run format:check / lint / typecheck clean
  • Manual probe via createServer:
    strict:false + True/TRUE/" TRUE "/1 → 503 (was 404 before on capitalised variants)
    strict:true  + False/FALSE/" FALSE "/0 → 404 (was 503 before)
    unknown "maybe" → falls back to server default
    
  • Existing openrouter-video.test.ts strict-header tests (lower-case) still pass (123 passed)

…d whitespace-tolerant

Clients and proxies often normalize or user-configure header values
with varying case (True/TRUE) or surrounding whitespace. The previous
strict check compared literally against "true"/"false"/"1"/"0",
so "True" or " true " silently fell through to the server default,
causing surprising 404s/503s and reasoning suppression mismatches.

Trim and lower-case the value before comparison so "True", "TRUE",
" true " etc. are accepted. Unrecognised values still fall back to
the server default.
@pkg-pr-new

pkg-pr-new Bot commented Sep 8, 2026

Copy link
Copy Markdown

Open in StackBlitz

npm i https://pkg.pr.new/@copilotkit/aimock@408

commit: 9ed42c3

The fix shipped with no test and no changelog line, and `strict-header.test.ts`
already had a `resolveStrictMode` block covering the exact-match cases — so the
new behaviour had an obvious home and simply wasn't filled in.

Six tests: casing on both directions, surrounding whitespace including tabs and
CRLF, the two combined, and the repeated-header array form (which takes the
same normalisation path but reaches it through a different branch).

Two of them are negative controls, and they are the point. Trimming and
lower-casing WIDEN what the comparison accepts, so the positive cases alone
would also pass on an implementation that treated any non-empty string as
truthy. So: `""`, `"   "`, `"truthy"`, `"t"`, `"yes"`, `"on"`, `"2"`, `"-1"`,
`"10"` must all still fall back to the server default, and trimming is
END-only — `"tr ue"` and `"fa lse"` are different tokens, not padded ones, and
must not match.

Mutation-tested — reverting `resolveStrictMode` to the pre-fix literal
comparison reds 4 of the 27 tests in the file; restored, 27 pass.

typecheck (all three configs) exit 0; full suite 180 files / 5644 tests;
eslint and prettier clean.
@jpr5
jpr5 merged commit 13e5e3d into CopilotKit:main Sep 9, 2026
24 checks passed
@jpr5 jpr5 mentioned this pull request Sep 9, 2026
jpr5 added a commit that referenced this pull request Sep 9, 2026
Cuts the accumulated `[Unreleased]` work. **72 non-merge commits since
v1.39.0, 42 of them `feat:`/`fix:`** — minor, not patch: two additive
features, no breaking changes.

**Prepared, not merged.** `publish-release.yml` fires on push-to-main,
so merging this publishes to npm, tags, cuts the GitHub Release,
dispatches the Docker build, and posts to `#oss-alerts`. Merge when you
want it live.

## What ships

**Added** — `GET /__aimock/fixtures` fixture-count introspection (#407)
· recorded OpenAI/OpenRouter token usage incl. OpenRouter `usage.cost`
(#368) · AG-UI subagent lifecycle events + `subagentRunId` attribution
(#391)

**Fixed** — `X-AIMock-Strict` parsed case-insensitively with whitespace
tolerated (#408) · non-websocket upgrade probes no longer 404 and the
body survives on every supported Node (#410#418) · AG-UI drift
collector no longer reports clean for an unreadable failure · AG-UI
canonical parser no longer drops a field after a trailing comment ·
`openrouter` no longer logged as an unknown SSE provider when recording

**Changed** — the AG-UI drift CI lane runs every `agui-*.drift.ts` guard
rather than one hardcoded file

## Version surfaces — seven

Found by grepping the repo for `1.39.0` rather than working from a
checklist, because the checklist is what went wrong at v1.34.0 (four of
six surfaces shipped stale). **Zero occurrences of the old string
remain:**

| # | file | |
|---|---|---|
| 1 | `package.json` | `version` |
| 2 | `charts/aimock/Chart.yaml` | `appVersion` |
| 3 | `.claude-plugin/plugin.json` | `version` |
| 4 | `.claude-plugin/marketplace.json` | the `^` range under
`plugins[0].source` |
| 5 | `packages/aimock-pytest/.../_version.py` | `AIMOCK_VERSION` — the
npm pin the pytest harness downloads by default |
| 6 | `docs/index.html` | the version badge |
| 7 | `packages/aimock-pytest/README.md` | the documented
`--aimock-version` default |

Two of those (6, 7) aren't in the release SOP's list but carry the
version string and would have shipped stale.

`packages/aimock-pytest/pyproject.toml` stays at **0.5.3** — that's the
Python package's own version, on its own PyPI cadence.

## Two things I checked rather than assumed

**Bumping `_version.py` in the release commit is safe.**
`publish-pytest` runs `npm view @copilotkit/aimock@$AIMOCK_VERSION`,
which would fail against an unpublished version — but it's `needs:
[build, publish]` and gated on `needs.publish.result == 'success'`, so
1.40.0 is on npm by the time it reads the pin. No chicken-and-egg.

**`package.json.description` is deliberately not hand-synced.** It
diverges from the README subtitle in git, which looks like the drift the
SOP warns about. It isn't: `publish-release.yml` rewrites it from the
README subtitle *in the runner* before `npm publish`, without
committing. Editing it here would fight the workflow.

## README

Gains a **Recorded token usage and cost** bullet. The feature list
enumerates record/replay capabilities individually (timing-aware replay,
multi-turn, ordered blocks), so a user-visible one landing without an
entry is a real omission. The new control-API route needs no README line
— the README documents no `/__aimock/*` routes at all; that lives in
`docs/control-api`.

## Verification

`typecheck` (all three configs) exit 0 · `build` exit 0 ·
release/publish-pin/drift-script workflow tests 44 passing · prettier
clean · commit body wrapped ≤100 cols for commitlint.

Full-suite note: five tests failed on a first run and all five passed in
isolation — `cli.test.ts` SIGTERM, `multimedia.test.ts` transcription
frame scheduling, `proxy-buffer-cap.test.ts`, and both
`publish-pin-workflow.test.ts` real-pip cases. All wall-clock or
network-bound, and none reachable from a version-string change. CI is
the arbiter.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

https://claude.ai/code/session_01Y445N6QQBeAdiLcEvEpqGe
pull Bot pushed a commit to TheTechOddBug/aimock that referenced this pull request Sep 9, 2026
Cuts the accumulated Unreleased work: 72 non-merge commits since v1.39.0, 42 of
them feat/fix. Minor, not patch — two additive features, no breaking changes.

### Added

- GET /__aimock/fixtures — read-only fixture-count introspection (CopilotKit#407)
- Recorded OpenAI/OpenRouter token usage, including OpenRouter usage.cost (CopilotKit#368)
- AG-UI subagent lifecycle events + subagentRunId attribution (CopilotKit#391)

### Changed

- The AG-UI drift CI lane runs every agui-*.drift.ts guard, not one file

### Fixed

- X-AIMock-Strict is parsed case-insensitively, whitespace tolerated (CopilotKit#408)
- Non-websocket upgrade probes no longer 404, and the body survives on every
  supported Node (CopilotKit#410, CopilotKit#418)
- The AG-UI drift collector no longer reports clean for an unreadable failure
- The AG-UI drift canonical parser no longer drops a field after a trailing
  comment
- openrouter is no longer logged as an unknown SSE provider when recording

Version surfaces bumped — seven, verified by grepping the repo for the old
string rather than working from a list (zero occurrences remain):
package.json, charts/aimock/Chart.yaml appVersion, .claude-plugin/plugin.json,
.claude-plugin/marketplace.json (the `^` range under plugins[0].source),
packages/aimock-pytest/src/aimock_pytest/_version.py (the npm pin the pytest
harness downloads by default), docs/index.html's version badge, and
packages/aimock-pytest/README.md's documented default.

packages/aimock-pytest/pyproject.toml stays at 0.5.3 — that is the Python
package's own version, released on its own PyPI cadence.

README gains a Recorded-token-usage-and-cost bullet: the feature list
enumerates record/replay capabilities individually, so a user-visible one
landing without an entry there is a real omission. The new control-API route
needs no README line — the README documents no /__aimock/* routes at all; it is
covered in docs/control-api.

package.json's description is deliberately NOT hand-synced: publish-release.yml
rewrites it from the README subtitle in the runner before npm publish, without
committing, which is why git and the npm page differ by design.
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.

2 participants