fix: preserve framework-owned eve input request kinds - #26
Merged
Conversation
ncosentino
enabled auto-merge (squash)
August 1, 2026 01:11
eve stamps every human-input request with a framework-owned kind of question, tool-approval, or session-limit so consumers route by the discriminator instead of shape-sniffing option lists, display hints, or tool names. The typed projection dropped that value, so a session-limit gate offering continue/stop was indistinguishable from an approve/deny tool prompt. EveInputRequest now exposes a strongly typed Kind alongside RawKind, which preserves the wire value. An unmodelled future kind reports Unknown with the raw value intact; a server that predates the discriminator reports Unknown with a null raw value. A present non-string kind fails as a protocol error rather than impersonating a legacy server. The pinned eve 0.27.6 fixture gains an approval-gated tool so the compatibility probe drives a real input.requested pause, asserts the legacy projection, and answers the approval to resume the turn. Closes #25 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 6f397bc8-d45e-4945-a1f7-eb8d8d91a4fc
ncosentino
force-pushed
the
feat/input-request-kinds
branch
from
August 1, 2026 01:20
56cdd22 to
9b53b68
Compare
ncosentino
added a commit
that referenced
this pull request
Aug 1, 2026
Advances the pinned compatibility baseline from eve `0.27.6` (message-stream protocol `19`) to eve **`0.29.4`** (protocol **`20`**), so the C# client is gated against what eve actually ships today. ## Why CI boots a **real** eve server and runs the C# client against it — that fixture is the strongest evidence this repository produces. It was pinned to `0.27.6`, which predates: - the durable `meta.id` stream event identifier (#24, merged in #27), and - the framework-owned input-request `kind` discriminator (#25, merged in #26). Both only exist from eve `0.28.0`. Against a `0.27.6` server the probe could therefore only prove the *absence-tolerance* half of each contract; the *presence* half was covered by handwritten payloads. Bounded catch-up reads were in the same position: `0.27.6` accepted `includeTailIndex=1` but never reported `x-eve-stream-tail-index`, so that path was unreachable against a real server. ## Upstream risk review Read the full eve changelog for `0.27.7` → `0.29.4`. There are **no major/breaking sections** — only minor and patch. The one entry marked "Breaking" (`7ff4f77`, in `0.28.0`) is a **TypeScript type** change (`MessageStreamEvent` becoming canonical, `HandleMessageStreamEvent` kept as a deprecated alias); it is additive on the wire and does not affect a .NET client. The `0.29.0` minor changes remove CLI surface (`/channels`, `eve channels add`, `eve trace` → `eve traces`). The fixture only invokes `eve build` and `eve start`, both verified working. ## Changes - Fixture pinned to `eve@0.29.4`. `ai` moves `7.0.34` → `7.0.42` to satisfy eve's `^7.0.38` peer range. **The shipped package remains dependency-free** — this is test-fixture-only. - `EveProtocol.ReferenceEveVersion` → `0.29.4`, `MessageStreamVersion` → `20`. - README reference commit → [`85c1dd7`](vercel/eve@85c1dd7) (the `eve@0.29.4` tag), which is what the `eve-client-upstream-radar` skill parses, so future parity scans compare from the new baseline. - Probe assertions flipped from *absent* to *required*: - every event of a turn carries an `evt_`-prefixed Crockford base32 ULID, and no identifier repeats; - the approval-gated pause arrives as `tool-approval` → `EveInputRequestKind.ToolApproval`; - a **real** bounded catch-up read is verified against the durable tail header instead of asserting the header is missing. - `run-client-probe.mjs` reads the fixture's installed eve version instead of hardcoding it, and the two workflow step names are now version-agnostic so the next bump does not touch CI. - `CHANGELOG.md` records the baseline move plus the two features merged in #26/#27, which those PRs did not add. - Documentation updated across `README.md`, `AGENTS.md`, `docs/index.md`, `docs/compatibility.md`, `docs/streaming.md`, `docs/attachments-and-input.md`, and `docs/authentication.md`. ## Validation Everything below was run on this branch. | Gate | Result | |---|---| | `dotnet format --verify-no-changes` | clean | | `dotnet build -c Release` | 0 warnings, 0 errors | | `dotnet test -c Release` | **100 passed**, 0 failed, 0 skipped | | `npm ci` + `npm run test:client` (real eve **0.29.4**) | passed | | `mkdocs build --strict` | built | | `python -m unittest discover -s scripts/tests` | 6 passed | | `Invoke-Pester` (upstream radar) | 16 passed, 0 failed | | radar baseline parse | `EveProtocol.cs = 0.29.4`, `README = 0.29.4 @ 85c1dd7…` — consistent | | `dotnet pack` + `validate-packages.ps1` | validated `0.1.0-alpha-0002-g455c5a97a3` | | `test-package-consumer.ps1` | clean consumer built | The flip was empirically driven, not assumed. Running the **old** probe unchanged against `0.29.4` failed with: ``` The text turn produced 'session.started' with identifier 'evt_01KYXGNEJ9H98126J96N1NDNDS', but eve 0.27.6 emits stream protocol 19, which stamps none. ``` That is the real server proving `meta.id` projection works end to end. Tail-index support was likewise confirmed twice — statically in eve's compiled channel (`EVE_STREAM_TAIL_INDEX_HEADER` is set when a tail is resolved) and dynamically by the now-strict probe assertion passing. ## Disclosed gaps **HIGH — none.** **MEDIUM** - The CI gate no longer exercises protocol-19 tolerance against a real server. Absent identifiers and absent discriminators are still handled, and are covered by contract tests, but there is no longer a live protocol-19 server in CI. Keeping one would mean running two fixtures; that tradeoff is documented in `docs/compatibility.md` rather than silently dropped. - The fixture `ai` bump is required by eve's peer range, so fixture and eve versions are now coupled on upgrade. **LOW** - `0.24.6` and `0.27.6` rows are retained in the compatibility table as tolerated-but-not-gated, reflecting reality rather than implying continued verification. - The probe hardcodes the `evt_` prefix and 26-character Crockford alphabet rather than importing a shared validator, matching upstream's own shape-only check. Copilot-Session: 6f397bc8-d45e-4945-a1f7-eb8d8d91a4fc
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #25.
Upstream change
vercel/eve#1319 —
fix(eve): classify input requests by framework-owned kind, merged as98d17c789bdc.Verified directly against upstream rather than the radar summary:
packages/eve/src/runtime/input/types.tsadds a requiredkindtoinputRequestSchema, typed asz.enum(["question", "session-limit", "tool-approval"]).packages/eve/src/client/message-action-parts.tsandmessage-reducer-types.tscarry it into the client-facing input request so consumers route by the discriminator instead of shape-sniffing options, display hints, or tool names.First released in eve 0.28.0 — confirmed by unpacking the npm tarballs:
0.27.13has no"session-limit"literal,0.28.0does.Changes
EveInputRequestKindmodelsQuestion,ToolApproval,SessionLimit, andUnknown.EveInputRequest.Kindprojects the discriminator;EveInputRequest.RawKindpreserves the wire value.Unknowncases stay distinguishable: a newer eve sending an unmodelled kind keeps its value inRawKind, while a server that predates the discriminator reportsnull.kindthat is present but not a string throwsEveProtocolExceptionrather than impersonating a legacy server — consistent with how the parser already rejects a malformedoptionsvalue.docs/attachments-and-input.mdanddocs/compatibility.mddocument routing and the twoUnknowncases.Validation
dotnet format --verify-no-changesdotnet build -c Releasedotnet test -c Releasenpm run test:client(real eve0.27.6server)mkdocs build --strictNew coverage: all three kinds; a
session-limitrequest carrying aconfirmationhint,continue/stopoptions, and a tool name — the exact shape the old heuristic misclassified; aquestioncarrying approve/deny options; an unmodelled future kind; an absent kind; a non-string kind; multiple requests in one event keeping their own kinds; and an assertion that the nestedaction.kindis never read as the request discriminator.The pinned eve
0.27.6fixture gains an approval-gated tool (agent/tools/request_approval.tswithapproval: always()), and the mock model emits a matching tool call. The compatibility probe therefore drives a realinput.requestedpause, asserts the legacy projection (Kind == Unknown,RawKind == null), then answers the approval and verifies the turn resumes.Disclosed gaps
HIGH — none.
MEDIUM
EveProtocol.ReferenceEveVersionstays0.27.6, because the issue requires all changes through the upstream release to land first, and eve 0.28.0 also carries the stream-event-id change tracked by [Upstream eve] Expose stable stream event IDs #24. Advancing the pinned fixture should be a separate PR once both are merged.0.27.6, discriminator presence is verified only against synthetic payloads. Only the legacy-tolerance half is verified against a real server.session-limitcannot be produced by the pinned fixture, so it is covered by contract tests only.LOW
EveInputRequestKindis a plain enum. Adding a future kind is additive and non-breaking, but a caller switching exhaustively today will fall through toUnknown;RawKindis the documented escape hatch.inputSchemarather than Zod, so no new fixture dependency and no lockfile change was needed.