Skip to content

feat(blob): add @vercel/blob/eve extension for Eve agents - #1092

Open
bensabic wants to merge 3 commits into
mainfrom
bensabic/blob-eve-extension-2196
Open

feat(blob): add @vercel/blob/eve extension for Eve agents#1092
bensabic wants to merge 3 commits into
mainfrom
bensabic/blob-eve-extension-2196

Conversation

@bensabic

Copy link
Copy Markdown

Exposes the Blob SDK as an Eve extension at the @vercel/blob/eve subpath, so agents can mount Blob tools without hand-writing wrappers.

// agent/extensions/blob.ts
import blob from '@vercel/blob/eve';

export default blob({});

Mounts list, head, get, put, del, copy, rename, and create_folder.

Safety and correctness

  • del and rename are gated on approval: always(). Both are irreversible — rename copies then deletes the source — and Eve treats an omitted approval as never(). Consumers can relax this with a directory mount override, documented in the README.
  • ctx.abortSignal is threaded into every tool, so cancelling a turn also cancels the in-flight Blob request.
  • get inlines at most 64 KiB of text. The byte count is enforced while reading rather than from content-length, which is absent on chunked responses and would otherwise let a body of any size through into the model's context.
  • del and put reject option combinations the SDK throws on (ifMatch with multiple targets; ifMatch with allowOverwrite: false) at schema validation, where the model gets an actionable message instead of a runtime error.
  • head reports a missing blob as { found: false } to match get, rather than throwing.

Packaging

  • zod is an optional peer dependency (^4) alongside the existing optional eve peer, so consumers of the core SDK don't install it.
  • scripts/restore-package-exports.mjs wraps eve extension build and repairs the exports field it rewrites — it repoints . at the extension and injects a top-level ./tools. The merge is driven from package.json itself, so adding an export needs no script change, and a failed or interrupted build restores package.json unchanged.
  • CreateFolderCommandOptions now declares storeId and oidcToken, which were already forwarded at runtime. Type-only, backward-compatible.

Tests

Adds 44 tests (172 → 216) covering the validation guards, abortSignal forwarding, get inline/omit branching including the content-length and multi-byte cases, head miss handling, and approval wiring.

eve is ESM-only and the extension uses .js specifiers, neither of which Jest's CJS pipeline handles. Rather than change the jest.config.cjs shared with src/, eve/__tests__/harness.ts bridges both locally: the .js specifiers resolve to the real TypeScript modules (so the SDK and HTTP path are genuinely exercised) and only the three eve/* entry points are shimmed.

Verification

  • pnpm test:node — 216 passing
  • pnpm type-check, pnpm check — clean
  • pnpm build leaves package.json byte-identical; verified idempotent, and verified that an interrupted build and an already-clobbered package.json both fail safely
  • Confirmed blob({}) type-checks against the built output and blob() does not

Follow-ups (not in this PR)

  • src/get.ts reports size: 0 when content-length is absent. The tool now defends against it, but making it null would be a breaking change to the public GetBlobResult type.
  • typesVersions hardcodes the eve subpaths, so a future new eve entry point would be picked up by exports but not by typesVersions.

@changeset-bot

changeset-bot Bot commented Jul 27, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 16900db

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 2 packages
Name Type
@vercel/blob Minor
vercel-storage-integration-test-suite Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@vercel

vercel Bot commented Jul 27, 2026

Copy link
Copy Markdown
Contributor

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
vercel-storage-next-integration-test-suite Ready Ready Preview Jul 27, 2026 1:27pm

Request Review

Comment thread packages/blob/eve/tools/get.ts Outdated
Exposes the Blob SDK as an Eve extension at the `@vercel/blob/eve`
subpath, so agents can mount `list`, `head`, `get`, `put`, `del`, `copy`,
`rename`, and `create_folder` tools without hand-writing wrappers.

Safety and correctness:

- `del` and `rename` are gated on `approval: always()`. Both are
  irreversible (rename copies then deletes the source), and Eve treats an
  omitted `approval` as `never()`. Consumers can relax this with a
  directory mount override, documented in the README.
- `ctx.abortSignal` is threaded into every tool, so cancelling a turn also
  cancels the in-flight Blob request.
