Skip to content

feat: scriptable citations (Responses) and model-generated files (Gemini) - #550

Merged
jpr5 merged 8 commits into
CopilotKit:mainfrom
abhiaiyer91:feat/citations-and-file-output
Oct 22, 2026
Merged

jpr5 merged 8 commits into
CopilotKit:mainfrom
abhiaiyer91:feat/citations-and-file-output

Conversation

@abhiaiyer91

Copy link
Copy Markdown
Contributor

Adds two optional fixture fields so downstream SDK tests can cover sources and generated files.

Both are carried on ResponseOverrides, so existing fixtures produce byte-identical output.

Test plan

  • New tests in responses.test.ts (citations, streamed + non-streamed + absent) and gemini.test.ts (files, streamed + non-streamed).
  • tsc --noEmit clean; full vitest run passes after pnpm build.

…tions

Downstream SDKs map Responses API url_citation annotations to source parts,
but fixtures could only produce empty annotations. A citations field on text
and content+tool-call responses now fills annotations on output_text parts and
emits response.output_text.annotation.added events when streaming.

Refs CopilotKit#547
Gemini returns generated images as inlineData parts, which SDKs surface as
file outputs. A files field on text responses now emits those parts ahead of
the text, both streamed and non-streamed.

Refs CopilotKit#548
@pkg-pr-new

pkg-pr-new Bot commented Oct 20, 2026 •

Copy link
Copy Markdown

Open in StackBlitz

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

commit: cf9cb15

@contextablemark contextablemark left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@abhiaiyer91 Picking this up from Jordan. The extension is useful, but these two findings need to be fixed before merge. Both still apply to head c1511cfa815fc041082740da6741532df929fcc4:

  1. [P1] Rename the new generated-file type to avoid breaking the existing public FixtureFile interface. src/types.ts:283 adds a second FixtureFile declaration; the existing fixture-document interface remains at line 1044. TypeScript merges them, so existing { fixtures: [...] } documents require mimeType and data, and generated-file objects require fixtures. Please rename the new interface (for example, FixtureGeneratedFile) and update ResponseOverrides.files. Add type coverage showing both existing fixture documents and generated-file entries remain valid. Today's CI confirms this: src/__tests__/gemini.test.ts(1263,57): error TS2741: Property 'fixtures' is missing ... but required in type 'FixtureFile'.
    CI evidence: https://github.com/CopilotKit/aimock/actions/runs/39055569629/job/118769540129

  2. [P2] Preserve Gemini files on combined responses. The new inlineData handling is confined to buildGeminiTextStreamChunks and buildGeminiTextResponse. buildGeminiContentWithToolCallsStreamChunks and buildGeminiContentWithToolCallsResponse still omit it. Adding toolCalls: [], nonempty tool calls, or text blocks to a response containing files silently drops those files in both streaming and non-streaming modes. Please carry files through both combined builders and add regression coverage for all three cases in both modes, preserving file/text/tool ordering.

The earlier runtime reproductions were run against this same unchanged head; I rechecked the current diff and CI today. Once both fixes are in and CI is green, I can re-review for merge.

Scope note: this implements Responses citations and Gemini generated files only. Please keep the remaining provider support in #547/#548 tracked rather than closing those broader requests as fully implemented.

jpr5 added 4 commits October 22, 2026 10:36
…dFile

The new FixtureFile interface merged with the existing fixture-document
FixtureFile, which broke typecheck and the public type. Rename it, export
FixtureCitation and FixtureGeneratedFile, and refresh the API reports.
The report change is additive only.
Combined content+toolCalls turns (including toolCalls: [] and blocks)
dropped fixture files on both the streaming and non-streaming wires.
Emit the same leading inlineData part that text turns emit. Fixtures
without files are unchanged.
@jpr5 jpr5 added the public-api-addition Intended public API report changes reviewed for compatibility; freshness remains required label Oct 22, 2026
@jpr5

