diff --git a/.changeset/eve-extension-and-eve-024.md b/.changeset/eve-extension-and-eve-024.md new file mode 100644 index 0000000..fff1e31 --- /dev/null +++ b/.changeset/eve-extension-and-eve-024.md @@ -0,0 +1,8 @@ +--- +"@upstash/agentkit-eve-extension": minor +"@upstash/agentkit-eve": minor +--- + +Add `@upstash/agentkit-eve-extension`: AgentKit as a mountable eve extension (eve ≥0.24). One file in `agent/extensions/` composes memory tools, schema-aware Redis Search tools, an opt-in durable chat-history hook, and a memory instructions fragment under one namespace. + +`@upstash/agentkit-eve` moves to **eve 0.24.6** and **ai 7.0.30** (stable). Breaking: eve ≥0.24 replaced the sandbox backend handle's `dispose()` with `shutdown()` (fires only on server shutdown; the Upstash Box backend now pauses the box), and the `eve` peer range is now `>=0.24.0`. diff --git a/CLAUDE.md b/CLAUDE.md index e18b525..47eb8d2 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -17,8 +17,10 @@ embeddings — keep that in mind when naming/among scoring. | `@upstash/agentkit-sdk` (`packages/sdk`) | Core, framework-agnostic primitives. **No `ai` dependency** (redis-only). | | `@upstash/agentkit-ai-sdk` (`packages/ai-sdk`) | Vercel AI SDK adapter. | | `@upstash/agentkit-eve` (`packages/eve`) | Eve framework adapter. Depends on the ai-sdk package. | +| `@upstash/agentkit-eve-extension` (`packages/eve-extension`) | AgentKit as a mountable **eve extension** (eve ≥0.24): one `agent/extensions/.ts` file composes memory tools, search tools, a chat-history hook, and an instructions fragment under `__*`. | -Examples (`examples/`): `ai-sdk-demo` (hand-written Next.js) and `eve-demo` (a real `eve` CLI scaffold). +Examples (`examples/`): `ai-sdk-demo` (hand-written Next.js), `eve-demo` (a real `eve` CLI scaffold), +and `eve-extension-demo` (a minimal eve scaffold that mounts the extension). `langchain` and `tanstack-ai` packages were **removed** — don't reintroduce them. ### Core SDK exports (`@upstash/agentkit-sdk`) @@ -66,9 +68,11 @@ Examples (`examples/`): `ai-sdk-demo` (hand-written Next.js) and `eve-demo` (a r `schema`+`name` — agent files must be self-contained, see eve-demo specifics), **plus** rate limiting: `createRateLimitAuth` (a ready eve route-auth `AuthFn`, `packages/eve/src/auth.ts`) and the core `createRateLimit` factory re-exported. **No model wrapper / no `./model` subpath.** -- **No chat history in eve** — `ChatHistory` is core/ai-sdk only. eve sessions are durable server-side - (Vercel Workflow) and `useEveAgent` has no `initialMessages` prop, so a stored transcript doesn't - round-trip cleanly; resume is via eve's `session` cursor, not us. +- **No chat history in the eve adapter** — `ChatHistory` is core/ai-sdk only here. eve sessions are + durable server-side (Vercel Workflow) and `useEveAgent` has no `initialMessages` prop, so a stored + transcript doesn't round-trip cleanly; resume is via eve's `session` cursor, not us. (The + **eve-extension** package does capture transcripts to Redis via its `chat_history` hook — write-side + only, same no-round-trip caveat.) - `./sandbox` → `upstash()` Upstash Box backend. **⚠ INCOMPLETE — see Known issues.** - Eve is file-centric, but the tool factories now **call `defineTool` internally** and return the branded `ToolDefinition` — users export them directly (no outer `defineTool(...)` wrap). Because of @@ -77,6 +81,34 @@ Examples (`examples/`): `ai-sdk-demo` (hand-written Next.js) and `eve-demo` (a r `eveChannel({ auth: [...] })`; it `.limit()`s, throws `ForbiddenError` (403) over the limit, else returns `null` to fall through to the real authenticators (`localDev()`/`vercelOidc()`/…). +### eve-extension (`packages/eve-extension`) +- Built with `eve extension build` (not tsup); `package.json` has `"eve": { "extension": "./extension" }`, + `eve` is a **peer** (`^0.24.6`), and `files` ships both `extension/` (source the consumer recompiles) + and `dist/` (mount factory + `./tools` re-exports). `prepare` runs the build on install. +- `extension/extension.ts` = `defineExtension({ config: zod })`; the default export is the mount factory. + Config knobs: `userId` (string or `(ctx: SessionContext) => string` — eve's public base of tool+hook + ctx, imported from `eve/tools`), `redis` (defaults `Redis.fromEnv()`), `memory{topK,minScore}`, + `search{schema,indexName,prefix,defaultLimit}`, `chatHistory: boolean | {prefix,indexName,ttlSeconds}` + (**off by default** — enable with `true` or a tuning object). + Non-JSON config values (`Redis`, functions, the `s` schema) pass through `z.custom` — fine, the mount + file is evaluated in the runtime. +- Contributions: static tools `recall_memory`/`save_memory`; **dynamic** tools `search`/`search_aggregate`/ + `search_count` (one `defineDynamic` per file, resolved at `session.started` — static modules evaluate at + discovery where mount config is **not yet bound**, so schema-derived descriptions/input schemas must be + built in a resolver; unconfigured `search` → resolver returns `null` and the tools don't exist); + hook `chat_history` (appends every `message.received`/`message.completed` via core `ChatHistory.getChat` + + `saveChat`, errors swallowed — a thrown hook fails the turn); `instructions.md` fragment (merges after + the agent's own instructions). Shared code lives in `extension/lib/runtime.ts` — extensions CAN have + internal shared modules (unlike agent files). +- `resolveUserId` defaults `auth.current?.principalId ?? auth.initiator?.principalId ?? session.id` and + **sanitizes `:` → `_`** (eve principal ids like `eve:app` and session ids would break core key-part + validation). `sessionId` is sanitized the same way. +- Consumers drop/override slots via a directory mount + `disableTool()` (that's the supported answer for + "I don't want tool X" — no config flags for it). Static memory tools are importable from + `@upstash/agentkit-eve-extension/tools` for `toolResultFrom`/overrides; dynamic search tools are not. +- What an extension **cannot** contribute (stays in `@upstash/agentkit-eve`): sandbox, channels/auth + (rate limiting), schedules, agent config. `defineCachedTool` also stays there (wraps user tools). + ## Naming history (so you don't resurrect old names) - ai-sdk caching: `cacheTools` → `cachedTool`+`cachedTools` → now **`cachedTools` only** (singular `cachedTool` removed; toolName = map key, `userId` scopes). - eve `cachedExecute` → **`defineCachedTool`** (cache key field: `cachePrefix` → `namespace` → **`toolName`**); `recall/saveMemoryTool` → **`defineMemoryRecallTool`/`defineMemorySaveTool`**. @@ -115,15 +147,18 @@ Examples (`examples/`): `ai-sdk-demo` (hand-written Next.js) and `eve-demo` (a r `agentkit:memory::`, `agentkit:chat::` (default prefixes shown). ## AI SDK version strategy — IMPORTANT -- **AI SDK v7-beta everywhere.** Every package + demo pins `ai` to exactly **`7.0.0-beta.178`** (the - version `eve@0.13.1` depends on — it pins an exact version, not a range). Providers: `@ai-sdk/openai` - and `@ai-sdk/provider` on `^4.0.0-beta`. -- **Why exact-pin and not a pnpm `override`:** because everyone (incl. eve's transitive dep) lands on the - same exact `ai`, pnpm installs a single copy. Two copies of `ai` cause type/identity breakage. An - override was tried and removed as unnecessary — keep it that way unless a dep forces a different `ai@7`. +- **AI SDK v7 stable everywhere.** Every package + demo pins `ai` to exactly **`7.0.30`**. `eve@0.24.x` + now declares `ai` as a **peer** (`^7.0.26`), so the apps/packages provide the single copy. Providers: + `@ai-sdk/openai` `^4.0.15`, `@ai-sdk/provider` `^4.0.3`, `@ai-sdk/react` `^4.0.33` (all stable). + (History: the repo was on `7.0.0-beta.178`, the exact version `eve@0.13.1` depended on.) +- **Why exact-pin and not a pnpm `override`:** because everyone lands on the same exact `ai`, pnpm + installs a single copy. Two copies of `ai` cause type/identity breakage. An override was tried and + removed as unnecessary — keep it that way unless a dep forces a different `ai@7`. - **No `pnpm.overrides` in root `package.json`.** Version alignment is the mechanism. (`@types/react` was also deduped by aligning `ai-sdk-demo` to `19.2.15`, not by an override.) -- `pnpm-workspace.yaml` sets `minimumReleaseAge: 0` so beta installs aren't gated. +- `pnpm-workspace.yaml` sets `minimumReleaseAge: 0` so fresh eve/ai releases aren't gated. +- Two `zod` 4.x copies exist in the lockfile (`@vercel/cli-config`, eve-transitive, wants its own) — + preexisting and harmless; our packages all resolve one shared zod. - **v7 type renames to know:** `ToolCallOptions` → **`ToolExecutionOptions`**. v7's `LanguageModelMiddleware = Omit & { specificationVersion?: string }` so middlewares need **no** `specificationVersion` (v6 required `'v3'`, v5 required none — don't add it on v7). @@ -133,8 +168,10 @@ Examples (`examples/`): `ai-sdk-demo` (hand-written Next.js) and `eve-demo` (a r except the `e2e.test.ts` files which hit **real OpenAI**. - **Models:** unit/e2e tests use `gpt-4o` (`TEST_MODEL`); READMEs + demos use `gpt-5.4-mini`. - Each package has `src/test-support.ts`: `hasRedisCreds`, `testRedis()` (`Redis.fromEnv`), - `uniquePrefix(label)`, `cleanupKeys(redis, prefix)` — loads repo-root `.env` via dotenv. ai-sdk also has - `hasOpenAIKey`, `TEST_MODEL`. Suites `describe.skipIf(!hasRedisCreds)` so they skip without creds. + `uniquePrefix(label)` (colon-separated — key prefixes only), `uniqueUserId(label)` (dash-separated — + use for **userIds**, which reject `:`), `cleanupKeys(redis, prefix)` — loads repo-root `.env` via + dotenv. ai-sdk also has `hasOpenAIKey`, `TEST_MODEL`. Suites `describe.skipIf(!hasRedisCreds)` so + they skip without creds. - vitest: `fileParallelism: false`, `testTimeout: 30_000`. - **Upstash DB caps at 10 search indexes** (`ERR Exceeded max index count of 10`). Tests must `drop()` / reuse indexes and run sequentially. There is **no** `SEARCH.LIST` command to enumerate them. @@ -157,17 +194,30 @@ Examples (`examples/`): `ai-sdk-demo` (hand-written Next.js) and `eve-demo` (a r `$count`, `$histogram`, `$percentiles`, `$cardinality`. ## Eve framework facts -- `eve@0.13.1` is on npm with subpath exports: `eve/tools`, `eve/sandbox`, `eve/sandbox/vercel`, `eve/next`, … -- **Import eve's real types — do NOT hand-roll them.** From `eve/tools`: `defineTool`, `ToolDefinition`, - `ToolContext`. From `eve/sandbox`: `defineSandbox`, `SandboxBackend`, `SandboxSession`, - `SandboxNetworkPolicy`, etc. (`eve` is a devDep of `packages/eve` for these type imports.) +- The repo is on **`eve@0.24.6`** (peer `>=0.24.0` in `packages/eve`, `^0.24.6` in the extension). + Subpath exports: `eve/tools`, `eve/hooks`, `eve/extension`, `eve/context`, `eve/instructions`, + `eve/sandbox`, `eve/sandbox/vercel`, `eve/channels/*`, `eve/next`, … +- **Import eve's real types — do NOT hand-roll them.** From `eve/tools`: `defineTool`, `defineDynamic`, + `disableTool`, `toolResultFrom`, `ToolDefinition`, `ToolContext`, **`SessionContext`** (base of tool + + hook ctx — use it for per-call `userId` fns). From `eve/hooks`: `defineHook`, `HookContext`. From + `eve/extension`: `defineExtension`. From `eve/sandbox`: `defineSandbox`, `SandboxBackend`, + `SandboxSession`, `SandboxNetworkPolicy`, etc. (`eve` is a devDep of `packages/eve` for these types.) - `ToolDefinition` = `{ description, inputSchema, execute(input, ctx: ToolContext), … }`. +- **Extensions** (eve ≥0.24): agent-shaped packages mounted under `agent/extensions/.ts`; contributions + compose as `__`. They may contribute tools/connections/skills/hooks/instructions — NOT sandbox, + channels, schedules, or agent config. Config binds at runtime (mount evaluation), not at discovery. + Hooks are observe-only (can't inject context or short-circuit); a thrown hook fails the turn. +- Stream events for transcripts: `message.received` (`data.message`: flattened user text) and + `message.completed` (`data.message: string | null`, fires multiple times per turn — interim text before + tool calls; `data.finishReason` tells terminal from narration). - Eve uses AI SDK **v7** models, which is why the repo standardized on v7 (so eve can keep depending on the ai-sdk package instead of duplicating middleware). - The real `SandboxBackend` is **two-phase**: `{ name, create(input) → SandboxBackendHandle, prewarm(input) → { reused } }`. `SandboxSession` = the AI SDK `Experimental_SandboxSession` (`run`, `spawn`, `readFile`→stream, `readBinaryFile`, `readTextFile`, `writeFile`/`writeBinaryFile`/`writeTextFile`) plus - `id`, `resolvePath`, `setNetworkPolicy`, `removePath`. + `id`, `resolvePath`, `setNetworkPolicy`, `removePath`. In eve ≥0.24 the handle's lifecycle method is + **`shutdown()`** (fires only on server shutdown; must leave the session reattachable) — the old + per-open `dispose()` is gone. ## @upstash/box (sandbox backend) - Optional peer dep of the eve package. `Box.create({ apiKey | UPSTASH_BOX_API_KEY, runtime, size, … })`; @@ -191,6 +241,16 @@ Examples (`examples/`): `ai-sdk-demo` (hand-written Next.js) and `eve-demo` (a r - Keep `ai-sdk-demo`'s `@types/react` pinned to `19.2.15` (and `react` 19.2.6) to match eve-demo and avoid a duplicate `@types/react` (causes a JSX `key` "unique symbol" type clash in eve-demo's build). +## examples/eve-extension-demo specifics +- A minimal `eve` CLI scaffold (agent + eve channel, no frontend) whose whole point is the one mount + file `agent/extensions/agentkit.ts`. Model: `openai("gpt-5.4-mini")`. +- Its mount reuses **eve-demo's** books index (`eve-demo-books`, same schema) — the DB caps at 10 search + indexes, so the demos share; seed data comes from eve-demo's `lib/books.ts` seeder. +- Needs a local `.env` (gitignored) with the Upstash + OpenAI creds — `eve dev` reads the project dir, + not the repo root. +- The extension demo's `userId` is the static `"demo-user"` so memory persists across sessions in an + unauthenticated local agent (the default derivation would fall back to the per-session id). + ## Commands ```bash pnpm build # tsup (ESM + dts) all packages @@ -244,8 +304,9 @@ pnpm -r --filter "./examples/*" build # build both demo apps lookup. `prewarm` builds **no** box when there's nothing to bake (no seed files/bootstrap). **Session reuse:** `create` reattaches to the box from `input.existingMetadata.boxId` (`Box.get`) — Eve re-opens a session many times and hands our captured `boxId` back, so without this every open spun a fresh box (the - "3 boxes per turn" bug). `dispose` is a **no-op** (the box persists for reuse; Box's idle lifecycle reaps - it), and `keepAlive` defaults to **false** (pause-based idle; `true` can't be paused and runs until + "3 boxes per turn" bug). `shutdown` (eve ≥0.24's replacement for the old per-open `dispose`) fires only + when the server stops: it `box.pause()`s (reattachable; failure tolerated — keep-alive boxes can't + pause), and `keepAlive` defaults to **false** (pause-based idle; `true` can't be paused and runs until deleted). **Path bridge:** Eve roots its tools at `/workspace` but Box sessions live in `/workspace/home`, so the backend remaps both `resolvePath` (file ops) and raw commands (`find /workspace …` → `/workspace/home`, URL-safe via lookbehind) through the exported `toBoxPath`/`rewriteWorkspacePaths`. diff --git a/README.md b/README.md index 35ac486..ba0f685 100644 --- a/README.md +++ b/README.md @@ -13,6 +13,7 @@ are powered by [Upstash Redis Search](https://upstash.com/docs/redis/search/intr | [`@upstash/agentkit-sdk`](./packages/sdk) | Core, framework-agnostic primitives. | | [`@upstash/agentkit-ai-sdk`](./packages/ai-sdk) | Adapter for the [Vercel AI SDK](https://ai-sdk.dev). | | [`@upstash/agentkit-eve`](./packages/eve) | Adapter for the Vercel Eve framework. | +| [`@upstash/agentkit-eve-extension`](./packages/eve-extension) | The same capabilities as a mountable [Eve extension](https://eve.dev/docs/extensions) — one file in `agent/extensions/` adds memory tools, search tools, and durable chat-history capture. | ## Core features @@ -30,8 +31,10 @@ are powered by [Upstash Redis Search](https://upstash.com/docs/redis/search/intr ## Examples -Runnable Next.js demos (real Upstash Redis + a mock/real model) live in [`examples/`](./examples): -[`ai-sdk-demo`](./examples/ai-sdk-demo) and [`eve-demo`](./examples/eve-demo). +Runnable demos (real Upstash Redis + a mock/real model) live in [`examples/`](./examples): +[`ai-sdk-demo`](./examples/ai-sdk-demo), [`eve-demo`](./examples/eve-demo), and +[`eve-extension-demo`](./examples/eve-extension-demo) (an eve agent that mounts +`@upstash/agentkit-eve-extension`). ## Development diff --git a/examples/ai-sdk-demo/package.json b/examples/ai-sdk-demo/package.json index 8280f95..f6ae7c8 100644 --- a/examples/ai-sdk-demo/package.json +++ b/examples/ai-sdk-demo/package.json @@ -8,12 +8,12 @@ "start": "next start" }, "dependencies": { - "@ai-sdk/openai": "^4.0.0-beta", - "@ai-sdk/react": "4.0.0-beta.182", + "@ai-sdk/openai": "^4.0.15", + "@ai-sdk/react": "^4.0.33", "@upstash/agentkit-ai-sdk": "workspace:*", "@upstash/agentkit-sdk": "workspace:*", "@upstash/redis": "^1.38.0", - "ai": "7.0.0-beta.178", + "ai": "7.0.30", "dotenv": "^16.4.5", "next": "16.2.9", "react": "19.2.6", diff --git a/examples/eve-demo/package.json b/examples/eve-demo/package.json index be46625..729a386 100644 --- a/examples/eve-demo/package.json +++ b/examples/eve-demo/package.json @@ -14,13 +14,13 @@ "typecheck": "tsgo --noEmit -p tsconfig.json" }, "dependencies": { - "@ai-sdk/openai": "^4.0.0-beta", + "@ai-sdk/openai": "^4.0.15", "@upstash/agentkit-eve": "workspace:*", "@upstash/box": "^0.5.1", "@upstash/redis": "^1.38.0", "@vercel/connect": "0.2.2", - "ai": "7.0.0-beta.178", - "eve": "^0.17.1", + "ai": "7.0.30", + "eve": "^0.24.6", "zod": "4.4.3", "@radix-ui/react-use-controllable-state": "1.2.2", "@shikijs/core": "4.1.0", diff --git a/examples/eve-extension-demo/.gitignore b/examples/eve-extension-demo/.gitignore new file mode 100644 index 0000000..eb095f0 --- /dev/null +++ b/examples/eve-extension-demo/.gitignore @@ -0,0 +1,10 @@ +node_modules +.env* +.eve +.vercel +.next +.output +.nitro +dist +.DS_Store +*.tsbuildinfo diff --git a/examples/eve-extension-demo/.vercelignore b/examples/eve-extension-demo/.vercelignore new file mode 100644 index 0000000..c9be7b5 --- /dev/null +++ b/examples/eve-extension-demo/.vercelignore @@ -0,0 +1,7 @@ +node_modules +.env* +.eve +.next +.output +.nitro +dist diff --git a/examples/eve-extension-demo/AGENTS.md b/examples/eve-extension-demo/AGENTS.md new file mode 100644 index 0000000..3459045 --- /dev/null +++ b/examples/eve-extension-demo/AGENTS.md @@ -0,0 +1,7 @@ +# eve Agent App + +This project uses the eve framework. Before writing code, read the relevant guide +from the installed eve package docs. In most installs, those docs are at +`node_modules/eve/docs/`. In workspaces or local package installs, resolve the +installed `eve` package location first and read its `docs/` directory. If +package docs are unavailable, use https://eve.dev/docs as a fallback. diff --git a/examples/eve-extension-demo/CLAUDE.md b/examples/eve-extension-demo/CLAUDE.md new file mode 100644 index 0000000..43c994c --- /dev/null +++ b/examples/eve-extension-demo/CLAUDE.md @@ -0,0 +1 @@ +@AGENTS.md diff --git a/examples/eve-extension-demo/README.md b/examples/eve-extension-demo/README.md new file mode 100644 index 0000000..34e9c43 --- /dev/null +++ b/examples/eve-extension-demo/README.md @@ -0,0 +1,42 @@ +# eve-extension-demo + +A minimal [eve](https://eve.dev) agent that mounts +[`@upstash/agentkit-eve-extension`](../../packages/eve-extension) — the whole integration is the one +file [`agent/extensions/agentkit.ts`](./agent/extensions/agentkit.ts). It gives the agent: + +- long-term memory tools (`agentkit__recall_memory` / `agentkit__save_memory`), +- schema-aware Redis Search tools over the shared demo books index + (`agentkit__search` / `agentkit__search_aggregate` / `agentkit__search_count`), +- durable chat-history capture into Upstash Redis (the `agentkit__chat_history` hook), and +- a memory instructions fragment merged into the system prompt. + +## Run it + +Create `.env` in this directory: + +``` +UPSTASH_REDIS_REST_URL=... +UPSTASH_REDIS_REST_TOKEN=... +OPENAI_API_KEY=... +``` + +Then: + +```bash +pnpm dev # eve dev +``` + +Talk to it (the eve dev TUI, or curl): + +```bash +curl -X POST http://127.0.0.1:3000/eve/v1/session \ + -H 'content-type: application/json' \ + -d '{"message":"My favorite author is Ursula K. Le Guin - remember that, then find her earliest book in the index."}' +``` + +The agent saves the fact to memory, `$smart`-searches the books index, and the whole transcript lands +at `agentkit:chat:demo-user:` in Redis. A follow-up session ("what author do I like?") +recalls the memory. + +The books index (`eve-demo-books`) is shared with [`eve-demo`](../eve-demo), which seeds it — run that +demo once, or seed a few `eve-demo-books:*` JSON docs yourself. diff --git a/examples/eve-extension-demo/agent/agent.ts b/examples/eve-extension-demo/agent/agent.ts new file mode 100644 index 0000000..2594c3f --- /dev/null +++ b/examples/eve-extension-demo/agent/agent.ts @@ -0,0 +1,6 @@ +import { openai } from "@ai-sdk/openai"; +import { defineAgent } from "eve"; + +export default defineAgent({ + model: openai("gpt-5.4-mini"), +}); diff --git a/examples/eve-extension-demo/agent/channels/eve.ts b/examples/eve-extension-demo/agent/channels/eve.ts new file mode 100644 index 0000000..dafc122 --- /dev/null +++ b/examples/eve-extension-demo/agent/channels/eve.ts @@ -0,0 +1,15 @@ +import { eveChannel } from "eve/channels/eve"; +import { localDev, placeholderAuth, vercelOidc } from "eve/channels/auth"; + +export default eveChannel({ + auth: [ + // Lets the eve TUI and your Vercel deployments reach the deployed agent. + vercelOidc(), + // Open on localhost for `eve dev` and the REPL; ignored in production. + localDev(), + // This placeholder will not allow browser requests in production. + // Replace it with your app's auth provider, like Auth.js or Clerk, + // or use none() for a public demo. + placeholderAuth(), + ], +}); diff --git a/examples/eve-extension-demo/agent/extensions/agentkit.ts b/examples/eve-extension-demo/agent/extensions/agentkit.ts new file mode 100644 index 0000000..58d13fc --- /dev/null +++ b/examples/eve-extension-demo/agent/extensions/agentkit.ts @@ -0,0 +1,23 @@ +import { s } from "@upstash/redis"; +import agentkit from "@upstash/agentkit-eve-extension"; + +// One mount composes every AgentKit contribution under the `agentkit` namespace (the file basename): +// the tools `agentkit__recall_memory`, `agentkit__save_memory`, `agentkit__search`, +// `agentkit__search_aggregate`, and `agentkit__search_count`, the chat-history hook (opted into +// below — it's off by default), and a memory instructions fragment. +// +// To drop a contribution, mount as a directory instead and disable its slot — see the +// @upstash/agentkit-eve-extension README. +export default agentkit({ + // Static for this single-user demo. In production derive the tenant per call, e.g. + // `(ctx) => ctx.session.auth.current?.principalId ?? ctx.session.id`. + userId: "demo-user", + search: { + // Same schema + index the eve-demo seeds (an Upstash database caps at 10 search indexes, + // so the demos share one books index). + schema: s.object({ title: s.string(), author: s.string().noTokenize(), year: s.number() }), + indexName: "eve-demo-books", + }, + // Off by default; the demo turns it on so every message is persisted to Upstash Redis. + chatHistory: true, +}); diff --git a/examples/eve-extension-demo/agent/instructions.md b/examples/eve-extension-demo/agent/instructions.md new file mode 100644 index 0000000..bce87b0 --- /dev/null +++ b/examples/eve-extension-demo/agent/instructions.md @@ -0,0 +1,3 @@ +# Identity + +You are a helpful assistant. diff --git a/examples/eve-extension-demo/package.json b/examples/eve-extension-demo/package.json new file mode 100644 index 0000000..fde687f --- /dev/null +++ b/examples/eve-extension-demo/package.json @@ -0,0 +1,29 @@ +{ + "name": "eve-extension-demo", + "version": "0.0.0", + "private": true, + "type": "module", + "imports": { + "#*": "./agent/*", + "#evals/*": "./evals/*" + }, + "scripts": { + "build": "eve build", + "dev": "eve dev", + "start": "eve start", + "typecheck": "tsc" + }, + "dependencies": { + "@ai-sdk/openai": "^4.0.15", + "@upstash/agentkit-eve-extension": "workspace:*", + "@upstash/redis": "^1.38.0", + "@vercel/connect": "0.2.2", + "ai": "7.0.30", + "eve": "^0.24.6", + "zod": "4.4.3" + }, + "devDependencies": { + "@types/node": "24.x", + "typescript": "7.0.2" + } +} diff --git a/examples/eve-extension-demo/tsconfig.json b/examples/eve-extension-demo/tsconfig.json new file mode 100644 index 0000000..37df954 --- /dev/null +++ b/examples/eve-extension-demo/tsconfig.json @@ -0,0 +1,13 @@ +{ + "compilerOptions": { + "target": "ES2022", + "module": "esnext", + "moduleResolution": "bundler", + "types": ["node"], + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "noEmit": true + }, + "include": ["agent/**/*.ts", "evals/**/*.ts"] +} diff --git a/package.json b/package.json index 33f2d65..8f9cec5 100644 --- a/package.json +++ b/package.json @@ -41,6 +41,6 @@ }, "packageManager": "pnpm@11.5.3", "engines": { - "node": ">=18" + "node": "24.x" } } diff --git a/packages/ai-sdk/package.json b/packages/ai-sdk/package.json index 1c9ef6d..d071021 100644 --- a/packages/ai-sdk/package.json +++ b/packages/ai-sdk/package.json @@ -52,9 +52,9 @@ "devDependencies": { "@upstash/redis": "^1.38.0", "dotenv": "^16.4.5", - "ai": "7.0.0-beta.178", - "@ai-sdk/provider": "^4.0.0-beta", - "@ai-sdk/openai": "^4.0.0-beta" + "ai": "7.0.30", + "@ai-sdk/provider": "^4.0.3", + "@ai-sdk/openai": "^4.0.15" }, "peerDependencies": { "ai": ">=7.0.0-beta" diff --git a/packages/ai-sdk/src/memory.test.ts b/packages/ai-sdk/src/memory.test.ts index e1b6921..0807f20 100644 --- a/packages/ai-sdk/src/memory.test.ts +++ b/packages/ai-sdk/src/memory.test.ts @@ -1,7 +1,7 @@ import { AgentMemory } from "@upstash/agentkit-sdk"; import { afterAll, describe, expect, it } from "vitest"; import { createMemoryTools } from "./memory.js"; -import { cleanupKeys, hasRedisCreds, testRedis, uniquePrefix } from "./test-support.js"; +import { cleanupKeys, hasRedisCreds, testRedis, uniqueUserId } from "./test-support.js"; const TOOL_OPTS = { toolCallId: "t", messages: [] } as never; function call(execute: unknown, input: unknown): Promise { @@ -11,7 +11,7 @@ function call(execute: unknown, input: unknown): Promise { describe.skipIf(!hasRedisCreds)("createMemoryTools (live Redis)", () => { const redis = testRedis(); // The tools own their AgentMemory (default `agentkit:memory` index); isolate this run by scope. - const ns = uniquePrefix("aisdk-mem"); + const ns = uniqueUserId("aisdk-mem"); const tools = createMemoryTools({ redis, userId: ns }); // A throwaway handle on the same default index, just to wait for indexing before recall. const index = new AgentMemory({ redis }).searchIndex; diff --git a/packages/ai-sdk/src/test-support.ts b/packages/ai-sdk/src/test-support.ts index af266f6..1f2c65f 100644 --- a/packages/ai-sdk/src/test-support.ts +++ b/packages/ai-sdk/src/test-support.ts @@ -29,6 +29,11 @@ export function uniquePrefix(label: string): string { return `test:${label}:${randomUUID().slice(0, 8)}`; } +/** A collision-proof, colon-free userId (core key-part validation rejects ':', the key separator). */ +export function uniqueUserId(label: string): string { + return `test-${label}-${randomUUID().slice(0, 8)}`; +} + /** Delete every key under a key prefix (best-effort cleanup in afterAll hooks). */ export async function cleanupKeys(redis: Redis, prefix: string): Promise { let cursor = "0"; diff --git a/packages/ai-sdk/src/tools.test.ts b/packages/ai-sdk/src/tools.test.ts index 2ddde97..268050d 100644 --- a/packages/ai-sdk/src/tools.test.ts +++ b/packages/ai-sdk/src/tools.test.ts @@ -2,7 +2,7 @@ import { tool } from "ai"; import { z } from "zod"; import { afterAll, describe, expect, it, vi } from "vitest"; import { cachedTools } from "./tools.js"; -import { cleanupKeys, hasRedisCreds, testRedis, uniquePrefix } from "./test-support.js"; +import { cleanupKeys, hasRedisCreds, testRedis, uniqueUserId } from "./test-support.js"; const TOOL_OPTS = { toolCallId: "t", messages: [] } as never; function call(execute: unknown, input: unknown): Promise { @@ -12,7 +12,7 @@ function call(execute: unknown, input: unknown): Promise { describe.skipIf(!hasRedisCreds)("cachedTools (live Redis)", () => { const redis = testRedis(); // The cache key is `agentkit:toolCache:::`; isolate this run by userId. - const userId = uniquePrefix("aisdk-tool").replace("test:", ""); + const userId = uniqueUserId("aisdk-tool"); afterAll(async () => { await cleanupKeys(redis, `agentkit:toolCache:${userId}`); diff --git a/packages/eve-extension/.gitignore b/packages/eve-extension/.gitignore new file mode 100644 index 0000000..fff71c6 --- /dev/null +++ b/packages/eve-extension/.gitignore @@ -0,0 +1,9 @@ +node_modules +.env* +.eve +.vercel +.output +.nitro +dist +.DS_Store +*.tsbuildinfo diff --git a/packages/eve-extension/AGENTS.md b/packages/eve-extension/AGENTS.md new file mode 100644 index 0000000..c241c97 --- /dev/null +++ b/packages/eve-extension/AGENTS.md @@ -0,0 +1,30 @@ +# eve Extension Package + +This package is an eve extension — a reusable package of tools, connections, +skills, hooks, and instruction fragments that a consuming agent mounts under +`agent/extensions/`. + +Before writing code, read the Extensions guide from the installed eve package +docs. In most installs, those docs are at `node_modules/eve/docs/extensions.md`. +In workspaces or local package installs, resolve the installed `eve` package +location first and read its `docs/extensions.md`. If package docs are +unavailable, use https://eve.dev/docs/extensions as a fallback. + +## Authoring + +- Declare the extension in `extension/extension.ts` with `defineExtension` from + `eve/extension`. Config is optional; read bound values via the handle's + `.config` in tools and hooks. +- Add contributions under `extension/` the same way as in an agent: + `tools/`, `connections/`, `skills/`, `hooks/`, and optional instruction + fragments. Names come from file paths; the mount supplies the namespace, so + name tools for what they do (`search`, not `crm_search`). +- An extension cannot declare `agent.ts`, `sandbox`, `schedules`, or nested + `extensions/` — those belong to the consuming agent. + +## Build and publish + +`eve extension build` (wired to `build`/`prepare`) compiles the mount factory +and tool re-exports into `dist/` and fills the package `exports` map. Ship both +`extension/` (source the consumer recompiles) and `dist/`. Keep `eve` as a peer +dependency so the consumer's eve is the one that runs. diff --git a/packages/eve-extension/CLAUDE.md b/packages/eve-extension/CLAUDE.md new file mode 100644 index 0000000..43c994c --- /dev/null +++ b/packages/eve-extension/CLAUDE.md @@ -0,0 +1 @@ +@AGENTS.md diff --git a/packages/eve-extension/README.md b/packages/eve-extension/README.md new file mode 100644 index 0000000..4a21fb3 --- /dev/null +++ b/packages/eve-extension/README.md @@ -0,0 +1,127 @@ +# @upstash/agentkit-eve-extension + +[Upstash AgentKit](https://upstash.com/) as an [**Eve extension**](https://eve.dev/docs/extensions): +one file in `agent/extensions/` mounts long-term memory tools, Redis Search tools, and durable +chat-history capture — all on **Upstash Redis**, all under one namespace. No per-tool files, no +repeated schemas; upgrades come through the package manager. + +| Contribution | What composes | +| --- | --- | +| `__recall_memory` / `__save_memory` | Long-term memory tools the model reads and writes. | +| `__search` / `__search_aggregate` / `__search_count` | Tools over a Redis Search index (this is how you do RAG). Present only when `search` is configured. | +| `__chat_history` (hook) | Persists every user/assistant message to Redis `ChatHistory` — a durable, `$smart`-searchable transcript store. Off by default; enable with `chatHistory: true`. | +| Instructions fragment | A short always-on rule teaching the model when to save/recall memories. | + +`` is the mount file's basename — the examples below use `agentkit`. + +Start from an eve project (eve ≥ 0.24), then: + +```bash +pnpm add @upstash/agentkit-eve-extension +``` + +Set `UPSTASH_REDIS_REST_URL` / `UPSTASH_REDIS_REST_TOKEN` in your env (the extension defaults to +`Redis.fromEnv()`). + +## Mount it + +Every field is optional. The smallest mount gives the agent memory tools: + +```ts +// agent/extensions/agentkit.ts +import agentkit from "@upstash/agentkit-eve-extension"; + +export default agentkit(); +``` + +Add `search` to turn on the search tools over one index: + +```ts +// agent/extensions/agentkit.ts +import { s } from "@upstash/redis"; +import agentkit from "@upstash/agentkit-eve-extension"; + +export default agentkit({ + search: { + schema: s.object({ title: s.string(), author: s.string().noTokenize(), year: s.number() }), + indexName: "books", + }, +}); +``` + +That's it — the tools appear to the model as `agentkit__recall_memory`, `agentkit__search`, …, and a +short memory instruction is added to your system prompt. + +### Options + +| Field | Default | | +| --- | --- | --- | +| `userId` | principal → session id | The tenant boundary for memory + chat history. A string (one shared scope), or `(ctx) => string` to derive it per call. | +| `search` | _off_ | `{ schema, indexName?, prefix?, defaultLimit? }`. Omit it and the search tools don't exist. | +| `memory` | — | `{ topK?, minScore? }` to tune recall. | +| `chatHistory` | `false` | `true` to capture transcripts, or `{ prefix?, indexName?, ttlSeconds? }` to tune where they're stored. | +| `redis` | `Redis.fromEnv()` | An explicit Upstash Redis client. | + +## What lands in Redis + +- `agentkit:memory::` — memories (searchable via the `agentkit:memory` index). +- `agentkit:chat::` — one JSON doc per session (only when `chatHistory` is + enabled): the raw transcript plus `$smart`-indexed user/model text. Read it back with `ChatHistory` + from `@upstash/agentkit-sdk` (`listChats` / `searchChats` / `getChat`). +- Your `search` index documents are whatever you seed under `` (`redis.json.set`). + +`userId` and `sessionId` are Redis key parts, so `:` in derived values is replaced with `_`. + +## The search tools are dynamic + +Their descriptions and input schemas are generated from your `search.schema` (field-by-field filter +guidance for the model), which is only known once the mount config binds at runtime. So they're +contributed as [dynamic tools](https://eve.dev/docs/guides/dynamic-capabilities) resolved at +`session.started` — and when `search` isn't configured they resolve to nothing instead of erroring. + +## Overriding or disabling contributions + +Mount as a directory to override per slot ([docs](https://eve.dev/docs/extensions#overrides)): + +``` +agent/extensions/agentkit/ + extension.ts # the mount: export default agentkit({ ... }) + tools/save_memory.ts # your override for agentkit__save_memory +``` + +Drop a tool you don't want: + +```ts +// agent/extensions/agentkit/tools/save_memory.ts +import { disableTool } from "eve/tools"; + +export default disableTool(); +``` + +Or re-define one — e.g. gate saves behind approval: + +```ts +// agent/extensions/agentkit/tools/save_memory.ts +import { defineTool } from "eve/tools"; +import { always } from "eve/tools/approval"; +import { save_memory } from "@upstash/agentkit-eve-extension/tools"; + +export default defineTool({ ...save_memory, approval: always() }); +``` + +The static memory tools can also be narrowed in your hooks with `toolResultFrom` (import them from +`@upstash/agentkit-eve-extension/tools`). The search tools are dynamic resolvers, so they aren't +importable as static definitions. + +## When to use this vs `@upstash/agentkit-eve` + +Use the extension when you want the batteries-included bundle under one mount. Use +[`@upstash/agentkit-eve`](../eve) when you need the pieces the extension can't carry — the Upstash +Box **sandbox backend** and the **rate-limit channel auth** (extensions can't contribute sandbox or +channel config) — or its `defineCachedTool` wrapper for your own tools. The two compose fine in one +agent. + +## Example + +[`examples/eve-extension-demo`](../../examples/eve-extension-demo) is a scaffolded eve agent with the +extension mounted: memory + book search + chat capture, end to end. diff --git a/packages/eve-extension/extension/extension.ts b/packages/eve-extension/extension/extension.ts new file mode 100644 index 0000000..8f7cae9 --- /dev/null +++ b/packages/eve-extension/extension/extension.ts @@ -0,0 +1,68 @@ +import { defineExtension } from "eve/extension"; +import type { SessionContext } from "eve/tools"; +import { z } from "zod"; +import type { Redis } from "@upstash/redis"; +import type { AnySearchSchema } from "@upstash/agentkit-sdk"; + +/** + * The user all data is scoped under (the tenant boundary for memory and chat history). A string + * shares one scope across all callers (fine for a single-user agent); a function derives it per call + * from eve's `SessionContext` — the base both tool and hook `ctx` extend, so one function serves + * every contribution (`(ctx) => ctx.session.auth.current?.principalId ?? …`). Defaults to the + * authenticated principal (`auth.current`, falling back to `initiator`), then the session id. + */ +const userId = z.union([ + z.string().min(1), + z.custom<(ctx: SessionContext) => string>((value) => typeof value === "function"), +]); + +export default defineExtension({ + config: z.object({ + userId: userId.optional(), + /** Upstash Redis client. Defaults to `Redis.fromEnv()` (`UPSTASH_REDIS_REST_URL`/`_TOKEN`). */ + redis: z.custom((value) => typeof value === "object" && value !== null).optional(), + /** Knobs for the `recall_memory` tool. */ + memory: z + .object({ + /** Max memories returned by a recall. */ + topK: z.number().int().positive().optional(), + /** Minimum BM25 relevance score for recall hits. */ + minScore: z.number().optional(), + }) + .optional(), + /** + * Enables the `search` / `aggregate` / `count` tools over one Upstash Redis Search index. Without + * this, those tools error at call time — configure it, or disable their slots with `disableTool()`. + */ + search: z + .object({ + /** The index schema, built with `s` from `@upstash/redis`. */ + schema: z.custom((value) => typeof value === "object" && value !== null), + /** Index name. Defaults to `"agentkit:search"`. */ + indexName: z.string().min(1).optional(), + /** Key prefix for indexed JSON documents. Defaults to `":"`. */ + prefix: z.string().min(1).optional(), + /** Default page size for the `search` tool. Defaults to 10. */ + defaultLimit: z.number().int().positive().optional(), + }) + .optional(), + /** + * Durable transcript capture into Upstash Redis `ChatHistory` (**off by default**): a hook + * appends every user and assistant message as it streams, keyed by `userId` + session id. Pass + * `true` to enable it with defaults, or an object to enable it and tune where chats are stored. + */ + chatHistory: z + .union([ + z.boolean(), + z.object({ + /** Base key prefix for stored chats; defaults to `agentkit:chat`. */ + prefix: z.string().min(1).optional(), + /** Redis Search index name. Defaults to the (identifier-safe) `prefix`. */ + indexName: z.string().min(1).optional(), + /** Optional TTL (seconds) per chat. Omit for no expiry. */ + ttlSeconds: z.number().int().positive().optional(), + }), + ]) + .optional(), + }), +}); diff --git a/packages/eve-extension/extension/hooks/chat_history.ts b/packages/eve-extension/extension/hooks/chat_history.ts new file mode 100644 index 0000000..e53b100 --- /dev/null +++ b/packages/eve-extension/extension/hooks/chat_history.ts @@ -0,0 +1,29 @@ +import { defineHook } from "eve/hooks"; +import { appendChatMessage } from "../lib/runtime"; + +/** + * Durable transcript capture: appends every user and assistant message to Upstash Redis + * `ChatHistory` (`agentkit:chat::`) as the session streams. Redis is the + * long-term, searchable source of truth — eve's own workflow store is pruned after a run completes. + * + * A thrown hook fails the turn, so persistence errors are logged and swallowed: losing one + * transcript write should never take the conversation down. + */ +export default defineHook({ + events: { + async "message.received"(event, ctx) { + try { + await appendChatMessage(ctx, "user", event.data.message); + } catch (error) { + console.warn("[agentkit] chat-history capture failed (user message):", error); + } + }, + async "message.completed"(event, ctx) { + try { + await appendChatMessage(ctx, "assistant", event.data.message ?? ""); + } catch (error) { + console.warn("[agentkit] chat-history capture failed (assistant message):", error); + } + }, + }, +}); diff --git a/packages/eve-extension/extension/instructions.md b/packages/eve-extension/extension/instructions.md new file mode 100644 index 0000000..f8c4a25 --- /dev/null +++ b/packages/eve-extension/extension/instructions.md @@ -0,0 +1,6 @@ +## Long-term memory + +You have long-term memory tools. When the user shares a durable fact about themselves — their +name, preferences, goals, or context that will matter in later conversations — save it with the +memory save tool without being asked. Before answering a question that depends on who the user is +or what they told you previously, recall memories first. diff --git a/packages/eve-extension/extension/lib/runtime.ts b/packages/eve-extension/extension/lib/runtime.ts new file mode 100644 index 0000000..0fedb2d --- /dev/null +++ b/packages/eve-extension/extension/lib/runtime.ts @@ -0,0 +1,127 @@ +import { + AgentMemory, + ChatHistory, + createSearchToolDefs, + type SearchToolDefs, +} from "@upstash/agentkit-sdk"; +import { Redis } from "@upstash/redis"; +import type { SessionContext } from "eve/tools"; +import extension from "../extension"; + +/** + * `userId` and `sessionId` become Redis key parts, and core key-part validation rejects `:` (the key + * separator). Derived values like eve principal ids (`eve:app`) or session ids can contain it. + */ +export function sanitizeId(value: string): string { + return value.replaceAll(":", "_"); +} + +/** Resolve the per-call user from config (string or function), defaulting to principal → session id. */ +export function resolveUserId(ctx: SessionContext): string { + const { userId } = extension.config; + if (typeof userId === "string") return sanitizeId(userId); + if (typeof userId === "function") return sanitizeId(userId(ctx)); + const auth = ctx.session.auth; + return sanitizeId(auth.current?.principalId ?? auth.initiator?.principalId ?? ctx.session.id); +} + +let redisClient: Redis | undefined; + +export function redis(): Redis { + return (redisClient ??= extension.config.redis ?? Redis.fromEnv()); +} + +let agentMemory: AgentMemory | undefined; + +export function memory(): AgentMemory { + return (agentMemory ??= new AgentMemory({ redis: redis() })); +} + +let searchToolDefs: SearchToolDefs | undefined; + +/** The core search/aggregate/count defs over the configured index. Throws when `search` is unset. */ +export function searchDefs(): SearchToolDefs { + if (searchToolDefs) return searchToolDefs; + const search = extension.config.search; + if (!search) { + throw new Error( + "[agentkit] The search/search_aggregate/search_count tools need configuration. Pass " + + "`search: { schema, indexName }` where the extension is mounted (agent/extensions/.ts), " + + "or disable these tool slots with `disableTool()` — see the @upstash/agentkit-eve-extension README.", + ); + } + searchToolDefs = createSearchToolDefs({ + redis: redis(), + schema: search.schema, + ...(search.indexName !== undefined ? { indexName: search.indexName } : {}), + ...(search.prefix !== undefined ? { prefix: search.prefix } : {}), + ...(search.defaultLimit !== undefined ? { defaultLimit: search.defaultLimit } : {}), + }); + return searchToolDefs; +} + +/** + * Module-load-safe variant: the search tools read their (schema-derived) input schemas at module + * evaluation, which must not throw when `search` is unconfigured or config isn't bound (e.g. the + * package's `./tools` export imported outside an eve runtime). + */ +export function trySearchDefs(): SearchToolDefs | null { + try { + return searchDefs(); + } catch { + return null; + } +} + +/** The message shape the chat-history hook stores (core `ChatHistory`'s default extractor reads it). */ +export interface StoredChatMessage { + role: "user" | "assistant"; + content: string; + createdAt: number; +} + +let chats: ChatHistory | null | undefined; + +/** + * The durable transcript store, or `null` unless the consumer opted in. Chat history is **off by + * default**: enabled only by `chatHistory: true` or a `chatHistory: {…}` tuning object. + */ +export function chatHistory(): ChatHistory | null { + if (chats !== undefined) return chats; + const config = extension.config.chatHistory; + if (!config) return (chats = null); // undefined or false → disabled + const options = config === true ? {} : config; + chats = new ChatHistory({ + redis: redis(), + ...(options.prefix !== undefined ? { prefix: options.prefix } : {}), + ...(options.indexName !== undefined ? { indexName: options.indexName } : {}), + ...(options.ttlSeconds !== undefined ? { ttlSeconds: options.ttlSeconds } : {}), + }); + return chats; +} + +/** + * Append one message to the session's stored transcript. Core `saveChat` replaces the whole array, + * so this reads the existing record and writes it back extended — stream events for one session are + * dispatched in order, so the read-modify-write doesn't race itself. + */ +export async function appendChatMessage( + ctx: SessionContext, + role: StoredChatMessage["role"], + content: string, +): Promise { + const history = chatHistory(); + if (!history || !content) return; + const userId = resolveUserId(ctx); + const sessionId = sanitizeId(ctx.session.id); + const existing = await history.getChat({ userId, sessionId }); + const messages = existing ? existing.messages : []; + messages.push({ role, content, createdAt: Date.now() }); + await history.saveChat({ + userId, + sessionId, + messages, + // First user message titles the chat; saveChat keeps the existing title on later writes. + ...(existing?.title === undefined && role === "user" ? { title: content.slice(0, 80) } : {}), + }); +} diff --git a/packages/eve-extension/extension/tools/recall_memory.ts b/packages/eve-extension/extension/tools/recall_memory.ts new file mode 100644 index 0000000..36bc541 --- /dev/null +++ b/packages/eve-extension/extension/tools/recall_memory.ts @@ -0,0 +1,32 @@ +import { defineTool } from "eve/tools"; +import { z } from "zod"; +import extension from "../extension"; +import { memory, resolveUserId } from "../lib/runtime"; + +export default defineTool({ + description: + "Recall the user's long-term memories. Pass `query` to find memories about a specific topic. " + + "To list ALL of the user's memories, call this with NO `query` at all — do not pass a " + + 'placeholder like "everything" or "all".', + inputSchema: z.object({ + query: z + .string() + .optional() + .describe( + "Topic or keywords to search memories for. Leave this out entirely to return every " + + "stored memory for the user.", + ), + }), + async execute({ query }, ctx) { + const { topK, minScore } = extension.config.memory ?? {}; + // recall() falls back to "everything for the user" when a query matches nothing, so a model + // that passes a placeholder like "everything" still gets results. + const hits = await memory().recall({ + query, + userId: resolveUserId(ctx), + ...(topK !== undefined ? { topK } : {}), + ...(minScore !== undefined ? { minScore } : {}), + }); + return hits.map((hit) => ({ text: hit.text, score: hit.score })); + }, +}); diff --git a/packages/eve-extension/extension/tools/save_memory.ts b/packages/eve-extension/extension/tools/save_memory.ts new file mode 100644 index 0000000..fb098a1 --- /dev/null +++ b/packages/eve-extension/extension/tools/save_memory.ts @@ -0,0 +1,19 @@ +import { defineTool } from "eve/tools"; +import { z } from "zod"; +import { memory, resolveUserId } from "../lib/runtime"; + +export default defineTool({ + description: + "Save a durable fact about the user to long-term memory so it can be recalled in future " + + "conversations (preferences, identity, goals, …).", + inputSchema: z.object({ + text: z + .string() + .min(1) + .describe("A concise, durable fact about the user to remember for later."), + }), + async execute({ text }, ctx) { + const record = await memory().add({ text, userId: resolveUserId(ctx) }); + return { id: record.id, saved: true }; + }, +}); diff --git a/packages/eve-extension/extension/tools/search.ts b/packages/eve-extension/extension/tools/search.ts new file mode 100644 index 0000000..b88e64d --- /dev/null +++ b/packages/eve-extension/extension/tools/search.ts @@ -0,0 +1,23 @@ +import { defineDynamic, defineTool } from "eve/tools"; +import { searchDefs, trySearchDefs } from "../lib/runtime"; + +/** + * Dynamic rather than static: the tool's description and input schema are derived from the + * consumer's `search.schema` (they teach the model the index fields and which filter operators + * apply), and that config is only bound at runtime — a `session.started` resolver sees it, a + * static module evaluated at discovery doesn't. Unconfigured → `null`, so the tool simply + * doesn't exist instead of erroring at call time. + */ +export default defineDynamic({ + events: { + "session.started": () => { + const defs = trySearchDefs(); + if (!defs) return null; + return defineTool({ + description: defs.search.description, + inputSchema: defs.search.inputSchema, + execute: (input: Record) => searchDefs().search.execute(input), + }); + }, + }, +}); diff --git a/packages/eve-extension/extension/tools/search_aggregate.ts b/packages/eve-extension/extension/tools/search_aggregate.ts new file mode 100644 index 0000000..0a068e1 --- /dev/null +++ b/packages/eve-extension/extension/tools/search_aggregate.ts @@ -0,0 +1,18 @@ +import { defineDynamic, defineTool } from "eve/tools"; +import { searchDefs, trySearchDefs } from "../lib/runtime"; + +// Dynamic for the same reason as `search`: the schema-derived description/input schema need the +// runtime-bound mount config. See tools/search.ts. +export default defineDynamic({ + events: { + "session.started": () => { + const defs = trySearchDefs(); + if (!defs) return null; + return defineTool({ + description: defs.aggregate.description, + inputSchema: defs.aggregate.inputSchema, + execute: (input: Record) => searchDefs().aggregate.execute(input), + }); + }, + }, +}); diff --git a/packages/eve-extension/extension/tools/search_count.ts b/packages/eve-extension/extension/tools/search_count.ts new file mode 100644 index 0000000..2663234 --- /dev/null +++ b/packages/eve-extension/extension/tools/search_count.ts @@ -0,0 +1,18 @@ +import { defineDynamic, defineTool } from "eve/tools"; +import { searchDefs, trySearchDefs } from "../lib/runtime"; + +// Dynamic for the same reason as `search`: the schema-derived description/input schema need the +// runtime-bound mount config. See tools/search.ts. +export default defineDynamic({ + events: { + "session.started": () => { + const defs = trySearchDefs(); + if (!defs) return null; + return defineTool({ + description: defs.count.description, + inputSchema: defs.count.inputSchema, + execute: (input: Record) => searchDefs().count.execute(input), + }); + }, + }, +}); diff --git a/packages/eve-extension/package.json b/packages/eve-extension/package.json new file mode 100644 index 0000000..5e8e137 --- /dev/null +++ b/packages/eve-extension/package.json @@ -0,0 +1,63 @@ +{ + "name": "@upstash/agentkit-eve-extension", + "version": "0.1.0", + "description": "Upstash AgentKit as an eve extension: long-term memory tools, Redis Search tools, and durable chat-history capture on Upstash Redis — mounted with one file.", + "license": "MIT", + "repository": { + "type": "git", + "url": "git+https://github.com/upstash/agentkit.git", + "directory": "packages/eve-extension" + }, + "homepage": "https://upstash.com/docs/redis/sdks/agentkit/eve", + "bugs": { + "url": "https://github.com/upstash/agentkit/issues" + }, + "type": "module", + "eve": { + "extension": "./extension" + }, + "files": [ + "extension", + "dist", + "README.md" + ], + "exports": { + ".": { + "types": "./dist/index.d.ts", + "default": "./dist/index.mjs" + }, + "./tools": { + "types": "./dist/tools/index.d.ts", + "default": "./dist/tools/index.mjs" + } + }, + "scripts": { + "build": "eve extension build", + "typecheck": "tsc" + }, + "keywords": [ + "upstash", + "eve", + "extension", + "vercel", + "ai", + "agent", + "memory", + "search", + "chat-history", + "redis" + ], + "dependencies": { + "@upstash/agentkit-sdk": "workspace:*", + "@upstash/redis": "^1.38.0", + "zod": "4.4.3" + }, + "devDependencies": { + "@types/node": "24.x", + "eve": "^0.24.6", + "typescript": "7.0.2" + }, + "peerDependencies": { + "eve": "^0.24.6" + } +} diff --git a/packages/eve-extension/tsconfig.json b/packages/eve-extension/tsconfig.json new file mode 100644 index 0000000..ba1682b --- /dev/null +++ b/packages/eve-extension/tsconfig.json @@ -0,0 +1,13 @@ +{ + "compilerOptions": { + "target": "ES2022", + "module": "esnext", + "moduleResolution": "bundler", + "types": ["node"], + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "noEmit": true + }, + "include": ["extension/**/*.ts"] +} diff --git a/packages/eve/README.md b/packages/eve/README.md index a4cee73..0c653bc 100644 --- a/packages/eve/README.md +++ b/packages/eve/README.md @@ -11,6 +11,12 @@ your `agent/` tree: | `upstash` (`@upstash/agentkit-eve/sandbox`) | Upstash Box sandbox backend for `defineSandbox`. | | `defineCachedTool` | A `defineTool` whose result is memoized in Redis. | +> **Prefer one mount file over per-tool files?** +> [`@upstash/agentkit-eve-extension`](../eve-extension) packages the memory + search tools (plus +> durable chat-history capture) as an [eve extension](https://eve.dev/docs/extensions) — mount it once +> in `agent/extensions/`. This package remains the home of the sandbox backend, the rate-limit auth +> gate, and `defineCachedTool`, which extensions can't carry. + Start from an eve project. Scaffold one (it installs `eve` and an AI-SDK provider for you): ```bash diff --git a/packages/eve/package.json b/packages/eve/package.json index e04ecbd..3943d86 100644 --- a/packages/eve/package.json +++ b/packages/eve/package.json @@ -56,14 +56,14 @@ "devDependencies": { "@upstash/box": "^0.5.1", "@upstash/redis": "^1.38.0", - "ai": "7.0.0-beta.178", + "ai": "7.0.30", "dotenv": "^16.4.5", - "eve": "^0.17.1" + "eve": "^0.24.6" }, "peerDependencies": { "@upstash/box": ">=0.5.0", "@upstash/redis": ">=1.38.0", - "eve": ">=0.0.1" + "eve": ">=0.24.0" }, "peerDependenciesMeta": { "@upstash/box": { diff --git a/packages/eve/src/memory.test.ts b/packages/eve/src/memory.test.ts index 236a62c..a10bcbb 100644 --- a/packages/eve/src/memory.test.ts +++ b/packages/eve/src/memory.test.ts @@ -1,14 +1,14 @@ import { AgentMemory } from "@upstash/agentkit-sdk"; import { afterAll, describe, expect, it } from "vitest"; import { defineMemoryRecallTool, defineMemorySaveTool } from "./memory.js"; -import { cleanupKeys, hasRedisCreds, testRedis, uniquePrefix } from "./test-support.js"; +import { cleanupKeys, hasRedisCreds, testRedis, uniqueUserId } from "./test-support.js"; const CTX = {} as never; describe.skipIf(!hasRedisCreds)("memory tools (live Redis)", () => { const redis = testRedis(); // The tools own their AgentMemory (default `agentkit:memory` index); isolate this run by userId. - const ns = uniquePrefix("eve-mem"); + const ns = uniqueUserId("eve-mem"); const recall = defineMemoryRecallTool({ redis, userId: ns }); const save = defineMemorySaveTool({ redis, userId: ns }); // A throwaway handle on the same default index, just to wait for indexing before recall. diff --git a/packages/eve/src/sandbox.test.ts b/packages/eve/src/sandbox.test.ts index 948eaf4..36f5233 100644 --- a/packages/eve/src/sandbox.test.ts +++ b/packages/eve/src/sandbox.test.ts @@ -90,7 +90,7 @@ describe.skipIf(!hasBoxCreds)("upstash() backend (live Upstash Box)", () => { const found = await session.run({ command: "find /workspace -name note.txt" }); expect(found.stdout).toContain("/workspace/home/note.txt"); } finally { - // dispose() is a no-op (boxes persist for reuse), so delete explicitly to clean up the test. + // Boxes persist for reuse (only server shutdown pauses them), so delete explicitly to clean up. await Box.delete({ boxIds: handle.session.id }).catch(() => {}); } }, 120_000); @@ -102,7 +102,7 @@ describe.skipIf(!hasBoxCreds)("upstash() backend (live Upstash Box)", () => { const first = await backend.create(createInput); const boxId = first.session.id; const state = await first.captureState(); - await first.dispose(); // no-op — the box persists for reuse + await first.shutdown(); // pauses the box — it stays reattachable from the captured state try { const second = await backend.create({ ...createInput, diff --git a/packages/eve/src/sandbox.ts b/packages/eve/src/sandbox.ts index ee9ce92..92c89e7 100644 --- a/packages/eve/src/sandbox.ts +++ b/packages/eve/src/sandbox.ts @@ -510,14 +510,15 @@ export class UpstashSandboxBackend implements SandboxBackend< }, }); - // Don't tear the box down here: Eve calls `dispose` at the end of every session-open, and the next - // open reattaches to this same box via `existingMetadata` (see `openBox`). Deleting/pausing it would - // force a fresh box each turn (the "N boxes per turn" bug; keep-alive boxes can't even be paused). - // A non-keep-alive box auto-pauses when idle and is reaped by Box's lifecycle, so this is a no-op — - // matching Eve's own Vercel backend. - const dispose = async (): Promise => {}; - - return { session, useSessionFn, captureState, dispose }; + // Eve calls `shutdown` only when the server itself is stopping (SIGINT/SIGTERM/nitro close): + // nothing may be left running, but the box must stay reattachable from `captureState`'s `boxId` + // on the next start. Pausing does exactly that (`openBox` reattaches via `Box.get`). Keep-alive + // boxes can't be paused — tolerate the failure, matching Eve's own Vercel backend's try/catch. + const shutdown = async (): Promise => { + await box.pause().catch(() => {}); + }; + + return { session, useSessionFn, captureState, shutdown }; } async prewarm( diff --git a/packages/eve/src/test-support.ts b/packages/eve/src/test-support.ts index 2e6e853..a15a69a 100644 --- a/packages/eve/src/test-support.ts +++ b/packages/eve/src/test-support.ts @@ -23,6 +23,11 @@ export function uniquePrefix(label: string): string { return `test:${label}:${randomUUID().slice(0, 8)}`; } +/** A collision-proof, colon-free userId (core key-part validation rejects ':', the key separator). */ +export function uniqueUserId(label: string): string { + return `test-${label}-${randomUUID().slice(0, 8)}`; +} + /** Delete every key under a key prefix (best-effort cleanup in afterAll hooks). */ export async function cleanupKeys(redis: Redis, prefix: string): Promise { let cursor = "0"; diff --git a/packages/eve/src/tools.test.ts b/packages/eve/src/tools.test.ts index 158f22a..9554422 100644 --- a/packages/eve/src/tools.test.ts +++ b/packages/eve/src/tools.test.ts @@ -1,14 +1,14 @@ import { z } from "zod"; import { afterAll, describe, expect, it, vi } from "vitest"; import { defineCachedTool } from "./tools.js"; -import { cleanupKeys, hasRedisCreds, testRedis, uniquePrefix } from "./test-support.js"; +import { cleanupKeys, hasRedisCreds, testRedis, uniqueUserId } from "./test-support.js"; const CTX = {} as never; describe.skipIf(!hasRedisCreds)("defineCachedTool (live Redis)", () => { const redis = testRedis(); // The tool owns its ToolCache (default `agentkit:toolCache` base); isolate this run by userId. - const ns = uniquePrefix("eve-tool").replace("test:", ""); + const ns = uniqueUserId("eve-tool"); afterAll(async () => { await cleanupKeys(redis, `agentkit:toolCache:${ns}`); @@ -36,7 +36,7 @@ describe.skipIf(!hasRedisCreds)("defineCachedTool (live Redis)", () => { description: "upper", inputSchema: z.object({ id: z.string() }), toolName: "upper", - userId: ({ id }) => `${ns}:${id}`, + userId: ({ id }) => `${ns}-${id}`, execute: fn, redis, }); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 0611b4b..664078c 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -45,11 +45,11 @@ importers: examples/ai-sdk-demo: dependencies: '@ai-sdk/openai': - specifier: ^4.0.0-beta - version: 4.0.0-canary.73(zod@4.4.3) + specifier: ^4.0.15 + version: 4.0.15(zod@4.4.3) '@ai-sdk/react': - specifier: 4.0.0-beta.182 - version: 4.0.0-beta.182(react@19.2.6)(zod@4.4.3) + specifier: ^4.0.33 + version: 4.0.33(react@19.2.6)(zod@4.4.3) '@upstash/agentkit-ai-sdk': specifier: workspace:* version: link:../../packages/ai-sdk @@ -60,8 +60,8 @@ importers: specifier: ^1.38.0 version: 1.38.0 ai: - specifier: 7.0.0-beta.178 - version: 7.0.0-beta.178(zod@4.4.3) + specifier: 7.0.30 + version: 7.0.30(zod@4.4.3) dotenv: specifier: ^16.4.5 version: 16.6.1 @@ -94,8 +94,8 @@ importers: examples/eve-demo: dependencies: '@ai-sdk/openai': - specifier: ^4.0.0-beta - version: 4.0.0-canary.73(zod@4.4.3) + specifier: ^4.0.15 + version: 4.0.15(zod@4.4.3) '@radix-ui/react-use-controllable-state': specifier: 1.2.2 version: 1.2.2(@types/react@19.2.15)(react@19.2.6) @@ -134,10 +134,10 @@ importers: version: 1.38.0 '@vercel/connect': specifier: 0.2.2 - version: 0.2.2(ai@7.0.0-beta.178(zod@4.4.3))(eve@0.17.1(@opentelemetry/api@1.9.1)(@upstash/redis@1.38.0)(ai@7.0.0-beta.178(zod@4.4.3))(chokidar@4.0.3)(dotenv@16.6.1)(jiti@2.7.0)(next@16.2.6(@opentelemetry/api@1.9.1)(react-dom@19.2.6(react@19.2.6))(react@19.2.6))(react@19.2.6)(rollup@4.62.0)(vite@5.4.21(@types/node@20.19.43)(lightningcss@1.32.0))) + version: 0.2.2(@ai-sdk/mcp@2.0.14(zod@4.4.3))(ai@7.0.30(zod@4.4.3))(eve@0.24.6(@opentelemetry/api@1.9.1)(@upstash/redis@1.38.0)(ai@7.0.30(zod@4.4.3))(chokidar@4.0.3)(dotenv@16.6.1)(jiti@2.7.0)(rollup@4.62.0)(vite@5.4.21(@types/node@20.19.43)(lightningcss@1.32.0))) ai: - specifier: 7.0.0-beta.178 - version: 7.0.0-beta.178(zod@4.4.3) + specifier: 7.0.30 + version: 7.0.30(zod@4.4.3) class-variance-authority: specifier: 0.7.1 version: 0.7.1 @@ -148,8 +148,8 @@ importers: specifier: 1.1.1 version: 1.1.1(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) eve: - specifier: ^0.17.1 - version: 0.17.1(@opentelemetry/api@1.9.1)(@upstash/redis@1.38.0)(ai@7.0.0-beta.178(zod@4.4.3))(chokidar@4.0.3)(dotenv@16.6.1)(jiti@2.7.0)(next@16.2.6(@opentelemetry/api@1.9.1)(react-dom@19.2.6(react@19.2.6))(react@19.2.6))(react@19.2.6)(rollup@4.62.0)(vite@5.4.21(@types/node@20.19.43)(lightningcss@1.32.0)) + specifier: ^0.24.6 + version: 0.24.6(@opentelemetry/api@1.9.1)(@upstash/redis@1.38.0)(ai@7.0.30(zod@4.4.3))(chokidar@4.0.3)(dotenv@16.6.1)(jiti@2.7.0)(rollup@4.62.0)(vite@5.4.21(@types/node@20.19.43)(lightningcss@1.32.0)) lucide-react: specifier: 1.16.0 version: 1.16.0(react@19.2.6) @@ -203,6 +203,37 @@ importers: specifier: 7.0.0-dev.20260523.1 version: 7.0.0-dev.20260523.1 + examples/eve-extension-demo: + dependencies: + '@ai-sdk/openai': + specifier: ^4.0.15 + version: 4.0.15(zod@4.4.3) + '@upstash/agentkit-eve-extension': + specifier: workspace:* + version: link:../../packages/eve-extension + '@upstash/redis': + specifier: ^1.38.0 + version: 1.38.0 + '@vercel/connect': + specifier: 0.2.2 + version: 0.2.2(@ai-sdk/mcp@2.0.14(zod@4.4.3))(ai@7.0.30(zod@4.4.3))(eve@0.24.6(@opentelemetry/api@1.9.1)(@upstash/redis@1.38.0)(ai@7.0.30(zod@4.4.3))(chokidar@4.0.3)(dotenv@16.6.1)(jiti@2.7.0)(rollup@4.62.0)(vite@5.4.21(@types/node@20.19.43)(lightningcss@1.32.0))) + ai: + specifier: 7.0.30 + version: 7.0.30(zod@4.4.3) + eve: + specifier: ^0.24.6 + version: 0.24.6(@opentelemetry/api@1.9.1)(@upstash/redis@1.38.0)(ai@7.0.30(zod@4.4.3))(chokidar@4.0.3)(dotenv@16.6.1)(jiti@2.7.0)(rollup@4.62.0)(vite@5.4.21(@types/node@20.19.43)(lightningcss@1.32.0)) + zod: + specifier: 4.4.3 + version: 4.4.3 + devDependencies: + '@types/node': + specifier: 24.x + version: 24.13.2 + typescript: + specifier: 7.0.2 + version: 7.0.2 + packages/ai-sdk: dependencies: '@upstash/agentkit-sdk': @@ -216,14 +247,14 @@ importers: version: 4.4.3 devDependencies: '@ai-sdk/openai': - specifier: ^4.0.0-beta - version: 4.0.0-canary.73(zod@4.4.3) + specifier: ^4.0.15 + version: 4.0.15(zod@4.4.3) '@ai-sdk/provider': - specifier: ^4.0.0-beta - version: 4.0.0-beta.19 + specifier: ^4.0.3 + version: 4.0.3 ai: - specifier: 7.0.0-beta.178 - version: 7.0.0-beta.178(zod@4.4.3) + specifier: 7.0.30 + version: 7.0.30(zod@4.4.3) dotenv: specifier: ^16.4.5 version: 16.6.1 @@ -247,14 +278,36 @@ importers: specifier: ^1.38.0 version: 1.38.0 ai: - specifier: 7.0.0-beta.178 - version: 7.0.0-beta.178(zod@4.4.3) + specifier: 7.0.30 + version: 7.0.30(zod@4.4.3) dotenv: specifier: ^16.4.5 version: 16.6.1 eve: - specifier: ^0.17.1 - version: 0.17.1(@opentelemetry/api@1.9.1)(@upstash/redis@1.38.0)(ai@7.0.0-beta.178(zod@4.4.3))(chokidar@4.0.3)(dotenv@16.6.1)(jiti@2.7.0)(next@16.2.9(@opentelemetry/api@1.9.1)(react-dom@19.2.6(react@19.2.6))(react@19.2.6))(react@19.2.6)(rollup@4.62.0)(vite@5.4.21(@types/node@20.19.43)(lightningcss@1.32.0)) + specifier: ^0.24.6 + version: 0.24.6(@opentelemetry/api@1.9.1)(@upstash/redis@1.38.0)(ai@7.0.30(zod@4.4.3))(chokidar@4.0.3)(dotenv@16.6.1)(jiti@2.7.0)(rollup@4.62.0)(vite@5.4.21(@types/node@20.19.43)(lightningcss@1.32.0)) + + packages/eve-extension: + dependencies: + '@upstash/agentkit-sdk': + specifier: workspace:* + version: link:../sdk + '@upstash/redis': + specifier: ^1.38.0 + version: 1.38.0 + zod: + specifier: 4.4.3 + version: 4.4.3 + devDependencies: + '@types/node': + specifier: 24.x + version: 24.13.2 + eve: + specifier: ^0.24.6 + version: 0.24.6(@opentelemetry/api@1.9.1)(@upstash/redis@1.38.0)(ai@7.0.30(zod@4.4.3))(chokidar@4.0.3)(dotenv@16.6.1)(jiti@2.7.0)(rollup@4.62.0)(vite@5.4.21(@types/node@20.19.43)(lightningcss@1.32.0)) + typescript: + specifier: 7.0.2 + version: 7.0.2 packages/sdk: dependencies: @@ -274,46 +327,36 @@ importers: packages: - '@ai-sdk/gateway@4.0.0-beta.109': - resolution: {integrity: sha512-W/1kLlPb6Bgbhwep3CA3R6do0HD7SXV5gyuz2XBLY1YABqgxYkw+IhEcjOYlmn9v+Tifjqy5yJqmWdSHMJhyPQ==} + '@ai-sdk/gateway@4.0.22': + resolution: {integrity: sha512-KdXJLa6O25fltJC/Lh8gnB1VaYMdX2mEKIalkohuItS+Ig523XxwK9e/Uuc66AvtrLIf7G51cDg3rDpP9mrxwQ==} engines: {node: '>=22'} peerDependencies: zod: ^3.25.76 || ^4.1.8 - '@ai-sdk/mcp@2.0.0-beta.66': - resolution: {integrity: sha512-Jodv9W0aHKRWBLfyGZ3pcc3bXLoEldgEqC4k+E1ozaL5XjYuZfqnjfsGpx6BkX3GCr3mVctPwRGG09QCYP5lJA==} + '@ai-sdk/mcp@2.0.14': + resolution: {integrity: sha512-Vf2THTWylnOiPUZgPEzTcbin+6pIBKMdJnDNAMHD0u+upNoO5747HMMFUJJnNNjk8eroAPomaVmF0w8gadA4WA==} engines: {node: '>=22'} peerDependencies: zod: ^3.25.76 || ^4.1.8 - '@ai-sdk/openai@4.0.0-canary.73': - resolution: {integrity: sha512-lGiceTzEDc2gLZvNeUkE8hiucW6nXzhhvsMD8DWshunabIXpO+SxC785mmPUwpxeyi3CeJUy/em1PgkVyneJ5g==} + '@ai-sdk/openai@4.0.15': + resolution: {integrity: sha512-JpTLQp5RUbRcs5nOyPEu5NRdxZLUnD/uCyT3qzy26D+iunCeL7KJV58ER9kwisAKnTjWravfNblaQNiWr20M9A==} engines: {node: '>=22'} peerDependencies: zod: ^3.25.76 || ^4.1.8 - '@ai-sdk/provider-utils@5.0.0-beta.49': - resolution: {integrity: sha512-7xnpAQLpW0KGIsh0CQERcIZuXEGqv7FtFW2BdFk14iuMxRMVpXhTVvEQyqkm6tWAbQ7OsGQJhO6M0Me9gHQ52g==} + '@ai-sdk/provider-utils@5.0.10': + resolution: {integrity: sha512-uPyec0+85dwxZYXtb8qe8gCjhjDfxP4LCDo/uRQS/iG+FIgYbHPRhr/ys281udG90bTaE18+5cxWraYaf8oHCw==} engines: {node: '>=22'} peerDependencies: zod: ^3.25.76 || ^4.1.8 - '@ai-sdk/provider-utils@5.0.0-canary.48': - resolution: {integrity: sha512-340rMqAnqHDZOKS9ayrRbNr0/Om+orcopAnqJ7ftfEskxDUCTcudmhAhctFMSQeMzDnKaBPgz+4iff/hn7PkqQ==} - engines: {node: '>=22'} - peerDependencies: - zod: ^3.25.76 || ^4.1.8 - - '@ai-sdk/provider@4.0.0-beta.19': - resolution: {integrity: sha512-Aca/KiGeRtMM7rOJ38Qio+Dc2V45PpiGoWgdrdtIkgM9zkhYpS043t0ggKoNOWgm/csv99XWGrfSF63PSkVeHw==} + '@ai-sdk/provider@4.0.3': + resolution: {integrity: sha512-e0CpNWJUY7OxAFAnCZkw+ri9QOHWwTs1tXP42782KFGCU07qt8NiXCrCVowyCB5dP2r5/Uls+g2oPd8kOJn9dw==} engines: {node: '>=22'} - '@ai-sdk/provider@4.0.0-canary.18': - resolution: {integrity: sha512-+XBwXEPkN+l/90bb7TaSK15jAq9ScsGrauJ/NLSGip0KX5Mf1pfEfqBWDuJMIYVRYHNdV1dDBy7JCoKUCQe/tA==} - engines: {node: '>=22'} - - '@ai-sdk/react@4.0.0-beta.182': - resolution: {integrity: sha512-o9YwQ1QELhsuXcdg2ZfJ1GyHrtsXBSjMtlxdGnOW36Jkqiv0DT1yARI2PQKX0Ge0vsCNGIbPoJzNrkDzRTcG+g==} + '@ai-sdk/react@4.0.33': + resolution: {integrity: sha512-THPfbAeW8kJo6x3rBbovVhUJA5AyUwKjQ8qehQ3qciD9BwJ9dnPl5yt3P5tH26WEWoZdgPbtK+NRu4UR9NV3yw==} engines: {node: '>=22'} peerDependencies: react: ^18 || ~19.0.1 || ~19.1.2 || ^19.2.1 @@ -2582,6 +2625,126 @@ packages: engines: {node: '>=16.20.0'} hasBin: true + '@typescript/typescript-aix-ppc64@7.0.2': + resolution: {integrity: sha512-MTKKkWB7p/0E9xi1d1tHtZ5PiLkGEMIq88pK2CubZjOsLtYTLqhgIgi6zepFa+9GHZ6h05NMCkQxGKiPXMxXtQ==} + engines: {node: '>=16.20.0'} + cpu: [ppc64] + os: [aix] + + '@typescript/typescript-darwin-arm64@7.0.2': + resolution: {integrity: sha512-gowzar9MwS/aRWp6f3a4KUqzRjAZjOsmGNCM6LcTgXum+dBfgsBVMN+AgvOCCbguXyick6LJhpBszxMebJ8syA==} + engines: {node: '>=16.20.0'} + cpu: [arm64] + os: [darwin] + + '@typescript/typescript-darwin-x64@7.0.2': + resolution: {integrity: sha512-SZ9xZInqApNlNGc9s0W1VSsktYSOe9cFqNOIqmN1Gs8SmkjKZYFt017G4VwPxASInODuAdbTW7sXiFUf893RgA==} + engines: {node: '>=16.20.0'} + cpu: [x64] + os: [darwin] + + '@typescript/typescript-freebsd-arm64@7.0.2': + resolution: {integrity: sha512-W5NH4y/J0plIIS5b2xvTEkU7JFxyqdMAOgf+Ilhl0vHQXKO5dZoxd+C/jEtq56c4F3wk71RB4BMRQ2XdI+bwYQ==} + engines: {node: '>=16.20.0'} + cpu: [arm64] + os: [freebsd] + + '@typescript/typescript-freebsd-x64@7.0.2': + resolution: {integrity: sha512-UMGDx5sTpzNw3WiPebH7l90IWfJggEd+egHt/q6p7/Cm3zqoV7VxkGXt+3DxPIw8CcmvAB0j3sVVfbhX+M4Tpw==} + engines: {node: '>=16.20.0'} + cpu: [x64] + os: [freebsd] + + '@typescript/typescript-linux-arm64@7.0.2': + resolution: {integrity: sha512-Qh4eU4/y3yDjnfjjyPYihMj5/ODIlmt+Bzu17OI+fiSRDW57QmU5SiN63exPRNJPKUzcc1INa1NXdrJ+MqHjUQ==} + engines: {node: '>=16.20.0'} + cpu: [arm64] + os: [linux] + + '@typescript/typescript-linux-arm@7.0.2': + resolution: {integrity: sha512-gffT3xPz9sR7j/YJExkyPntrI0P2EP9XbOyWzth2/Gs0RstK+90RBcO0ncXoXy/beYll1SXw846Nf2zdnEz0QQ==} + engines: {node: '>=16.20.0'} + cpu: [arm] + os: [linux] + + '@typescript/typescript-linux-loong64@7.0.2': + resolution: {integrity: sha512-uEHck9i8hoAzXPiYRib1O7miOnz23SxIeVl6F4LXox+qov1K35jHcEW6VHKvZI+pyvl7fZEP4MCU5LYvIq1GuQ==} + engines: {node: '>=16.20.0'} + cpu: [loong64] + os: [linux] + + '@typescript/typescript-linux-mips64el@7.0.2': + resolution: {integrity: sha512-R4KvAMnE43W5Qeqb0Ly56O3mWMWIAgsMyz36DCaycd5nbg/9kzm0liw3JocfRqyJY0KPmzFjbswozXyW0DnIYA==} + engines: {node: '>=16.20.0'} + cpu: [mips64el] + os: [linux] + + '@typescript/typescript-linux-ppc64@7.0.2': + resolution: {integrity: sha512-DORx5b3sd/4S7eayxm4FQv+A7CrkUIGRaHiwI8oiHTAI1fAPWhF4J0vAlkC8biAlHSVVwxMQ3tjZ2/DVbnQiiA==} + engines: {node: '>=16.20.0'} + cpu: [ppc64] + os: [linux] + + '@typescript/typescript-linux-riscv64@7.0.2': + resolution: {integrity: sha512-wf0jqEDOjrPRnKwYRyyJDRo11KMbvMFrU+q4zqKyChODBzvlkbhNQfKvLxQCcwTpdDaXSHZTVuh0JoCrKCUMHQ==} + engines: {node: '>=16.20.0'} + cpu: [riscv64] + os: [linux] + + '@typescript/typescript-linux-s390x@7.0.2': + resolution: {integrity: sha512-IkwJc3L7yhytWd/ewjyxNDfOmswCm9GWMJT/ue/dU4aZNbwZeYAetq42VyLmsmSjvoX7z74X6ZaYCtzAr0EuGw==} + engines: {node: '>=16.20.0'} + cpu: [s390x] + os: [linux] + + '@typescript/typescript-linux-x64@7.0.2': + resolution: {integrity: sha512-EYdf2cNg7rgCWJnxCdJ+F3V39O8ihb37eHAu1LK8oAFizgTQbPOK7zHHXbPt8rX24COqODXeI3sIf0fCXG7H/A==} + engines: {node: '>=16.20.0'} + cpu: [x64] + os: [linux] + + '@typescript/typescript-netbsd-arm64@7.0.2': + resolution: {integrity: sha512-+polYF4MF04aPpO5FTkHran9yUQDSXqy5GiSDKpsll5jy3l3+g9QLhpf39T+ePtefhXLOGrLl0QIjkQP6VnelA==} + engines: {node: '>=16.20.0'} + cpu: [arm64] + os: [netbsd] + + '@typescript/typescript-netbsd-x64@7.0.2': + resolution: {integrity: sha512-8YIT0EHM/3dq10ZOVF/A7pc/YSMtbcecct4rWtexrnSCHOPcpC2KTLXfTCR6vDpnSiY12heNb1GiN/wu+T/FyA==} + engines: {node: '>=16.20.0'} + cpu: [x64] + os: [netbsd] + + '@typescript/typescript-openbsd-arm64@7.0.2': + resolution: {integrity: sha512-APT8+ClYnuYm1u9+kgGXoMj2VzWzcymwh2gNSQVySHfkRDGOTVkoWLjCmOQSaO+PoqQ57B0flRp9SA+7GnnkzQ==} + engines: {node: '>=16.20.0'} + cpu: [arm64] + os: [openbsd] + + '@typescript/typescript-openbsd-x64@7.0.2': + resolution: {integrity: sha512-yX7s+Q0Dln0Dt9tEzZsAjXXR/+ytBM7AlglaqyeMPxQszJ1JhlJdZ6jLA+IzldHtflX81em7lDao1xXu+aRRkg==} + engines: {node: '>=16.20.0'} + cpu: [x64] + os: [openbsd] + + '@typescript/typescript-sunos-x64@7.0.2': + resolution: {integrity: sha512-dLJDGaLZ1D4HPQn62u1n8mBDkJREwMsAkCdkwd4Ieqw+x3TUyTsqY0YiBCtE6H6OzzgGk3iuZ3vFWRS+E8/d1g==} + engines: {node: '>=16.20.0'} + cpu: [x64] + os: [sunos] + + '@typescript/typescript-win32-arm64@7.0.2': + resolution: {integrity: sha512-Gyl1Vy6OsWesLzmq+EP0Fb7b4Nid5232AvcA2SFcdYreldpNtYFFofPjnt62y9hQy7VTaZp65ICJjuAQRaVcIQ==} + engines: {node: '>=16.20.0'} + cpu: [arm64] + os: [win32] + + '@typescript/typescript-win32-x64@7.0.2': + resolution: {integrity: sha512-0BQ3HkAHHlKLSp1qRvf3SUhGpGsDuhB/jgFw75guyqbxJqEaS0Cw/VFO8i2nHglJUzQCRtMMR/IBAKE3ETMC4g==} + engines: {node: '>=16.20.0'} + cpu: [x64] + os: [win32] + '@ungap/structured-clone@1.3.1': resolution: {integrity: sha512-mUFwbeTqrVgDQxFveS+df2yfap6iuP20NAKAsBt5jDEoOTDew+zwLAOilHCeQJOVSvmgCX4ogqIrA0mnyr08yQ==} @@ -2683,8 +2846,8 @@ packages: engines: {node: '>=0.4.0'} hasBin: true - ai@7.0.0-beta.178: - resolution: {integrity: sha512-kOfIbf23FDkvvfDHOS1gIjImBF9MlYut8fEMlps57vvS622VKL1PeEDLJ181aVd2LPSiDr3SupAMSzI2rEaW3w==} + ai@7.0.30: + resolution: {integrity: sha512-tm0gTAHSWBdDfW4P2mj+LlglGCUQTSA9ktyS2PsPgVhxNO9gYWTSnRAehiJ3h1lYsJBp1Zgbz3RH2lezJXagiw==} engines: {node: '>=22'} peerDependencies: zod: ^3.25.76 || ^4.1.8 @@ -3212,46 +3375,25 @@ packages: resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==} engines: {node: '>=0.10.0'} - eve@0.17.1: - resolution: {integrity: sha512-EI9+ohx9VMoYeoDXGGJqmrGbH0+tH5WH+hWWOBusx+pDD6ounZ8qyDL1UWIcSr1MR2lpXmaIWUPiTHOeqK7eQg==} + eve@0.24.6: + resolution: {integrity: sha512-/WuGD6gAzv7DviM0bN0y4813bJSbBTeiTHVUR1GAOIH7PUCOCFY7lXbrDOG7Wk/mTa2IBmT4Om3L+nudCc+02g==} engines: {node: '>=24'} hasBin: true peerDependencies: '@opentelemetry/api': ^1.0.0 - '@sveltejs/kit': ^2.0.0 - ai: ^7.0.0 + ai: ^7.0.26 braintrust: ^3.0.0 just-bash: ^3.0.0 microsandbox: ^0.5.0 - next: ^16.0.0 - nuxt: ^4.0.0 - react: ^19.0.0 - svelte: ^5.0.0 - vite: ^8.0.0 - vue: ^3.5.0 peerDependenciesMeta: '@opentelemetry/api': optional: true - '@sveltejs/kit': - optional: true braintrust: optional: true just-bash: optional: true microsandbox: optional: true - next: - optional: true - nuxt: - optional: true - react: - optional: true - svelte: - optional: true - vite: - optional: true - vue: - optional: true eventsource-parser@3.1.0: resolution: {integrity: sha512-kJezFj9YFAMLeORyi7aCLxLbD5/qWMQnoMVlVPyHIll7lgRJCc3JVln9Vgl9nwQi0YkMnhdGTMNn7CkRRAptMg==} @@ -4680,6 +4822,11 @@ packages: engines: {node: '>=14.17'} hasBin: true + typescript@7.0.2: + resolution: {integrity: sha512-8FYau96o3NKOhbjKi/qNvG/W5jhzxkbdm5sj9AbZ/5T5sWqn3hJgLfGx27sRKZWTvyzCP8dLRBTf5tBTSRVUNA==} + engines: {node: '>=16.20.0'} + hasBin: true + ufo@1.6.4: resolution: {integrity: sha512-JFNbkD1Svwe0KvGi8GOeLcP4kAWQ609twvCdcHxq1oSL8svv39ZuSvajcD8B+5D0eL4+s1Is2D/O6KN3qcTeRA==} @@ -4952,56 +5099,44 @@ packages: snapshots: - '@ai-sdk/gateway@4.0.0-beta.109(zod@4.4.3)': + '@ai-sdk/gateway@4.0.22(zod@4.4.3)': dependencies: - '@ai-sdk/provider': 4.0.0-beta.19 - '@ai-sdk/provider-utils': 5.0.0-beta.49(zod@4.4.3) + '@ai-sdk/provider': 4.0.3 + '@ai-sdk/provider-utils': 5.0.10(zod@4.4.3) '@vercel/oidc': 3.2.0 zod: 4.4.3 - '@ai-sdk/mcp@2.0.0-beta.66(zod@4.4.3)': + '@ai-sdk/mcp@2.0.14(zod@4.4.3)': dependencies: - '@ai-sdk/provider': 4.0.0-beta.19 - '@ai-sdk/provider-utils': 5.0.0-beta.49(zod@4.4.3) + '@ai-sdk/provider': 4.0.3 + '@ai-sdk/provider-utils': 5.0.10(zod@4.4.3) pkce-challenge: 5.0.1 zod: 4.4.3 - '@ai-sdk/openai@4.0.0-canary.73(zod@4.4.3)': - dependencies: - '@ai-sdk/provider': 4.0.0-canary.18 - '@ai-sdk/provider-utils': 5.0.0-canary.48(zod@4.4.3) - zod: 4.4.3 - - '@ai-sdk/provider-utils@5.0.0-beta.49(zod@4.4.3)': + '@ai-sdk/openai@4.0.15(zod@4.4.3)': dependencies: - '@ai-sdk/provider': 4.0.0-beta.19 - '@standard-schema/spec': 1.1.0 - '@workflow/serde': 4.1.0 - eventsource-parser: 3.1.0 + '@ai-sdk/provider': 4.0.3 + '@ai-sdk/provider-utils': 5.0.10(zod@4.4.3) zod: 4.4.3 - '@ai-sdk/provider-utils@5.0.0-canary.48(zod@4.4.3)': + '@ai-sdk/provider-utils@5.0.10(zod@4.4.3)': dependencies: - '@ai-sdk/provider': 4.0.0-canary.18 + '@ai-sdk/provider': 4.0.3 '@standard-schema/spec': 1.1.0 '@workflow/serde': 4.1.0 eventsource-parser: 3.1.0 zod: 4.4.3 - '@ai-sdk/provider@4.0.0-beta.19': + '@ai-sdk/provider@4.0.3': dependencies: json-schema: 0.4.0 - '@ai-sdk/provider@4.0.0-canary.18': + '@ai-sdk/react@4.0.33(react@19.2.6)(zod@4.4.3)': dependencies: - json-schema: 0.4.0 - - '@ai-sdk/react@4.0.0-beta.182(react@19.2.6)(zod@4.4.3)': - dependencies: - '@ai-sdk/mcp': 2.0.0-beta.66(zod@4.4.3) - '@ai-sdk/provider': 4.0.0-beta.19 - '@ai-sdk/provider-utils': 5.0.0-beta.49(zod@4.4.3) - ai: 7.0.0-beta.178(zod@4.4.3) + '@ai-sdk/mcp': 2.0.14(zod@4.4.3) + '@ai-sdk/provider': 4.0.3 + '@ai-sdk/provider-utils': 5.0.10(zod@4.4.3) + ai: 7.0.30(zod@4.4.3) react: 19.2.6 swr: 2.4.1(react@19.2.6) throttleit: 2.1.0 @@ -7082,6 +7217,66 @@ snapshots: '@typescript/native-preview-win32-arm64': 7.0.0-dev.20260523.1 '@typescript/native-preview-win32-x64': 7.0.0-dev.20260523.1 + '@typescript/typescript-aix-ppc64@7.0.2': + optional: true + + '@typescript/typescript-darwin-arm64@7.0.2': + optional: true + + '@typescript/typescript-darwin-x64@7.0.2': + optional: true + + '@typescript/typescript-freebsd-arm64@7.0.2': + optional: true + + '@typescript/typescript-freebsd-x64@7.0.2': + optional: true + + '@typescript/typescript-linux-arm64@7.0.2': + optional: true + + '@typescript/typescript-linux-arm@7.0.2': + optional: true + + '@typescript/typescript-linux-loong64@7.0.2': + optional: true + + '@typescript/typescript-linux-mips64el@7.0.2': + optional: true + + '@typescript/typescript-linux-ppc64@7.0.2': + optional: true + + '@typescript/typescript-linux-riscv64@7.0.2': + optional: true + + '@typescript/typescript-linux-s390x@7.0.2': + optional: true + + '@typescript/typescript-linux-x64@7.0.2': + optional: true + + '@typescript/typescript-netbsd-arm64@7.0.2': + optional: true + + '@typescript/typescript-netbsd-x64@7.0.2': + optional: true + + '@typescript/typescript-openbsd-arm64@7.0.2': + optional: true + + '@typescript/typescript-openbsd-x64@7.0.2': + optional: true + + '@typescript/typescript-sunos-x64@7.0.2': + optional: true + + '@typescript/typescript-win32-arm64@7.0.2': + optional: true + + '@typescript/typescript-win32-x64@7.0.2': + optional: true + '@ungap/structured-clone@1.3.1': {} '@upsetjs/venn.js@2.0.0': @@ -7116,12 +7311,13 @@ snapshots: dependencies: execa: 5.1.1 - '@vercel/connect@0.2.2(ai@7.0.0-beta.178(zod@4.4.3))(eve@0.17.1(@opentelemetry/api@1.9.1)(@upstash/redis@1.38.0)(ai@7.0.0-beta.178(zod@4.4.3))(chokidar@4.0.3)(dotenv@16.6.1)(jiti@2.7.0)(next@16.2.6(@opentelemetry/api@1.9.1)(react-dom@19.2.6(react@19.2.6))(react@19.2.6))(react@19.2.6)(rollup@4.62.0)(vite@5.4.21(@types/node@20.19.43)(lightningcss@1.32.0)))': + '@vercel/connect@0.2.2(@ai-sdk/mcp@2.0.14(zod@4.4.3))(ai@7.0.30(zod@4.4.3))(eve@0.24.6(@opentelemetry/api@1.9.1)(@upstash/redis@1.38.0)(ai@7.0.30(zod@4.4.3))(chokidar@4.0.3)(dotenv@16.6.1)(jiti@2.7.0)(rollup@4.62.0)(vite@5.4.21(@types/node@20.19.43)(lightningcss@1.32.0)))': dependencies: '@vercel/oidc': 3.6.1 optionalDependencies: - ai: 7.0.0-beta.178(zod@4.4.3) - eve: 0.17.1(@opentelemetry/api@1.9.1)(@upstash/redis@1.38.0)(ai@7.0.0-beta.178(zod@4.4.3))(chokidar@4.0.3)(dotenv@16.6.1)(jiti@2.7.0)(next@16.2.6(@opentelemetry/api@1.9.1)(react-dom@19.2.6(react@19.2.6))(react@19.2.6))(react@19.2.6)(rollup@4.62.0)(vite@5.4.21(@types/node@20.19.43)(lightningcss@1.32.0)) + '@ai-sdk/mcp': 2.0.14(zod@4.4.3) + ai: 7.0.30(zod@4.4.3) + eve: 0.24.6(@opentelemetry/api@1.9.1)(@upstash/redis@1.38.0)(ai@7.0.30(zod@4.4.3))(chokidar@4.0.3)(dotenv@16.6.1)(jiti@2.7.0)(rollup@4.62.0)(vite@5.4.21(@types/node@20.19.43)(lightningcss@1.32.0)) '@vercel/oidc@3.2.0': {} @@ -7179,11 +7375,11 @@ snapshots: acorn@8.17.0: {} - ai@7.0.0-beta.178(zod@4.4.3): + ai@7.0.30(zod@4.4.3): dependencies: - '@ai-sdk/gateway': 4.0.0-beta.109(zod@4.4.3) - '@ai-sdk/provider': 4.0.0-beta.19 - '@ai-sdk/provider-utils': 5.0.0-beta.49(zod@4.4.3) + '@ai-sdk/gateway': 4.0.22(zod@4.4.3) + '@ai-sdk/provider': 4.0.3 + '@ai-sdk/provider-utils': 5.0.10(zod@4.4.3) zod: 4.4.3 ajv@6.15.0: @@ -7734,63 +7930,12 @@ snapshots: esutils@2.0.3: {} - eve@0.17.1(@opentelemetry/api@1.9.1)(@upstash/redis@1.38.0)(ai@7.0.0-beta.178(zod@4.4.3))(chokidar@4.0.3)(dotenv@16.6.1)(jiti@2.7.0)(next@16.2.6(@opentelemetry/api@1.9.1)(react-dom@19.2.6(react@19.2.6))(react@19.2.6))(react@19.2.6)(rollup@4.62.0)(vite@5.4.21(@types/node@20.19.43)(lightningcss@1.32.0)): - dependencies: - ai: 7.0.0-beta.178(zod@4.4.3) - nitro: 3.0.260610-beta(@upstash/redis@1.38.0)(chokidar@4.0.3)(dotenv@16.6.1)(jiti@2.7.0)(rollup@4.62.0)(vite@5.4.21(@types/node@20.19.43)(lightningcss@1.32.0)) - optionalDependencies: - '@opentelemetry/api': 1.9.1 - next: 16.2.6(@opentelemetry/api@1.9.1)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) - react: 19.2.6 - vite: 5.4.21(@types/node@20.19.43)(lightningcss@1.32.0) - transitivePeerDependencies: - - '@azure/app-configuration' - - '@azure/cosmos' - - '@azure/data-tables' - - '@azure/identity' - - '@azure/keyvault-secrets' - - '@azure/storage-blob' - - '@capacitor/preferences' - - '@deno/kv' - - '@electric-sql/pglite' - - '@libsql/client' - - '@netlify/blobs' - - '@netlify/runtime' - - '@planetscale/database' - - '@upstash/redis' - - '@vercel/blob' - - '@vercel/functions' - - '@vercel/kv' - - '@vercel/queue' - - aws4fetch - - better-sqlite3 - - chokidar - - dotenv - - drizzle-orm - - giget - - idb-keyval - - ioredis - - jiti - - lru-cache - - miniflare - - mongodb - - mysql2 - - rollup - - sqlite3 - - uploadthing - - wrangler - - xml2js - - zephyr-agent - - eve@0.17.1(@opentelemetry/api@1.9.1)(@upstash/redis@1.38.0)(ai@7.0.0-beta.178(zod@4.4.3))(chokidar@4.0.3)(dotenv@16.6.1)(jiti@2.7.0)(next@16.2.9(@opentelemetry/api@1.9.1)(react-dom@19.2.6(react@19.2.6))(react@19.2.6))(react@19.2.6)(rollup@4.62.0)(vite@5.4.21(@types/node@20.19.43)(lightningcss@1.32.0)): + eve@0.24.6(@opentelemetry/api@1.9.1)(@upstash/redis@1.38.0)(ai@7.0.30(zod@4.4.3))(chokidar@4.0.3)(dotenv@16.6.1)(jiti@2.7.0)(rollup@4.62.0)(vite@5.4.21(@types/node@20.19.43)(lightningcss@1.32.0)): dependencies: - ai: 7.0.0-beta.178(zod@4.4.3) + ai: 7.0.30(zod@4.4.3) nitro: 3.0.260610-beta(@upstash/redis@1.38.0)(chokidar@4.0.3)(dotenv@16.6.1)(jiti@2.7.0)(rollup@4.62.0)(vite@5.4.21(@types/node@20.19.43)(lightningcss@1.32.0)) optionalDependencies: '@opentelemetry/api': 1.9.1 - next: 16.2.9(@opentelemetry/api@1.9.1)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) - react: 19.2.6 - vite: 5.4.21(@types/node@20.19.43)(lightningcss@1.32.0) transitivePeerDependencies: - '@azure/app-configuration' - '@azure/cosmos' @@ -7826,6 +7971,7 @@ snapshots: - rollup - sqlite3 - uploadthing + - vite - wrangler - xml2js - zephyr-agent @@ -9636,6 +9782,29 @@ snapshots: typescript@5.9.3: {} + typescript@7.0.2: + optionalDependencies: + '@typescript/typescript-aix-ppc64': 7.0.2 + '@typescript/typescript-darwin-arm64': 7.0.2 + '@typescript/typescript-darwin-x64': 7.0.2 + '@typescript/typescript-freebsd-arm64': 7.0.2 + '@typescript/typescript-freebsd-x64': 7.0.2 + '@typescript/typescript-linux-arm': 7.0.2 + '@typescript/typescript-linux-arm64': 7.0.2 + '@typescript/typescript-linux-loong64': 7.0.2 + '@typescript/typescript-linux-mips64el': 7.0.2 + '@typescript/typescript-linux-ppc64': 7.0.2 + '@typescript/typescript-linux-riscv64': 7.0.2 + '@typescript/typescript-linux-s390x': 7.0.2 + '@typescript/typescript-linux-x64': 7.0.2 + '@typescript/typescript-netbsd-arm64': 7.0.2 + '@typescript/typescript-netbsd-x64': 7.0.2 + '@typescript/typescript-openbsd-arm64': 7.0.2 + '@typescript/typescript-openbsd-x64': 7.0.2 + '@typescript/typescript-sunos-x64': 7.0.2 + '@typescript/typescript-win32-arm64': 7.0.2 + '@typescript/typescript-win32-x64': 7.0.2 + ufo@1.6.4: {} uncrypto@0.1.3: {} diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index f571ab0..4e0eed7 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -7,3 +7,15 @@ allowBuilds: unrs-resolver: true # The whole repo is on AI SDK v7-beta; don't gate beta installs by release age. minimumReleaseAge: 0 + +minimumReleaseAgeExclude: + - "@ai-sdk/*" + - "@rolldown/*" + - "@vercel/*" + - "@workflow/*" + - ai + - experimental-ai-sdk-code-mode + - eve + - nitro + - rolldown + - workflow