Skip to content

Update dependency @copilotkit/aimock to v1.47.0 - #2277

Open
renovate[bot] wants to merge 1 commit into
mainfrom
renovate/copilotkit-aimock-1.x
Open

renovate[bot] wants to merge 1 commit into
mainfrom
renovate/copilotkit-aimock-1.x

Conversation

@renovate

@renovate renovate Bot commented Oct 31, 2026

Copy link
Copy Markdown
Contributor

Note

This PR body was truncated due to platform limits.

This PR contains the following updates:

Package Change Age Confidence
@copilotkit/aimock (source) 1.35.0 → 1.47.0 age confidence

Release Notes

CopilotKit/aimock (@​copilotkit/aimock)

v1.47.0

Compare Source

Added
  • Fixture citations serve sources on Responses, Chat Completions, Claude and Gemini (#​550, #​578)
  • Fixture files serve generated images on Gemini and OpenAI Responses (#​550, #​578)
  • Raw passthrough fixtures send provider SSE events or JSON exactly as written (#​580)
  • Opt-in nonStreamingTimingAllProviders delays non-streaming replies on every provider (#​579)
Fixed
  • Client disconnects stop and journal every LLM reply; Docker image on Node 22.23.4 (#​579, #​576)

v1.46.0

Compare Source

Added
  • Gemini model list/detail, Anthropic model detail, and token-count routes (#​542, #​551)
  • Opt-in policies: requiredHeaders, native authErrors, native modelCatalog (#​542, #​566, #​568)
  • Opt-in fixture nonStreamingTiming: true delays non-streaming OpenAI chat replies (#​545, #​567)
  • Anthropic/Bedrock cache usage kept in replay and record; Responses phase override (#​531, #​542)
Fixed
  • Ollama embed batches, AG-UI thread/run IDs, empty MCP reads, Anthropic aborts (#​533, #​553, #​557)

v1.45.1

Compare Source

Changed
  • OpenRouter Video: openai/sora-2 now returns OpenRouter's 400 removal error, not a job (#​520)
Fixed
  • MCP tools/list: a tool registered without inputSchema now lists {"type":"object"} (#​529)
  • MCP tools/list: a registered inputSchema with no type now gets "type": "object" (#​529)

v1.45.0

Compare Source

Every fixture, config and request that 1.44.0 accepted behaves the same by default. New behavior that would change those inputs is opt-in.

Added
  • Record live MCP servers into mcpFakes files and replay them offline: --mcp-record <mount>=<url> and --mcp-proxy-only <mount>=<url> (llmock bin and Docker image), llm.record.mcp with the opt-in llm.enableMcpRecording: true for aimock --config (without the opt-in, llm.record.mcp is ignored with a warning, as 1.44.0 ignored it), and MCPMock.enableRecording() / disableRecording() (#​509).
  • Recordings never contain headers and redact known secrets (AIMOCK_RECORD_SECRET_VALUES, AIMOCK_MCP_UPSTREAM_AUTH); a file is not written while a secret remains (#​509).
  • Recorded MCP fakes replay list order, progress and log notifications, and call timing (scaled by --replay-speed). The recorded mcpFakes keys (list, recorded, timing, notifications, durationMs) need 1.45.0 or later (#​509).
  • Per-test MCP fake report at GET /__aimock/mcp/fakes/report (#​509).
  • Vitest and Jest: fakesFor() and fakesReport() on a new AimockFakesHandle returned by useAimock(), and opt-in useAimock({ fakesReport: "warn" | "fail" }) (default "off"); Jest option testIdRoot. New exports assertFakesReport, AimockFakesReportError and AimockTestIdCollisionError (#​509).
  • aimock-pytest 0.7.0: fakes_for, fakes_report and assert_fakes_report (#​509), and AIMockServer opt-ins enable_misbehavior and responses_tools (#​508, #​511).
  • Workflow-target examples: a Mastra Workflow and a LangGraph JS graph call MCP fakes with no LLM (examples/mcp-workflow-targets), plus record and report proofs (#​509).
  • Model misbehavior faults (invalid tool arguments, schema violations, unknown tools, duplicate IDs, length stops, empty responses, refusals, content filtering, reasoning-only) across provider wires, with seeded rates, /__aimock/misbehavior runtime controls, journal evidence and the aimock_misbehavior_total metric. Off unless enabled with enableMisbehavior: true (programmatic, including useAimock), llmock --misbehavior / aimock validate --misbehavior, or aimock --misbehavior, or the config opt-in llm.enableMisbehavior: true (an llm.misbehavior key alone never enables it, as in 1.44.0). Invalid misbehavior settings throw the new MisbehaviorConfigError (#​508).
  • Namespaced function calls and custom tool calls on the OpenAI Responses API, over HTTP, SSE and WebSocket, plus match.toolNamespace (#​511). Like the fixture fields below, match.toolNamespace applies only with responsesTools: "extended"; in the default mode it is ignored, as in 1.44.0.
  • Responses namespaced and custom tool calls: with responsesTools: "extended" (CLI --responses-tools extended, also on aimock validate; config llm.responsesTools), tools inside namespace tools, custom tools and additional_tools / tool_search_output tools are visible to toolName, toolNamespace and predicates, custom tool rounds count for hasToolResult, toolCallId and turnIndex, and fixtures can answer with namespaced calls and customToolCalls / responsesBlocks. The default "legacy" keeps 1.44.0 behavior. New types: CustomToolCallMessage, ResponsesToolsMode (#​511).
  • Record mode keeps Responses namespaces and custom tool calls (customToolCalls, responsesBlocks, CollapseResult.customToolCalls) in streamed recordings when responsesTools: "extended" is set (#​513).
  • Strict tool arguments: with strictToolArguments (CLI --strict-tool-arguments, config llm.strictToolArguments), malformed tool-call argument strings are sent unchanged on string wires and return an explicit error on object wires. The default keeps 1.44.0 behavior (#​501).
  • Vertex and Bedrock modeled-contract comparisons for the misbehavior faults in the daily drift checks (#​508).
Fixed
  • MCP recording checks its config at start, redacts credentials in errors, fails loudly when the upstream dies mid-body (502 or a terminated stream, logged and journaled), and defaults aimock --config with record mounts to logLevel: "warn" (#​517).
  • Kept 1.44.0 default behavior for strict tool arguments, model misbehavior, Responses namespaced and custom tools, llm.record.mcp, fixture identity and the exported stream helpers, and made each new behavior opt-in (#​518).
  • Kept the published types compatible with 1.44.0: AimockHandle, Mountable and McpFakeAddOrigin keep their 1.44.0 shape, and every entry point type-checks with skipLibCheck: false (#​517).
  • Align Cohere streaming length-stop faults with captured provider framing (#​508).
  • Retain drift evidence across retries and for oversized records (#​510, #​512).
  • Classify claude-haiku-5-5 in the drift model registry (#​515).
  • Docs: dark-site font smoothing, consistent homepage comparison headings, the test-suite highlight, and the copy-button fallback (#​504, #​506, #​514).
  • Invalid opt-in values never stop startup: an invalid responsesTools warns once and runs "legacy", and a non-boolean llm.strictToolArguments, llm.enableMisbehavior or llm.enableMcpRecording warns once and stays off (#​518).

v1.44.0

Compare Source

Added
  • Script MCP tool answers and errors per scenario, including aimock-pytest 0.6.0 support.
  • Mock OpenAI Vector Stores, file batches and deterministic search (#​497).
  • Discover supported HTTP routes through the OpenAPI 3.1 catalog (#​451).
Fixed
  • Reject malformed requests and fixtures with clear validation errors (#​474).
  • Correct video status, browser PUT access, model classification and comparison docs.

v1.43.0

Compare Source

BREAKING — aimock -h is --help, not --host: aimock -h 0.0.0.0 exits 1 with Error: Unexpected argument '0.0.0.0'. This command does not take positional arguments. Migration: --host <string> (long form only). The llmock bin (the Docker ENTRYPOINT) keeps -h, --host (#​453).

Added
  • OpenAI GPT-Live mock, record and offline replay on GET /v1/live/sessions, with onLive fixtures (#​468)
  • Live sessions enforce message/audio/queue/lifetime limits; recordings sanitize credentials (#​468)
  • Live drift canaries for lifecycle, audio/transcript, delegation and usage, attributed to openai-live (#​468)
  • Chaos rateLimitRate / --chaos-ratelimit: deterministic 429 with Retry-After (#​449)
  • Chaos latencyMs / --chaos-latency now actually delays responses on every path (#​449)
  • OpenAI Files API mock — byte-exact uploads, create-purpose enum, CORS on faults (#​445)
  • OpenAI fine-tuning jobs mock — deterministic lifecycle, events, cursor pages (#​447)
  • OpenAI Batches API mock — create/list/retrieve/cancel with real output files (#​446)
  • X-Request-Id echoed or minted on every response; ?requestId= filters the journal (#​450)
  • aimock validate lints fixture files or directories offline, failing on broken files (#​453)
  • ElevenLabs Voice Design record/replay — design, save-as-voice, and voice slot management (#​452)
  • ElevenLabs Voice Design provenance block and strict-mode 503 coverage (#​454)
Changed
  • BREAKING: aimock -h is --help, matching aimock convert -h and aimock validate -h; the host override is --host only. The llmock bin (the Docker entrypoint) keeps its own -h, --host — see the note above (#​453)
  • aimock --config "", --port "" and --host "" are usage errors naming the option (#​453)
  • Realtime OpenAI-Beta: realtime=v1 now returns the real sunset rejection, not a session (#​461)
  • POST /v1/images/variations now replays the real removal 404; OpenAI deleted it (#​462)
  • ChaosAction gains "rateLimit" — an exhaustive switch over it needs a case (#​449)
  • applyChaosAsync() returns false | "handled" | "unwritable" instead of a bare boolean (#​449)
  • Journal headers now ALWAYS carry x-request-id — exact toEqual asserts break (#​450)
Deprecated
  • Synchronous applyChaos() warns once per process; it skips chaos latency. Use await applyChaosAsync(...) (#​449)
Fixed
  • Reasoning-first chat streams with content and tool calls carry the assistant role in the first chunk (#​470)
  • Journals and metrics reflect delivered vs interrupted responses; one-shot errors never double-deliver (#​466)
  • Nonstreaming OpenAI chat returns block text and tools; text-only blocks finish normally on every provider (#​467)
  • AG-UI drift reads generated 1.0 schemas; AGUIRunStartedEvent gains optional protocolVersion (#​469)
  • Drift reports list unverifiedSurfaces; offline Bedrock/Vertex checks no longer skip silently (#​460)
  • Drift docs match actual coverage; ElevenLabs drift separates vendor observations from fixture checks (#​465)
  • Chaos no longer treats committed headers as a dead response; only status-writing actions are skipped (#​449)
  • Chaos skip logs name what happened; aimock_chaos_triggered_total counts only written responses (#​449)
  • A request whose chaos latency was cancelled by client hang-up is no longer served or journalled (#​449)
  • Moderations echoes the request's model; default is now omni-moderation-latest (#​459)
  • Image endpoints default to gpt-image-1 — dall-e-2/dall-e-3 were removed (#​459)
  • AG-UI record/proxy forwards the caller's headers and raw body upstream; Accept is forced to SSE (#​455)
  • The AG-UI recorder refuses to write a fixture from a non-stream or empty 2xx upstream reply (#​455)
  • A MINTED x-request-id is no longer forwarded upstream in record/proxy mode (#​450)

v1.42.1

Compare Source

Fixed
  • Identify reasoning-first tool streams as assistant messages for MCP Apps (#​470).

v1.42.0

Compare Source

Added
  • Filtering and pagination on GET /__aimock/journal, with an X-Total-Count header (#​435)
  • Runtime chaos control at /__aimock/chaos, and a ?include=fixtures fixture dump (#​435)
Fixed
  • Every wrapper path journals now — exact-length or X-Total-Count asserts see more (#​441)
  • Moderation, rerank and search honour strict mode; non-object JSON bodies now 400 (#​440, #​438)
  • POST /__aimock/error validates its payload; quoted multipart boundaries parse (#​434)

v1.41.0

Compare Source

Added
  • BytePlus Ark (Seedance) video record/replay — submit, poll, retrieve raw Ark tasks (#​424)
  • New --provider-byteplus flag to proxy BytePlus Ark to an upstream base (#​424)
Changed
  • /api/v3 chat and image requests are attributed to the byteplus provider (#​424)
  • aimock-pytest now defaults to @copilotkit/aimock 1.41.0.
Fixed
  • aimock now fails at startup if a BytePlus base already ends in /api/v3 (#​424)

v1.40.0

Compare Source

Added
  • GET /__aimock/fixtures — read-only fixture-count introspection. The control API could already add fixtures (POST) and clear them (DELETE), but offered no way to ASK what was loaded, so a CI job or test harness wanting to confirm its tape was registered had to send a probe chat request and infer the answer from the reply. GET /__aimock/fixtures returns { count }. It sits behind the same AIMOCK_API_KEYS boundary and CORS headers as every other /__aimock/* route, and serializes the count ONLY — fixtures carry predicate closures, so none of their contents cross the wire (#​407)

  • AG-UI subagent lifecycle events, and subagentRunId attribution mirrored where canonical declares it. Upstream @ag-ui/core added SUBAGENT_STARTED / SUBAGENT_FINISHED / SUBAGENT_ERROR and threaded an optional subagentRunId through the events a subagent can emit; aimock's AG-UI types carried none of it. The three literals now join AGUIEventType and the AGUIEvent union, backed by AGUISubagentStartedEvent (required subagentRunId and name, optional description / parentSubagentRunId, plus parentToolCallId / parentMessageId so an agents-as-tools subagent can be correlated to the call that spawned it without inspecting rawEvent.metadata), AGUISubagentFinishedEvent (optional result, plus an AGUISubagentFinishedOutcome of { type: "success" } or { type: "suspended"; interruptIds?: string[] } — AGUIRunFinishedOutcome one level down, where absent still means success, interruptIds names only the run-level interrupts that subagent directly owns, and unlike RUN_FINISHED.outcome the field postdates the valueless-field cleanup so it never tolerates null), and AGUISubagentErrorEvent (message, optional code). subagentRunId itself is declared per event, not on a shared base type, because that is how canonical declares it: optional on 24 event interfaces, required on the three subagent events, and deliberately absent from RUN_STARTED / RUN_FINISHED / RUN_ERROR, MESSAGES_SNAPSHOT and the deprecated THINKING_* family. Putting it once on AGUIBaseEvent would have cleared the drift report while granting the field to events canonical does not give it. The same optional field is mirrored onto AGUIMessage and AGUIInterrupt. Canonical types.ts declares it INDEPENDENTLY on BaseMessageSchema, ToolMessageSchema, ActivityMessageSchema and ReasoningMessageSchema — the latter three are standalone z.objects that do NOT extend the base, so there is no inheritance carrying the field to them — and on InterruptSchema; aimock deliberately flattens the canonical message union into one role-discriminated AGUIMessage, which is why two edits cover all four message declarations. So a replayed message or approval request keeps the attribution that lets a client group it under the subagent that produced it instead of reading as root-raised. All four new names — the three event interfaces and AGUISubagentFinishedOutcome — are exported from both the package root and the agui-stub entrypoint, guarded by a public-surface test that fails whenever any agui-types.ts type is missing from either barrel. The AGUIMessage / AGUIInterrupt mirroring is NOT guarded: the existing drift suite walks *EventSchema declarations only and cannot see the non-event schemas, so nothing fails if that field is later dropped — closing that gap is deferred to the drift-harness rewrite rather than bolted on here. Scope: this is a type surface only. Unlike 1.39.0's AGUITokenUsage, which was reachable through AGUIBuildOpts, no RUNTIME code CONSUMES these types — they are exported and guarded, but agui-handler.ts gained no builder and no build option, so aimock can TYPE a subagent event but cannot yet EMIT one. Every added field is optional or sits on a new type, so existing fixtures and callers are byte-identical (#​391)

  • Recorded OpenAI/OpenRouter token usage — including OpenRouter usage.cost (#​368). Collapsing a streaming OpenAI-compatible chat completion previously dropped the final usage frame (the chat.completion.chunk with an empty choices array and a populated usage), because the collapser skipped every chunk without choices. A recorded fixture therefore kept content / reasoning / tool calls / timings but no token counts at all, and replay could only ever serve the ceil(length / 4) estimate or a hand-authored response.usage override. OpenRouter's provider-reported cost was never captured, so an app that bills from real provider cost could not e2e-test its wallet/ledger path from a tape (the same gap #​269 closed for fal's x-fal-billable-units).

    • Record: collapseOpenAISSE now captures the last non-null usage object on the stream (CollapseResult.usage), and the non-streaming recorder captures the completion envelope's usage.
    • Persist: the recorder writes it to the fixture's response.usage via sanitizeRecordedUsage, which keeps each field by its documented shape: the numeric token scalars (cost among them) and any unmodelled scalar extra such as OpenRouter's native_tokens_*, but only when finite; the cost_details / prompt_tokens_details / completion_tokens_details objects, keeping their finite-numeric inner scalars; and the is_byok boolean. What it drops is anything that fits none of those shapes — an unmodelled extra that is not a finite number, or a modelled field of the wrong type — so a recorded fixture always passes the load-time validator (a separate stage in fixture-loader.ts that type-checks each documented field per its type and independently rejects, for example, a non-numeric usage scalar in a hand-authored fixture). Back-compatible: a stream that reported no usage records no usage key and the fixture stays byte-identical to before.
    • Replay: recorded counts win over estimation (existing resolveUsage precedence), and OpenRouter-shaped responses emit the recorded cost / breakdowns on both the final streaming usage chunk and the non-streaming envelope. ResponseOverrides.usage accepts forward-compat extra keys, and OpenRouter shaping now passes any such key through verbatim rather than dropping it.
    • Note: capturing cost requires the recorded request to actually elicit a usage frame — stream_options: { include_usage: true } on OpenAI-compatible streams (OpenRouter sends it regardless), or a non-streaming response. Plain OpenAI (/v1/...) replays continue to emit token counts only; cost is OpenRouter-shaped output.
Changed
  • The AG-UI drift CI lane runs every agui-*.drift.ts guard, not one hardcoded file. The agui-schema-drift job in .github/workflows/test-drift.yml and runAgUiDriftTests in scripts/drift-report-collector.ts both invoked npx vitest run src/__tests__/drift/agui-schema.drift.ts. That positional argument is a vitest FILENAME FILTER (substring match on the test file path), not a single file, so both now pass the path prefix src/__tests__/drift/agui- and every AG-UI drift file runs — one today, and any future guard automatically — under a documented naming contract: an AG-UI drift guard MUST be named src/__tests__/drift/agui-<something>.drift.ts to land in this lane. The workflow's paths: trigger widens to src/__tests__/drift/agui-*.drift.ts to match. vitest.config.drift.ts already scopes include to src/__tests__/drift/**/*.drift.ts, so the prefix cannot pull in *.test.ts helpers from the same directory, and the lane still needs no provider API keys — these files read src/agui-types.ts and the cloned canonical ../ag-ui checkout only, unlike the credit-burning drift leg. The collector retains the single filename as report metadata (the file a human opens when drift is reported), no longer as a run target.
  • aimock-pytest 0.5.3 now defaults to @copilotkit/aimock 1.39.0.
Fixed
  • X-AIMock-Strict is parsed case-insensitively and tolerates surrounding whitespace. resolveStrictMode compared the raw header value against "true" / "false" / "1" / "0" literally, so the variants clients, proxies and shell snippets actually emit — True, TRUE, " true " — matched nothing and fell through to the server default. A caller on a --strict false server asking for X-AIMock-Strict: True kept getting 404 instead of 503, and on a --strict true server X-AIMock-Strict: False stayed strict; the same header gates reasoning suppression, so the mismatch could also surface as a flaky reasoning assertion. The value is now trimmed and lower-cased before comparison. Unrecognised values still fall back to the server default, and trimming is end-only — "tr ue" is still not a match (#​408)

  • A non-WebSocket upgrade probe no longer 404s, and its request body survives on every supported Node. Node fires "upgrade" rather than "request" for ANY Connection: Upgrade header, whatever protocol is named, so langchain4j's OkHttp client — which speculatively sends Upgrade: h2c on plain requests to probe for HTTP/2 cleartext — had GET /api/tags and POST /api/chat land in the WebSocket-only handler and take an unconditional 404. Such a request is now replayed onto the socket with Upgrade/Connection stripped and re-parsed as an ordinary request. Where the body lives depends on the Node version, and both are replayed: Node <= 24 detaches the parser at "upgrade" and leaves body bytes in head, while Node >= 26 parses them onto req and leaves head empty — with the bytes on neither the socket nor head, so a head-only replay left the re-parsed request waiting on a Content-Length worth of bytes that never arrived and the connection hung open instead of answering. Real WebSocket upgrades are untouched (the guard matches the Upgrade token case-insensitively, so websocket and WebSocket both still get 101), the replayed request still passes the AIMOCK_API_KEYS boundary rather than bypassing it, and keep-alive reuse of the same socket afterwards still works. The unit and pytest CI matrices gain Node 26, which is inside the declared engines range (>=20.15.0) and was previously untested — the gap that let the body-loss case pass CI (#​410)

  • The AG-UI drift collector no longer reports "clean" for a failure it could not read. collectAgUiDriftEntries recognizes three failure-message shapes; any FAILED assertion matching none of them was dropped outright — no entry, no counter, no warning — and a FAILED assertion carrying no message at all was skipped before it was even looked at. Both paths produced zero entries, exit 0 and conclusion: "clean", so a genuinely failing agui-schema.drift.ts assertion (should parse aimock event types fails with a bare expect message that matches no shape) certified AG-UI as drift-free. Every such failure now takes the collector's EXISTING quarantine lane — held for review at exit 5, the same "never silently swallowed" contract the HTTP leg already used — per-assertion and unconditional, so an unreadable failure survives a mixed run in which other failures DID parse. Separately, a ZERO-exit AG-UI run whose stdout is not vitest JSON now THROWS instead of returning an empty ("no failures") result, matching what the HTTP twin runDriftTests() has always done on the same condition: the two legs no longer disagree about whether garbage output is clean. Drift-harness tooling, not runtime behavior — no published surface changes (#​391)

  • The AG-UI drift suite's canonical-schema parser no longer drops the field declared after a trailing comment. extractExtendFields stripped whole-line comments only (/^\s*\/\/.*$/gm). Upstream's STATE_DELTA declares delta: z.array(z.any()), // JSON Patch (RFC 6902), and after the top-level comma split that trailing comment heads the NEXT entry, so the field-name match failed and the entry was discarded silently — which is why the drift report read canonical as declaring subagentRunId on 23 events rather than 24, and flagged STATE_DELTA.subagentRunId as aimock-only. Comments are now stripped wherever they appear; no canonical schema literal contains //, so the unconditional strip is safe. This is drift-harness tooling, not runtime behavior — no published surface changes (#​391)

  • openrouter is no longer treated as an unknown SSE provider when recording. collapseStreamingResponse's provider switch had cases for openai / azure / anthropic / gemini / cohere / bedrock but none for openrouter, even though it is a first-class RecordProviderKey that the server sets on every /api/v1/chat/completions request. Recording a streaming OpenRouter completion therefore hit the default arm and logged [stream-collapse] unknown SSE provider "openrouter", falling back to OpenAI SSE format on every recorded stream. The collapse itself was already correct (the fallback is the OpenAI collapser, and OpenRouter speaks the OpenAI SSE wire format), so this is a log-noise/diagnostics fix with no behavior change — but the warning claimed aimock did not recognize a provider it ships first-class support for, which is actively misleading while debugging a recording.

v1.39.0

Compare Source

Added
  • GET /__aimock/fixtures — read-only fixture-count introspection. The control API could already add fixtures (POST) and clear them (DELETE), but offered no way to ASK what was loaded, so a CI job or test harness wanting to confirm its tape was registered had to send a probe chat request and infer the answer from the reply. GET /__aimock/fixtures returns { count }. It sits behind the same AIMOCK_API_KEYS boundary and CORS headers as every other /__aimock/* route, and serializes the count ONLY — fixtures carry predicate closures, so none of their contents cross the wire (#​407)

  • AG-UI subagent lifecycle events, and subagentRunId attribution mirrored where canonical declares it. Upstream @ag-ui/core added SUBAGENT_STARTED / SUBAGENT_FINISHED / SUBAGENT_ERROR and threaded an optional subagentRunId through the events a subagent can emit; aimock's AG-UI types carried none of it. The three literals now join AGUIEventType and the AGUIEvent union, backed by AGUISubagentStartedEvent (required subagentRunId and name, optional description / parentSubagentRunId, plus parentToolCallId / parentMessageId so an agents-as-tools subagent can be correlated to the call that spawned it without inspecting rawEvent.metadata), AGUISubagentFinishedEvent (optional result, plus an AGUISubagentFinishedOutcome of { type: "success" } or { type: "suspended"; interruptIds?: string[] } — AGUIRunFinishedOutcome one level down, where absent still means success, interruptIds names only the run-level interrupts that subagent directly owns, and unlike RUN_FINISHED.outcome the field postdates the valueless-field cleanup so it never tolerates null), and AGUISubagentErrorEvent (message, optional code). subagentRunId itself is declared per event, not on a shared base type, because that is how canonical declares it: optional on 24 event interfaces, required on the three subagent events, and deliberately absent from RUN_STARTED / RUN_FINISHED / RUN_ERROR, MESSAGES_SNAPSHOT and the deprecated THINKING_* family. Putting it once on AGUIBaseEvent would have cleared the drift report while granting the field to events canonical does not give it. The same optional field is mirrored onto AGUIMessage and AGUIInterrupt. Canonical types.ts declares it INDEPENDENTLY on BaseMessageSchema, ToolMessageSchema, ActivityMessageSchema and ReasoningMessageSchema — the latter three are standalone z.objects that do NOT extend the base, so there is no inheritance carrying the field to them — and on InterruptSchema; aimock deliberately flattens the canonical message union into one role-discriminated AGUIMessage, which is why two edits cover all four message declarations. So a replayed message or approval request keeps the attribution that lets a client group it under the subagent that produced it instead of reading as root-raised. All four new names — the three event interfaces and AGUISubagentFinishedOutcome — are exported from both the package root and the agui-stub entrypoint, guarded by a public-surface test that fails whenever any agui-types.ts type is missing from either barrel. The AGUIMessage / AGUIInterrupt mirroring is NOT guarded: the existing drift suite walks *EventSchema declarations only and cannot see the non-event schemas, so nothing fails if that field is later dropped — closing that gap is deferred to the drift-harness rewrite rather than bolted on here. Scope: this is a type surface only. Unlike 1.39.0's AGUITokenUsage, which was reachable through AGUIBuildOpts, no RUNTIME code CONSUMES these types — they are exported and guarded, but agui-handler.ts gained no builder and no build option, so aimock can TYPE a subagent event but cannot yet EMIT one. Every added field is optional or sits on a new type, so existing fixtures and callers are byte-identical (#​391)

  • Recorded OpenAI/OpenRouter token usage — including OpenRouter usage.cost (#​368). Collapsing a streaming OpenAI-compatible chat completion previously dropped the final usage frame (the chat.completion.chunk with an empty choices array and a populated usage), because the collapser skipped every chunk without choices. A recorded fixture therefore kept content / reasoning / tool calls / timings but no token counts at all, and replay could only ever serve the ceil(length / 4) estimate or a hand-authored response.usage override. OpenRouter's provider-reported cost was never captured, so an app that bills from real provider cost could not e2e-test its wallet/ledger path from a tape (the same gap #​269 closed for fal's x-fal-billable-units).

    • Record: collapseOpenAISSE now captures the last non-null usage object on the stream (CollapseResult.usage), and the non-streaming recorder captures the completion envelope's usage.
    • Persist: the recorder writes it to the fixture's response.usage via sanitizeRecordedUsage, which keeps each field by its documented shape: the numeric token scalars (cost among them) and any unmodelled scalar extra such as OpenRouter's native_tokens_*, but only when finite; the cost_details / prompt_tokens_details / completion_tokens_details objects, keeping their finite-numeric inner scalars; and the is_byok boolean. What it drops is anything that fits none of those shapes — an unmodelled extra that is not a finite number, or a modelled field of the wrong type — so a recorded fixture always passes the load-time validator (a separate stage in fixture-loader.ts that type-checks each documented field per its type and independently rejects, for example, a non-numeric usage scalar in a hand-authored fixture). Back-compatible: a stream that reported no usage records no usage key and the fixture stays byte-identical to before.
    • Replay: recorded counts win over estimation (existing resolveUsage precedence), and OpenRouter-shaped responses emit the recorded cost / breakdowns on both the final streaming usage chunk and the non-streaming envelope. ResponseOverrides.usage accepts forward-compat extra keys, and OpenRouter shaping now passes any such key through verbatim rather than dropping it.
    • Note: capturing cost requires the recorded request to actually elicit a usage frame — stream_options: { include_usage: true } on OpenAI-compatible streams (OpenRouter sends it regardless), or a non-streaming response. Plain OpenAI (/v1/...) replays continue to emit token counts only; cost is OpenRouter-shaped output.
Changed
  • The AG-UI drift CI lane runs every agui-*.drift.ts guard, not one hardcoded file. The agui-schema-drift job in .github/workflows/test-drift.yml and runAgUiDriftTests in scripts/drift-report-collector.ts both invoked npx vitest run src/__tests__/drift/agui-schema.drift.ts. That positional argument is a vitest FILENAME FILTER (substring match on the test file path), not a single file, so both now pass the path prefix src/__tests__/drift/agui- and every AG-UI drift file runs — one today, and any future guard automatically — under a documented naming contract: an AG-UI drift guard MUST be named src/__tests__/drift/agui-<something>.drift.ts to land in this lane. The workflow's paths: trigger widens to src/__tests__/drift/agui-*.drift.ts to match. vitest.config.drift.ts already scopes include to src/__tests__/drift/**/*.drift.ts, so the prefix cannot pull in *.test.ts helpers from the same directory, and the lane still needs no provider API keys — these files read src/agui-types.ts and the cloned canonical ../ag-ui checkout only, unlike the credit-burning drift leg. The collector retains the single filename as report metadata (the file a human opens when drift is reported), no longer as a run target.
  • aimock-pytest 0.5.3 now defaults to @copilotkit/aimock 1.39.0.
Fixed
  • X-AIMock-Strict is parsed case-insensitively and tolerates surrounding whitespace. resolveStrictMode compared the raw header value against "true" / "false" / "1" / "0" literally, so the variants clients, proxies and shell snippets actually emit — True, TRUE, " true " — matched nothing and fell through to the server default. A caller on a --strict false server asking for X-AIMock-Strict: True kept getting 404 instead of 503, and on a --strict true server X-AIMock-Strict: False stayed strict; the same header gates reasoning suppression, so the mismatch could also surface as a flaky reasoning assertion. The value is now trimmed and lower-cased before comparison. Unrecognised values still fall back to the server default, and trimming is end-only — "tr ue" is still not a match (#​408)

  • A non-WebSocket upgrade probe no longer 404s, and its request body survives on every supported Node. Node fires "upgrade" rather than "request" for ANY Connection: Upgrade header, whatever protocol is named, so langchain4j's OkHttp client — which speculatively sends Upgrade: h2c on plain requests to probe for HTTP/2 cleartext — had GET /api/tags and POST /api/chat land in the WebSocket-only handler and take an unconditional 404. Such a request is now replayed onto the socket with Upgrade/Connection stripped and re-parsed as an ordinary request. Where the body lives depends on the Node version, and both are replayed: Node <= 24 detaches the parser at "upgrade" and leaves body bytes in head, while Node >= 26 parses them onto req and leaves head empty — with the bytes on neither the socket nor head, so a head-only replay left the re-parsed request waiting on a Content-Length worth of bytes that never arrived and the connection hung open instead of answering. Real WebSocket upgrades are untouched (the guard matches the Upgrade token case-insensitively, so websocket and WebSocket both still get 101), the replayed request still passes the AIMOCK_API_KEYS boundary rather than bypassing it, and keep-alive reuse of the same socket afterwards still works. The unit and pytest CI matrices gain Node 26, which is inside the declared engines range (>=20.15.0) and was previously untested — the gap that let the body-loss case pass CI (#​410)

  • The AG-UI drift collector no longer reports "clean" for a failure it could not read. collectAgUiDriftEntries recognizes three failure-message shapes; any FAILED assertion matching none of them was dropped outright — no entry, no counter, no warning — and a FAILED assertion carrying no message at all was skipped before it was even looked at. Both paths produced zero entries, exit 0 and conclusion: "clean", so a genuinely failing agui-schema.drift.ts assertion (should parse aimock event types fails with a bare expect message that matches no shape) certified AG-UI as drift-free. Every such failure now takes the collector's EXISTING quarantine lane — held for review at exit 5, the same "never silently swallowed" contract the HTTP leg already used — per-assertion and unconditional, so an unreadable failure survives a mixed run in which other failures DID parse. Separately, a ZERO-exit AG-UI run whose stdout is not vitest JSON now THROWS instead of returning an empty ("no failures") result, matching what the HTTP twin runDriftTests() has always done on the same condition: the two legs no longer disagree about whether garbage output is clean. Drift-harness tooling, not runtime behavior — no published surface changes (#​391)

  • The AG-UI drift suite's canonical-schema parser no longer drops the field declared after a trailing comment. extractExtendFields stripped whole-line comments only (/^\s*\/\/.*$/gm). Upstream's STATE_DELTA declares delta: z.array(z.any()), // JSON Patch (RFC 6902), and after the top-level comma split that trailing comment heads the NEXT entry, so the field-name match failed and the entry was discarded silently — which is why the drift report read canonical as declaring subagentRunId on 23 events rather than 24, and flagged STATE_DELTA.subagentRunId as aimock-only. Comments are now stripped wherever they appear; no canonical schema literal contains //, so the unconditional strip is safe. This is drift-harness tooling, not runtime behavior — no published surface changes (#​391)

  • openrouter is no longer treated as an unknown SSE provider when recording. collapseStreamingResponse's provider switch had cases for openai / azure / anthropic / gemini / cohere / bedrock but none for openrouter, even though it is a first-class RecordProviderKey that the server sets on every /api/v1/chat/completions request. Recording a streaming OpenRouter completion therefore hit the default arm and logged [stream-collapse] unknown SSE provider "openrouter", falling back to OpenAI SSE format on every recorded stream. The collapse itself was already correct (the fallback is the OpenAI collapser, and OpenRouter speaks the OpenAI SSE wire format), so this is a log-noise/diagnostics fix with no behavior change — but the warning claimed aimock did not recognize a provider it ships first-class support for, which is actively misleading while debugging a recording.

v1.38.0

Compare Source

Added
  • OpenAI transcription replay for gpt-transcribe HTTP streams and gpt-live-transcribe Realtime sessions. Recorded responses retain transcript languages and usage metadata; replay supports progressive transcript events, timing controls, strict fixture matching, and interruption behavior.
  • Opt-in inbound API-key validation for HTTP, control, mount, and WebSocket boundaries. Validated test credentials are never forwarded by the generic record/proxy path; auth-enabled proxying requires a configured provider credential.
  • API-key egress isolation is request-scoped across generic, AG-UI, fal, and video record paths. Authorization schemes are case-insensitive, and OpenRouter off-origin unsigned URLs never receive a configured provider credential.
  • aimock-pytest 0.5.2 is released by the Release workflow after npm @copilotkit/aimock 1.38.0; the PyPI job verifies the pinned npm package is available before building a wheel.
  • Reasoning encrypted_content on the Responses API. aimock now synthesizes an opaque (base64) reasoning.encrypted_content blob on the TERMINAL reasoning item (response.output_item.done and non-streaming output[]). Emitted only when the request wants it: include: ["reasoning.encrypted_content"] OR a stateless request (store: false / ZDR). Stored / opted-out replays stay byte-identical. A request carrying the EXPLICIT include: ["reasoning.encrypted_content"] opt-in, against a reasoning-CAPABLE model, now gets a reasoning item even when the fixture declares NO reasoning summary — a summary: [] item that exists purely to carry the blob, matching what real OpenAI returns when summaries were never requested. This is the shape a fixture recorded from the real stateless agent-framework flow has, so gating the item on a declared summary starved the exact case the feature exists to serve. The synthesized item leads output[] / takes output_index 0 and shifts the rest, emits output_item.added → output_item.done with no summary-text events (there is no summary part to describe), and is suppressed for non-reasoning models (gpt-4o etc.), which have no reasoning channel at all. Note the two gates differ in width on purpose: store: false still attaches the blob to a reasoning item that a declared summary already produces, but it does NOT synthesize an item where the fixture declares none — creating an output item that did not previously exist is a bigger behavior change than adding a field to one already on the wire, so it takes the gate that is directly observable in the request. agent-framework-openai >= 1.11.0 sends include and never store, so the narrower gate costs the feature nothing. This lets agent-framework-openai >= 1.11.0 replay reasoning-paired tool calls on a stateless reasoning + multi-tool chain (related: microsoft/agent-framework#7233). Applies to both the HTTP and WebSocket Responses transports. Known limitations: the blob is withheld from the in-progress added item, which is aimock's own simplification rather than upstream parity — real OpenAI populates added too (recorded captures carry a shorter blob there beside an empty summary, re-encrypted by done), but the field is anyOf: [string, null] and non-required, so omitting it is contract-legal and no known consumer requires it; and aimock skips inbound reasoning items, so it cannot reproduce OpenAI's invalid_encrypted_content rejection of a mismatched blob/id.
  • OpenRouter chat / LLM router simulation. Requests whose original path starts with /api/v1/ (point any OpenAI SDK at a baseURL ending /api/v1) are detected as OpenRouter and shaped to match real OpenRouter bytes: a gen- id prefix (a fixture id override still wins), a top-level provider (default = the winning model slug's author, fixture-overridable via provider), both finish_reason and native_finish_reason on every choice/delta, an always-present system_fingerprint and service_tier (null by default), message.reasoning, and a rich usage with a fixture-scriptable cost + cost_details (emitted only when a fixture supplies a cost — never fabricated), plus is_byok / prompt_tokens_details / completion_tokens_details when overridden. Callers on the plain OpenAI /v1/... base are byte-for-byte unchanged.
  • models[] fallback (router failover) simulation. When the request body carries models: [...], aimock walks [model, ...models] in order and serves the first fixture returning a NON-error response; a 429/503 error fixture on a candidate simulates a runtime provider failure and falls through to the next. The winning slug is echoed back as the top-level model, so a test asserts failover by reading response.model. (Deliberate non-goal: an unknown/invalid model is a fixture miss, not OpenRouter's up-front invalid-model 400.)
  • OpenRouter discovery endpoints GET /api/v1/models (catalog synthesized from loaded chat fixtures), GET /api/v1/key, and GET /api/v1/credits, matching the real response shapes.
  • OpenRouter error envelope { error: { message, code } } (numeric code == HTTP status, optional free-form metadata) for OpenRouter requests; the OpenAI error shape is retained for /v1/....
  • Opt-in : OPENROUTER PROCESSING SSE keepalive (fixture option openRouterProcessing, default off): one comment line emitted before the first data frame, matching real OpenRouter streams.
  • OpenRouter request extensions (provider, models, route, reasoning, plugins, prediction, usage) and attribution headers (HTTP-Referer, X-OpenRouter-Title, legacy X-Title) are accepted and journaled, never required or rejected.
Fixed
  • GET /__aimock/journal no longer leaks accepted inbound credentials. Journal header redaction is now derived from the same registry that decides which headers are accepted as credentials, so the two can no longer drift apart. Previously only authorization, x-api-key and api-key were redacted while x-goog-api-key and xi-api-key were accepted as credentials and journaled in plaintext — so a real Gemini / Veo / ElevenLabs key sent on those headers appeared verbatim in the journal, which is unauthenticated unless inbound API-key validation is enabled. Affects 1.37.4 and earlier. A test asserts every accepted header redacts, so adding a new accepted header without redacting it now fails.

v1.37.4

Compare Source

Fixed
  • hasToolResult record and replay are symmetric again: both the recorder (buildFixtureMatch) and the matcher (matchFixtureDiagnostic) now derive the value from one shared currentTurnHasToolResult helper. The recorder previously STAMPED the value with a whole-conversation messages.some((m) => m.role === "tool") while the matcher (as of 1.37.3) CHECKS it scoped to the current turn, so a genuinely-recorded multi-turn fixture (a fresh user turn whose history still carried an earlier turn's tool result) was stamped true but matched false and could never replay its own recorded request. Sharing one predicate makes the two sides structurally unable to drift (#​319)
  • Anthropic tool_result blocks bundled WITH accompanying text in a single user message now normalize with the text message BEFORE the tool message ([..., user(text), tool]), so a leg-2-with-text turn is correctly classified as carrying a tool result. Previously the tool message was emitted first ([..., tool, user(text)]), which is byte-identical to a fresh leg-1 turn and was misclassified false. The common tool_result-only follow-up (no text) is unaffected (#​319)

[v1.37.3](https://redirect.gith

[!IMPORTANT]
✂ PR body was truncated to here.


Configuration

📅 Schedule: (in timezone America/Los_Angeles)

  • Branch creation
    • At any time (no schedule defined)
  • Automerge
    • At any time (no schedule defined)

🚦 Automerge: Disabled by config. Please merge this manually once you are satisfied.

♻ Rebasing: Whenever PR becomes conflicted, or you tick the rebase/retry checkbox.

🔕 Ignore: Close this PR and you won't be reminded about this update again.


  • If you want to rebase/retry this PR, check this box

This PR was generated by Mend Renovate. View the repository job log.

@coderabbitai

coderabbitai Bot commented Oct 31, 2026 •

Copy link
Copy Markdown
Contributor

Important

Review skipped

Auto reviews are limited based on label configuration.

🏷️ Required labels (at least one): 1
  • coderabbit-review-active

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

Run configuration

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • Trigger review
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

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

@github-actions

github-actions Bot commented Oct 31, 2026 •

Copy link
Copy Markdown
Contributor

Review status

This PR was opened by an automated account. A human maintainer must verify the change intent, provenance, and validation before merging.

Current step: Awaiting fresh human maintainer or CODEOWNER approval.

Review-state labels are managed by this workflow; do not edit them manually. community-approved is managed the same way — do not add or remove it manually. It signals a fresh community code approval for the current head as an advisory priority only; maintainer review is still required.

@codecov

codecov Bot commented Oct 31, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

@github-actions github-actions Bot added the awaiting-maintainer CodeRabbit approved; waiting for a human maintainer label Oct 31, 2026

This branch has not been deployed

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

Labels

awaiting-maintainer CodeRabbit approved; waiting for a human maintainer

Projects

None yet

Development

Successfully merging this pull request may close these issues.

0 participants