jpr5 commented Oct 22, 2026

Copy link
Copy Markdown
Contributor

Thanks for this, @abhiaiyer91! Citations and generated files are a really nice addition. We finished off the two review items on top of your commits (yours are untouched) so this can ship:

  • The new file type is now FixtureGeneratedFile, so it no longer merges with the existing FixtureFile fixture-document type. Both new types are exported, and the API reports are refreshed.
  • Gemini now serves files on combined turns too (toolCalls: [], real tool calls, and blocks), streaming and non-streaming, with regression tests.

We also merged main in and added a changelog entry crediting you. Fixtures that don't use files or citations produce the same output as before.

Tool-only turns (no text) dropped fixture files on both wires. Emit the
inlineData part after any thought part and before the functionCall parts,
the same position as text and combined turns. Fixtures without files are
unchanged.
@jpr5
jpr5 merged commit b1bb65e into CopilotKit:main Oct 22, 2026
30 checks passed
jpr5 added a commit that referenced this pull request Oct 23, 2026
Closes #547
Closes #548

#550 added citations on the OpenAI Responses API and generated files on
Gemini. This PR covers the remaining providers, adds file citations on
Responses, and makes malformed `citations` and `files` fail loudly
instead of being served or dropped silently. It reuses the same fixture
fields (`citations`, `files`) and the same defaults: the title falls
back to the URL, and URL citation offsets default to the whole text.

## What each provider does now

