Repository navigation
Update dependency @copilotkit/aimock to v1.47.0 - #2277
renovate[bot] wants to merge 1 commit into
Conversation
|
Important Review skippedAuto reviews are limited based on label configuration. 🏷️ Required labels (at least one): 1
Please check the settings in the CodeRabbit UI or the
|
Review statusThis 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. |
Codecov Report✅ All modified and coverable lines are covered by tests. 📢 Thoughts on this report? Let us know! |
Note
This PR body was truncated due to platform limits.
This PR contains the following updates:
1.35.0→1.47.0Release Notes
CopilotKit/aimock (@copilotkit/aimock)
v1.47.0Compare Source
Added
citationsserve sources on Responses, Chat Completions, Claude and Gemini (#550, #578)filesserve generated images on Gemini and OpenAI Responses (#550, #578)nonStreamingTimingAllProvidersdelays non-streaming replies on every provider (#579)Fixed
v1.46.0Compare Source
Added
requiredHeaders, nativeauthErrors, nativemodelCatalog(#542, #566, #568)nonStreamingTiming: truedelays non-streaming OpenAI chat replies (#545, #567)phaseoverride (#531, #542)Fixed
v1.45.1Compare Source
Changed
openai/sora-2now returns OpenRouter's 400 removal error, not a job (#520)Fixed
tools/list: a tool registered withoutinputSchemanow lists{"type":"object"}(#529)tools/list: a registeredinputSchemawith notypenow gets"type": "object"(#529)v1.45.0Compare 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
mcpFakesfiles and replay them offline:--mcp-record <mount>=<url>and--mcp-proxy-only <mount>=<url>(llmockbin and Docker image),llm.record.mcpwith the opt-inllm.enableMcpRecording: trueforaimock --config(without the opt-in,llm.record.mcpis ignored with a warning, as 1.44.0 ignored it), andMCPMock.enableRecording()/disableRecording()(#509).AIMOCK_RECORD_SECRET_VALUES,AIMOCK_MCP_UPSTREAM_AUTH); a file is not written while a secret remains (#509).--replay-speed). The recordedmcpFakeskeys (list,recorded,timing,notifications,durationMs) need 1.45.0 or later (#509).GET /__aimock/mcp/fakes/report(#509).fakesFor()andfakesReport()on a newAimockFakesHandlereturned byuseAimock(), and opt-inuseAimock({ fakesReport: "warn" | "fail" })(default"off"); Jest optiontestIdRoot. New exportsassertFakesReport,AimockFakesReportErrorandAimockTestIdCollisionError(#509).fakes_for,fakes_reportandassert_fakes_report(#509), andAIMockServeropt-insenable_misbehaviorandresponses_tools(#508, #511).examples/mcp-workflow-targets), plus record and report proofs (#509)./__aimock/misbehaviorruntime controls, journal evidence and theaimock_misbehavior_totalmetric. Off unless enabled withenableMisbehavior: true(programmatic, includinguseAimock),llmock --misbehavior/aimock validate --misbehavior, oraimock --misbehavior, or the config opt-inllm.enableMisbehavior: true(anllm.misbehaviorkey alone never enables it, as in 1.44.0). Invalid misbehavior settings throw the newMisbehaviorConfigError(#508).match.toolNamespace(#511). Like the fixture fields below,match.toolNamespaceapplies only withresponsesTools: "extended"; in the default mode it is ignored, as in 1.44.0.responsesTools: "extended"(CLI--responses-tools extended, also onaimock validate; configllm.responsesTools), tools insidenamespacetools,customtools andadditional_tools/tool_search_outputtools are visible totoolName,toolNamespaceand predicates, custom tool rounds count forhasToolResult,toolCallIdandturnIndex, and fixtures can answer with namespaced calls andcustomToolCalls/responsesBlocks. The default"legacy"keeps 1.44.0 behavior. New types:CustomToolCallMessage,ResponsesToolsMode(#511).customToolCalls,responsesBlocks,CollapseResult.customToolCalls) in streamed recordings whenresponsesTools: "extended"is set (#513).strictToolArguments(CLI--strict-tool-arguments, configllm.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).Fixed
aimock --configwith record mounts tologLevel: "warn"(#517).llm.record.mcp, fixture identity and the exported stream helpers, and made each new behavior opt-in (#518).AimockHandle,MountableandMcpFakeAddOriginkeep their 1.44.0 shape, and every entry point type-checks withskipLibCheck: false(#517).claude-haiku-5-5in the drift model registry (#515).responsesToolswarns once and runs"legacy", and a non-booleanllm.strictToolArguments,llm.enableMisbehaviororllm.enableMcpRecordingwarns once and stays off (#518).v1.44.0Compare Source
Added
Fixed
v1.43.0Compare Source
Added
GET /v1/live/sessions, withonLivefixtures (#468)openai-live(#468)rateLimitRate/--chaos-ratelimit: deterministic 429 withRetry-After(#449)latencyMs/--chaos-latencynow actually delays responses on every path (#449)X-Request-Idechoed or minted on every response;?requestId=filters the journal (#450)aimock validatelints fixture files or directories offline, failing on broken files (#453)Changed
aimock -his--help, matchingaimock convert -handaimock validate -h; the host override is--hostonly. Thellmockbin (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)OpenAI-Beta: realtime=v1now returns the real sunset rejection, not a session (#461)POST /v1/images/variationsnow replays the real removal 404; OpenAI deleted it (#462)ChaosActiongains"rateLimit"— an exhaustive switch over it needs a case (#449)applyChaosAsync()returnsfalse | "handled" | "unwritable"instead of a bareboolean(#449)headersnow ALWAYS carryx-request-id— exacttoEqualasserts break (#450)Deprecated
applyChaos()warns once per process; it skips chaos latency. Useawait applyChaosAsync(...)(#449)Fixed
AGUIRunStartedEventgains optionalprotocolVersion(#469)unverifiedSurfaces; offline Bedrock/Vertex checks no longer skip silently (#460)aimock_chaos_triggered_totalcounts only written responses (#449)model; default is nowomni-moderation-latest(#459)gpt-image-1—dall-e-2/dall-e-3were removed (#459)Acceptis forced to SSE (#455)x-request-idis no longer forwarded upstream in record/proxy mode (#450)v1.42.1Compare Source
Fixed
v1.42.0Compare Source
Added
GET /__aimock/journal, with anX-Total-Countheader (#435)/__aimock/chaos, and a?include=fixturesfixture dump (#435)Fixed
X-Total-Countasserts see more (#441)400(#440, #438)POST /__aimock/errorvalidates its payload; quoted multipart boundaries parse (#434)v1.41.0Compare Source
Added
--provider-byteplusflag to proxy BytePlus Ark to an upstream base (#424)Changed
/api/v3chat and image requests are attributed to thebyteplusprovider (#424)aimock-pytestnow defaults to@copilotkit/aimock1.41.0.Fixed
/api/v3(#424)v1.40.0Compare 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/fixturesreturns{ count }. It sits behind the sameAIMOCK_API_KEYSboundary 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
subagentRunIdattribution mirrored where canonical declares it. Upstream@ag-ui/coreaddedSUBAGENT_STARTED/SUBAGENT_FINISHED/SUBAGENT_ERRORand threaded an optionalsubagentRunIdthrough the events a subagent can emit; aimock's AG-UI types carried none of it. The three literals now joinAGUIEventTypeand theAGUIEventunion, backed byAGUISubagentStartedEvent(requiredsubagentRunIdandname, optionaldescription/parentSubagentRunId, plusparentToolCallId/parentMessageIdso an agents-as-tools subagent can be correlated to the call that spawned it without inspectingrawEvent.metadata),AGUISubagentFinishedEvent(optionalresult, plus anAGUISubagentFinishedOutcomeof{ type: "success" }or{ type: "suspended"; interruptIds?: string[] }—AGUIRunFinishedOutcomeone level down, where absent still means success,interruptIdsnames only the run-level interrupts that subagent directly owns, and unlikeRUN_FINISHED.outcomethe field postdates the valueless-field cleanup so it never toleratesnull), andAGUISubagentErrorEvent(message, optionalcode).subagentRunIditself 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 fromRUN_STARTED/RUN_FINISHED/RUN_ERROR,MESSAGES_SNAPSHOTand the deprecatedTHINKING_*family. Putting it once onAGUIBaseEventwould have cleared the drift report while granting the field to events canonical does not give it. The same optional field is mirrored ontoAGUIMessageandAGUIInterrupt. Canonicaltypes.tsdeclares it INDEPENDENTLY onBaseMessageSchema,ToolMessageSchema,ActivityMessageSchemaandReasoningMessageSchema— the latter three are standalonez.objects that do NOT extend the base, so there is no inheritance carrying the field to them — and onInterruptSchema; aimock deliberately flattens the canonical message union into onerole-discriminatedAGUIMessage, 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 andAGUISubagentFinishedOutcome— are exported from both the package root and theagui-stubentrypoint, guarded by a public-surface test that fails whenever anyagui-types.tstype is missing from either barrel. TheAGUIMessage/AGUIInterruptmirroring is NOT guarded: the existing drift suite walks*EventSchemadeclarations 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'sAGUITokenUsage, which was reachable throughAGUIBuildOpts, no RUNTIME code CONSUMES these types — they are exported and guarded, butagui-handler.tsgained 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 (thechat.completion.chunkwith an emptychoicesarray and a populatedusage), 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 theceil(length / 4)estimate or a hand-authoredresponse.usageoverride. OpenRouter's provider-reportedcostwas 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'sx-fal-billable-units).collapseOpenAISSEnow captures the last non-nullusageobject on the stream (CollapseResult.usage), and the non-streaming recorder captures the completion envelope'susage.response.usageviasanitizeRecordedUsage, which keeps each field by its documented shape: the numeric token scalars (costamong them) and any unmodelled scalar extra such as OpenRouter'snative_tokens_*, but only when finite; thecost_details/prompt_tokens_details/completion_tokens_detailsobjects, keeping their finite-numeric inner scalars; and theis_byokboolean. 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 infixture-loader.tsthat type-checks each documented field per its type and independently rejects, for example, a non-numericusagescalar in a hand-authored fixture). Back-compatible: a stream that reported no usage records nousagekey and the fixture stays byte-identical to before.resolveUsageprecedence), and OpenRouter-shaped responses emit the recordedcost/ breakdowns on both the final streaming usage chunk and the non-streaming envelope.ResponseOverrides.usageaccepts forward-compat extra keys, and OpenRouter shaping now passes any such key through verbatim rather than dropping it.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;costis OpenRouter-shaped output.Changed
agui-*.drift.tsguard, not one hardcoded file. Theagui-schema-driftjob in.github/workflows/test-drift.ymlandrunAgUiDriftTestsinscripts/drift-report-collector.tsboth invokednpx 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 prefixsrc/__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 namedsrc/__tests__/drift/agui-<something>.drift.tsto land in this lane. The workflow'spaths:trigger widens tosrc/__tests__/drift/agui-*.drift.tsto match.vitest.config.drift.tsalready scopesincludetosrc/__tests__/drift/**/*.drift.ts, so the prefix cannot pull in*.test.tshelpers from the same directory, and the lane still needs no provider API keys — these files readsrc/agui-types.tsand the cloned canonical../ag-uicheckout only, unlike the credit-burningdriftleg. 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-pytest0.5.3 now defaults to@copilotkit/aimock1.39.0.Fixed
X-AIMock-Strictis parsed case-insensitively and tolerates surrounding whitespace.resolveStrictModecompared 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 falseserver asking forX-AIMock-Strict: Truekept getting404instead of503, and on a--strict trueserverX-AIMock-Strict: Falsestayed 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 ANYConnection: Upgradeheader, whatever protocol is named, so langchain4j's OkHttp client — which speculatively sendsUpgrade: h2con plain requests to probe for HTTP/2 cleartext — hadGET /api/tagsandPOST /api/chatland in the WebSocket-only handler and take an unconditional404. Such a request is now replayed onto the socket withUpgrade/Connectionstripped 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 inhead, while Node >= 26 parses them ontoreqand leavesheadempty — with the bytes on neither the socket norhead, so ahead-only replay left the re-parsed request waiting on aContent-Lengthworth of bytes that never arrived and the connection hung open instead of answering. Real WebSocket upgrades are untouched (the guard matches theUpgradetoken case-insensitively, sowebsocketandWebSocketboth still get101), the replayed request still passes theAIMOCK_API_KEYSboundary 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 declaredenginesrange (>=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.
collectAgUiDriftEntriesrecognizes 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 andconclusion: "clean", so a genuinely failingagui-schema.drift.tsassertion (should parse aimock event typesfails with a bareexpectmessage 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 twinrunDriftTests()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.
extractExtendFieldsstripped whole-line comments only (/^\s*\/\/.*$/gm). Upstream'sSTATE_DELTAdeclaresdelta: 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 declaringsubagentRunIdon 23 events rather than 24, and flaggedSTATE_DELTA.subagentRunIdas 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)openrouteris no longer treated as an unknown SSE provider when recording.collapseStreamingResponse's provider switch had cases foropenai/azure/anthropic/gemini/cohere/bedrockbut none foropenrouter, even though it is a first-classRecordProviderKeythat the server sets on every/api/v1/chat/completionsrequest. Recording a streaming OpenRouter completion therefore hit thedefaultarm and logged[stream-collapse] unknown SSE provider "openrouter", falling back to OpenAI SSE formaton 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.0Compare 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/fixturesreturns{ count }. It sits behind the sameAIMOCK_API_KEYSboundary 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
subagentRunIdattribution mirrored where canonical declares it. Upstream@ag-ui/coreaddedSUBAGENT_STARTED/SUBAGENT_FINISHED/SUBAGENT_ERRORand threaded an optionalsubagentRunIdthrough the events a subagent can emit; aimock's AG-UI types carried none of it. The three literals now joinAGUIEventTypeand theAGUIEventunion, backed byAGUISubagentStartedEvent(requiredsubagentRunIdandname, optionaldescription/parentSubagentRunId, plusparentToolCallId/parentMessageIdso an agents-as-tools subagent can be correlated to the call that spawned it without inspectingrawEvent.metadata),AGUISubagentFinishedEvent(optionalresult, plus anAGUISubagentFinishedOutcomeof{ type: "success" }or{ type: "suspended"; interruptIds?: string[] }—AGUIRunFinishedOutcomeone level down, where absent still means success,interruptIdsnames only the run-level interrupts that subagent directly owns, and unlikeRUN_FINISHED.outcomethe field postdates the valueless-field cleanup so it never toleratesnull), andAGUISubagentErrorEvent(message, optionalcode).subagentRunIditself 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 fromRUN_STARTED/RUN_FINISHED/RUN_ERROR,MESSAGES_SNAPSHOTand the deprecatedTHINKING_*family. Putting it once onAGUIBaseEventwould have cleared the drift report while granting the field to events canonical does not give it. The same optional field is mirrored ontoAGUIMessageandAGUIInterrupt. Canonicaltypes.tsdeclares it INDEPENDENTLY onBaseMessageSchema,ToolMessageSchema,ActivityMessageSchemaandReasoningMessageSchema— the latter three are standalonez.objects that do NOT extend the base, so there is no inheritance carrying the field to them — and onInterruptSchema; aimock deliberately flattens the canonical message union into onerole-discriminatedAGUIMessage, 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 andAGUISubagentFinishedOutcome— are exported from both the package root and theagui-stubentrypoint, guarded by a public-surface test that fails whenever anyagui-types.tstype is missing from either barrel. TheAGUIMessage/AGUIInterruptmirroring is NOT guarded: the existing drift suite walks*EventSchemadeclarations 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'sAGUITokenUsage, which was reachable throughAGUIBuildOpts, no RUNTIME code CONSUMES these types — they are exported and guarded, butagui-handler.tsgained 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 (thechat.completion.chunkwith an emptychoicesarray and a populatedusage), 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 theceil(length / 4)estimate or a hand-authoredresponse.usageoverride. OpenRouter's provider-reportedcostwas 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'sx-fal-billable-units).collapseOpenAISSEnow captures the last non-nullusageobject on the stream (CollapseResult.usage), and the non-streaming recorder captures the completion envelope'susage.response.usageviasanitizeRecordedUsage, which keeps each field by its documented shape: the numeric token scalars (costamong them) and any unmodelled scalar extra such as OpenRouter'snative_tokens_*, but only when finite; thecost_details/prompt_tokens_details/completion_tokens_detailsobjects, keeping their finite-numeric inner scalars; and theis_byokboolean. 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 infixture-loader.tsthat type-checks each documented field per its type and independently rejects, for example, a non-numericusagescalar in a hand-authored fixture). Back-compatible: a stream that reported no usage records nousagekey and the fixture stays byte-identical to before.resolveUsageprecedence), and OpenRouter-shaped responses emit the recordedcost/ breakdowns on both the final streaming usage chunk and the non-streaming envelope.ResponseOverrides.usageaccepts forward-compat extra keys, and OpenRouter shaping now passes any such key through verbatim rather than dropping it.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;costis OpenRouter-shaped output.Changed
agui-*.drift.tsguard, not one hardcoded file. Theagui-schema-driftjob in.github/workflows/test-drift.ymlandrunAgUiDriftTestsinscripts/drift-report-collector.tsboth invokednpx 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 prefixsrc/__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 namedsrc/__tests__/drift/agui-<something>.drift.tsto land in this lane. The workflow'spaths:trigger widens tosrc/__tests__/drift/agui-*.drift.tsto match.vitest.config.drift.tsalready scopesincludetosrc/__tests__/drift/**/*.drift.ts, so the prefix cannot pull in*.test.tshelpers from the same directory, and the lane still needs no provider API keys — these files readsrc/agui-types.tsand the cloned canonical../ag-uicheckout only, unlike the credit-burningdriftleg. 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-pytest0.5.3 now defaults to@copilotkit/aimock1.39.0.Fixed
X-AIMock-Strictis parsed case-insensitively and tolerates surrounding whitespace.resolveStrictModecompared 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 falseserver asking forX-AIMock-Strict: Truekept getting404instead of503, and on a--strict trueserverX-AIMock-Strict: Falsestayed 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 ANYConnection: Upgradeheader, whatever protocol is named, so langchain4j's OkHttp client — which speculatively sendsUpgrade: h2con plain requests to probe for HTTP/2 cleartext — hadGET /api/tagsandPOST /api/chatland in the WebSocket-only handler and take an unconditional404. Such a request is now replayed onto the socket withUpgrade/Connectionstripped 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 inhead, while Node >= 26 parses them ontoreqand leavesheadempty — with the bytes on neither the socket norhead, so ahead-only replay left the re-parsed request waiting on aContent-Lengthworth of bytes that never arrived and the connection hung open instead of answering. Real WebSocket upgrades are untouched (the guard matches theUpgradetoken case-insensitively, sowebsocketandWebSocketboth still get101), the replayed request still passes theAIMOCK_API_KEYSboundary 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 declaredenginesrange (>=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.
collectAgUiDriftEntriesrecognizes 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 andconclusion: "clean", so a genuinely failingagui-schema.drift.tsassertion (should parse aimock event typesfails with a bareexpectmessage 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 twinrunDriftTests()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.
extractExtendFieldsstripped whole-line comments only (/^\s*\/\/.*$/gm). Upstream'sSTATE_DELTAdeclaresdelta: 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 declaringsubagentRunIdon 23 events rather than 24, and flaggedSTATE_DELTA.subagentRunIdas 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)openrouteris no longer treated as an unknown SSE provider when recording.collapseStreamingResponse's provider switch had cases foropenai/azure/anthropic/gemini/cohere/bedrockbut none foropenrouter, even though it is a first-classRecordProviderKeythat the server sets on every/api/v1/chat/completionsrequest. Recording a streaming OpenRouter completion therefore hit thedefaultarm and logged[stream-collapse] unknown SSE provider "openrouter", falling back to OpenAI SSE formaton 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.0Compare Source
Added
gpt-transcribeHTTP streams andgpt-live-transcribeRealtime sessions. Recorded responses retain transcript languages and usage metadata; replay supports progressive transcript events, timing controls, strict fixture matching, and interruption behavior.aimock-pytest0.5.2is released by the Release workflow after npm@copilotkit/aimock1.38.0; the PyPI job verifies the pinned npm package is available before building a wheel.encrypted_contenton the Responses API. aimock now synthesizes an opaque (base64)reasoning.encrypted_contentblob on the TERMINAL reasoning item (response.output_item.doneand non-streamingoutput[]). 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 EXPLICITinclude: ["reasoning.encrypted_content"]opt-in, against a reasoning-CAPABLE model, now gets a reasoning item even when the fixture declares NOreasoningsummary — asummary: []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 leadsoutput[]/ takesoutput_index0 and shifts the rest, emitsoutput_item.added→output_item.donewith 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: falsestill 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 sendsincludeand neverstore, so the narrower gate costs the feature nothing. This letsagent-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-progressaddeditem, which is aimock's own simplification rather than upstream parity — real OpenAI populatesaddedtoo (recorded captures carry a shorter blob there beside an empty summary, re-encrypted bydone), but the field isanyOf: [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'sinvalid_encrypted_contentrejection of a mismatched blob/id./api/v1/(point any OpenAI SDK at abaseURLending/api/v1) are detected as OpenRouter and shaped to match real OpenRouter bytes: agen-id prefix (a fixtureidoverride still wins), a top-levelprovider(default = the winning model slug's author, fixture-overridable viaprovider), bothfinish_reasonandnative_finish_reasonon every choice/delta, an always-presentsystem_fingerprintandservice_tier(null by default),message.reasoning, and a richusagewith a fixture-scriptablecost+cost_details(emitted only when a fixture supplies a cost — never fabricated), plusis_byok/prompt_tokens_details/completion_tokens_detailswhen overridden. Callers on the plain OpenAI/v1/...base are byte-for-byte unchanged.models[]fallback (router failover) simulation. When the request body carriesmodels: [...], aimock walks[model, ...models]in order and serves the first fixture returning a NON-error response; a429/503error fixture on a candidate simulates a runtime provider failure and falls through to the next. The winning slug is echoed back as the top-levelmodel, so a test asserts failover by readingresponse.model. (Deliberate non-goal: an unknown/invalid model is a fixture miss, not OpenRouter's up-front invalid-model 400.)GET /api/v1/models(catalog synthesized from loaded chat fixtures),GET /api/v1/key, andGET /api/v1/credits, matching the real response shapes.{ error: { message, code } }(numericcode== HTTP status, optional free-formmetadata) for OpenRouter requests; the OpenAI error shape is retained for/v1/....: OPENROUTER PROCESSINGSSE keepalive (fixture optionopenRouterProcessing, default off): one comment line emitted before the first data frame, matching real OpenRouter streams.provider,models,route,reasoning,plugins,prediction,usage) and attribution headers (HTTP-Referer,X-OpenRouter-Title, legacyX-Title) are accepted and journaled, never required or rejected.Fixed
GET /__aimock/journalno 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 onlyauthorization,x-api-keyandapi-keywere redacted whilex-goog-api-keyandxi-api-keywere 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.4Compare Source
Fixed
hasToolResultrecord and replay are symmetric again: both the recorder (buildFixtureMatch) and the matcher (matchFixtureDiagnostic) now derive the value from one sharedcurrentTurnHasToolResulthelper. The recorder previously STAMPED the value with a whole-conversationmessages.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 stampedtruebut matchedfalseand could never replay its own recorded request. Sharing one predicate makes the two sides structurally unable to drift (#319)tool_resultblocks 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 misclassifiedfalse. The commontool_result-only follow-up (no text) is unaffected (#319)[
v1.37.3](https://redirect.githConfiguration
📅 Schedule: (in timezone America/Los_Angeles)
🚦 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.
This PR was generated by Mend Renovate. View the repository job log.