Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@

## [Unreleased]

### Added

- **AG-UI `RUN_FINISHED` and `RUN_ERROR` events now carry an optional `usage` field.** AG-UI's canonical `events.ts` declares an optional `usage: TokenUsage[]` on both terminal event schemas, but aimock's event interfaces carried neither. A new exported `AGUITokenUsage` type — numeric-only, mirroring `@ag-ui/core`'s `TokenUsageSchema` field for field (`provider` / `model` labels plus non-negative-integer `inputTokens`, `outputTokens`, `totalTokens`, `reasoningTokens`, `cachedInputTokens`, and no content-bearing or identifying fields) — is added as `usage?: AGUITokenUsage[]` on `AGUIRunFinishedEvent` and `AGUIRunErrorEvent`, and exported from both the package root and the `agui-stub` entrypoint. The field is reachable, not merely declarative: `AGUIBuildOpts` gains a `usage` option that is threaded to the terminal `RUN_FINISHED` of every builder and to `RUN_ERROR` in `buildErrorResponse`. Following the repo's existing usage policy (Bedrock / Cohere / Gemini), token counts are never synthesized from fixture content — `usage` is emitted only when a caller supplies it explicitly, so existing callers see byte-identical events. It is an array so a run that invokes multiple models keeps their counts separate (#378)

### Changed

- `POST /__aimock/reset` is now the canonical full reset and returns a plain `{ "reset": true }` with no deprecation header or body fields. `POST /__aimock/reset/journal` is unaffected.
Expand All @@ -15,6 +19,13 @@
- `POST /__aimock/reset/fixtures` — now a deprecated alias for `POST /__aimock/reset`. The name promised a fixtures-only reset while it always performed the full reset, so `/reset` is the honest route and the deprecation moves onto the alias: it still performs the same full reset but emits a `Deprecation: true` response header, `deprecated` / `deprecation` fields in the body, and a log warning. Use `POST /__aimock/reset` for a full reset, `POST /__aimock/reset/journal` for a journal-only one, or `DELETE /__aimock/fixtures` to clear fixtures and nothing else.
- The alias's **reset semantics are unchanged** — it clears exactly what it always did, so existing callers keep working. Its **response body is additively extended**: it now carries `deprecated` and `deprecation` alongside `reset`, plus the `Deprecation: true` header. A caller asserting strict equality on the old `{ "reset": true }` body will need to relax that assertion; a caller reading `body.reset` is unaffected.

### Fixed

- **Recording a tool-call turn over the OpenAI Responses API now collapses to `toolCalls` instead of an empty assistant turn.** `collapseOpenAISSE` handled Responses-API text, reasoning and web-search output items but let a catch-all (`if (parsed.type?.startsWith("response.")) continue;`) silently skip the entire function-call sequence — `response.output_item.added` for a `function_call` item, `response.function_call_arguments.delta` / `.done`, and `response.output_item.done` for a `function_call`. A tool-call-only turn therefore collapsed to empty `content` with no `toolCalls` (logged `Stream collapse produced empty content — fixture may be incomplete`), and replaying that fixture yielded an empty assistant turn with no tool invocation. Only fresh Responses-API recordings broke, because the older Chat-Completions path already accumulated `choices[0].delta.tool_calls`. aimock now accumulates the function-call events into the existing tool-call map keyed by `output_index`, capturing the Responses `call_id` (the id a tool result references — **not** the internal `fc_…` item id) as the tool-call `id`, and emits `{ id, name, arguments }` with arguments normalized to valid JSON — identical to the Chat-Completions shape and the checked-in fixtures. Scope: this fixes the stream collapser only; it does not address the fixture-capture race in which a post-tool-result turn can still be draining when the capture probe exits (#380)
- **A tool-first OpenAI Responses turn now replays its assistant text and tool calls in the recorded order.** Follow-up to #380. `collapseOpenAISSE`'s Responses-API `response.output_text.delta` handler accumulated `content += parsed.delta` but never pushed a text order-atom, so `buildOrderedBlocks` returned `undefined` for any Responses stream. A Responses turn whose `function_call` arrives **before** its `output_text` therefore collapsed to a fixture with no `blocks`, and because the Responses replay path consumes `blocks`, replay silently lost the tool-before-text ordering. The handler now pushes `{ kind: "text", text: parsed.delta }` for each non-empty delta — mirroring the Chat-Completions path — so a mixed text-and-tool Responses turn carries an ordered `blocks` array and replays in the sequence it was recorded (#382)
- **A Gemini Live turn that both speaks and calls a function is now expressible over the Live protocol, and an audio turn keeps its companions.** The Live handler's audio branch emitted a single `serverContent` frame (audio `inlineData` + `turnComplete: true`) and returned, silently dropping the `toolCalls` and text `content` that `AudioResponse` documents as preserved. Because `serverContent` and `toolCall` are alternatives of one `LiveServerMessage` union (not sibling parts of a single list as on HTTP `generateContent`), a speak-and-call turn is now emitted as separate messages in the order the live provider was observed to use: a `serverContent` (`modelTurn.parts` = audio first, then the text companion) strictly **before** the `toolCall` (`functionCalls`), then a final `serverContent{ turnComplete: true }`. The assistant entry pushed to conversation history now records the recorded text `content` (instead of the literal `"[audio]"`) and the tool calls, with tool-call ids resolved once so the wire message and history agree. An audio-only turn (no `toolCalls`) is unchanged except that it now includes its text companion part when the fixture carries one. Not handled: `AudioResponse.reasoning` — this handler emits no thought channel in any branch, so replaying reasoning from the audio branch alone is deliberately left out as a separate gap (#378)
- **The CLI installs its `SIGINT` / `SIGTERM` handlers before announcing readiness, so a supervisor that shuts the server down the instant it starts no longer kills it ungracefully.** Previously the CLI logged `aimock server listening on <url>` and only then registered the signal handlers. Pipe writes are synchronous on Linux, so a supervisor reacting to that readiness line could deliver `SIGTERM` while the process was still mid-statement, in the window before the handler existed; the signal reached Node's default disposition, which re-raises it, and the process died with a null exit code without running its own shutdown (closing the file watcher and the server) — most visible under container / process supervisors. Both entrypoints (`cli.ts` and `aimock-cli.ts`) now register the handlers first and log readiness last, closing that race (observed on linux/node20 by freezing the child at the announcement: 55/60 iterations died with `code=null signal=SIGTERM` before the change, 0/60 after). The handlers are installed once the server is already listening, so a signal delivered during earlier startup — strictly before readiness — remains outside their scope (#374)

## [1.38.0] - 2026-08-03

### Added
Expand Down
Loading