**`citations`** (#547)

- **Chat Completions:** `message.annotations` gets `{type:
"url_citation", url_citation: {url, title, start_index, end_index}}`,
the shape of `ChatCompletionMessage.Annotation` in the openai SDK. When
streaming, one chunk with `delta.annotations` is sent after the content
and before the finish chunk. The SDK's `ChatCompletionStream` copies it
into `message.annotations`.
- **Anthropic Messages:** the text block gets `citations: [{type:
"web_search_result_location", cited_text, url, title, encrypted_index}]`
(`CitationsWebSearchResultLocation` in `@anthropic-ai/sdk`). The real
`encrypted_index` is opaque, so aimock fills in a stable base64
placeholder. When streaming, one `citations_delta` per citation comes
after the text deltas and before `content_block_stop`, in the same order
as the streaming example in the Anthropic citations docs. On a `blocks`
turn with several text blocks, each citation goes to the text block that
holds its start.
- **Gemini (including Vertex AI):** the candidate gets
`groundingMetadata` with one `groundingChunks[].web {uri, title}` per
citation and `groundingSupports[] {segment {partIndex, startIndex,
endIndex, text}, groundingChunkIndices}`. Segment offsets are UTF-8 byte
offsets into the text of the part named by `partIndex`, as the `Segment`
docs in `@google/genai` require. A citation that spans several text
parts becomes one support per part. When streaming, each chunk that
holds cited text carries the supports for its own text.
- **OpenAI Responses:** `url_citation` annotations as in #550, now also
split correctly across several text blocks. A citation with `type:
"file"` (`fileId`, `filename`, optional `index`, which defaults to the
end of the text) becomes a `file_citation` annotation `{type, file_id,
filename, index}` on `output_text`, streamed through
`response.output_text.annotation.added`. Chat Completions, Anthropic and
Gemini have no wire shape for a file citation, so they skip it.

**`files`** (#548)

- **OpenAI Responses:** each file becomes an `image_generation_call`
output item `{type, id, status, result}`, with `result` holding the
base64 bytes. The items come after the reasoning and web-search items
and before the message and tool-call items, on text, tool-only,
content+toolCalls and `blocks` turns. When streaming, each item gets
`output_item.added` (`result: null`), then
`response.image_generation_call.in_progress`, `.generating` and
`.completed`, then `output_item.done`. Only `image/*` files can be
carried this way; any other MIME type answers a 500 with
`aimock_unsupported_generated_file`.
- **Gemini:** files are served as `inlineData` parts, as #550 added.
- **Chat Completions and Anthropic Messages:** these wires have no
generated-file shape, so a fixture that sets `files` is rejected before
any byte is written. The response is a 500 with `code:
"aimock_unsupported_generated_file"` in the provider's error envelope.
It uses the same journaling and error path as `UnsupportedToolCallError`
for `customToolCalls`. Before this PR the files were dropped silently
with a 200.

## Validation and error codes

Fixture `citations` and `files` are checked on every request that serves
them on Chat Completions, Anthropic Messages, Gemini and the OpenAI
Responses API. A malformed value answers a 500 before any content is
written:

- `aimock_invalid_fixture_citation`: `citations` is not an array, an
entry is not an object, `url` (or `fileId` / `filename` for a file
citation) is missing or not a non-empty string, an offset is not an
integer, offsets fall outside the text (`0 <= startIndex <= endIndex <=
text length`, `0 <= index <= text length`), an offset splits a UTF-16
surrogate pair, an entry mixes URL and file citation fields, or the turn
has no text to cite (tool calls only, `blocks` with no text block, or
empty content).
- `aimock_invalid_fixture_file`: `files` is not an array, or an entry
lacks a non-empty string `mimeType` or a string `data`.
- `aimock_unsupported_generated_file`: any file on Chat Completions or
Anthropic Messages, or a non-`image/*` file on the OpenAI Responses API.

Each message names the field, says what was wrong, and says how to fix
it. A `null` value or `null` optional field is treated as absent, so
JSON fixtures that write `null` for an unset key keep working. The new
`InvalidFixtureOutputError` (behind the two `aimock_invalid_fixture_*`
codes) and `UnsupportedGeneratedFileError` are exported from
`@copilotkit/aimock`. Other wires (Bedrock, Ollama, Cohere, Gemini
Interactions, Gemini Live, Realtime) do not read these keys and ignore
them, as before.

## Behavior under misbehavior faults

The fixture is checked on its original turn before any fault applies, so
the result is the same with or without `--misbehavior`. Only an invalid
misbehavior config answers its own 400 or 501 first.

- A fixture the wire rejects (malformed `citations` or `files`,
generated files on Chat Completions or Anthropic, a non-`image/*` file
on Responses) is never faulted. It takes the normal path, is journaled
as a 500 that keeps its fixture, and consumes no fault `times` budget or
seed ordinal. `aimock_misbehavior_total` does not count it. This holds
on HTTP and on the Responses WebSocket.
- A valid fixture under a fault that changes the reply text (refusal,
empty response, content filter, reasoning only) is served without its
`citations`, so no citation points into text the client never receives.
A fault that keeps the text keeps the citations.
- A non-stream Chat Completions fault is journaled before its timing
wait, and a client that hangs up during the timing wait of any
non-stream Chat Completions reply is journaled as interrupted, not as
served.

## Load warnings

Fixture load validation reports malformed `citations` and `files` as
warnings, with the same problem text the 500 carries, plus which 500 the
requests will answer. The fixture still loads, as in the release that
added the keys. For example:

```
[aimock] Fixture 1: citations[0] offsets must satisfy 0 <= startIndex <= endIndex <= text length (6), got startIndex 0, endIndex 99; fix the offsets, or omit them to cite the whole text — requests it serves on Chat Completions, Anthropic Messages, Gemini or the OpenAI Responses API answer a 500 (aimock_invalid_fixture_citation)
```

## Compatibility

- The change is additive. New public API: `ChatCompletionAnnotation`,
`annotations?` on `ChatCompletionMessage` and `SSEDelta`,
`FixtureUrlCitation`, `FixtureFileCitation`, `InvalidFixtureOutputError`
and `UnsupportedGeneratedFileError`. The API reports for every entry
point are updated because they carry the citation types transitively.
- `FixtureCitation` changed from an interface to the union
`FixtureUrlCitation | FixtureFileCitation`. It has never been released:
#550 merged after 1.46.0, and the type is not in the v1.46.0 tag.
`FixtureUrlCitation` has the same fields as the old interface, plus an
optional `type: "url"`.
- Fixtures without `citations` or `files` produce the same responses as
before. I compared 16 responses (Chat Completions, Responses, Anthropic
and Gemini, streaming and non-streaming, text and content+toolCalls
fixtures) between base `508dd9b9` and this branch. After normalizing ids
and timestamps they are byte-identical. The tests also assert that no
`annotations` key, `citations` key, `groundingMetadata` or
`image_generation_call` appears when the field is absent.
- Fixtures with malformed `citations` or `files` that used to be served
with a 200 now answer a 500 on the four wires above. That is the
intended fail-loud behavior, and the load warning names each such
fixture.

## Local red-green proof (real server, real wire)

I started the CLI server (`npx tsx src/cli.ts -f fixtures.json`) from
base `508dd9b9` (RED) and from this branch (GREEN), and sent the same
requests to each.

### Citations and files

RED (base): no citations on Chat Completions, Anthropic or Gemini, no
`image_generation_call`, and files ignored with a 200.

```
===== chat non-stream (cite)
{"id":"chatcmpl-ABR70VXJpPbtwn9o","object":"chat.completion","created":1792794119,"model":"gpt-4o","choices":[{"index":0,"message":{"role":"assistant","content":"Paris is the capital of France.","refusal":null},"logprobs":null,"finish_reason":"stop"}],"usage":{"prompt_tokens":1,"completion_tokens":8,"total_tokens":9}}
HTTP 200
===== anthropic non-stream (cite)
{"id":"msg_2Oqvf9l5-9Vmv84Q","type":"message","role":"assistant","content":[{"type":"text","text":"Paris is the capital of France."}],"model":"claude-sonnet-4-5","stop_reason":"end_turn","stop_sequence":null,"usage":{"input_tokens":0,"output_tokens":0}}
HTTP 200
===== gemini non-stream (cite)
{"candidates":[{"content":{"role":"model","parts":[{"text":"Paris is the capital of France."}]},"finishReason":"STOP","index":0}],"usageMetadata":{"promptTokenCount":0,"candidatesTokenCount":0,"totalTokenCount":0}}
HTTP 200
===== responses non-stream (file)
{"id":"resp-0iBUzUlYjZNai8Ey","object":"response","created_at":1792794119,"model":"gpt-4o","status":"completed","output":[{"type":"message","id":"msg-7D1aKAwUVCFjfhYO","status":"completed","role":"assistant","content":[{"type":"output_text","text":"Here is your chart.","annotations":[]}]}],"usage":{"input_tokens":0,"output_tokens":0,"total_tokens":0}}
HTTP 200
===== chat non-stream (file)
{"id":"chatcmpl-xGemf9eHOvlINMFB","object":"chat.completion","created":1792794119,"model":"gpt-4o","choices":[{"index":0,"message":{"role":"assistant","content":"Here is your chart.","refusal":null},"logprobs":null,"finish_reason":"stop"}],"usage":{"prompt_tokens":1,"completion_tokens":5,"total_tokens":6}}
HTTP 200
===== anthropic non-stream (file)
{"id":"msg_AZdaedU5PiA5IeHJ","type":"message","role":"assistant","content":[{"type":"text","text":"Here is your chart."}],"model":"claude-sonnet-4-5","stop_reason":"end_turn","stop_sequence":null,"usage":{"input_tokens":0,"output_tokens":0}}
HTTP 200
```

GREEN (this branch), same requests:

```
===== chat non-stream (cite)
{"id":"chatcmpl-WXkfcPuLdYFBJub6","object":"chat.completion","created":1792794122,"model":"gpt-4o","choices":[{"index":0,"message":{"role":"assistant","content":"Paris is the capital of France.","refusal":null,"annotations":[{"type":"url_citation","url_citation":{"url":"https://example.com/paris","title":"Paris","start_index":0,"end_index":5}},{"type":"url_citation","url_citation":{"url":"https://example.com/france","title":"https://example.com/france","start_index":0,"end_index":31}}]},"logprobs":null,"finish_reason":"stop"}],"usage":{"prompt_tokens":1,"completion_tokens":8,"total_tokens":9}}
HTTP 200
===== anthropic non-stream (cite)
{"id":"msg_glKfFbKTh0KO7CGA","type":"message","role":"assistant","content":[{"type":"text","text":"Paris is the capital of France.","citations":[{"type":"web_search_result_location","cited_text":"Paris","url":"https://example.com/paris","title":"Paris","encrypted_index":"YWltb2NrLWNpdGF0aW9uOjA6aHR0cHM6Ly9leGFtcGxlLmNvbS9wYXJpcw=="},{"type":"web_search_result_location","cited_text":"Paris is the capital of France.","url":"https://example.com/france","title":"https://example.com/france","encrypted_index":"YWltb2NrLWNpdGF0aW9uOjE6aHR0cHM6Ly9leGFtcGxlLmNvbS9mcmFuY2U="}]}],"model":"claude-sonnet-4-5","stop_reason":"end_turn","stop_sequence":null,"usage":{"input_tokens":0,"output_tokens":0}}
HTTP 200
===== gemini non-stream (cite)
{"candidates":[{"content":{"role":"model","parts":[{"text":"Paris is the capital of France."}]},"finishReason":"STOP","groundingMetadata":{"groundingChunks":[{"web":{"uri":"https://example.com/paris","title":"Paris"}},{"web":{"uri":"https://example.com/france","title":"https://example.com/france"}}],"groundingSupports":[{"segment":{"partIndex":0,"startIndex":0,"endIndex":5,"text":"Paris"},"groundingChunkIndices":[0]},{"segment":{"partIndex":0,"startIndex":0,"endIndex":31,"text":"Paris is the capital of France."},"groundingChunkIndices":[1]}]},"index":0}],"usageMetadata":{"promptTokenCount":0,"candidatesTokenCount":0,"totalTokenCount":0}}
HTTP 200
===== responses non-stream (file)
{"id":"resp-SdNRLMU3Ar8AIbNP","object":"response","created_at":1792794122,"model":"gpt-4o","status":"completed","output":[{"type":"image_generation_call","id":"ig-0TcEztbhS3UKMcgT","status":"completed","result":"iVBORw0KGgo="},{"type":"message","id":"msg-yQ0AN_JYl54A_5fW","status":"completed","role":"assistant","content":[{"type":"output_text","text":"Here is your chart.","annotations":[]}]}],"usage":{"input_tokens":0,"output_tokens":0,"total_tokens":0}}
HTTP 200
===== chat non-stream (file)
{"error":{"message":"aimock: fixture \"files\" (model-generated files) are only supported on Gemini and the OpenAI Responses API; this request arrived on OpenAI Chat Completions","type":"server_error","code":"aimock_unsupported_generated_file"}}
HTTP 500
===== anthropic non-stream (file)
{"type":"error","error":{"type":"api_error","code":"aimock_unsupported_generated_file","message":"aimock: fixture \"files\" (model-generated files) are only supported on Gemini and the OpenAI Responses API; this request arrived on Anthropic Messages"}}
HTTP 500
```

### File citations, validation and faults

RED (base): a file citation is served as a broken `url_citation` with no
URL, malformed citations and files are served with a 200, a PDF on
Responses is dropped silently, and a malformed fixture under a refusal
fault is faulted.

```
===== responses non-stream (file citation)
HTTP 200
... "content":[{"type":"output_text","text":"See the attached report.","annotations":[{"type":"url_citation","start_index":0,"end_index":24}]}] ...
===== chat non-stream (out-of-range citation offsets)
HTTP 200
... "message":{"role":"assistant","content":"Short.","refusal":null} ...
===== anthropic non-stream (file missing data)
HTTP 200
... "content":[{"type":"text","text":"Chart."}] ...
===== responses non-stream (non-image file)
HTTP 200
... "output":[{"type":"message", ... "text":"Doc.","annotations":[]}]}] ...
===== chat non-stream (malformed citation under refusal fault)
HTTP 200
... "message":{"role":"assistant","content":null,"refusal":"I can't help with that."} ...
```

GREEN (this branch), same requests:

```
===== responses non-stream (file citation)
HTTP 200
... "content":[{"type":"output_text","text":"See the attached report.","annotations":[{"type":"file_citation","file_id":"file-abc123","filename":"report.pdf","index":24}]}] ...
===== responses stream (file citation)
event: response.output_text.annotation.added
data: {"type":"response.output_text.annotation.added", ... "annotation_index":0,"annotation":{"type":"file_citation","file_id":"file-abc123","filename":"report.pdf","index":24}}
===== chat non-stream (out-of-range citation offsets)
HTTP 500
{"error":{"message":"aimock: invalid fixture \"citations\": citations[0] offsets must satisfy 0 <= startIndex <= endIndex <= text length (6), got startIndex 0, endIndex 99; fix the offsets, or omit them to cite the whole text","type":"server_error","code":"aimock_invalid_fixture_citation"}}
===== gemini non-stream (out-of-range citation offsets)
HTTP 500
{"error":{"code":500,"message":"aimock: invalid fixture \"citations\": citations[0] offsets must satisfy ...","status":"INTERNAL","details":[{"@type":"type.googleapis.com/google.rpc.ErrorInfo","reason":"AIMOCK_INVALID_FIXTURE_CITATION","domain":"aimock","metadata":{"code":"aimock_invalid_fixture_citation"}}]}}
===== anthropic non-stream (file missing data)
HTTP 500
{"type":"error","error":{"type":"api_error","code":"aimock_invalid_fixture_file","message":"aimock: invalid fixture \"files\": files[0].data must be a string, got undefined; set the base64-encoded file bytes"}}
===== responses non-stream (non-image file)
HTTP 500
{"error":{"message":"aimock: fixture \"files\" on the OpenAI Responses API carry only image/* files (as image_generation_call items); got mimeType \"application/pdf\"","type":"server_error","code":"aimock_unsupported_generated_file"}}
===== chat non-stream (malformed citation under refusal fault)
HTTP 500
{"error":{"message":"aimock: invalid fixture \"citations\": citations[0].url must be a non-empty string, got undefined; set the cited source URL","type":"server_error","code":"aimock_invalid_fixture_citation"}}
===== chat non-stream (valid citation under refusal fault)
HTTP 200
... "message":{"role":"assistant","content":null,"refusal":"I can't help with that."} ...   (no annotations: the refusal replaced the cited text)
```

With `--validate-on-load`, the base server prints no warning for these
fixtures. This branch prints one warning per malformed fixture (fixtures
1, 2 and 4), as shown in the load warnings section above.

### Through the official SDK clients

Earlier on this branch I also ran the citation and file fixtures through
openai 6.49.0 and @anthropic-ai/sdk 0.78.0 against the running server.
Non-streaming and accumulated streaming results both carried the
annotations and citations shown above. The Responses client reported
output types `image_generation_call,message` with `result:
iVBORw0KGgo=`. The Chat Completions and Anthropic clients raised a 500
with `aimock_unsupported_generated_file` for the `files` fixture.

## Tests and quality gates

- New tests cover each provider (`server.test.ts`, `messages.test.ts`,
`gemini.test.ts`, `responses.test.ts`), the builders' cited source text,
validation and load warnings
(`fixture-citations-files-validation.test.ts`), the fault matrix and
fault journaling (`misbehavior-citations.test.ts`,
`misbehavior-fixture-output-validation.test.ts`,
`misbehavior-fault-journal-order.test.ts`,
`ws-responses-fault-journal-order.test.ts`), and the
`InvalidFixtureOutputError` export.
- `pnpm format:check`, `pnpm lint`, `pnpm typecheck`, `pnpm build` and
`pnpm api:report` are clean. `pnpm test`: 18539 tests pass.

## Deferred

These are out of scope for this PR and are tracked in our follow-ups
list:

- The other wires (Bedrock, Ollama, Cohere, Gemini Interactions, Gemini
Live, Realtime) still ignore `citations` and `files`. Each needs a
decision between serving them and rejecting them like Chat Completions
and Anthropic.
- File citations are skipped on Chat Completions, Anthropic and Gemini
without a warning, because those wires have no shape for them.
- `files[].data` is not checked for empty or non-base64 content.
- The misbehavior planner's check, and the load validator's text lookup
for malformed `blocks`, treat any thrown error as "cannot serve" without
logging it. The fixture still answers its coded 500 on the normal path,
and `validateBlocks` reports malformed blocks as a load error.
- On non-stream Chat Completions, a citation error waits out the
configured latency before the 500.
- Type tightening (for example, marking file-only fields as `never` on
`FixtureUrlCitation`) and some additional test cases.
@jpr5 jpr5 mentioned this pull request Oct 27, 2026
jpr5 added a commit that referenced this pull request Oct 27, 2026
Release v1.47.0. Merging this PR publishes through
`publish-release.yml`: npm, the git tag, the GitHub Release and Slack.

## Changelog

### 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)

The detailed notes from `[Unreleased]` are condensed into these bullets.
The full detail is in the linked PRs. The drift-harness and CI changes
(#583–#587) do not change what users see, so they are left out.

## Docs (separate commit)

- The fixtures page gets a new **Citations and generated files**
section: the fixture shape, the wire shape for each provider, the error
codes, and override-table and provider-matrix rows. The Responses, Chat
Completions, Claude and Gemini pages link to it.
- What's New has three 1.47.0 cards: citations and generated images, raw
passthrough, and stream realism (disconnects and timing on every
provider). The two 1.46.0 cards are merged into one. The GPT-Live card
is removed because it shipped in 1.43.0, more than a month ago.
- The README has new feature bullets for citations and files, raw
passthrough and client disconnects. The Streaming Physics bullet now
mentions `nonStreamingTimingAllProviders`.
- The competitive matrix has three new hand-reviewed rows:
provider-native citations, model-generated images, and stopping on
client disconnect. Each competitor cell was checked against a shallow
clone of that competitor's source on 2026-10-26, and an HTML comment on
each row records the evidence. The rows are added to `HAND_ROWS`, so the
weekly scan never rewrites them. The homepage ND count in the test is
now 56.

## Version carriers (`git grep -F 1.46.0`)

`package.json`, `charts/aimock/Chart.yaml` appVersion,
`.claude-plugin/plugin.json`, `.claude-plugin/marketplace.json`
(`^1.47.0`), the `docs/index.html` banner, the
`packages/aimock-pytest/README.md` default, and `_version.py`
`AIMOCK_VERSION`. These are not bumped: the CHANGELOG history, the
`version-note` labels for 1.46.0 features, and `pyproject.toml`.

## Local verification

- format:check, lint, typecheck, build, api:report with no `etc/` diff,
test:exports, the docs and matrix tests, and the full `pnpm test`.
- The citations and files wire shapes were exercised against the built
server for Chat, Responses, Claude and Gemini, including the 500 for
`files` on Chat Completions.
- Screenshots of What's New, the matrix and the fixtures section at 375,
430 and 1440 px.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

public-api-addition Intended public API report changes reviewed for compatibility; freshness remains required

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Feature: model-generated files (inline images) in chat/responses turns Feature: scriptable citations / annotations on text responses

3 participants