- `get` inlines at most 64 KiB of text. The byte count is enforced while
  reading rather than from `content-length`, which is absent on chunked
  responses and would otherwise let a body of any size through into the
  model's context.
- `del` and `put` reject option combinations the SDK throws on (`ifMatch`
  with multiple targets; `ifMatch` with `allowOverwrite: false`) at schema
  validation, where the model gets an actionable message.
- `head` reports a missing blob as `{ found: false }` to match `get`,
  rather than throwing.

Packaging:

- `zod` is an optional peer dependency (`^4`) alongside the existing
  optional `eve` peer, so consumers of the core SDK do not install it.
- `scripts/restore-package-exports.mjs` wraps `eve extension build` and
  repairs the `exports` field it rewrites (it repoints `.` at the
  extension and injects a top-level `./tools`). The merge is driven from
  package.json itself, so adding an export requires no script change, and
  a failed or interrupted build restores package.json unchanged.
- The eve CLI requires Node >=24 while the core SDK supports >=20 and CI
  exercises 20.x/22.x, so the extension build is skipped with a notice on
  older versions instead of failing the whole build. `prepublishOnly`
  passes `--require-eve` so a publish from an unsupported Node fails
  loudly rather than shipping a tarball whose `./eve` exports point at
  files that were never emitted, and the publint job is pinned to Node 24
  so it still validates the real published artifact.
- `CreateFolderCommandOptions` now declares `storeId` and `oidcToken`,
  which were already forwarded at runtime. Type-only change.

Adds 44 tests covering the validation guards, abortSignal forwarding,
`get` inline/omit branching, `head` miss handling, and approval wiring.
`content-type` values carry optional parameters and are case-insensitive,
so comparing the raw header against `application/json` missed ordinary
responses like `application/json; charset=utf-8` and returned them with no
inline text. Parse off the parameters and lower-case before matching.

Also treats the `+json` structured suffix as inlineable, so types such as
`application/ld+json` and `application/problem+json` are inlined like
plain JSON.

Adds 9 cases covering parameterised, upper-case and `+json` types, plus
negative cases pinning that `image/png`, `application/octet-stream` and
`application/json-seq` are still omitted. Five of them fail against the
previous strict equality check.
Comment thread packages/blob/eve/extension.ts Outdated
Comment on lines +4 to +8
* `eve extension build` rewrites the `exports` field in place, assuming it owns
* the whole package: it repoints `.` at the extension entry (`./dist/eve/...`)
* and injects a top-level `./tools` entry. For `@vercel/blob` that is wrong --
* `.` and `./client` are the real package entry points, and the extension is
* published under the `./eve` namespace instead.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Why does the extension not have it's own package.json?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Mainly to keep it discoverable from the package people already have installed, and to avoid a second package that needs its own versioning plus its own dependency on @vercel/blob, which can then drift across releases.

This script is the cost of that. It reads the export list from package.json rather than hardcoding one, restores cleanly if the build fails partway, and refuses to run against an already broken package.json. I tested those paths.

Happy to split it out if you'd rather, it's a contained change.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

We already have this kind of behavior on the CLI if it makes sense. CLI exports some of the blob SDK functionality from another package. I agree with @ctgowrie and we can mirror that behavior

import { rename } from '../../src/rename.js';
import { blobCommandOptions } from '../lib/options.js';

export default defineTool({

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Not sure if this is a useful tool. I'd constrain the set a bit more

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Kept the full set so it maps one to one with the exported SDK methods. Anyone who knows @vercel/blob can guess what's there, and an unused tool only costs its description in the prompt.

rename also isn't quite free to compose. Doing it as copy then del means two calls, and del is approval gated, so a move would prompt the user twice.

A Vercel OIDC token is resolved per request -- `@vercel/oidc` reads the
`x-vercel-oidc-token` request-context header and refreshes an expired
token -- so a value bound once at mount time goes stale. Worse, passing
one explicitly short-circuits that refresh, since `resolveBlobAuth` only
falls through to `getVercelOidcToken()` when no `oidcToken` option is set.

Removing the option leaves the SDK to pick up the current token on its
own, so OIDC auth now works by setting `storeId` (or `BLOB_STORE_ID`) and
nothing else.
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.

3 participants