Skip to content

fix: preserve framework-owned eve input request kinds - #26

Merged
ncosentino merged 1 commit into
mainfrom
feat/input-request-kinds
Aug 1, 2026
Merged

fix: preserve framework-owned eve input request kinds#26
ncosentino merged 1 commit into
mainfrom
feat/input-request-kinds

Conversation

@ncosentino

Copy link
Copy Markdown
Owner

Closes #25.

Upstream change

vercel/eve#1319fix(eve): classify input requests by framework-owned kind, merged as 98d17c789bdc.

Verified directly against upstream rather than the radar summary:

  • packages/eve/src/runtime/input/types.ts adds a required kind to inputRequestSchema, typed as z.enum(["question", "session-limit", "tool-approval"]).
  • packages/eve/src/client/message-action-parts.ts and message-reducer-types.ts carry 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.13 has no "session-limit" literal, 0.28.0 does.

Changes

  • EveInputRequestKind models Question, ToolApproval, SessionLimit, and Unknown.
  • EveInputRequest.Kind projects the discriminator; EveInputRequest.RawKind preserves the wire value.
  • The two Unknown cases stay distinguishable: a newer eve sending an unmodelled kind keeps its value in RawKind, while a server that predates the discriminator reports null.
  • A kind that is present but not a string throws EveProtocolException rather than impersonating a legacy server — consistent with how the parser already rejects a malformed options value.
  • docs/attachments-and-input.md and docs/compatibility.md document routing and the two Unknown cases.

Validation

Gate Result
dotnet format --verify-no-changes clean
dotnet build -c Release 0 warnings, 0 errors
dotnet test -c Release 88 passed, 0 failed, 0 skipped (79 pre-existing + 9 new)
npm run test:client (real eve 0.27.6 server) passed
mkdocs build --strict built

New coverage: all three kinds; a session-limit request carrying a confirmation hint, continue/stop options, and a tool name — the exact shape the old heuristic misclassified; a question carrying 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 nested action.kind is never read as the request discriminator.

The pinned eve 0.27.6 fixture gains an approval-gated tool (agent/tools/request_approval.ts with approval: always()), and the mock model emits a matching tool call. The compatibility probe therefore drives a real input.requested pause, asserts the legacy projection (Kind == Unknown, RawKind == null), then answers the approval and verifies the turn resumes.

Disclosed gaps

HIGH — none.

MEDIUM

  • The compatibility baseline is intentionally not advanced. EveProtocol.ReferenceEveVersion stays 0.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.
  • Because the pinned fixture is 0.27.6, discriminator presence is verified only against synthetic payloads. Only the legacy-tolerance half is verified against a real server.
  • session-limit cannot be produced by the pinned fixture, so it is covered by contract tests only.

LOW

  • EveInputRequestKind is a plain enum. Adding a future kind is additive and non-breaking, but a caller switching exhaustively today will fall through to Unknown; RawKind is the documented escape hatch.
  • The fixture tool uses a plain JSON Schema inputSchema rather than Zod, so no new fixture dependency and no lockfile change was needed.

@ncosentino
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
ncosentino force-pushed the feat/input-request-kinds branch from 56cdd22 to 9b53b68 Compare August 1, 2026 01:20
@ncosentino
ncosentino merged commit 455c5a9 into main Aug 1, 2026
10 checks passed
@ncosentino
ncosentino deleted the feat/input-request-kinds branch August 1, 2026 01:23
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
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Upstream eve] Preserve framework-owned input request kinds

1 participant