feat(blob): add @vercel/blob/eve extension for Eve agents - #1092
feat(blob): add @vercel/blob/eve extension for Eve agents#1092bensabic wants to merge 3 commits into
Conversation
🦋 Changeset detectedLatest commit: 16900db The changes in this PR will be included in the next version bump. This PR includes changesets to release 2 packages
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 |
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
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.
1397561 to
c69d9d5
Compare
`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.
| * `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. |
There was a problem hiding this comment.
Why does the extension not have it's own package.json?
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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({ |
There was a problem hiding this comment.
Not sure if this is a useful tool. I'd constrain the set a bit more
There was a problem hiding this comment.
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.
Exposes the Blob SDK as an Eve extension at the
@vercel/blob/evesubpath, so agents can mount Blob tools without hand-writing wrappers.Mounts
list,head,get,put,del,copy,rename, andcreate_folder.Safety and correctness
delandrenameare gated onapproval: always(). Both are irreversible —renamecopies then deletes the source — and Eve treats an omittedapprovalasnever(). Consumers can relax this with a directory mount override, documented in the README.ctx.abortSignalis threaded into every tool, so cancelling a turn also cancels the in-flight Blob request.getinlines at most 64 KiB of text. The byte count is enforced while reading rather than fromcontent-length, which is absent on chunked responses and would otherwise let a body of any size through into the model's context.delandputreject option combinations the SDK throws on (ifMatchwith multiple targets;ifMatchwithallowOverwrite: false) at schema validation, where the model gets an actionable message instead of a runtime error.headreports a missing blob as{ found: false }to matchget, rather than throwing.Packaging
zodis an optional peer dependency (^4) alongside the existing optionalevepeer, so consumers of the core SDK don't install it.scripts/restore-package-exports.mjswrapseve extension buildand repairs theexportsfield it rewrites — it repoints.at the extension and injects a top-level./tools. The merge is driven frompackage.jsonitself, so adding an export needs no script change, and a failed or interrupted build restorespackage.jsonunchanged.CreateFolderCommandOptionsnow declaresstoreIdandoidcToken, which were already forwarded at runtime. Type-only, backward-compatible.Tests
Adds 44 tests (172 → 216) covering the validation guards,
abortSignalforwarding,getinline/omit branching including thecontent-lengthand multi-byte cases,headmiss handling, and approval wiring.eveis ESM-only and the extension uses.jsspecifiers, neither of which Jest's CJS pipeline handles. Rather than change thejest.config.cjsshared withsrc/,eve/__tests__/harness.tsbridges both locally: the.jsspecifiers resolve to the real TypeScript modules (so the SDK and HTTP path are genuinely exercised) and only the threeeve/*entry points are shimmed.Verification
pnpm test:node— 216 passingpnpm type-check,pnpm check— cleanpnpm buildleavespackage.jsonbyte-identical; verified idempotent, and verified that an interrupted build and an already-clobberedpackage.jsonboth fail safelyblob({})type-checks against the built output andblob()does notFollow-ups (not in this PR)
src/get.tsreportssize: 0whencontent-lengthis absent. The tool now defends against it, but making itnullwould be a breaking change to the publicGetBlobResulttype.typesVersionshardcodes the eve subpaths, so a future new eve entry point would be picked up byexportsbut not bytypesVersions.