feat(eve): UCP connection preset and checkout-handoff contract - #2689
feat(eve): UCP connection preset and checkout-handoff contract#2689malewis5 wants to merge 1 commit into
Conversation
Add `eve/commerce/ucp` for the buying side of the Universal Commerce Protocol, plus a `prepareRequest` hook on OpenAPI connections and a minimal eve + Next.js commerce-agent template. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Signed-off-by: Matt Lewis <29663600+malewis5@users.noreply.github.com> Co-Authored-By: Matt Lewis <29663600+malewis5@users.noreply.github.com>
Bundle + Package Summary:
|
| Area | Metric | Baseline | Current | Delta |
|---|---|---|---|---|
| Package | Packed tarball | 8.58 MB | 8.59 MB | +15.1 kB |
| Package | Unpacked publish size | 32.48 MB | 32.53 MB | +49.2 kB |
| Package | Installed footprint | 76.21 MB | 76.26 MB | +49.2 kB |
| Package | Published files | 3620 | 3633 | +13 |
| Package | Installed files | 7344 | 7357 | +13 |
| Package | Installed package instances | 35 | 35 | 0 |
| Package | Distinct installed package names | 34 | 34 | 0 |
| Package | Installed dependency edges | 53 | 53 | 0 |
| Package | Installed optional peer edges | 47 | 47 | 0 |
| Runtime | Unique function payloads | 2 | 2 | 0 |
| Runtime | Total function bytes | 18.91 MB | 18.91 MB | +516 B |
| Runtime | Public routes | 16 | 16 | 0 |
Changed function payloads vs main (c4f9d32) (2)
| Function | Status | Baseline | Current | Delta | Route changes |
|---|---|---|---|---|---|
functions/__server.func |
changed | 9.45 MB | 9.45 MB | +258 B |
none |
functions/.well-known/workflow/v1/flow.func |
changed | 9.45 MB | 9.46 MB | +258 B |
none |
eve init install
| Metric | Baseline | Current | Delta |
|---|---|---|---|
| Installed footprint | 114.64 MB | 114.69 MB | +49.2 kB |
| Installed packages | 120 | 120 | 0 |
| dependencies | 4 | 4 | 0 |
| devDependencies | 2 | 2 | 0 |
| Dependency package bytes | 47.16 MB | 47.21 MB | +49.2 kB |
| devDependency package bytes | 5.04 MB | 5.04 MB | 0 B ➖ |
Build Metadata
- Preset:
vercel - Nitro:
nitro@3.0.260610-beta - Output directory:
apps/fixtures/weather-agent/.vercel/output - Build metadata timestamp: 2026-08-28T00:33:21.769Z
- Route aliases: 16 public, 1 internal (17 total aliases)
- Vercel routes in config: 19
- Severity legend: 🔴 dominant/large, 🟠 notable, 🟡 watch, ⚪ small
Package Drill-Down
Package Details
- Package:
eve@0.47.2 - Package directory:
packages/eve - Tarball: 8.59 MB (
eve-0.47.2.tgz) - Unpacked payload: 32.53 MB across 3633 published files
- Installed footprint: 76.26 MB across 7357 installed files
- Installed root package: 31.17 MB
- Installed dependencies: 45.08 MB
- Installed package instances: 35
- Distinct installed package names: 34
- Installed dependency edges: 53
- Installed optional peer edges: 47
- Runtime dependencies: 2
- Peer dependencies: 5 (4 optional)
Installed footprint is measured from an isolated temporary npm install of the packed tarball.
Graph metrics read only package.json files in package directories directly beneath a node_modules boundary, including nested boundaries. Each directory is one package instance; distinct names come from those manifests. Dependency edges count each unique name in dependencies or optionalDependencies per instance; optional peer edges count peerDependencies marked optional.
Heavy installed dependencies
eve: 31.17 MB (40.9%)@rolldown/binding-linux-x64-gnu: 19.33 MB (25.3%)ai: 6.85 MB (9.0%)zod: 5.07 MB (6.6%)undici: 3.50 MB (4.6%)
Publish payload breakdown
Published file size
🔴 dist/src/compiled/shadcn-registry/index.js [#############...........] 9.76 MB 30.0%
🟠 dist/src/compiled/@photon-ai/chat-adapter-ime... [###.....................] 2.44 MB 7.5%
🟠 dist/src/compiled/experimental-ai-sdk-code-mo... [##......................] 1.51 MB 4.6%
🟡 dist/src/compiled/@vercel/blob/index.js [#.......................] 901.8 kB 2.8%
🟡 dist/src/compiled/_chunks/workflow/undici-Dzn... [#.......................] 512.7 kB 1.6%
🔴 Other published files [########################] 17.40 MB 53.5%
Installed footprint breakdown
Installed package size
🔴 eve [########################] 31.17 MB 40.9%
🔴 @rolldown/binding-linux-x64-gnu [###############.........] 19.33 MB 25.3%
🔴 ai [#####...................] 6.85 MB 9.0%
🔴 zod [####....................] 5.07 MB 6.6%
🟠 undici [###.....................] 3.50 MB 4.6%
🟠 nitro [##......................] 2.41 MB 3.2%
🔴 Other installed packages [######..................] 7.93 MB 10.4%
Runtime dependencies (2)
| Package | Range | Notes |
|---|---|---|
nitro |
3.0.260610-beta |
|
undici |
8.9.0 |
Peer dependencies (5)
| Package | Range | Notes |
|---|---|---|
@opentelemetry/api |
^1.0.0 |
optional peer |
ai |
catalog: |
|
braintrust |
^3.0.0 |
optional peer |
just-bash |
^3.1.0 |
optional peer |
microsandbox |
^0.5.0 |
optional peer |
eve init install drill-down
eve init install details
- Command:
eve init my-agent - Package manager:
npm - Installed footprint: 114.69 MB across 9253 installed files
- Installed packages: 120 total (114 transitive-only)
- dependencies: 4 direct packages totaling 47.21 MB
- devDependencies: 2 direct packages totaling 5.04 MB
- Other transitive package files: 62.44 MB
Installed footprint is measured from an isolated temporary eve init my-agent using the current packed eve tarball.
Heavy installed dependencies
eve: 31.17 MB (27.2%)@typescript/typescript-linux-x64: 27.95 MB (24.4%)@rolldown/binding-linux-x64-gnu: 19.33 MB (16.8%)zod: 9.02 MB (7.9%)ai: 6.85 MB (6.0%)
Installed footprint breakdown
Installed package size
🔴 eve [########################] 31.17 MB 27.2%
🔴 @typescript/typescript-linux-x64 [######################..] 27.95 MB 24.4%
🔴 @rolldown/binding-linux-x64-gnu [###############.........] 19.33 MB 16.8%
🔴 zod [#######.................] 9.02 MB 7.9%
🔴 ai [#####...................] 6.85 MB 6.0%
🟠 undici [###.....................] 3.50 MB 3.1%
🔴 Other installed packages [#############...........] 16.87 MB 14.7%
dependencies (4)
| Package | Range | Installed size | Share |
|---|---|---|---|
@vercel/connect |
1.0.0 |
167.9 kB | 0.1% |
ai |
^7.0.58 |
6.85 MB | 6.0% |
eve |
file:eve-0.47.2.tgz |
31.17 MB | 27.2% |
zod |
4.4.3 |
9.02 MB | 7.9% |
devDependencies (2)
| Package | Range | Installed size | Share |
|---|---|---|---|
@types/node |
24.x |
2.54 MB | 2.2% |
typescript |
7.0.2 |
2.50 MB | 2.2% |
Function Drill-Down
Payload Size Graph
Unique function payload size and share of total
🔴 functions/.well-known/workflow/v1/flow.func [########################] 9.46 MB 50.0%
🔴 functions/__server.func [########################] 9.45 MB 50.0%
Top Function Payloads
🟠 functions/.well-known/workflow/v1/flow.func • 1 public route • 9.46 MB
| Metric | Value |
|---|---|
| Public routes | /.well-known/workflow/v1/flow |
| Runtime | nodejs24.x |
| Handler | index.mjs |
| Payload | 9.46 MB |
| Function files | 9.46 MB across 111 files |
| Traced dependencies | 0 B |
| Signal | 🟠 Bundled file index.mjs is 1.93 MB (20.4%) |
🟠 🔎 Dependency Analysis
📦 Bundled files:
Bundled file size
🟠 index.mjs [##########..............] 1.93 MB 20.4%
🟡 _libs/undici.mjs [#####...................] 980.5 kB 10.4%
🟡 _chunks/decode_jwt-Col8JOnf.mjs [####....................] 702.6 kB 7.4%
🟡 _chunks/frames-Dl2Hcma4.mjs [####....................] 678.7 kB 7.2%
🟡 _chunks/chatgpt-model.mjs [####....................] 666.2 kB 7.0%
🔴 Other bundled files [########################] 4.50 MB 47.6%
🧾 Vercel Config
{
"handler": "index.mjs",
"launcherType": "Nodejs",
"shouldAddHelpers": false,
"supportsResponseStreaming": true,
"runtime": "nodejs24.x",
"maxDuration": "max",
"experimentalTriggers": [
{
"type": "queue/v2beta",
"topic": "__eve776561746865722d6167656e74_wkf_workflow_*",
"consumer": "default",
"retryAfterSeconds": 5,
"initialDelaySeconds": 0
}
],
"environment": {
"WORKFLOW_PRECONDITION_GUARD": "1"
}
}🟠 functions/__server.func • 15 public routes, 1 internal alias • 9.45 MB
| Metric | Value |
|---|---|
| Public routes | //eve/v1/callback/[token]/eve/v1/connections/[name]/callback/[attemptId]/[token]/eve/v1/connections/[name]/callback/[token]/eve/v1/health/eve/v1/info/eve/v1/session/eve/v1/session/[parentSessionId]/subagents/[callId]/[childSessionId]/stream/eve/v1/session/[sessionId]/eve/v1/session/[sessionId]/cancel/eve/v1/session/[sessionId]/clear/eve/v1/session/[sessionId]/compact/eve/v1/session/[sessionId]/reset/eve/v1/session/[sessionId]/stream/eve/v1/task-input/[token] |
| Internal aliases | /__server |
| Runtime | nodejs24.x |
| Handler | index.mjs |
| Payload | 9.45 MB |
| Function files | 9.45 MB across 111 files |
| Traced dependencies | 0 B |
| Signal | 🟠 Bundled file index.mjs is 1.93 MB (20.4%) |
🟠 🔎 Dependency Analysis
📦 Bundled files:
Bundled file size
🟠 index.mjs [##########..............] 1.93 MB 20.4%
🟡 _libs/undici.mjs [#####...................] 980.5 kB 10.4%
🟡 _chunks/decode_jwt-Col8JOnf.mjs [####....................] 702.6 kB 7.4%
🟡 _chunks/frames-Dl2Hcma4.mjs [####....................] 678.7 kB 7.2%
🟡 _chunks/chatgpt-model.mjs [####....................] 666.2 kB 7.0%
🔴 Other bundled files [########################] 4.50 MB 47.6%
🧾 Vercel Config
{
"handler": "index.mjs",
"launcherType": "Nodejs",
"shouldAddHelpers": false,
"supportsResponseStreaming": true,
"runtime": "nodejs24.x"
}Build Timing: e2e/fixtures/agent-tools-sandbox
This is an informational timing measurement inside eve build, from preflight through publication. Output-size measurement and profile writing are excluded.
Build mode: deployable Vercel build with sandbox template prewarm included.
- Build pipeline: 2.99 s -> 2.50 s (-485.6 ms) vs
main (c4f9d32). - Timing is informational: shared GitHub runners are too variable for a hard timing budget.
Detailed phase timings vs `main (c4f9d32)`
| Phase | Baseline | Current | Delta |
|---|---|---|---|
extension.check |
1.2 ms | 1.2 ms | 0.0 ms |
project.resolve |
0.3 ms | 0.4 ms | +0.1 ms |
workspace.create |
0.7 ms | 0.8 ms | +0.1 ms |
host.prepare |
750.8 ms | 446.1 ms | -304.7 ms |
vercel.service-prefix.resolve |
2.2 ms | 2.5 ms | +0.3 ms |
nitro.create |
260.6 ms | 264.6 ms | +4.0 ms |
sandbox.prewarm |
424.5 ms | 261.8 ms | -162.7 ms |
nitro.cache.prepare |
0.2 ms | 0.3 ms | +0.1 ms |
nitro.prepare |
0.8 ms | 0.8 ms | 0.0 ms |
nitro.public-assets |
0.8 ms | 0.8 ms | 0.0 ms |
nitro.prerender |
0.4 ms | 0.4 ms | 0.0 ms |
nitro.bundle |
1.49 s | 1.46 s | -22.7 ms |
nitro.cache.write |
0.3 ms | 0.5 ms | +0.2 ms |
vercel.workflow-function.materialize |
50.2 ms | 49.7 ms | -0.5 ms |
agent-summary.emit |
0.7 ms | 0.8 ms | +0.1 ms |
nitro.close |
0.1 ms | 0.1 ms | 0.0 ms |
output.publish |
3.5 ms | 3.6 ms | +0.1 ms |
workspace.remove |
2.3 ms | 2.3 ms | 0.0 ms |
Problem
An eve agent that wants to buy over the Universal Commerce Protocol can already point an OpenAPI connection at a merchant's shopping service — and it goes badly. The canonical UCP REST contract types
UCP-Agent,Idempotency-Key,Request-Id,Authorization,X-API-Key,Signature,Signature-Input, andContent-Digestas ordinary header parameters, and marks the first three required. So all of them land in the model-facing input schema, and the model is asked to invent the agent's identity, its credentials, and its cryptographic signatures. Two of the thirteen inputs oncreate_checkoutare actually the operation's own.The second problem is downstream. A checkout response spreads "what happens next" across four places —
status,messages[].severity,continue_url, and the per-session embedded service binding — and every application that touches UCP has to re-derive the same rules to decide whether the agent keeps working, the buyer gets redirected, or the merchant's checkout gets embedded.The existing
docs/protocols/ucp.mdxcovers the selling side only: publishing a profile and serving endpoints.Solution
Three parts.
eve/commerce/ucp— a generic UCP connection preset.defineUcpConnectionreturns anOpenAPIConnectionDefinition, so there is no new connection protocol and no new runtime client. What it adds is the protocol plumbing:agent.profileintoUCP-Agent: profile="..."as an RFC 8941 dictionary, and rejects a profile a merchant could not verify (non-https, relative) at definition time rather than on the firstprofile_unreachable.nullintoolCall.providedArguments, which strips it from the model-facing schema (including fromrequired) and writes no header.create_checkoutis left withbody,get_checkoutwithid. As a side effect a prompt-injectedAuthorizationcan no longer displace the connection's credential. AuthoredprovidedArgumentsare applied last, so a caller can re-expose one.Idempotency-Key(mutating methods) andRequest-Id(all methods) are derived from eve's replay-stablecallId, so a durable turn that resumes and re-issues the same tool call sends the same key and the merchant replays its cached response instead of placing a second order. Distinct tool calls always get distinct keys.Content-Digestover the exact bytes sent. Signing goes through WebCrypto, whose ECDSA output is already the fixed-widthr||sencoding the RFC requires rather than the DER most server-side libraries emit by default.A
prepareRequesthook on OpenAPI connections.headersandprovidedArgumentsboth resolve before the body exists, so no existing primitive can produce a value derived from the bytes being sent — which is what a content digest, and a signature over it, are.prepareRequestreceives the fully-built request (method, absolute URL, resolved headers, serialized body,callId,toolName) and merges the headers it returns. It is generic on purpose: HTTP Message Signatures, content digests, AWS SigV4. The UCP preset is its first consumer.resolveUcpCheckoutHandoff— the checkout-handoff contract. One discriminated union overconversational(withnext: "update" | "complete" | "poll"and the blockers to clear),continue_url,embedded,completed,canceled, andfailed. It accepts either a raw checkout body or an eve OpenAPI tool result, so a connection tool's output goes straight in. Notable rules: the buyer takes over onrequires_escalationor on anyrequires_buyer_input/requires_buyer_reviewseverity regardless of the status the merchant reported, whileunrecoverablealone stays conversational because the spec allows retrying with new inputs;embeddedis chosen only when this response carries an embedded binding withconfig.delegate(service-level support in the profile is not enough), and the returned URL hasec_version, the intersectedec_delegate, and anyec_auth/ec_color_schemeapplied.Template.
apps/templates/commerce-agent— an agent with the UCP connection, instructions that hold it to preparing rather than placing orders, a channel serving its own/.well-known/ucp, and a Next.js UI that renders all three handoff branches. Its route handler re-reads the session from the merchant rather than trusting the browser's copy, which also exercises the signer and identity helpers outside a connection.Scope boundaries
Buying side only; the selling-side guide is unchanged apart from a cross-link. No Shopify-specific behavior — the existing
eve initShopify integration is untouched. No multi-protocol abstraction: MCP, A2A, and Embedded Checkout'sec.*message channel are out, andprepareRequestis OpenAPI-only because MCP request construction lives inside the SDK. Cart, catalog, order, and payment-handler semantics stay author-owned.Validation
pnpm test:unit(7345 passed),pnpm test:integration(685 passed),pnpm typecheck(45/45),pnpm lint,pnpm guard:invariants,pnpm guard:fixtures,pnpm check:deps,pnpm docs:check.defineUcpConnectionthrough the realOpenApiConnectionClientagainst a trimmed copy of the published contract (same header parameters, samerequiredflags) and asserts the projected schema, the sent headers, idempotency-key reuse across a replayedcallId, that model-supplied protocol headers are discarded, and that the emitted signature verifies against the public key over the base built from the headers actually sent.next buildandeve buildboth run clean in the new template, so the connection passes eve's authored-definition validator and compiler for real.pnpm update:extension-contracts --update connection: classified structurally backward compatible, epoch 8 retained, bumped to 9.pnpm test:scenario,pnpm test:tui, and the e2e suites.The commit carries the DCO
Signed-off-bytrailer but is not GPG-signed — no verified signing key was available in the environment it was authored in.🤖 Generated with Claude Code