Repository navigation
chore(deps): update dependency @copilotkit/aimock to v1.47.0 - #2277
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: The required review sequence passed. Remaining merge requirements apply. 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! |
2d3a8a4 to
8e6a9fe
Compare
252a905 to
3b5a3f5
Compare
Edited/Blocked NotificationRenovate will not automatically rebase this PR, because it does not recognize the last commit author and assumes somebody else may have edited the PR. You can manually request rebase by checking the rebase/retry box above. |
3b5a3f5 to
5da6bd3
Compare
Brings in Zoo-Code-Org#2343 (apply_diff auto-approves edits outside the workspace), Zoo-Code-Org#2283 (task abort/abortReason run-state latches), Zoo-Code-Org#2324 (Turbo cache input contracts), Zoo-Code-Org#2327 (CSS transitions for visual themes) and Zoo-Code-Org#2277 (aimock 1.47.0), so the required arms judge this branch against the content they actually check out. The merge is textually clean and ApplyDiffTool.ts lands as main's content with this branch's hunks applied on top, which is the composition the arms need. The only reconciliation needed was in main's new applyDiffTool.outsideWorkspace.spec.ts: its diff-view double stubbed open() as a no-op and left originalContent at "", which is the fixture shape applyDiffTool.failureReporting.spec.ts warns about. The stale-base guard added on this branch reads open()'s re-read as the save baseline, so that double reported a stale base that never happened and execute returned before asking for approval - nine failures in CI's windows lane. The double now re-reads through the same fs mock and provides the stat the tool takes around its read; main's c assertions and expectations are untouched.
Brings 1404 up to the content the required arms actually check out: the 20 non-merge main commits between its merge base 3ee042d and main tip e487cd7, including Zoo-Code-Org#2283 (task abort/abortReason run-state latches, which turn Task.abort and Task.abortReason into getter-only accessors), the settings round-trip unit e943530, Zoo-Code-Org#2343, Zoo-Code-Org#2324, Zoo-Code-Org#2327 and Zoo-Code-Org#2277. 29 of this branch's 43 delta files were also edited on main and every one of them auto-merged. Nothing had to be reconciled, and that was measured rather than assumed: tsc --noEmit over the merged tree exits 0 with zero error rows once @roo-code/types resolves to the merged packages/types (it reported 25 perWriteCheckpoints rows while resolving to a sibling checkout's stale types, which is a resolution artifact, not a defect in this tree). The accessor hazard that class of merge usually produces is absent here: this branch's Task.ts never assigns this.abort or this.abortReason (its three mentions are comparisons), and the five spec-side writes all land on object-literal doubles that declare abort as a mutable property or on a local { abort: boolean } alias. Control: injecting 'this.abort = true' inside the getter body produces exactly one row, TS2540 Cannot assign to 'abort' because it is a read-only property, and removing it returns the program to exit 0, so the check would have caught the hazard if the merge had created it.
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
Configuration
📅 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.