diff --git a/.changeset/slimy-windows-count.md b/.changeset/slimy-windows-count.md new file mode 100644 index 000000000000..008b27091fe5 --- /dev/null +++ b/.changeset/slimy-windows-count.md @@ -0,0 +1,5 @@ +--- +"@ai-sdk/harness-eve": major +--- + +feat(harness-eve): add experimental eve harness diff --git a/.github/konsistent.json b/.github/konsistent.json index 7833564055f1..1cf9c3f054a0 100644 --- a/.github/konsistent.json +++ b/.github/konsistent.json @@ -136,7 +136,6 @@ "from": "./${harnessId}-harness" } ], - "exportConstants": ["${harnessId.toCamelCase()}"], "exportTypes": [ { "name": "${harnessId.toPascalCase()}HarnessSettings", @@ -146,6 +145,14 @@ "haveFiles": ["${harnessId}-harness.ts"] } }, + { + "name": "harness-package-must-export-default-harness-instance", + "description": "Every generic harness package must export a default harness instance.", + "excludeFiles": ["packages/harness-eve/src/index.ts"], + "must": { + "exportConstants": ["${harnessId.toCamelCase()}"] + } + }, { "name": "harness-file-must-export-harness-creator-and-settings-type", "description": "Every harness file must export a harness creator function and a harness settings type.", @@ -211,6 +218,7 @@ "name": "harness-auth-file-must-export-relevant-symbols", "description": "Every harness auth file must export the relevant symbols.", "for": { "files": ["${harnessId}-auth.ts"] }, + "excludeFiles": ["packages/harness-eve/src/eve-auth.ts"], "must": { "import": [ { diff --git a/content/docs/03-ai-sdk-harnesses/05-harness-adapters.mdx b/content/docs/03-ai-sdk-harnesses/05-harness-adapters.mdx index b8f40b3a43d6..52dafa5472f5 100644 --- a/content/docs/03-ai-sdk-harnesses/05-harness-adapters.mdx +++ b/content/docs/03-ai-sdk-harnesses/05-harness-adapters.mdx @@ -17,6 +17,7 @@ The AI SDK includes the following harness adapters: - [Claude Code](/providers/ai-sdk-harnesses/claude-code) (`@ai-sdk/harness-claude-code`) - [Codex](/providers/ai-sdk-harnesses/codex) (`@ai-sdk/harness-codex`) - [Deep Agents](/providers/ai-sdk-harnesses/deepagents) (`@ai-sdk/harness-deepagents`) +- [Eve](/providers/ai-sdk-harnesses/eve) (`@ai-sdk/harness-eve`) - [OpenCode](/providers/ai-sdk-harnesses/opencode) (`@ai-sdk/harness-opencode`) - [Pi](/providers/ai-sdk-harnesses/pi) (`@ai-sdk/harness-pi`) @@ -33,5 +34,6 @@ The AI SDK includes the following harness adapters: | [Claude Code](/providers/ai-sdk-harnesses/claude-code) | Sandbox bridge | | | | | | [Codex](/providers/ai-sdk-harnesses/codex) | Sandbox bridge | | | | | | [Deep Agents](/providers/ai-sdk-harnesses/deepagents) | Sandbox bridge | | | | via auto-rejection | +| [Eve](/providers/ai-sdk-harnesses/eve) | Remote Eve agent | | | | via auto-rejection | | [OpenCode](/providers/ai-sdk-harnesses/opencode) | Sandbox bridge | | | | via auto-rejection | | [Pi](/providers/ai-sdk-harnesses/pi) | Host process | | | | | diff --git a/content/providers/02-ai-sdk-harnesses/06-eve.mdx b/content/providers/02-ai-sdk-harnesses/06-eve.mdx new file mode 100644 index 000000000000..4a20f0fe2ce1 --- /dev/null +++ b/content/providers/02-ai-sdk-harnesses/06-eve.mdx @@ -0,0 +1,223 @@ +--- +title: Eve +description: Learn how to use the Eve harness adapter. +--- + +# Eve Harness + +The Eve harness adapter connects `HarnessAgent` to a specific remote +[Eve](https://eve.dev/) agent. Eve does not expose a generic harness process; +the harness is part of the Eve agent application, so the adapter requires the +URL of the Eve agent you want to run. + + + Harness packages are **experimental**. The Eve adapter is especially + experimental because the remote Eve agent owns its tools, skills, model, and + sandbox. + + +## Setup + + + + + + + + + + + + + + + + +## Import + +```ts +import { createEve } from '@ai-sdk/harness-eve'; +``` + +The package does not export an `eve` constant. Each harness instance needs a +specific Eve agent URL. + +## Basic Usage + +```ts +import { HarnessAgent } from '@ai-sdk/harness/agent'; +import { createEve } from '@ai-sdk/harness-eve'; +import { createJustBashSandbox } from '@ai-sdk/sandbox-just-bash'; + +const agent = new HarnessAgent({ + harness: createEve({ + url: process.env.EVE_AGENT_URL!, + }), + sandbox: createJustBashSandbox(), +}); + +const session = await agent.createSession(); + +try { + const result = await agent.stream({ + session, + prompt: 'Check the test failures and explain the smallest fix.', + }); + + for await (const part of result.stream) { + if (part.type === 'text-delta') { + process.stdout.write(part.text); + } + } +} finally { + await session.destroy(); +} +``` + +## Adapter Settings + +Use `createEve()` to configure the remote agent connection: + +```ts +const harness = createEve({ + url: 'https://my-eve-agent.vercel.app', + auth: 'auto', +}); +``` + +Settings: + +- `url`: remote Eve agent URL. +- `auth`: Eve client auth mode. Defaults to `auto`. +- `headers`: additional headers for the Eve client. +- `redirect`: fetch redirect policy. +- `maxReconnectAttempts`: stream reconnect attempts. +- `preserveCompletedSessions`: whether Eve keeps continuation state after + completed turns. Defaults to `true`. + +## Authentication + +By default, remote Eve agent URLs use Eve's public Vercel OIDC auth helper. +That helper reads `VERCEL_OIDC_TOKEN` and refreshes development tokens when +needed. This matches the credential-bearing request path used by remote Eve +development clients. + +```ts +const harness = createEve({ + url: process.env.EVE_AGENT_URL!, +}); +``` + +For a public Eve agent that does not require auth, opt out explicitly: + +```ts +const harness = createEve({ + url: process.env.EVE_AGENT_URL!, + auth: 'none', +}); +``` + +Explicit credential modes are also available: + +```ts +const harness = createEve({ + url: process.env.EVE_AGENT_URL!, + auth: { + type: 'vercel-oidc', + token: process.env.VERCEL_OIDC_TOKEN, + }, +}); +``` + +If the resolver needs an explicit Vercel scope, pass `team` and `project` +instead of a static token: + +```ts +const harness = createEve({ + url: process.env.EVE_AGENT_URL!, + auth: { + type: 'vercel-oidc', + team: 'team_...', + project: 'prj_...', + }, +}); +``` + +When `VERCEL_AUTOMATION_BYPASS_SECRET` is set, the adapter forwards it as the +Vercel deployment protection bypass header. + +Supported auth modes: + +- `auto`: uses Eve's Vercel OIDC resolver for remote URLs; local URLs can run + without auth when no token is available. +- `none`: sends no Eve auth headers. +- `vercel-oidc`: sends a Vercel OIDC token. +- `bearer`: sends a bearer token. +- `basic`: sends basic auth credentials. + +## Sandbox + +`HarnessAgent` still requires a sandbox provider, but Eve does not use the AI +SDK sandbox. The remote Eve agent uses its own sandbox configuration and does +not expose a public API for the AI SDK adapter to replace it. + +Use `@ai-sdk/sandbox-just-bash` when you only need a lightweight required +sandbox provider: + +```ts +const agent = new HarnessAgent({ + harness: createEve({ + url: process.env.EVE_AGENT_URL!, + }), + sandbox: createJustBashSandbox(), +}); +``` + +## Built-in Tools + +The adapter exposes these Eve framework tools through `agent.tools`: + +- `read` +- `write` +- `bash` +- `grep` +- `glob` +- `webSearch` +- `web_fetch` +- `todo` +- `agent` +- `load_skill` +- `connection_search` + +Eve-authored tools and other agent-specific tools are surfaced as +provider-executed dynamic tool calls when the remote agent requests them. + +Eve confirmation approvals are supported. `ask_question` and other non- +confirmation input requests are not supported by this adapter. + +## Limitations + +- Host-defined AI SDK tools are not supported. +- `activeTools` and `inactiveTools` are not supported. +- Host-provided skills are not supported. +- The AI SDK cannot provide or replace the remote Eve sandbox. +- Manual compaction is not supported. +- Eve `ask_question` requests are not supported. + +## Related + +- [HarnessAgent](/docs/ai-sdk-harnesses/harness-agent) +- [Harness tools](/docs/ai-sdk-harnesses/tools) +- [Harness adapters](/docs/ai-sdk-harnesses/harness-adapters) diff --git a/content/providers/02-ai-sdk-harnesses/index.mdx b/content/providers/02-ai-sdk-harnesses/index.mdx index 60cda7c1086c..80ee76b024c1 100644 --- a/content/providers/02-ai-sdk-harnesses/index.mdx +++ b/content/providers/02-ai-sdk-harnesses/index.mdx @@ -21,6 +21,12 @@ and response primitives. description: 'Use Codex through the AI SDK harness abstraction.', href: '/providers/ai-sdk-harnesses/codex', }, + { + title: 'Eve', + description: + 'Use a remote Eve agent through the AI SDK harness abstraction.', + href: '/providers/ai-sdk-harnesses/eve', + }, { title: 'OpenCode', description: 'Use OpenCode through the AI SDK harness abstraction.', diff --git a/examples/ai-functions/package.json b/examples/ai-functions/package.json index 32aa3ffbf1c6..aa0abafd0268 100644 --- a/examples/ai-functions/package.json +++ b/examples/ai-functions/package.json @@ -31,6 +31,7 @@ "@ai-sdk/harness-claude-code": "workspace:*", "@ai-sdk/harness-codex": "workspace:*", "@ai-sdk/harness-deepagents": "workspace:*", + "@ai-sdk/harness-eve": "workspace:*", "@ai-sdk/harness-opencode": "workspace:*", "@ai-sdk/harness-pi": "workspace:*", "@ai-sdk/huggingface": "workspace:*", diff --git a/examples/ai-functions/src/harness-agent/eve/active-tools.ts b/examples/ai-functions/src/harness-agent/eve/active-tools.ts new file mode 100644 index 000000000000..8234cace3926 --- /dev/null +++ b/examples/ai-functions/src/harness-agent/eve/active-tools.ts @@ -0,0 +1,52 @@ +import { HarnessAgent } from '@ai-sdk/harness/agent'; +import { createEve } from '@ai-sdk/harness-eve'; +import { createJustBashSandbox } from '@ai-sdk/sandbox-just-bash'; +import { tool } from 'ai'; +import { z } from 'zod'; +import { printFullStream } from '../../lib/print-full-stream'; +import { run } from '../../lib/run'; + +run(async () => { + const url = process.env.EVE_BASIC_AGENT_URL; + if (!url) { + throw new Error('Set EVE_BASIC_AGENT_URL to a remote Eve agent URL.'); + } + + const weather = tool({ + description: 'Get the current temperature for a city.', + inputSchema: z.object({ city: z.string() }), + execute: async ({ city }: { city: string }) => { + const temps: Record = { + Paris: 12, + Tokyo: 18, + Reykjavik: 3, + }; + return { city, celsius: temps[city] ?? 20 }; + }, + }); + + const agent = new HarnessAgent({ + harness: createEve({ url }), + sandbox: createJustBashSandbox(), // Ignored by eve harness. + tools: { weather }, + activeTools: ['weather'], + }); + + let exitCode = 0; + const session = await agent.createSession(); + try { + const result = await agent.stream({ + session, + prompt: + 'Use the `weather` tool for Paris, then read README.md and summarize both results in one sentence.', + }); + + await printFullStream({ result }); + } catch (err) { + exitCode = 1; + console.error('[example] failed:', err); + } finally { + await session.destroy(); + process.exit(exitCode); + } +}); diff --git a/examples/ai-functions/src/harness-agent/eve/generate-text.ts b/examples/ai-functions/src/harness-agent/eve/generate-text.ts new file mode 100644 index 000000000000..0ad5ef32219e --- /dev/null +++ b/examples/ai-functions/src/harness-agent/eve/generate-text.ts @@ -0,0 +1,29 @@ +import { HarnessAgent } from '@ai-sdk/harness/agent'; +import { createEve } from '@ai-sdk/harness-eve'; +import { createJustBashSandbox } from '@ai-sdk/sandbox-just-bash'; +import { run } from '../../lib/run'; + +run(async () => { + const url = process.env.EVE_BASIC_AGENT_URL; + if (!url) { + throw new Error('Set EVE_BASIC_AGENT_URL to a remote Eve agent URL.'); + } + + const agent = new HarnessAgent({ + harness: createEve({ url }), + sandbox: createJustBashSandbox(), // Ignored by eve harness. + }); + + const session = await agent.createSession(); + try { + const result = await agent.generate({ + session, + prompt: 'In one sentence, what is the capital of France?', + }); + console.log('text:', result.text); + console.log('finishReason:', result.finishReason); + console.log('usage:', result.usage); + } finally { + await session.destroy(); + } +}); diff --git a/examples/ai-functions/src/harness-agent/eve/inactive-tools.ts b/examples/ai-functions/src/harness-agent/eve/inactive-tools.ts new file mode 100644 index 000000000000..158ededb61d0 --- /dev/null +++ b/examples/ai-functions/src/harness-agent/eve/inactive-tools.ts @@ -0,0 +1,52 @@ +import { HarnessAgent } from '@ai-sdk/harness/agent'; +import { createEve } from '@ai-sdk/harness-eve'; +import { createJustBashSandbox } from '@ai-sdk/sandbox-just-bash'; +import { tool } from 'ai'; +import { z } from 'zod'; +import { printFullStream } from '../../lib/print-full-stream'; +import { run } from '../../lib/run'; + +run(async () => { + const url = process.env.EVE_BASIC_AGENT_URL; + if (!url) { + throw new Error('Set EVE_BASIC_AGENT_URL to a remote Eve agent URL.'); + } + + const weather = tool({ + description: 'Get the current temperature for a city.', + inputSchema: z.object({ city: z.string() }), + execute: async ({ city }: { city: string }) => { + const temps: Record = { + Paris: 12, + Tokyo: 18, + Reykjavik: 3, + }; + return { city, celsius: temps[city] ?? 20 }; + }, + }); + + const agent = new HarnessAgent({ + harness: createEve({ url }), + sandbox: createJustBashSandbox(), // Ignored by eve harness. + tools: { weather }, + inactiveTools: ['read', 'bash', 'grep', 'glob'], + }); + + let exitCode = 0; + const session = await agent.createSession(); + try { + const result = await agent.stream({ + session, + prompt: + 'Use the `weather` tool for Paris, then read README.md and summarize both results in one sentence.', + }); + + await printFullStream({ result }); + } catch (err) { + exitCode = 1; + console.error('[example] failed:', err); + } finally { + await session.destroy(); + process.exit(exitCode); + } +}); diff --git a/examples/ai-functions/src/harness-agent/eve/stream-text.ts b/examples/ai-functions/src/harness-agent/eve/stream-text.ts new file mode 100644 index 000000000000..edf23a80db01 --- /dev/null +++ b/examples/ai-functions/src/harness-agent/eve/stream-text.ts @@ -0,0 +1,32 @@ +import { HarnessAgent } from '@ai-sdk/harness/agent'; +import { createEve } from '@ai-sdk/harness-eve'; +import { createJustBashSandbox } from '@ai-sdk/sandbox-just-bash'; +import { printFullStream } from '../../lib/print-full-stream'; +import { run } from '../../lib/run'; + +run(async () => { + const url = process.env.EVE_BASIC_AGENT_URL; + if (!url) { + throw new Error('Set EVE_BASIC_AGENT_URL to a remote Eve agent URL.'); + } + + const agent = new HarnessAgent({ + harness: createEve({ url }), + sandbox: createJustBashSandbox(), // Ignored by eve harness. + }); + + const session = await agent.createSession(); + try { + const result = await agent.stream({ + session, + prompt: 'Recite the first sentence of "A Tale of Two Cities".', + }); + + await printFullStream({ result }); + + console.log('finishReason:', await result.finishReason); + console.log('usage:', await result.usage); + } finally { + await session.destroy(); + } +}); diff --git a/examples/ai-functions/tsconfig.json b/examples/ai-functions/tsconfig.json index 0eacc06af9ae..b86592e45333 100644 --- a/examples/ai-functions/tsconfig.json +++ b/examples/ai-functions/tsconfig.json @@ -108,6 +108,9 @@ { "path": "../../packages/harness-deepagents" }, + { + "path": "../../packages/harness-eve" + }, { "path": "../../packages/harness-opencode" }, diff --git a/examples/harness-e2e-next/agent/harness/eve/basic-agent.ts b/examples/harness-e2e-next/agent/harness/eve/basic-agent.ts new file mode 100644 index 000000000000..e73049bf7548 --- /dev/null +++ b/examples/harness-e2e-next/agent/harness/eve/basic-agent.ts @@ -0,0 +1,147 @@ +import type { + HarnessV1NetworkSandboxSession, + HarnessV1SandboxProvider, +} from '@ai-sdk/harness'; +import { + HarnessAgent, + createFileReporter, + createTraceTreeReporter, +} from '@ai-sdk/harness/agent'; +import { createEve } from '@ai-sdk/harness-eve'; +import type { InferUITools, UIMessage } from 'ai'; + +const url = process.env.EVE_BASIC_AGENT_URL; + +if (!url) { + throw new Error('Set EVE_BASIC_AGENT_URL to a remote Eve agent URL.'); +} + +export const eveHarnessAgent = new HarnessAgent({ + harness: createEve({ url }), + sandbox: createInertEveExampleSandbox(), + debug: { enabled: true }, + telemetry: { + integrations: [ + createTraceTreeReporter(), + createFileReporter({ dir: '.harness-observability/eve/basic' }), + ], + }, +}); + +/* + * Derived from `agent.tools` directly rather than `InferAgentUIMessage`. The latter extracts the tool set via `AGENT extends Agent`, which infers `string` for HarnessAgent because its + * generate/stream parameters intersect `AgentCallParameters<...>` with the + * required-`session` extension and that disrupts structural inference. Going + * through the `tools` field side-steps the issue while preserving the same + * concrete UIMessage shape. + * + * TODO: revert to `InferAgentUIMessage` once `session` + * is supported natively as part of `AgentCallParameters`, so the intersection + * in HarnessAgent's generate/stream parameters can be dropped. + */ +export type EveHarnessAgentMessage = UIMessage< + unknown, + never, + InferUITools +>; + +/* + * Eve agents execute against their own remote runtime. HarnessAgent still + * requires a sandbox provider for session bookkeeping and for the generic + * host-tool contract, but this example must not import a concrete sandbox + * runtime into the Next route bundle. This inert provider handles the + * framework's working-directory probes and lifecycle calls while all Eve tool + * execution stays inside the configured remote Eve agent. + */ +function createInertEveExampleSandbox(): HarnessV1SandboxProvider { + return { + specificationVersion: 'harness-sandbox-v1', + providerId: 'eve-example-inert-sandbox', + createSession: async options => { + options?.abortSignal?.throwIfAborted(); + const session = createInertEveExampleSandboxSession({ + sessionId: options?.sessionId, + }); + + if (options?.onFirstCreate != null) { + await options.onFirstCreate(session.restricted(), { + abortSignal: options.abortSignal, + }); + } + + return session; + }, + resumeSession: async options => { + options.abortSignal?.throwIfAborted(); + return createInertEveExampleSandboxSession({ + sessionId: options.sessionId, + }); + }, + }; +} + +const INERT_SANDBOX_WORK_DIR = '/tmp/eve-harness-sandbox'; + +function createInertEveExampleSandboxSession({ + sessionId, +}: { + readonly sessionId?: string; +}): HarnessV1NetworkSandboxSession { + const sandboxSession: HarnessV1NetworkSandboxSession = { + id: sessionId ?? 'eve-example-inert-sandbox', + description: + 'Inert sandbox placeholder. The remote Eve agent owns its execution environment.', + defaultWorkingDirectory: INERT_SANDBOX_WORK_DIR, + ports: [], + getPortUrl: async ({ port }) => { + throw new Error( + `Eve basic example inert sandbox does not expose port ${port}.`, + ); + }, + run: async ({ command }) => { + if (command === 'pwd') { + return { + exitCode: 0, + stdout: `${INERT_SANDBOX_WORK_DIR}\n`, + stderr: '', + }; + } + if (command.startsWith('mkdir ')) { + return { exitCode: 0, stdout: '', stderr: '' }; + } + return { + exitCode: 1, + stdout: '', + stderr: + 'The Eve basic example uses an inert sandbox placeholder; commands are executed by the remote Eve agent.', + }; + }, + spawn: async () => ({ + stdout: emptyStream(), + stderr: emptyStream(), + wait: async () => ({ exitCode: 1 }), + kill: async () => {}, + }), + readFile: async () => null, + readBinaryFile: async () => null, + readTextFile: async () => null, + writeFile: async () => {}, + writeBinaryFile: async () => {}, + writeTextFile: async () => {}, + stop: async () => {}, + destroy: async () => {}, + restricted: () => sandboxSession, + }; + + return sandboxSession; +} + +function emptyStream(): ReadableStream { + return new ReadableStream({ + start(controller) { + controller.close(); + }, + }); +} diff --git a/examples/harness-e2e-next/app/api/harness/eve/basic/route.ts b/examples/harness-e2e-next/app/api/harness/eve/basic/route.ts new file mode 100644 index 000000000000..11573ef7c760 --- /dev/null +++ b/examples/harness-e2e-next/app/api/harness/eve/basic/route.ts @@ -0,0 +1,35 @@ +import { eveHarnessAgent } from '@/agent/harness/eve/basic-agent'; +import { + detachAndPersist, + resumeOrCreateSession, +} from '@/util/harness-resume-store'; +import { + convertToModelMessages, + createUIMessageStreamResponse, + toUIMessageStream, + type UIMessage, +} from 'ai'; + +export async function POST(request: Request) { + const body: { + id?: string; + messages: UIMessage[]; + } = await request.json(); + + if (!body.id) { + return new Response('Missing chat id', { status: 400 }); + } + const chatId = body.id; + const messages = await convertToModelMessages(body.messages); + + const session = await resumeOrCreateSession(eveHarnessAgent, chatId); + + const result = await eveHarnessAgent.stream({ session, messages }); + + return createUIMessageStreamResponse({ + stream: toUIMessageStream({ + stream: result.stream, + onFinish: () => detachAndPersist(chatId, session), + }), + }); +} diff --git a/examples/harness-e2e-next/app/harness/eve/basic/page.tsx b/examples/harness-e2e-next/app/harness/eve/basic/page.tsx new file mode 100644 index 000000000000..fa2a7113ee56 --- /dev/null +++ b/examples/harness-e2e-next/app/harness/eve/basic/page.tsx @@ -0,0 +1,16 @@ +import ChatIdProvider from '@/components/chat-id-provider'; +import EveHarnessChat from '@/components/eve-harness-chat'; + +export const metadata = { + title: 'Eve — Basic', +}; + +const STORAGE_KEY = 'harness-eve-basic-chat-id'; + +export default function HarnessEvePage() { + return ( + + + + ); +} diff --git a/examples/harness-e2e-next/app/page.tsx b/examples/harness-e2e-next/app/page.tsx index 057c8089674a..77308ef764f7 100644 --- a/examples/harness-e2e-next/app/page.tsx +++ b/examples/harness-e2e-next/app/page.tsx @@ -49,6 +49,11 @@ const HARNESSES = [ 'workflow', ], }, + { + slug: 'eve', + label: 'Eve', + variants: ['basic'], + }, { slug: 'opencode', label: 'OpenCode', diff --git a/examples/harness-e2e-next/components/eve-harness-chat.tsx b/examples/harness-e2e-next/components/eve-harness-chat.tsx new file mode 100644 index 000000000000..ba72f5843c04 --- /dev/null +++ b/examples/harness-e2e-next/components/eve-harness-chat.tsx @@ -0,0 +1,164 @@ +'use client'; + +import type { EveHarnessAgentMessage } from '@/agent/harness/eve/basic-agent'; +import { Response } from '@/components/ai-elements/response'; +import { useChatId } from '@/components/chat-id-provider'; +import ChatInput from '@/components/chat-input'; +import DynamicToolView from '@/components/tool/dynamic-tool-view'; +import HarnessBashToolView from '@/components/tool/harness-bash-tool-view'; +import HarnessFileToolView from '@/components/tool/harness-file-tool-view'; +import HarnessToolView from '@/components/tool/harness-tool-view'; +import { useChat } from '@ai-sdk/react'; +import { DefaultChatTransport } from 'ai'; + +export default function EveHarnessChat({ + apiRoute, + exampleLabel, +}: { + apiRoute: string; + exampleLabel: string; +}) { + const { chatId, resetChatId } = useChatId(); + const { error, status, sendMessage, messages, regenerate } = + useChat({ + id: chatId, + transport: new DefaultChatTransport({ + api: apiRoute, + }), + }); + + return ( +
+

Eve — {exampleLabel}

+

+ chat id: {chatId} + +

+ + {messages.map(message => ( +
+ {message.role === 'user' ? 'You: ' : 'AI: '} + {message.parts.map((part, index) => { + switch (part.type) { + case 'text': { + return ( + + {part.text} + + ); + } + case 'reasoning': { + return ( + + {part.text} + + ); + } + case 'file': + case 'reasoning-file': { + if (part.mediaType.startsWith('image/')) { + return ( + // eslint-disable-next-line @next/next/no-img-element + Generated image + ); + } + return null; + } + case 'tool-bash': { + return ; + } + case 'tool-read': + case 'tool-write': { + return ; + } + case 'tool-glob': + case 'tool-grep': + case 'tool-webSearch': + case 'tool-web_fetch': + case 'tool-todo': + case 'tool-agent': + case 'tool-load_skill': + case 'tool-connection_search': { + const input = + typeof part.input === 'object' && part.input !== null + ? (part.input as Record) + : undefined; + const toolArg = + input?.pattern ?? + input?.path ?? + input?.url ?? + input?.query ?? + input?.name ?? + input?.agent ?? + input?.connection; + + return ( + + ); + } + case 'dynamic-tool': { + return ; + } + } + })} +
+ ))} + + {status === 'submitted' && ( +
+ )} + + {error && ( +
+
+ {error.message || String(error)} +
+ +
+ )} + +
+ + sendMessage({ text })} + /> +
+ ); +} diff --git a/examples/harness-e2e-next/package.json b/examples/harness-e2e-next/package.json index ef6d7c606212..8c6c552d5452 100644 --- a/examples/harness-e2e-next/package.json +++ b/examples/harness-e2e-next/package.json @@ -13,6 +13,7 @@ "@ai-sdk/harness-claude-code": "workspace:*", "@ai-sdk/harness-codex": "workspace:*", "@ai-sdk/harness-deepagents": "workspace:*", + "@ai-sdk/harness-eve": "workspace:*", "@ai-sdk/harness-opencode": "workspace:*", "@ai-sdk/harness-pi": "workspace:*", "@ai-sdk/workflow-harness": "workspace:*", diff --git a/examples/harness-e2e-next/tsconfig.json b/examples/harness-e2e-next/tsconfig.json index 29501edfc987..d34a37c87990 100644 --- a/examples/harness-e2e-next/tsconfig.json +++ b/examples/harness-e2e-next/tsconfig.json @@ -52,6 +52,9 @@ { "path": "../../packages/harness-codex" }, + { + "path": "../../packages/harness-eve" + }, { "path": "../../packages/harness-pi" }, diff --git a/examples/harness-e2e-tui/agents/eve/basic-agent.ts b/examples/harness-e2e-tui/agents/eve/basic-agent.ts new file mode 100644 index 000000000000..8c842963e89b --- /dev/null +++ b/examples/harness-e2e-tui/agents/eve/basic-agent.ts @@ -0,0 +1,150 @@ +import type { + HarnessV1NetworkSandboxSession, + HarnessV1SandboxProvider, +} from '@ai-sdk/harness'; +import { + HarnessAgent, + createFileReporter, + createTraceTreeReporter, +} from '@ai-sdk/harness/agent'; +import { createEve } from '@ai-sdk/harness-eve'; +import type { InferUITools, UIMessage } from 'ai'; +import { loadEnvFiles } from '../../lib/load-env-files'; + +loadEnvFiles({ entrypointUrl: import.meta.url }); + +const url = process.env.EVE_BASIC_AGENT_URL; + +if (!url) { + throw new Error('Set EVE_BASIC_AGENT_URL to a remote Eve agent URL.'); +} + +export const eveHarnessAgent = new HarnessAgent({ + harness: createEve({ url }), + sandbox: createInertEveExampleSandbox(), + debug: { enabled: true }, + telemetry: { + integrations: [ + createTraceTreeReporter(), + createFileReporter({ dir: '.harness-observability/eve/basic' }), + ], + }, +}); + +/* + * Derived from `agent.tools` directly rather than `InferAgentUIMessage`. The latter extracts the tool set via `AGENT extends Agent`, which infers `string` for HarnessAgent because its + * generate/stream parameters intersect `AgentCallParameters<...>` with the + * required-`session` extension and that disrupts structural inference. Going + * through the `tools` field side-steps the issue while preserving the same + * concrete UIMessage shape. + * + * TODO: revert to `InferAgentUIMessage` once `session` + * is supported natively as part of `AgentCallParameters`, so the intersection + * in HarnessAgent's generate/stream parameters can be dropped. + */ +export type EveHarnessAgentMessage = UIMessage< + unknown, + never, + InferUITools +>; + +/* + * Eve agents execute against their own remote runtime. HarnessAgent still + * requires a sandbox provider for session bookkeeping and for the generic + * host-tool contract, but this example must not import a concrete sandbox + * runtime. This inert provider handles the framework's working-directory + * probes and lifecycle calls while all Eve tool execution stays inside the + * configured remote Eve agent. + */ +function createInertEveExampleSandbox(): HarnessV1SandboxProvider { + return { + specificationVersion: 'harness-sandbox-v1', + providerId: 'eve-example-inert-sandbox', + createSession: async options => { + options?.abortSignal?.throwIfAborted(); + const session = createInertEveExampleSandboxSession({ + sessionId: options?.sessionId, + }); + + if (options?.onFirstCreate != null) { + await options.onFirstCreate(session.restricted(), { + abortSignal: options.abortSignal, + }); + } + + return session; + }, + resumeSession: async options => { + options.abortSignal?.throwIfAborted(); + return createInertEveExampleSandboxSession({ + sessionId: options.sessionId, + }); + }, + }; +} + +const INERT_SANDBOX_WORK_DIR = '/tmp/eve-harness-sandbox'; + +function createInertEveExampleSandboxSession({ + sessionId, +}: { + readonly sessionId?: string; +}): HarnessV1NetworkSandboxSession { + const sandboxSession: HarnessV1NetworkSandboxSession = { + id: sessionId ?? 'eve-example-inert-sandbox', + description: + 'Inert sandbox placeholder. The remote Eve agent owns its execution environment.', + defaultWorkingDirectory: INERT_SANDBOX_WORK_DIR, + ports: [], + getPortUrl: async ({ port }) => { + throw new Error( + `Eve basic example inert sandbox does not expose port ${port}.`, + ); + }, + run: async ({ command }) => { + if (command === 'pwd') { + return { + exitCode: 0, + stdout: `${INERT_SANDBOX_WORK_DIR}\n`, + stderr: '', + }; + } + if (command.startsWith('mkdir ')) { + return { exitCode: 0, stdout: '', stderr: '' }; + } + return { + exitCode: 1, + stdout: '', + stderr: + 'The Eve basic example uses an inert sandbox placeholder; commands are executed by the remote Eve agent.', + }; + }, + spawn: async () => ({ + stdout: emptyStream(), + stderr: emptyStream(), + wait: async () => ({ exitCode: 1 }), + kill: async () => {}, + }), + readFile: async () => null, + readBinaryFile: async () => null, + readTextFile: async () => null, + writeFile: async () => {}, + writeBinaryFile: async () => {}, + writeTextFile: async () => {}, + stop: async () => {}, + destroy: async () => {}, + restricted: () => sandboxSession, + }; + + return sandboxSession; +} + +function emptyStream(): ReadableStream { + return new ReadableStream({ + start(controller) { + controller.close(); + }, + }); +} diff --git a/examples/harness-e2e-tui/harness/eve/basic.ts b/examples/harness-e2e-tui/harness/eve/basic.ts new file mode 100644 index 000000000000..f3fcbdc1be7a --- /dev/null +++ b/examples/harness-e2e-tui/harness/eve/basic.ts @@ -0,0 +1,8 @@ +import { eveHarnessAgent } from '../../agents/eve/basic-agent'; +import { runTUI } from '../../lib/run-tui'; + +await runTUI({ + agent: eveHarnessAgent, + entrypointUrl: import.meta.url, + title: 'Eve — Basic', +}); diff --git a/examples/harness-e2e-tui/lib/load-env-files.ts b/examples/harness-e2e-tui/lib/load-env-files.ts new file mode 100644 index 000000000000..3b6bf8e36829 --- /dev/null +++ b/examples/harness-e2e-tui/lib/load-env-files.ts @@ -0,0 +1,23 @@ +import { existsSync } from 'node:fs'; +import { dirname } from 'node:path'; +import { fileURLToPath } from 'node:url'; +import { config } from 'dotenv'; + +export function loadEnvFiles(options: { entrypointUrl: string }) { + const entrypointDir = dirname(fileURLToPath(options.entrypointUrl)); + const cwd = process.cwd(); + const dirs = entrypointDir === cwd ? [entrypointDir] : [entrypointDir, cwd]; + const envFiles = ['.env.local', '.env']; + const loaded = new Set(); + + for (const dir of dirs) { + for (const envFile of envFiles) { + const path = `${dir}/${envFile}`; + if (loaded.has(path) || !existsSync(path)) { + continue; + } + config({ path }); + loaded.add(path); + } + } +} diff --git a/examples/harness-e2e-tui/lib/run-tui.ts b/examples/harness-e2e-tui/lib/run-tui.ts index 974a2c4342c3..90fb6a8bcfa8 100644 --- a/examples/harness-e2e-tui/lib/run-tui.ts +++ b/examples/harness-e2e-tui/lib/run-tui.ts @@ -1,13 +1,10 @@ -import { existsSync } from 'node:fs'; -import { dirname } from 'node:path'; -import { fileURLToPath } from 'node:url'; import type { HarnessAgent, HarnessAgentSession } from '@ai-sdk/harness/agent'; import { runAgentTUI, type AgentTUIAgent, type RunAgentTUIOptions, } from '@ai-sdk/tui'; -import { config } from 'dotenv'; +import { loadEnvFiles } from './load-env-files'; type TUIHarnessAgent = HarnessAgent; @@ -75,22 +72,3 @@ function withSession({ session, }; } - -function loadEnvFiles(options: { entrypointUrl: string }) { - const entrypointDir = dirname(fileURLToPath(options.entrypointUrl)); - const cwd = process.cwd(); - const dirs = entrypointDir === cwd ? [entrypointDir] : [entrypointDir, cwd]; - const envFiles = ['.env.local', '.env']; - const loaded = new Set(); - - for (const dir of dirs) { - for (const envFile of envFiles) { - const path = `${dir}/${envFile}`; - if (loaded.has(path) || !existsSync(path)) { - continue; - } - config({ path }); - loaded.add(path); - } - } -} diff --git a/examples/harness-e2e-tui/package.json b/examples/harness-e2e-tui/package.json index be590e2f484a..1763db14877f 100644 --- a/examples/harness-e2e-tui/package.json +++ b/examples/harness-e2e-tui/package.json @@ -11,6 +11,7 @@ "@ai-sdk/harness-claude-code": "workspace:*", "@ai-sdk/harness-codex": "workspace:*", "@ai-sdk/harness-deepagents": "workspace:*", + "@ai-sdk/harness-eve": "workspace:*", "@ai-sdk/harness-opencode": "workspace:*", "@ai-sdk/harness-pi": "workspace:*", "@ai-sdk/sandbox-vercel": "workspace:*", diff --git a/examples/harness-e2e-tui/tsconfig.json b/examples/harness-e2e-tui/tsconfig.json index cccaad048835..c0e647895e95 100644 --- a/examples/harness-e2e-tui/tsconfig.json +++ b/examples/harness-e2e-tui/tsconfig.json @@ -43,6 +43,9 @@ { "path": "../../packages/harness-codex" }, + { + "path": "../../packages/harness-eve" + }, { "path": "../../packages/harness-pi" }, diff --git a/packages/harness-eve/CHANGELOG.md b/packages/harness-eve/CHANGELOG.md new file mode 100644 index 000000000000..dcf8dea7c95e --- /dev/null +++ b/packages/harness-eve/CHANGELOG.md @@ -0,0 +1 @@ +# @ai-sdk/harness-eve diff --git a/packages/harness-eve/README.md b/packages/harness-eve/README.md new file mode 100644 index 000000000000..36ce9e0f6957 --- /dev/null +++ b/packages/harness-eve/README.md @@ -0,0 +1,116 @@ +# AI SDK - Eve Harness + +The **Eve harness adapter** connects `HarnessAgent` to a specific remote +[Eve](https://eve.dev/) agent. + +This package is experimental. Eve owns the agent definition, built-in tools, +skills, model, and sandbox. The adapter does not start a generic Eve harness; +it connects to the Eve agent URL you provide. + +## Installation + +```bash +pnpm add @ai-sdk/harness @ai-sdk/harness-eve @ai-sdk/sandbox-just-bash +``` + +## Usage + +```ts +import { HarnessAgent } from '@ai-sdk/harness/agent'; +import { createEve } from '@ai-sdk/harness-eve'; +import { createJustBashSandbox } from '@ai-sdk/sandbox-just-bash'; + +const agent = new HarnessAgent({ + harness: createEve({ + url: process.env.EVE_AGENT_URL!, + }), + sandbox: createJustBashSandbox(), +}); + +const session = await agent.createSession(); + +try { + const result = await agent.generate({ + session, + prompt: 'Summarize the repository state.', + }); + + console.log(result.text); +} finally { + await session.destroy(); +} +``` + +`HarnessAgent` requires a sandbox provider, but Eve runs against the sandbox +configured by the remote Eve agent. The AI SDK sandbox is created by the +framework and is not used by this adapter. + +## Authentication + +By default, remote Eve agent URLs use Eve's public Vercel OIDC auth helper. +That helper reads `VERCEL_OIDC_TOKEN` and refreshes development tokens when +needed: + +```ts +const harness = createEve({ + url: process.env.EVE_AGENT_URL!, +}); +``` + +For a public Eve agent that does not require auth, opt out explicitly: + +```ts +const harness = createEve({ + url: process.env.EVE_AGENT_URL!, + auth: 'none', +}); +``` + +You can also pass explicit credentials: + +```ts +const harness = createEve({ + url: process.env.EVE_AGENT_URL!, + auth: { + type: 'vercel-oidc', + token: process.env.VERCEL_OIDC_TOKEN, + }, +}); +``` + +If the Vercel OIDC resolver needs an explicit Vercel scope, pass `team` and +`project` instead of a static token: + +```ts +const harness = createEve({ + url: process.env.EVE_AGENT_URL!, + auth: { + type: 'vercel-oidc', + team: 'team_...', + project: 'prj_...', + }, +}); +``` + +When `VERCEL_AUTOMATION_BYPASS_SECRET` is set, the adapter forwards it as the +Vercel deployment protection bypass header. + +Supported auth modes: + +- `auto` +- `none` +- `{ type: 'vercel-oidc', token?, team?, project?, expirationBufferMs? }` +- `{ type: 'bearer', token }` +- `{ type: 'basic', username, password }` + +## Limitations + +- No `eve` default export is provided. Use `createEve({ url })`. +- Host-defined AI SDK tools are not supported. +- `activeTools` and `inactiveTools` are not supported. +- Host-provided skills are not supported. +- Custom AI SDK sandbox control is not supported by Eve. +- Manual compaction is not supported. +- Eve `ask_question` requests are not supported. + +Eve confirmation approvals are supported through tool approval requests. diff --git a/packages/harness-eve/package.json b/packages/harness-eve/package.json new file mode 100644 index 000000000000..5c12de10e4e0 --- /dev/null +++ b/packages/harness-eve/package.json @@ -0,0 +1,74 @@ +{ + "name": "@ai-sdk/harness-eve", + "version": "0.0.0", + "type": "module", + "license": "Apache-2.0", + "sideEffects": false, + "main": "./dist/index.js", + "types": "./dist/index.d.ts", + "source": "./src/index.ts", + "files": [ + "dist/**/*", + "src", + "!src/**/*.test.ts", + "!src/**/*.test-d.ts", + "!src/**/__snapshots__", + "!src/**/__fixtures__", + "CHANGELOG.md", + "README.md" + ], + "scripts": { + "build": "pnpm clean && tsup --tsconfig tsconfig.build.json", + "build:watch": "pnpm clean && tsup --watch", + "clean": "del-cli dist *.tsbuildinfo", + "type-check": "tsc --build", + "test": "pnpm test:node", + "test:watch": "vitest --config vitest.node.config.js", + "test:node": "vitest --config vitest.node.config.js --run" + }, + "exports": { + "./package.json": "./package.json", + ".": { + "types": "./dist/index.d.ts", + "import": "./dist/index.js", + "default": "./dist/index.js" + } + }, + "dependencies": { + "@ai-sdk/harness": "workspace:*", + "@ai-sdk/provider": "workspace:*", + "@ai-sdk/provider-utils": "workspace:*", + "eve": "0.16.2" + }, + "peerDependencies": { + "zod": "^3.25.76 || ^4.1.8" + }, + "devDependencies": { + "@types/node": "22.19.19", + "@vercel/ai-tsconfig": "workspace:*", + "tsup": "^8.5.1", + "typescript": "5.8.3", + "zod": "3.25.76" + }, + "engines": { + "node": ">=22" + }, + "publishConfig": { + "access": "public", + "provenance": true + }, + "homepage": "https://ai-sdk.dev/docs", + "repository": { + "type": "git", + "url": "https://github.com/vercel/ai", + "directory": "packages/harness-eve" + }, + "bugs": { + "url": "https://github.com/vercel/ai/issues" + }, + "keywords": [ + "ai", + "eve", + "harness" + ] +} diff --git a/packages/harness-eve/src/eve-auth.test.ts b/packages/harness-eve/src/eve-auth.test.ts new file mode 100644 index 000000000000..c546e7947aec --- /dev/null +++ b/packages/harness-eve/src/eve-auth.test.ts @@ -0,0 +1,189 @@ +import { beforeEach, describe, expect, it, vi } from 'vitest'; +import { resolveEveClientOptions } from './eve-auth'; + +const mocks = vi.hoisted(() => ({ + vercelOidc: vi.fn( + ( + options: { + readonly expirationBufferMs?: number; + readonly project?: string; + readonly team?: string; + } = {}, + ) => + async () => ({ + headers: { + authorization: `Bearer oidc:${options.team ?? 'default'}:${options.project ?? 'default'}:${options.expirationBufferMs ?? 'default'}`, + }, + }), + ), +})); + +vi.mock('eve/agents/auth', () => ({ + vercelOidc: mocks.vercelOidc, +})); + +describe('resolveEveClientOptions', () => { + beforeEach(() => { + mocks.vercelOidc.mockClear(); + }); + + it('uses Eve Vercel OIDC auth by default', async () => { + const options = resolveEveClientOptions({ + settings: { url: 'https://eve.test' }, + env: { VERCEL_OIDC_TOKEN: 'oidc-token' }, + }); + + expect(options.host).toBe('https://eve.test'); + expect(options.redirect).toBe('manual'); + expect(options.preserveCompletedSessions).toBe(true); + expect(options.auth).toHaveProperty('vercelOidc'); + + if (!options.auth || !('vercelOidc' in options.auth)) { + throw new Error('Expected Vercel OIDC auth.'); + } + + const token = options.auth.vercelOidc.token; + expect(typeof token === 'function' ? await token() : token).toBe( + 'oidc:default:default:default', + ); + expect(mocks.vercelOidc).toHaveBeenCalledWith({}); + }); + + it('can disable auth entirely', () => { + const options = resolveEveClientOptions({ + settings: { url: new URL('https://eve.test'), auth: 'none' }, + env: {}, + }); + + expect(options.host).toBe('https://eve.test/'); + expect(options.auth).toBeUndefined(); + expect(options.redirect).toBeUndefined(); + }); + + it('uses Eve Vercel OIDC auth for remote auto auth without an ambient token', async () => { + const options = resolveEveClientOptions({ + settings: { url: 'https://eve.test' }, + env: {}, + }); + + if (!options.auth || !('vercelOidc' in options.auth)) { + throw new Error('Expected Vercel OIDC auth.'); + } + + const token = options.auth.vercelOidc.token; + expect(typeof token === 'function' ? await token() : token).toBe( + 'oidc:default:default:default', + ); + }); + + it('allows local auto auth without an OIDC token', () => { + const options = resolveEveClientOptions({ + settings: { url: 'http://localhost:3000' }, + env: {}, + }); + + expect(options.auth).toBeUndefined(); + expect(options.redirect).toBeUndefined(); + }); + + it('adds the Vercel automation bypass header when available', async () => { + const options = resolveEveClientOptions({ + settings: { + url: 'https://eve.test', + auth: 'none', + headers: async () => ({ 'x-user-header': 'value' }), + }, + env: { + VERCEL_AUTOMATION_BYPASS_SECRET: ' bypass-secret ', + }, + }); + + const headers = + typeof options.headers === 'function' + ? await options.headers() + : options.headers; + + expect(headers).toEqual({ + 'x-vercel-protection-bypass': 'bypass-secret', + 'x-user-header': 'value', + }); + }); + + it('uses Eve Vercel OIDC auth options without an explicit token', async () => { + const options = resolveEveClientOptions({ + settings: { + url: 'https://eve.test', + auth: { + type: 'vercel-oidc', + team: 'team_test', + project: 'prj_test', + expirationBufferMs: 123, + }, + }, + env: {}, + }); + + if (!options.auth || !('vercelOidc' in options.auth)) { + throw new Error('Expected Vercel OIDC auth.'); + } + + const token = options.auth.vercelOidc.token; + expect(typeof token === 'function' ? await token() : token).toBe( + 'oidc:team_test:prj_test:123', + ); + expect(mocks.vercelOidc).toHaveBeenCalledWith({ + expirationBufferMs: 123, + project: 'prj_test', + team: 'team_test', + }); + }); + + it('passes explicit Vercel OIDC tokens through without Eve auth resolution', async () => { + const options = resolveEveClientOptions({ + settings: { + url: 'https://eve.test', + auth: { type: 'vercel-oidc', token: 'explicit-token' }, + }, + env: {}, + }); + + if (!options.auth || !('vercelOidc' in options.auth)) { + throw new Error('Expected Vercel OIDC auth.'); + } + + const token = options.auth.vercelOidc.token; + expect(typeof token === 'function' ? await token() : token).toBe( + 'explicit-token', + ); + expect(mocks.vercelOidc).not.toHaveBeenCalled(); + }); + + it('passes explicit bearer and basic auth through to the Eve client', () => { + expect( + resolveEveClientOptions({ + settings: { + url: 'https://eve.test', + auth: { type: 'bearer', token: 'bearer-token' }, + }, + }).auth, + ).toEqual({ bearer: 'bearer-token' }); + + expect( + resolveEveClientOptions({ + settings: { + url: 'https://eve.test', + auth: { + type: 'basic', + username: 'user', + password: 'password', + }, + }, + }).auth, + ).toEqual({ + basic: { + username: 'user', + password: 'password', + }, + }); + }); +}); diff --git a/packages/harness-eve/src/eve-auth.ts b/packages/harness-eve/src/eve-auth.ts new file mode 100644 index 000000000000..4aa16d742c26 --- /dev/null +++ b/packages/harness-eve/src/eve-auth.ts @@ -0,0 +1,192 @@ +import type { + ClientAuth, + ClientOptions, + ClientRedirectPolicy, + HeadersValue, + TokenValue, +} from 'eve/client'; +import { + vercelOidc as createEveVercelOidcAuth, + type VercelOidcOptions, +} from 'eve/agents/auth'; + +export type EveAuthOptions = + | 'auto' + | 'none' + | ({ + readonly type: 'vercel-oidc'; + readonly token?: TokenValue; + } & VercelOidcOptions) + | { readonly type: 'bearer'; readonly token: TokenValue } + | { + readonly type: 'basic'; + readonly username: string; + readonly password: TokenValue; + }; + +export type EveClientSettings = { + readonly url: string | URL; + readonly auth?: EveAuthOptions; + readonly headers?: HeadersValue; + readonly redirect?: ClientRedirectPolicy; + readonly maxReconnectAttempts?: number; + readonly preserveCompletedSessions?: boolean; +}; + +type Env = { + readonly VERCEL_AUTOMATION_BYPASS_SECRET?: string; + readonly VERCEL_OIDC_TOKEN?: string; +}; + +const VERCEL_PROTECTION_BYPASS_HEADER = 'x-vercel-protection-bypass'; + +export function resolveEveClientOptions({ + settings, + env = process.env, +}: { + readonly settings: EveClientSettings; + readonly env?: Env; +}): ClientOptions { + const host = + typeof settings.url === 'string' ? settings.url : settings.url.toString(); + const auth = resolveEveAuth({ auth: settings.auth, env, host }); + const headers = resolveEveHeaders({ env, headers: settings.headers }); + return { + host, + ...(auth ? { auth } : {}), + ...(headers ? { headers } : {}), + ...(settings.redirect || auth + ? { redirect: settings.redirect ?? 'manual' } + : {}), + ...(settings.maxReconnectAttempts != null + ? { maxReconnectAttempts: settings.maxReconnectAttempts } + : {}), + preserveCompletedSessions: settings.preserveCompletedSessions ?? true, + }; +} + +function resolveEveHeaders({ + env, + headers, +}: { + readonly env: Env; + readonly headers?: HeadersValue; +}): HeadersValue | undefined { + const bypassSecret = env.VERCEL_AUTOMATION_BYPASS_SECRET?.trim(); + if (!bypassSecret) { + return headers; + } + + const bypassHeaders = { + [VERCEL_PROTECTION_BYPASS_HEADER]: bypassSecret, + }; + + if (headers == null) { + return bypassHeaders; + } + + return async () => ({ + ...bypassHeaders, + ...(typeof headers === 'function' ? await headers() : headers), + }); +} + +function resolveEveAuth({ + auth = 'auto', + env, + host, +}: { + readonly auth: EveAuthOptions | undefined; + readonly env: Env; + readonly host: string; +}): ClientAuth | undefined { + if (auth === 'none') { + return undefined; + } + + if (auth === 'auto') { + if (!env.VERCEL_OIDC_TOKEN) { + if (isLocalUrl({ url: host })) { + return undefined; + } + } + + return { + vercelOidc: { + token: createVercelOidcTokenResolver({}), + }, + }; + } + + switch (auth.type) { + case 'vercel-oidc': + return { + vercelOidc: { + token: auth.token ?? createVercelOidcTokenResolver({ options: auth }), + }, + }; + case 'bearer': + return { bearer: auth.token }; + case 'basic': + return { + basic: { + username: auth.username, + password: auth.password, + }, + }; + } +} + +function createVercelOidcTokenResolver({ + options = {}, +}: { + readonly options?: VercelOidcOptions; +}): () => Promise { + const auth = createEveVercelOidcAuth({ + ...(options.expirationBufferMs !== undefined + ? { expirationBufferMs: options.expirationBufferMs } + : {}), + ...(options.project !== undefined ? { project: options.project } : {}), + ...(options.team !== undefined ? { team: options.team } : {}), + }); + + return async () => { + const { headers } = await auth(); + const authorization = findHeader({ + headers, + name: 'authorization', + })?.trim(); + const match = /^Bearer\s+(.+)$/iu.exec(authorization ?? ''); + if (!match) { + throw new Error('Eve Vercel OIDC auth did not return a bearer token.'); + } + return match[1]!.trim(); + }; +} + +function findHeader({ + headers, + name, +}: { + readonly headers: Readonly>; + readonly name: string; +}): string | undefined { + const normalizedName = name.toLowerCase(); + for (const [key, value] of Object.entries(headers)) { + if (key.toLowerCase() === normalizedName) { + return value; + } + } + return undefined; +} + +function isLocalUrl({ url }: { readonly url: string }): boolean { + try { + const { hostname } = new URL(url); + return ( + hostname === 'localhost' || hostname === '127.0.0.1' || hostname === '::1' + ); + } catch { + return false; + } +} diff --git a/packages/harness-eve/src/eve-harness.test.ts b/packages/harness-eve/src/eve-harness.test.ts new file mode 100644 index 000000000000..b8d3e79bbe90 --- /dev/null +++ b/packages/harness-eve/src/eve-harness.test.ts @@ -0,0 +1,669 @@ +import { HarnessCapabilityUnsupportedError } from '@ai-sdk/harness'; +import { afterEach, describe, expect, it, vi } from 'vitest'; +import * as index from './index'; +import { createEve } from './eve-harness'; + +describe('createEve adapter', () => { + afterEach(() => { + vi.unstubAllGlobals(); + }); + + it('exports only a factory and declares Eve built-in tools', () => { + expect(index).toHaveProperty('createEve'); + expect(index).not.toHaveProperty('eve'); + + const harness = createEve({ url: 'https://eve.test' }); + expect(harness.harnessId).toBe('eve'); + expect(harness.specificationVersion).toBe('harness-v1'); + expect(harness.supportsBuiltinToolApprovals).toBe(true); + expect(harness.getBootstrap).toBeUndefined(); + expect(Object.keys(harness.builtinTools).sort()).toEqual([ + 'agent', + 'bash', + 'connection_search', + 'glob', + 'grep', + 'load_skill', + 'read', + 'todo', + 'webSearch', + 'web_fetch', + 'write', + ]); + expect(harness.builtinTools.read.nativeName).toBe('read_file'); + expect(harness.builtinTools.write.nativeName).toBe('write_file'); + expect(harness.builtinTools.webSearch.nativeName).toBe('web_search'); + expect(harness.builtinTools).not.toHaveProperty('ask_question'); + }); + + it('streams text, reasoning, usage, and finish events from Eve', async () => { + const mock = mockEveFetch({ + streams: [ + [ + sessionStartedEvent(), + { + type: 'reasoning.appended', + data: { + reasoningDelta: 'thinking', + reasoningSoFar: 'thinking', + sequence: 1, + stepIndex: 0, + turnId: 'turn-1', + }, + }, + { + type: 'reasoning.completed', + data: { + reasoning: 'thinking', + sequence: 2, + stepIndex: 0, + turnId: 'turn-1', + }, + }, + { + type: 'message.appended', + data: { + messageDelta: 'Hello', + messageSoFar: 'Hello', + sequence: 3, + stepIndex: 0, + turnId: 'turn-1', + }, + }, + { + type: 'message.completed', + data: { + finishReason: 'stop', + message: 'Hello world', + sequence: 4, + stepIndex: 0, + turnId: 'turn-1', + }, + }, + { + type: 'step.completed', + data: { + finishReason: 'stop', + sequence: 5, + stepIndex: 0, + turnId: 'turn-1', + usage: { + inputTokens: 10, + outputTokens: 2, + cacheReadTokens: 3, + cacheWriteTokens: 4, + }, + }, + }, + { type: 'session.completed' }, + ], + ], + }); + + const session = await createStartedSession(); + const parts: unknown[] = []; + const control = await session.doPromptTurn({ + prompt: 'hello', + instructions: 'be concise', + emit: part => parts.push(part), + }); + await control.done; + + expect(mock.postBodies[0]).toEqual({ + message: 'hello', + clientContext: 'be concise', + }); + expect(parts).toMatchInlineSnapshot(` + [ + { + "modelId": "anthropic/claude-sonnet-4-6", + "type": "stream-start", + }, + { + "id": "eve-reasoning-0-1", + "type": "reasoning-start", + }, + { + "delta": "thinking", + "id": "eve-reasoning-0-1", + "type": "reasoning-delta", + }, + { + "id": "eve-reasoning-0-1", + "type": "reasoning-end", + }, + { + "id": "eve-text-0-1", + "type": "text-start", + }, + { + "delta": "Hello", + "id": "eve-text-0-1", + "type": "text-delta", + }, + { + "delta": " world", + "id": "eve-text-0-1", + "type": "text-delta", + }, + { + "id": "eve-text-0-1", + "type": "text-end", + }, + { + "finishReason": { + "raw": "stop", + "unified": "stop", + }, + "type": "finish-step", + "usage": { + "inputTokens": { + "cacheRead": 3, + "cacheWrite": 4, + "noCache": 3, + "total": 10, + }, + "outputTokens": { + "reasoning": undefined, + "text": undefined, + "total": 2, + }, + "raw": { + "cacheReadTokens": 3, + "cacheWriteTokens": 4, + "inputTokens": 10, + "outputTokens": 2, + }, + }, + }, + { + "finishReason": { + "raw": "stop", + "unified": "stop", + }, + "totalUsage": { + "inputTokens": { + "cacheRead": 3, + "cacheWrite": 4, + "noCache": 3, + "total": 10, + }, + "outputTokens": { + "reasoning": undefined, + "text": undefined, + "total": 2, + }, + "raw": {}, + }, + "type": "finish", + }, + ] + `); + }); + + it('marks agent-specific Eve tools as dynamic provider-executed calls', async () => { + mockEveFetch({ + streams: [ + [ + sessionStartedEvent(), + { + type: 'actions.requested', + data: { + actions: [ + { + kind: 'tool-call', + callId: 'call-1', + toolName: 'project_tool', + input: { task: 'inspect' }, + }, + ], + sequence: 1, + stepIndex: 0, + turnId: 'turn-1', + }, + }, + { + type: 'action.result', + data: { + result: { + kind: 'tool-result', + callId: 'call-1', + toolName: 'project_tool', + output: { ok: true }, + }, + sequence: 2, + stepIndex: 0, + status: 'completed', + turnId: 'turn-1', + }, + }, + { + type: 'step.completed', + data: { + finishReason: 'tool-calls', + sequence: 3, + stepIndex: 0, + turnId: 'turn-1', + }, + }, + { type: 'session.completed' }, + ], + ], + }); + + const session = await createStartedSession(); + const parts: unknown[] = []; + const control = await session.doPromptTurn({ + prompt: 'use the project tool', + emit: part => parts.push(part), + }); + await control.done; + + expect(parts).toContainEqual({ + type: 'tool-call', + toolCallId: 'call-1', + toolName: 'project_tool', + input: JSON.stringify({ task: 'inspect' }), + providerExecuted: true, + dynamic: true, + }); + expect(parts).toContainEqual({ + type: 'tool-result', + toolCallId: 'call-1', + toolName: 'project_tool', + result: { ok: true }, + dynamic: true, + }); + }); + + it('supports Eve confirmation approvals through inputResponses', async () => { + const mock = mockEveFetch({ + streams: [ + [ + sessionStartedEvent(), + { + type: 'actions.requested', + data: { + actions: [ + { + kind: 'tool-call', + callId: 'call-1', + toolName: 'bash', + input: { command: 'pwd' }, + }, + ], + sequence: 1, + stepIndex: 0, + turnId: 'turn-1', + }, + }, + { + type: 'input.requested', + data: { + requests: [ + { + action: { + kind: 'tool-call', + callId: 'call-1', + toolName: 'bash', + input: { command: 'pwd' }, + }, + display: 'confirmation', + options: [ + { id: 'approve', label: 'Approve' }, + { id: 'deny', label: 'Deny' }, + ], + prompt: 'Run command?', + requestId: 'approval-1', + }, + ], + sequence: 2, + stepIndex: 0, + turnId: 'turn-1', + }, + }, + { + type: 'step.completed', + data: { + finishReason: 'tool-calls', + sequence: 3, + stepIndex: 0, + turnId: 'turn-1', + }, + }, + { + type: 'session.waiting', + data: { wait: 'next-user-message' }, + }, + ], + [ + { + type: 'action.result', + data: { + result: { + kind: 'tool-result', + callId: 'call-1', + toolName: 'bash', + output: '/workspace', + }, + sequence: 4, + stepIndex: 0, + status: 'completed', + turnId: 'turn-1', + }, + }, + { + type: 'message.completed', + data: { + finishReason: 'stop', + message: 'The working directory is /workspace.', + sequence: 5, + stepIndex: 1, + turnId: 'turn-1', + }, + }, + { + type: 'step.completed', + data: { + finishReason: 'stop', + sequence: 6, + stepIndex: 1, + turnId: 'turn-1', + }, + }, + { type: 'session.completed' }, + ], + ], + }); + + const session = await createStartedSession(); + const firstParts: unknown[] = []; + const first = await session.doPromptTurn({ + prompt: 'run pwd', + emit: part => firstParts.push(part), + }); + await first.done; + + expect(firstParts).toContainEqual({ + type: 'tool-approval-request', + approvalId: 'approval-1', + toolCallId: 'call-1', + }); + + const state = await session.doSuspendTurn(); + const resumed = await createStartedSession({ + continueFrom: { + ...state, + pendingToolApprovals: [ + { + approvalId: 'approval-1', + toolCallId: 'call-1', + toolName: 'bash', + input: JSON.stringify({ command: 'pwd' }), + kind: 'builtin', + providerExecuted: true, + }, + ], + }, + }); + const secondParts: unknown[] = []; + const second = await resumed.doContinueTurn({ + emit: part => secondParts.push(part), + }); + + await second.submitToolApproval?.({ + approvalId: 'approval-1', + approved: true, + reason: 'ok', + }); + await second.done; + + expect(mock.postBodies[1]).toEqual({ + continuationToken: 'continuation-1', + inputResponses: [ + { + requestId: 'approval-1', + optionId: 'approve', + text: 'ok', + }, + ], + }); + expect(secondParts).toContainEqual({ + type: 'tool-result', + toolCallId: 'call-1', + toolName: 'bash', + result: '/workspace', + }); + }); + + it('throws for unsupported custom tools, skills, and ask_question requests', async () => { + mockEveFetch({ + streams: [ + [ + sessionStartedEvent(), + { + type: 'actions.requested', + data: { + actions: [ + { + kind: 'tool-call', + callId: 'call-1', + toolName: 'ask_question', + input: { question: 'Proceed?' }, + }, + ], + sequence: 1, + stepIndex: 0, + turnId: 'turn-1', + }, + }, + ], + [ + sessionStartedEvent(), + { + type: 'input.requested', + data: { + requests: [ + { + action: { + kind: 'tool-call', + callId: 'call-1', + toolName: 'ask_question', + input: { question: 'Proceed?' }, + }, + display: 'select', + prompt: 'Proceed?', + requestId: 'question-1', + }, + ], + sequence: 1, + stepIndex: 0, + turnId: 'turn-1', + }, + }, + ], + ], + }); + + const harness = createEve({ url: 'https://eve.test' }); + await expect( + harness.doStart({ + sessionId: 'session-1', + sandboxSession: {} as never, + sessionWorkDir: '/tmp/eve', + skills: [{ name: 'skill', content: 'content' }] as never, + }), + ).rejects.toSatisfy(HarnessCapabilityUnsupportedError.isInstance); + + await expect( + harness.doStart({ + sessionId: 'session-1', + sandboxSession: {} as never, + sessionWorkDir: '/tmp/eve', + ...({ + builtinToolFiltering: { mode: 'deny', toolNames: ['bash'] }, + } as { readonly builtinToolFiltering: unknown }), + }), + ).rejects.toSatisfy(HarnessCapabilityUnsupportedError.isInstance); + + const session = await createStartedSession(); + await expect( + session.doPromptTurn({ + prompt: 'hello', + tools: [{ name: 'custom' }], + emit: () => {}, + }), + ).rejects.toSatisfy(HarnessCapabilityUnsupportedError.isInstance); + + const control = await session.doPromptTurn({ + prompt: 'ask', + emit: () => {}, + }); + await expect(control.done).rejects.toSatisfy( + HarnessCapabilityUnsupportedError.isInstance, + ); + + const next = await session.doPromptTurn({ + prompt: 'ask again', + emit: () => {}, + }); + await expect(next.done).rejects.toSatisfy( + HarnessCapabilityUnsupportedError.isInstance, + ); + }); +}); + +async function createStartedSession( + options: { + readonly continueFrom?: Parameters< + ReturnType['doStart'] + >[0]['continueFrom']; + } = {}, +) { + return await createEve({ url: 'https://eve.test', auth: 'none' }).doStart({ + sessionId: 'session-1', + sandboxSession: {} as never, + sessionWorkDir: '/tmp/eve', + ...(options.continueFrom ? { continueFrom: options.continueFrom } : {}), + }); +} + +function mockEveFetch({ + streams, +}: { + readonly streams: ReadonlyArray>>; +}): { + readonly postBodies: Array>; +} { + const postBodies: Array> = []; + const remainingStreams = [...streams]; + + vi.stubGlobal( + 'fetch', + vi.fn(async (input: RequestInfo | URL, init?: RequestInit) => { + const url = new URL(String(input)); + if (url.pathname === '/eve/v1/info') { + return jsonResponse(createAgentInfo()); + } + if ( + init?.method === 'POST' && + (url.pathname === '/eve/v1/session' || + url.pathname === '/eve/v1/session/eve-session-1') + ) { + postBodies.push(init.body == null ? {} : JSON.parse(String(init.body))); + return jsonResponse({ + sessionId: 'eve-session-1', + continuationToken: `continuation-${postBodies.length}`, + }); + } + if (url.pathname === '/eve/v1/session/eve-session-1/stream') { + const events = remainingStreams.shift() ?? []; + return new Response( + events.map(event => JSON.stringify(event)).join('\n'), + ); + } + return new Response(`Unhandled ${url.pathname}`, { status: 404 }); + }), + ); + + return { postBodies }; +} + +function jsonResponse(body: unknown): Response { + return new Response(JSON.stringify(body), { + headers: { 'content-type': 'application/json' }, + }); +} + +function sessionStartedEvent(): Record { + return { + type: 'session.started', + data: { + runtime: { + agentId: 'agent-1', + agentName: 'Eve test agent', + eveVersion: '0.17.1', + modelId: 'anthropic/claude-sonnet-4-6', + }, + }, + }; +} + +function createAgentInfo(): Record { + return { + agent: { + agentRoot: '/agent', + appRoot: '/app', + model: { + id: 'anthropic/claude-sonnet-4-6', + }, + name: 'Eve test agent', + }, + capabilities: { devRoutes: false }, + channels: { + authored: [], + available: [], + disabledFramework: [], + framework: [], + }, + connections: [], + diagnostics: { + discoveryErrors: 0, + discoveryWarnings: 0, + }, + hooks: [], + instructions: { + dynamic: [], + static: null, + }, + kind: 'eve-agent-info', + mode: 'production', + sandbox: null, + schedules: [], + skills: { + dynamic: [], + static: [], + }, + subagents: { + local: [], + total: 0, + }, + tools: { + authored: [], + available: [], + disabledFramework: [], + dynamic: [], + framework: [], + reserved: [], + }, + version: 1, + workflow: { + enabled: true, + toolName: 'agent', + }, + workspace: { + resourceRoot: null, + rootEntries: [], + }, + }; +} diff --git a/packages/harness-eve/src/eve-harness.ts b/packages/harness-eve/src/eve-harness.ts new file mode 100644 index 000000000000..8cee05a13770 --- /dev/null +++ b/packages/harness-eve/src/eve-harness.ts @@ -0,0 +1,1044 @@ +import { + commonTool, + HarnessCapabilityUnsupportedError, + type HarnessV1, + type HarnessV1BuiltinTool, + type HarnessV1ContinueTurnState, + type HarnessV1Prompt, + type HarnessV1PromptControl, + type HarnessV1ResumeSessionState, + type HarnessV1Session, + type HarnessV1StreamPart, + type HarnessV1ToolSpec, +} from '@ai-sdk/harness'; +import type { + JSONValue, + LanguageModelV4FinishReason, + LanguageModelV4Usage, +} from '@ai-sdk/provider'; +import { tool } from '@ai-sdk/provider-utils'; +import { + Client, + type ActionResultStreamEvent, + type ActionsRequestedStreamEvent, + type AgentInfoResult, + type AssistantStepFinishReason, + type ClientSession, + type HandleMessageStreamEvent, + type InputRequest, + type InputResponse, + type SendTurnPayload, + type SessionState, + type StepCompletedStreamEvent, +} from 'eve/client'; +import { z } from 'zod/v4'; +import { resolveEveClientOptions, type EveClientSettings } from './eve-auth'; + +export type EveHarnessSettings = EveClientSettings; + +const optionalUnknownRecord = z.record(z.string(), z.unknown()).optional(); + +const EVE_BUILTIN_TOOLS = { + read: commonTool('read', { + nativeName: 'read_file', + toolUseKind: 'readonly', + description: 'Read file contents.', + inputSchema: z.looseObject({ + file_path: z.string().optional(), + path: z.string().optional(), + }), + }), + write: commonTool('write', { + nativeName: 'write_file', + toolUseKind: 'edit', + description: 'Write content to a file.', + inputSchema: z.looseObject({ + file_path: z.string().optional(), + path: z.string().optional(), + content: z.string().optional(), + }), + }), + bash: commonTool('bash', { + nativeName: 'bash', + toolUseKind: 'bash', + description: 'Execute a shell command.', + inputSchema: z.looseObject({ + command: z.string().optional(), + timeout: z.number().optional(), + }), + }), + grep: commonTool('grep', { + nativeName: 'grep', + toolUseKind: 'readonly', + description: 'Search file contents.', + inputSchema: z.looseObject({ + pattern: z.string().optional(), + path: z.string().optional(), + glob: z.string().optional(), + include: z.string().optional(), + exclude: z.string().optional(), + maxResults: z.number().optional(), + }), + }), + glob: commonTool('glob', { + nativeName: 'glob', + toolUseKind: 'readonly', + description: 'Find files matching a glob pattern.', + inputSchema: z.looseObject({ + pattern: z.string().optional(), + path: z.string().optional(), + maxResults: z.number().optional(), + }), + }), + webSearch: commonTool('webSearch', { + nativeName: 'web_search', + toolUseKind: 'readonly', + description: 'Search the web.', + inputSchema: z.looseObject({ + query: z.string().optional(), + maxResults: z.number().optional(), + }), + }), + web_fetch: tool({ + description: 'Fetch and inspect web content.', + inputSchema: z.looseObject({ + url: z.string().optional(), + prompt: z.string().optional(), + }), + }), + todo: tool({ + description: 'Manage the Eve agent todo list.', + inputSchema: z.looseObject({ + todos: z.array(z.unknown()).optional(), + }), + }), + agent: tool({ + description: 'Delegate work to an Eve subagent.', + inputSchema: z.looseObject({ + name: z.string().optional(), + agent: z.string().optional(), + prompt: z.string().optional(), + description: z.string().optional(), + input: optionalUnknownRecord, + }), + }), + load_skill: tool({ + description: 'Load an Eve skill into the active turn.', + inputSchema: z.looseObject({ + name: z.string().optional(), + skill: z.string().optional(), + }), + }), + connection_search: tool({ + description: 'Search an Eve connection.', + inputSchema: z.looseObject({ + query: z.string().optional(), + connection: z.string().optional(), + name: z.string().optional(), + }), + }), +} as const satisfies Record>; + +const eveResumeStateSchema = z.object({ + eveSession: z.object({ + continuationToken: z.string().optional(), + sessionId: z.string().optional(), + streamIndex: z.number(), + }), +}); + +type EveResumeState = z.infer; + +const EVE_NATIVE_TO_WIRE_TOOL_NAMES = new Map([ + ['read_file', 'read'], + ['write_file', 'write'], + ['web_search', 'webSearch'], + ['load-skill', 'load_skill'], + ['load_skill', 'load_skill'], + ['subagent-call', 'agent'], + ['remote-agent-call', 'agent'], +]); + +const EVE_STATIC_BUILTIN_TOOL_NAMES = new Set(Object.keys(EVE_BUILTIN_TOOLS)); + +export function createEve( + settings: EveHarnessSettings, +): HarnessV1 { + return { + specificationVersion: 'harness-v1', + harnessId: 'eve', + builtinTools: EVE_BUILTIN_TOOLS, + supportsBuiltinToolApprovals: true, + lifecycleStateSchema: eveResumeStateSchema, + doStart: async startOpts => { + startOpts.abortSignal?.throwIfAborted(); + assertNoSkills({ + skills: startOpts.skills ?? [], + }); + assertNoBuiltinToolFiltering({ + builtinToolFiltering: ( + startOpts as { + readonly builtinToolFiltering?: unknown; + } + ).builtinToolFiltering, + }); + + const lifecycleState = startOpts.continueFrom ?? startOpts.resumeFrom; + const client = new Client(resolveEveClientOptions({ settings })); + const info = await client.info(); + startOpts.abortSignal?.throwIfAborted(); + + const session = client.session( + readEveSessionState({ data: lifecycleState?.data }), + ); + + return createEveSession({ + agentInfo: info, + clientSession: session, + continueFrom: startOpts.continueFrom, + isResume: lifecycleState != null, + sessionId: startOpts.sessionId, + }); + }, + }; +} + +function createEveSession({ + agentInfo, + clientSession, + continueFrom, + isResume, + sessionId, +}: { + readonly agentInfo: AgentInfoResult; + readonly clientSession: ClientSession; + readonly continueFrom?: HarnessV1ContinueTurnState; + readonly isResume: boolean; + readonly sessionId: string; +}): HarnessV1Session { + const modelId = agentInfo.agent.model.id; + const pendingContinueApprovalIds = new Set( + continueFrom?.pendingToolApprovals?.map(approval => approval.approvalId) ?? + [], + ); + + let activeAbort: ((reason: unknown) => void) | undefined; + let activeDone: Promise | undefined; + let suspended = false; + + const lifecycleState = ( + type: 'continue-turn' | 'resume-session', + ): HarnessV1ContinueTurnState | HarnessV1ResumeSessionState => ({ + type, + harnessId: 'eve', + specificationVersion: 'harness-v1', + data: createEveResumeState({ state: clientSession.state }), + }); + + const createControl = ({ + abortSignal, + emit, + expectedApprovalIds, + initialPayload, + replayCurrentStream, + }: { + readonly abortSignal?: AbortSignal; + readonly emit: (event: HarnessV1StreamPart) => void; + readonly expectedApprovalIds?: ReadonlySet; + readonly initialPayload?: SendTurnPayload; + readonly replayCurrentStream?: boolean; + }): HarnessV1PromptControl => { + const translator = createEveStreamTranslator({ modelId }); + const expected = expectedApprovalIds ?? new Set(); + const approvalResponses = new Map(); + + let emittedStreamStart = false; + let doneSettled = false; + let pendingJobs = 0; + let waitingForApprovals = expected.size > 0; + let approvalSendTriggered = false; + let tail = Promise.resolve(); + let resolveDone!: () => void; + let rejectDone!: (error: unknown) => void; + const done = new Promise((resolve, reject) => { + resolveDone = resolve; + rejectDone = reject; + }); + + const emitStreamStart = (): void => { + if (emittedStreamStart) return; + emittedStreamStart = true; + emit({ type: 'stream-start', modelId }); + }; + + const settleIfIdle = (): void => { + if (!doneSettled && pendingJobs === 0 && !waitingForApprovals) { + doneSettled = true; + resolveDone(); + } + }; + + const fail = (error: unknown): void => { + if (doneSettled) return; + doneSettled = true; + rejectDone(error); + }; + + const enqueue = (work: () => Promise): Promise => { + pendingJobs += 1; + const run = tail.then(work); + tail = run.then( + () => {}, + error => { + fail(error); + }, + ); + void run + .finally(() => { + pendingJobs -= 1; + settleIfIdle(); + }) + .catch(() => {}); + return run; + }; + + const consumeIterable = async ( + iterable: AsyncIterable, + ): Promise => { + emitStreamStart(); + const abort = createLinkedAbortController({ abortSignal }); + activeAbort = reason => { + suspended = true; + abort.controller.abort(reason); + }; + try { + for await (const event of iterableWithAbort({ + iterable, + signal: abort.signal, + })) { + for (const part of translator.translate({ event })) { + emit(part); + } + } + } catch (error) { + if (!suspended || !isAbortError({ error })) { + throw error; + } + } finally { + abort.cleanup(); + if (activeAbort != null) { + activeAbort = undefined; + } + } + }; + + const sendAndConsume = async (payload: SendTurnPayload): Promise => { + const abort = createLinkedAbortController({ abortSignal }); + activeAbort = reason => { + suspended = true; + abort.controller.abort(reason); + }; + try { + emitStreamStart(); + const response = await clientSession.send({ + ...payload, + signal: abort.signal, + }); + await consumeIterable(response); + } catch (error) { + if (!suspended || !isAbortError({ error })) { + throw error; + } + } finally { + abort.cleanup(); + if (activeAbort != null) { + activeAbort = undefined; + } + } + }; + + if (initialPayload != null) { + activeDone = enqueue(() => sendAndConsume(initialPayload)); + } else if (replayCurrentStream) { + activeDone = enqueue(() => consumeIterable(clientSession.stream())); + } else { + settleIfIdle(); + } + + return { + submitToolResult: async () => { + unsupported( + 'custom host tool results because Eve agents define and execute their own tools', + ); + }, + submitToolApproval: async input => { + const response: InputResponse = { + requestId: input.approvalId, + optionId: input.approved ? 'approve' : 'deny', + ...(input.reason ? { text: input.reason } : {}), + }; + + if (expected.size === 0) { + activeDone = enqueue(() => + sendAndConsume({ inputResponses: [response] }), + ); + return; + } + + if (!expected.has(input.approvalId)) { + unsupported( + `tool approval '${input.approvalId}' because it is not pending in the Eve session`, + ); + } + + approvalResponses.set(input.approvalId, response); + if ( + !approvalSendTriggered && + approvalResponses.size === expected.size + ) { + approvalSendTriggered = true; + waitingForApprovals = false; + activeDone = enqueue(() => + sendAndConsume({ + inputResponses: [...approvalResponses.values()], + }), + ); + } + }, + done, + }; + }; + + return { + sessionId, + isResume, + modelId, + doPromptTurn: async options => { + assertNoHostTools({ tools: options.tools ?? [] }); + const payload = promptToEvePayload({ + instructions: options.instructions, + prompt: options.prompt, + }); + return createControl({ + abortSignal: options.abortSignal, + emit: options.emit, + initialPayload: payload, + }); + }, + doCompact: async () => { + unsupported('manual compaction'); + }, + doContinueTurn: async options => { + assertNoHostTools({ tools: options.tools ?? [] }); + const hasPendingApprovals = pendingContinueApprovalIds.size > 0; + return createControl({ + abortSignal: options.abortSignal, + emit: options.emit, + expectedApprovalIds: pendingContinueApprovalIds, + replayCurrentStream: !hasPendingApprovals, + }); + }, + doSuspendTurn: async () => { + if (activeAbort != null) { + activeAbort(new DOMException('Suspended', 'AbortError')); + } + await activeDone?.catch(() => {}); + return lifecycleState('continue-turn') as HarnessV1ContinueTurnState; + }, + doDetach: async () => { + return lifecycleState('resume-session') as HarnessV1ResumeSessionState; + }, + doStop: async () => { + return lifecycleState('resume-session') as HarnessV1ResumeSessionState; + }, + doDestroy: async () => {}, + }; +} + +function createEveStreamTranslator({ modelId }: { readonly modelId: string }): { + translate(input: { + readonly event: HandleMessageStreamEvent; + }): HarnessV1StreamPart[]; +} { + const observedToolNames = new Map(); + let textBlock: + | { readonly id: string; readonly stepIndex: number; soFar: string } + | undefined; + let reasoningBlock: + | { readonly id: string; readonly stepIndex: number; soFar: string } + | undefined; + let nextTextId = 0; + let nextReasoningId = 0; + let lastFinishReason = mapFinishReason({ raw: 'stop' }); + let totalUsage = emptyUsage(); + let sawInputRequest = false; + + const closeText = (): HarnessV1StreamPart[] => { + if (!textBlock) return []; + const part: HarnessV1StreamPart = { type: 'text-end', id: textBlock.id }; + textBlock = undefined; + return [part]; + }; + + const closeReasoning = (): HarnessV1StreamPart[] => { + if (!reasoningBlock) return []; + const part: HarnessV1StreamPart = { + type: 'reasoning-end', + id: reasoningBlock.id, + }; + reasoningBlock = undefined; + return [part]; + }; + + const closeBlocks = (): HarnessV1StreamPart[] => [ + ...closeReasoning(), + ...closeText(), + ]; + + return { + translate({ event }) { + switch (event.type) { + case 'session.started': + return []; + + case 'turn.started': + case 'message.received': + case 'step.started': + case 'turn.completed': + case 'result.completed': + case 'compaction.requested': + return []; + + case 'message.appended': { + const parts: HarnessV1StreamPart[] = closeReasoning(); + if (!textBlock || textBlock.stepIndex !== event.data.stepIndex) { + textBlock = { + id: `eve-text-${event.data.stepIndex}-${++nextTextId}`, + stepIndex: event.data.stepIndex, + soFar: '', + }; + parts.push({ type: 'text-start', id: textBlock.id }); + } + textBlock.soFar = event.data.messageSoFar; + parts.push({ + type: 'text-delta', + id: textBlock.id, + delta: event.data.messageDelta, + }); + return parts; + } + + case 'message.completed': { + const parts: HarnessV1StreamPart[] = []; + const message = event.data.message ?? ''; + lastFinishReason = mapFinishReason({ raw: event.data.finishReason }); + if (!message) { + parts.push(...closeText()); + return parts; + } + if (!textBlock) { + const id = `eve-text-${event.data.stepIndex}-${++nextTextId}`; + return [ + { type: 'text-start', id }, + { type: 'text-delta', id, delta: message }, + { type: 'text-end', id }, + ]; + } + if (message.startsWith(textBlock.soFar)) { + const missing = message.slice(textBlock.soFar.length); + if (missing.length > 0) { + parts.push({ + type: 'text-delta', + id: textBlock.id, + delta: missing, + }); + } + } + parts.push(...closeText()); + return parts; + } + + case 'reasoning.appended': { + const parts: HarnessV1StreamPart[] = closeText(); + if ( + !reasoningBlock || + reasoningBlock.stepIndex !== event.data.stepIndex + ) { + reasoningBlock = { + id: `eve-reasoning-${event.data.stepIndex}-${++nextReasoningId}`, + stepIndex: event.data.stepIndex, + soFar: '', + }; + parts.push({ + type: 'reasoning-start', + id: reasoningBlock.id, + }); + } + reasoningBlock.soFar = event.data.reasoningSoFar; + parts.push({ + type: 'reasoning-delta', + id: reasoningBlock.id, + delta: event.data.reasoningDelta, + }); + return parts; + } + + case 'reasoning.completed': { + const parts: HarnessV1StreamPart[] = []; + if (!reasoningBlock) { + const id = `eve-reasoning-${event.data.stepIndex}-${++nextReasoningId}`; + return [ + { type: 'reasoning-start', id }, + { type: 'reasoning-delta', id, delta: event.data.reasoning }, + { type: 'reasoning-end', id }, + ]; + } + if (event.data.reasoning.startsWith(reasoningBlock.soFar)) { + const missing = event.data.reasoning.slice( + reasoningBlock.soFar.length, + ); + if (missing.length > 0) { + parts.push({ + type: 'reasoning-delta', + id: reasoningBlock.id, + delta: missing, + }); + } + } + parts.push(...closeReasoning()); + return parts; + } + + case 'actions.requested': + return translateActionsRequested({ + event, + observedToolNames, + }); + + case 'input.requested': { + sawInputRequest = true; + return translateInputRequested({ + event, + observedToolNames, + }); + } + + case 'action.result': + return translateActionResult({ + event, + observedToolNames, + }); + + case 'step.completed': { + totalUsage = addUsage({ + left: totalUsage, + right: mapStepUsage({ event }), + }); + lastFinishReason = mapFinishReason({ raw: event.data.finishReason }); + return [ + ...closeBlocks(), + { + type: 'finish-step', + finishReason: lastFinishReason, + usage: mapStepUsage({ event }), + }, + ]; + } + + case 'compaction.completed': + return [ + { + type: 'compaction', + trigger: 'auto', + summary: `Eve compacted the remote session with ${modelId}.`, + }, + ]; + + case 'session.waiting': + case 'session.completed': + return [ + ...closeBlocks(), + { + type: 'finish', + finishReason: + event.type === 'session.waiting' && sawInputRequest + ? mapFinishReason({ raw: 'tool-calls' }) + : lastFinishReason, + totalUsage, + }, + ]; + + case 'authorization.required': + unsupported('Eve connection authorization requests'); + + case 'authorization.completed': + return []; + + case 'subagent.called': + case 'subagent.started': + case 'subagent.event': + case 'subagent.completed': + return []; + + case 'step.failed': + case 'turn.failed': + case 'session.failed': + throw new Error(event.data.message); + } + }, + }; +} + +function translateActionsRequested({ + event, + observedToolNames, +}: { + readonly event: ActionsRequestedStreamEvent; + readonly observedToolNames: Map; +}): HarnessV1StreamPart[] { + return event.data.actions.map(action => { + const nativeName = + action.kind === 'tool-call' + ? action.toolName + : action.kind === 'load-skill' + ? 'load_skill' + : action.kind; + if (nativeName === 'ask_question') { + unsupported('Eve ask_question tool calls'); + } + const { dynamic, wireName } = resolveEveToolName({ nativeName }); + observedToolNames.set(action.callId, wireName); + return { + type: 'tool-call', + toolCallId: action.callId, + toolName: wireName, + input: JSON.stringify('input' in action ? action.input : {}), + providerExecuted: true, + ...(dynamic ? { dynamic: true } : {}), + ...(nativeName !== wireName ? { nativeName } : {}), + }; + }); +} + +function translateInputRequested({ + event, + observedToolNames, +}: { + readonly event: { + readonly data: { readonly requests: readonly InputRequest[] }; + readonly type: 'input.requested'; + }; + readonly observedToolNames: Map; +}): HarnessV1StreamPart[] { + const parts: HarnessV1StreamPart[] = []; + + for (const request of event.data.requests) { + if (request.action.toolName === 'ask_question') { + unsupported('Eve ask_question input requests'); + } + if (request.display !== 'confirmation') { + unsupported(`Eve '${request.display ?? 'unknown'}' input requests`); + } + + if (!observedToolNames.has(request.action.callId)) { + const { dynamic, wireName } = resolveEveToolName({ + nativeName: request.action.toolName, + }); + observedToolNames.set(request.action.callId, wireName); + parts.push({ + type: 'tool-call', + toolCallId: request.action.callId, + toolName: wireName, + input: JSON.stringify(request.action.input), + providerExecuted: true, + ...(dynamic ? { dynamic: true } : {}), + ...(request.action.toolName !== wireName + ? { nativeName: request.action.toolName } + : {}), + }); + } + + parts.push({ + type: 'tool-approval-request', + approvalId: request.requestId, + toolCallId: request.action.callId, + }); + } + + return parts; +} + +function translateActionResult({ + event, + observedToolNames, +}: { + readonly event: ActionResultStreamEvent; + readonly observedToolNames: Map; +}): HarnessV1StreamPart[] { + const result = event.data.result; + const nativeName = + result.kind === 'tool-result' + ? result.toolName + : result.kind === 'load-skill-result' + ? 'load_skill' + : 'agent'; + const { dynamic, wireName } = resolveEveToolName({ nativeName }); + return [ + { + type: 'tool-result', + toolCallId: result.callId, + toolName: observedToolNames.get(result.callId) ?? wireName, + result: (result.output ?? null) as NonNullable, + ...(event.data.status === 'failed' || result.isError + ? { isError: true } + : {}), + ...(dynamic ? { dynamic: true } : {}), + }, + ]; +} + +function promptToEvePayload({ + instructions, + prompt, +}: { + readonly instructions?: string; + readonly prompt: HarnessV1Prompt; +}): SendTurnPayload { + if (typeof prompt === 'string') { + return { + message: prompt, + ...(instructions ? { clientContext: instructions } : {}), + }; + } + + if (prompt.providerOptions != null) { + unsupported('provider-specific prompt options'); + } + + return { + message: prompt.content as SendTurnPayload['message'], + ...(instructions ? { clientContext: instructions } : {}), + }; +} + +function readEveSessionState({ + data, +}: { + readonly data: unknown; +}): SessionState | undefined { + const parsed = eveResumeStateSchema.safeParse(data); + if (!parsed.success) { + return undefined; + } + return parsed.data.eveSession; +} + +function createEveResumeState({ + state, +}: { + readonly state: SessionState; +}): EveResumeState { + return { + eveSession: { + ...(state.continuationToken + ? { continuationToken: state.continuationToken } + : {}), + ...(state.sessionId ? { sessionId: state.sessionId } : {}), + streamIndex: state.streamIndex, + }, + }; +} + +function resolveEveToolName({ nativeName }: { readonly nativeName: string }): { + readonly dynamic: boolean; + readonly wireName: string; +} { + const wireName = EVE_NATIVE_TO_WIRE_TOOL_NAMES.get(nativeName) ?? nativeName; + return { + wireName, + dynamic: !EVE_STATIC_BUILTIN_TOOL_NAMES.has(wireName), + }; +} + +function mapStepUsage({ + event, +}: { + readonly event: StepCompletedStreamEvent; +}): LanguageModelV4Usage { + return { + inputTokens: { + total: event.data.usage?.inputTokens, + noCache: + event.data.usage?.inputTokens == null + ? undefined + : event.data.usage.inputTokens - + (event.data.usage.cacheReadTokens ?? 0) - + (event.data.usage.cacheWriteTokens ?? 0), + cacheRead: event.data.usage?.cacheReadTokens, + cacheWrite: event.data.usage?.cacheWriteTokens, + }, + outputTokens: { + total: event.data.usage?.outputTokens, + text: undefined, + reasoning: undefined, + }, + raw: (event.data.usage ?? {}) as Record, + }; +} + +function emptyUsage(): LanguageModelV4Usage { + return { + inputTokens: { + total: undefined, + noCache: undefined, + cacheRead: undefined, + cacheWrite: undefined, + }, + outputTokens: { + total: undefined, + text: undefined, + reasoning: undefined, + }, + raw: {}, + }; +} + +function addUsage({ + left, + right, +}: { + readonly left: LanguageModelV4Usage; + readonly right: LanguageModelV4Usage; +}): LanguageModelV4Usage { + return { + inputTokens: { + total: addOptional(left.inputTokens.total, right.inputTokens.total), + noCache: addOptional(left.inputTokens.noCache, right.inputTokens.noCache), + cacheRead: addOptional( + left.inputTokens.cacheRead, + right.inputTokens.cacheRead, + ), + cacheWrite: addOptional( + left.inputTokens.cacheWrite, + right.inputTokens.cacheWrite, + ), + }, + outputTokens: { + total: addOptional(left.outputTokens.total, right.outputTokens.total), + text: addOptional(left.outputTokens.text, right.outputTokens.text), + reasoning: addOptional( + left.outputTokens.reasoning, + right.outputTokens.reasoning, + ), + }, + raw: {}, + }; +} + +function addOptional(left?: number, right?: number): number | undefined { + if (left == null) return right; + if (right == null) return left; + return left + right; +} + +function mapFinishReason({ + raw, +}: { + readonly raw: AssistantStepFinishReason; +}): LanguageModelV4FinishReason { + switch (raw) { + case 'stop': + case 'length': + case 'content-filter': + case 'tool-calls': + return { unified: raw, raw }; + case 'error': + return { unified: 'error', raw }; + case 'other': + return { unified: 'other', raw }; + } +} + +async function* iterableWithAbort({ + iterable, + signal, +}: { + readonly iterable: AsyncIterable; + readonly signal: AbortSignal; +}): AsyncIterable { + signal.throwIfAborted(); + for await (const event of iterable) { + signal.throwIfAborted(); + yield event; + } +} + +function createLinkedAbortController({ + abortSignal, +}: { + readonly abortSignal?: AbortSignal; +}): { + readonly cleanup: () => void; + readonly controller: AbortController; + readonly signal: AbortSignal; +} { + const controller = new AbortController(); + const onAbort = (): void => { + controller.abort(abortSignal?.reason); + }; + + if (abortSignal?.aborted) { + onAbort(); + } else { + abortSignal?.addEventListener('abort', onAbort, { once: true }); + } + + return { + cleanup: () => abortSignal?.removeEventListener('abort', onAbort), + controller, + signal: controller.signal, + }; +} + +function isAbortError({ error }: { readonly error: unknown }): boolean { + return ( + error instanceof DOMException && + (error.name === 'AbortError' || error.name === 'TimeoutError') + ); +} + +function assertNoHostTools({ + tools, +}: { + readonly tools: ReadonlyArray; +}): void { + if (tools.length === 0) return; + unsupported('custom host tools because Eve agents define their own tools'); +} + +function assertNoBuiltinToolFiltering({ + builtinToolFiltering, +}: { + readonly builtinToolFiltering: unknown; +}): void { + if (builtinToolFiltering == null) return; + unsupported( + 'activeTools or inactiveTools because Eve agents control their own tool set', + ); +} + +function assertNoSkills({ + skills, +}: { + readonly skills: ReadonlyArray; +}): void { + if (skills.length === 0) return; + unsupported('custom skills because Eve agents define their own skills'); +} + +function unsupported(message: string): never { + throw new HarnessCapabilityUnsupportedError({ + harnessId: 'eve', + message: `Harness 'eve' does not support ${message}.`, + }); +} diff --git a/packages/harness-eve/src/index.ts b/packages/harness-eve/src/index.ts new file mode 100644 index 000000000000..389a2e10c24b --- /dev/null +++ b/packages/harness-eve/src/index.ts @@ -0,0 +1,3 @@ +export { createEve } from './eve-harness'; +export type { EveHarnessSettings } from './eve-harness'; +export type { EveAuthOptions } from './eve-auth'; diff --git a/packages/harness-eve/tsconfig.build.json b/packages/harness-eve/tsconfig.build.json new file mode 100644 index 000000000000..80b6a0a84612 --- /dev/null +++ b/packages/harness-eve/tsconfig.build.json @@ -0,0 +1,6 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "composite": false + } +} diff --git a/packages/harness-eve/tsconfig.json b/packages/harness-eve/tsconfig.json new file mode 100644 index 000000000000..18acfcf63ebd --- /dev/null +++ b/packages/harness-eve/tsconfig.json @@ -0,0 +1,19 @@ +{ + "extends": "./node_modules/@vercel/ai-tsconfig/ts-library.json", + "compilerOptions": { + "composite": true, + "rootDir": "src", + "outDir": "dist" + }, + "exclude": [ + "dist", + "build", + "node_modules", + "tsup.config.ts" + ], + "references": [ + { "path": "../harness" }, + { "path": "../provider" }, + { "path": "../provider-utils" } + ] +} diff --git a/packages/harness-eve/tsup.config.ts b/packages/harness-eve/tsup.config.ts new file mode 100644 index 000000000000..82e07d8080b2 --- /dev/null +++ b/packages/harness-eve/tsup.config.ts @@ -0,0 +1,9 @@ +import { defineConfig } from 'tsup'; + +export default defineConfig({ + entry: { index: 'src/index.ts' }, + format: ['esm'], + target: 'es2022', + dts: true, + sourcemap: true, +}); diff --git a/packages/harness-eve/turbo.json b/packages/harness-eve/turbo.json new file mode 100644 index 000000000000..620b8380e744 --- /dev/null +++ b/packages/harness-eve/turbo.json @@ -0,0 +1,12 @@ +{ + "extends": [ + "//" + ], + "tasks": { + "build": { + "outputs": [ + "**/dist/**" + ] + } + } +} diff --git a/packages/harness-eve/vitest.node.config.js b/packages/harness-eve/vitest.node.config.js new file mode 100644 index 000000000000..34079d16828e --- /dev/null +++ b/packages/harness-eve/vitest.node.config.js @@ -0,0 +1,8 @@ +import { defineConfig } from 'vitest/config'; + +export default defineConfig({ + test: { + environment: 'node', + include: ['**/*.test.ts', '**/*.test.tsx'], + }, +}); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 00e85757d3c1..fc2700512f57 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -310,6 +310,9 @@ importers: '@ai-sdk/harness-deepagents': specifier: workspace:* version: link:../../packages/harness-deepagents + '@ai-sdk/harness-eve': + specifier: workspace:* + version: link:../../packages/harness-eve '@ai-sdk/harness-opencode': specifier: workspace:* version: link:../../packages/harness-opencode @@ -608,6 +611,9 @@ importers: '@ai-sdk/harness-deepagents': specifier: workspace:* version: link:../../packages/harness-deepagents + '@ai-sdk/harness-eve': + specifier: workspace:* + version: link:../../packages/harness-eve '@ai-sdk/harness-opencode': specifier: workspace:* version: link:../../packages/harness-opencode @@ -699,6 +705,9 @@ importers: '@ai-sdk/harness-deepagents': specifier: workspace:* version: link:../../packages/harness-deepagents + '@ai-sdk/harness-eve': + specifier: workspace:* + version: link:../../packages/harness-eve '@ai-sdk/harness-opencode': specifier: workspace:* version: link:../../packages/harness-opencode @@ -1528,7 +1537,7 @@ importers: version: 3.5.12 nuxt: specifier: 3.21.7 - version: 3.21.7(@parcel/watcher@2.5.6)(@types/node@22.19.19)(@upstash/redis@1.38.0)(@vercel/functions@3.6.0(@aws-sdk/credential-provider-web-identity@3.972.49))(@vue/compiler-sfc@3.5.38)(aws4fetch@1.0.20)(cac@6.7.14)(commander@13.1.0)(db0@0.3.4)(ioredis@5.11.1)(less@4.4.0)(lightningcss@1.32.0)(magicast@0.5.3)(optionator@0.9.4)(oxlint@1.56.0)(rollup-plugin-visualizer@7.0.1(rollup@4.62.0))(rollup@4.62.0)(sass@1.90.0)(srvx@0.11.16)(terser@5.48.0)(tsx@4.22.0)(typescript@5.9.3)(vite@7.3.5(@types/node@22.19.19)(jiti@2.7.0)(less@4.4.0)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.48.0)(tsx@4.22.0)(yaml@2.9.0))(yaml@2.9.0) + version: 3.21.7(@parcel/watcher@2.5.6)(@types/node@22.19.19)(@upstash/redis@1.38.0)(@vercel/functions@3.6.0(@aws-sdk/credential-provider-web-identity@3.972.49))(@vue/compiler-sfc@3.5.38)(aws4fetch@1.0.20)(cac@6.7.14)(commander@13.1.0)(db0@0.3.4)(ioredis@5.11.1)(less@4.4.0)(lightningcss@1.32.0)(magicast@0.5.3)(optionator@0.9.4)(oxlint@1.56.0)(rolldown@1.1.3)(rollup-plugin-visualizer@7.0.1(rolldown@1.1.3)(rollup@4.62.0))(rollup@4.62.0)(sass@1.90.0)(srvx@0.11.16)(terser@5.48.0)(tsx@4.22.0)(typescript@5.9.3)(vite@7.3.5(@types/node@22.19.19)(jiti@2.7.0)(less@4.4.0)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.48.0)(tsx@4.22.0)(yaml@2.9.0))(yaml@2.9.0) tailwindcss: specifier: 3.4.15 version: 3.4.15(ts-node@10.9.2(@swc/core@1.15.3(@swc/helpers@0.5.21))(@types/node@22.19.19)(typescript@5.9.3)) @@ -2664,6 +2673,37 @@ importers: specifier: 3.25.76 version: 3.25.76 + packages/harness-eve: + dependencies: + '@ai-sdk/harness': + specifier: workspace:* + version: link:../harness + '@ai-sdk/provider': + specifier: workspace:* + version: link:../provider + '@ai-sdk/provider-utils': + specifier: workspace:* + version: link:../provider-utils + eve: + specifier: 0.16.2 + version: 0.16.2(9f588373a316ebe0585ec16084e36741) + devDependencies: + '@types/node': + specifier: 22.19.19 + version: 22.19.19 + '@vercel/ai-tsconfig': + specifier: workspace:* + version: link:../../tools/tsconfig + tsup: + specifier: ^8.5.1 + version: 8.5.1(@swc/core@1.15.3(@swc/helpers@0.5.21))(jiti@2.7.0)(postcss@8.5.15)(tsx@4.22.0)(typescript@5.8.3)(yaml@2.9.0) + typescript: + specifier: 5.8.3 + version: 5.8.3 + zod: + specifier: 3.25.76 + version: 3.25.76 + packages/harness-opencode: dependencies: '@ai-sdk/harness': @@ -5568,6 +5608,9 @@ packages: '@emnapi/core@1.10.0': resolution: {integrity: sha512-yq6OkJ4p82CAfPl0u9mQebQHKPJkY7WrIuk205cTYnYe+k2Z8YBh11FrbRG/H6ihirqcacOgl2BIO8oyMQLeXw==} + '@emnapi/core@1.11.1': + resolution: {integrity: sha512-RSvbQmHzdKzNsLYa/wHrbc3KN4sYLKAdPZxqiM2HATqv/SBk2/ENSHpvXGaLOMcsAyz0poEGqkmmKYG3OWiJEQ==} + '@emnapi/runtime@1.10.0': resolution: {integrity: sha512-ewvYlk86xUoGI0zQRNq/mC+16R1QeDlKQy21Ki3oSYXNgLb45GV1P6A0M+/s6nyCuNDqe5VpaY84BzXGwVbwFA==} @@ -5577,6 +5620,9 @@ packages: '@emnapi/wasi-threads@1.2.1': resolution: {integrity: sha512-uTII7OYF+/Mes/MrcIOYp5yOtSMLBWSIoLPpcgwipoiKbli6k322tcoFsxoIIxPDqW01SQGAgko4EzZi2BNv2w==} + '@emnapi/wasi-threads@1.2.2': + resolution: {integrity: sha512-c95qOXkHdydNKhscBTebqEC1CVAZpyqOfVfBzQ1qgzyl3gfeldUjIggDbIZgDKsHLgnsM+igH7TJ/eAasaVuMA==} + '@esbuild/aix-ppc64@0.23.1': resolution: {integrity: sha512-6VhYk1diRqrhBAqpJEdjASR/+WVRtfjpqKuNw11cLiaWpAT/Uu+nokB+UJnevzy/P9C/ty6AOe0dwueMrGh/iQ==} engines: {node: '>=18'} @@ -8050,6 +8096,12 @@ packages: '@emnapi/core': ^1.7.1 '@emnapi/runtime': ^1.7.1 + '@napi-rs/wasm-runtime@1.1.6': + resolution: {integrity: sha512-ZLv/JdUfkvOy9eCnnBaGfiO+XimbjebAeO+MRQqD/B+FR1tnRN0tpKSJHRbE8sFfS6aqsXZ67TQjfwfsxULVbg==} + peerDependencies: + '@emnapi/core': ^1.7.1 + '@emnapi/runtime': ^1.7.1 + '@nestjs/cli@10.4.9': resolution: {integrity: sha512-s8qYd97bggqeK7Op3iD49X2MpFtW4LVNLAwXFkfbRxKME6IYT7X0muNTJ2+QfI8hpbNx9isWkrLWIp+g5FOhiA==} engines: {node: '>= 16.14'} @@ -9364,6 +9416,9 @@ packages: '@oxc-project/types@0.132.0': resolution: {integrity: sha512-FESMOxil5Se014ui/Eq8fT5uHJo6nIRwH0PfJrZJXs6Gek3ZVFOrpUv3YIZT20m+extU98Hg1Ym72U58rlsxUQ==} + '@oxc-project/types@0.137.0': + resolution: {integrity: sha512-WT+Gb24i8hmvo85AIv2oEYouEXkRlKAlT9WaCa3TfLgNCN+GhrJOGZuIlMouAh38Qe4QOx26eUOVsq70qXrywA==} + '@oxc-transform/binding-android-arm-eabi@0.132.0': resolution: {integrity: sha512-UEC6fwIer1e2H8+KYXfhfYMsDgqxrG93lCj3FkrKkJ2O05rikqiJLYGd9ZntmKne+9bOMMuznVKLGErub++mAg==} engines: {node: ^20.19.0 || >=22.12.0} @@ -10212,6 +10267,101 @@ packages: peerDependencies: '@redis/client': ^1.0.0 + '@rolldown/binding-android-arm64@1.1.3': + resolution: {integrity: sha512-DT6Z3PhvioeHMvxo+xHc3KtqggrI7CCTXCmC2h/5zUlp5jVitv7XEy+9q5/7v8IolhlioawpMo8Kg0EEBy7J0g==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [android] + + '@rolldown/binding-darwin-arm64@1.1.3': + resolution: {integrity: sha512-0NwgwsjM7LrsuVnXMK3koTpagBNOhloc/BNjKqZjv4V5zI5r13qx69uVhRx+o5Z0yy4Hzq+lpy7TAgUG/ocvrw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [darwin] + + '@rolldown/binding-darwin-x64@1.1.3': + resolution: {integrity: sha512-YtiBp4disu6V560loT6PjMdiRaWmVvDNrUunAalbiFx2ggeJwxdAsgZMcoGP17uyAsTwAj5V1niksxlHnVQ1Sw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [darwin] + + '@rolldown/binding-freebsd-x64@1.1.3': + resolution: {integrity: sha512-yD3EkEdXk2LypPxnf/kSZHirarsI8gcPzc62SukhR9VJTyvV+F9Q/GxWNuCojc7sXyuVC4DxRGhdDK4X8VSsbw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [freebsd] + + '@rolldown/binding-linux-arm-gnueabihf@1.1.3': + resolution: {integrity: sha512-c+8vieQbsD7HNAHKIA34w0GJ9FedFFuJGD+7E6vz7Q3uqAIugL5p45fhlsj4UaAsHpcmlqugBWMhA0/j7o0sIg==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm] + os: [linux] + + '@rolldown/binding-linux-arm64-gnu@1.1.3': + resolution: {integrity: sha512-50jD0uUwLvur7Zz9LHz17kaAdTPjn5wN93hEgjvmYFRZwiR7ZJYovTd5ipyWJDAnXKvZ+wgc+/Ika6dwSF5OcA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [linux] + libc: [glibc] + + '@rolldown/binding-linux-arm64-musl@1.1.3': + resolution: {integrity: sha512-BO9+oPL8K9poZJBfYPsXNtYjPE5uM3qeehT3aFcW4LITOl+iSqhp0abzjR2nWBUNjIZeKXjAEWBZ64WjNoHd6w==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [linux] + libc: [musl] + + '@rolldown/binding-linux-ppc64-gnu@1.1.3': + resolution: {integrity: sha512-f3VpLB1vQ0Eo6ecr/6cekLnvYMFF4YBFoVGkfkvPLq1bAkbAwHYQPZKoAmG6OJyTcxxoC+AvezGx/S1obNC0Mw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [ppc64] + os: [linux] + libc: [glibc] + + '@rolldown/binding-linux-s390x-gnu@1.1.3': + resolution: {integrity: sha512-AmurZ26Pqx/RI9N1gzEOCklkKXl927yjfXWUUS0O7Puh8ARM/Ob8qfrD3qnWksScdw6cSrW5PSHE9DyLu7+PtA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [s390x] + os: [linux] + libc: [glibc] + + '@rolldown/binding-linux-x64-gnu@1.1.3': + resolution: {integrity: sha512-JJpqs8bRGITDOdbkNKnlojzBabbOHrqjSvDr0IVsZObE1lBcPjxItUEY9eWIDbxaJ3cGrXPWGfGkIxFijg/URg==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [linux] + libc: [glibc] + + '@rolldown/binding-linux-x64-musl@1.1.3': + resolution: {integrity: sha512-rSJcdjPxzA/by/6/rYs+v+bXU7UjvnbUWz8MJb6kh6+knqB1dCrtHg0uu7C/4haqJvqdkYHQ5IGn+tCH9GLW/g==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [linux] + libc: [musl] + + '@rolldown/binding-openharmony-arm64@1.1.3': + resolution: {integrity: sha512-hQ3/PYkDJICgevvyNcVrihVeqq7k1Pp3VZ9lY+dauAYUJKO+auqApvANhvR1An9BhmqYKvW2Mu1F9u4DXSMLxQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [openharmony] + + '@rolldown/binding-wasm32-wasi@1.1.3': + resolution: {integrity: sha512-Elcv/BtML9lXrV6JuKITc/grN2kYV9gjsQpW8Jfw4ioK0TOkjBjye0nnyqQNy9STNaI20lXNaQBRrD5gSgR0Yg==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [wasm32] + + '@rolldown/binding-win32-arm64-msvc@1.1.3': + resolution: {integrity: sha512-2DrEfhluH9yhiaFApmsjsjwrSYbNcY1oFTzYSP1a535jDbV98zCFanA/96TBUd0iDFcxGmw9QRExwGCXz3U+/g==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [win32] + + '@rolldown/binding-win32-x64-msvc@1.1.3': + resolution: {integrity: sha512-OL4OMk7UPXOeVGGd3qo5zJyPIljf4AFgk5QAkPPS+OoLuOOozhuaQGC18MxVTnw/06q93gShAJzlwnSCY9YtqA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [win32] + '@rolldown/pluginutils@1.0.0-beta.27': resolution: {integrity: sha512-+d0F4MKMCbeVUJwG96uQ4SgAznZNSq93I3V+9NHA4OpvqG8mRCpGdKmK8l/dl02h2CCDHwW2FqilnTyDcAnqjA==} @@ -11404,6 +11554,9 @@ packages: '@tybys/wasm-util@0.10.2': resolution: {integrity: sha512-RoBvJ2X0wuKlWFIjrwffGw1IqZHKQqzIchKaadZZfnNpsAYp2mM0h36JtPCjNDAHGgYez/15uMBpfGwchhiMgg==} + '@tybys/wasm-util@0.10.3': + resolution: {integrity: sha512-F3fo1MYrRJYL3zER0OUOmkutjr1Vp23m7OsSgp7nq4SP6OqX6C/56XFIPAl5bt3zaBRjmW7SGz3u/6LwFpYcOg==} + '@types/aria-query@5.0.4': resolution: {integrity: sha512-rfT93uj5s0PRL7EzccGMs3brplhcrghnDoV26NqKhCAS1hVo+WdNsPvE/yb6ilfr5hi2MEk6d5EWJTKdxg8jVw==} @@ -14241,6 +14394,24 @@ packages: resolution: {integrity: sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==} engines: {node: '>=6'} + env-runner@0.1.14: + resolution: {integrity: sha512-qdk5mmgFsd+zPg3r1bkZ+IbvpfUfypyDvNhMGypSMRpz7kOa/kI6SpW8fgyukuEM4Lo24M65r+1Ne0DtT7vFBA==} + hasBin: true + peerDependencies: + '@netlify/runtime': ^4.1.23 + '@vercel/queue': ^0.2.0 + miniflare: ^4.20260515.0 + wrangler: ^4.0.0 + peerDependenciesMeta: + '@netlify/runtime': + optional: true + '@vercel/queue': + optional: true + miniflare: + optional: true + wrangler: + optional: true + environment@1.1.0: resolution: {integrity: sha512-xUtoPkMggbz0MPyPiIWr1Kp4aeWJjDZ6SMvURhimjdZgsRuDplF5/s9hcgGhyXMhs+6vpnuoiZ2kFiu3FMnS8Q==} engines: {node: '>=18'} @@ -14405,6 +14576,47 @@ packages: resolution: {integrity: sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==} engines: {node: '>= 0.6'} + eve@0.16.2: + resolution: {integrity: sha512-zBq/dd2nDfI3aaDrmviWN2IlRbTkdYXhyUCxrif/+wCe5i49vf84//X8KR+nEexmkNnd4s9oenbQVTU27fR86g==} + engines: {node: '>=24'} + hasBin: true + peerDependencies: + '@opentelemetry/api': ^1.0.0 + '@sveltejs/kit': ^2.0.0 + ai: ^7.0.0 + 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 + event-target-shim@5.0.1: resolution: {integrity: sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==} engines: {node: '>=6'} @@ -15039,6 +15251,16 @@ packages: h3@1.15.11: resolution: {integrity: sha512-L3THSe2MPeBwgIZVSH5zLdBBU90TOxarvhK9d04IDY2AmVS8j2Jz2LIWtwsGOU3lu2I5jCN7FNvVfY2+XyF+mg==} + h3@2.0.1-rc.22: + resolution: {integrity: sha512-Esv0DMIuPkCTSWCA0vO73vcTqwzH1wjSrAO1TXNu/K3up1sZHa9EKMapbmxCDYBeymC3fVTk4qxp7ogQWQ+KgA==} + engines: {node: '>=20.11.1'} + hasBin: true + peerDependencies: + crossws: ^0.4.1 + peerDependenciesMeta: + crossws: + optional: true + hachure-fill@0.5.2: resolution: {integrity: sha512-3GKBOn+m2LX9iq+JC1064cSFprJY4jL1jCXTcpnfER5HYE2l/4EfWSGzkPa/ZDBmYI0ZOEj5VHV/eKnPGkHuOg==} @@ -17055,6 +17277,40 @@ packages: sass: optional: true + nf3@0.3.17: + resolution: {integrity: sha512-N9zEWySuJFw+gR0lhS5863YsvNeudOdqRyFvNb+jMXbeTJOdrjDqkCpDginIZfUm0LzT1t1nCRiDeqQm/8kirQ==} + + nitro@3.0.260610-beta: + resolution: {integrity: sha512-KPb4L5yaF/Rx/xoGMpgHRJvZhbhGiqbRKOwwPLCH9jKTKTsEUHLjnJas85AeCzaswqa8Wi52eQBtRsODC4PS0Q==} + engines: {node: ^20.19.0 || >=22.12.0} + hasBin: true + peerDependencies: + '@vercel/queue': ^0.3.0 + dotenv: '*' + giget: '*' + jiti: ^2.7.0 + rollup: ^4.61.1 + vite: ^7 || ^8 + xml2js: ^0.6.2 + zephyr-agent: ^0.2.0 + peerDependenciesMeta: + '@vercel/queue': + optional: true + dotenv: + optional: true + giget: + optional: true + jiti: + optional: true + rollup: + optional: true + vite: + optional: true + xml2js: + optional: true + zephyr-agent: + optional: true + nitropack@2.13.4: resolution: {integrity: sha512-tX7bT6zxNeMwkc6hxHiZeUoTOjVrcjoh1Z3cmxOlodIqjl4HISgqfGOmkWSayky3Nv9Z5+KQH52F8nmXJY5AAA==} engines: {node: ^20.19.0 || >=22.12.0} @@ -17299,6 +17555,9 @@ packages: resolution: {integrity: sha512-9miFgM2OFba7hB+pRgvtV84pYTBaoTHohvmIgiRt6dRIzbwEOIaNaP+dIlGs2fNFoB0SeISs0Jz5WFVRid6Xyg==} engines: {node: '>=12.20.0'} + ocache@0.1.5: + resolution: {integrity: sha512-kNNnkkVQup/QDvmTz8Q84wc2ntiyoVHDxa6eHWKt5qdGAmFRBIxy83rxgCYEjW0x06UJ9E3P6VgM2yY4rOBH4w==} + ofetch@1.5.1: resolution: {integrity: sha512-2W4oUZlVaqAPAil6FUg/difl6YhqhUR7x2eZY4bQCko22UXg3hptq9KLQdqFClV+Wu85UX7hNtdGTngi/1BxcA==} @@ -18704,6 +18963,11 @@ packages: robust-predicates@3.0.3: resolution: {integrity: sha512-NS3levdsRIUOmiJ8FZWCP7LG3QpJyrs/TE0Zpf1yvZu8cAJJ6QMW92H1c7kWpdIHo8RvmLxN/o2JXTKHp74lUA==} + rolldown@1.1.3: + resolution: {integrity: sha512-1F1eEtUBtFvcGm1HQ9TiUIUHPQG7mSAODrhIzjxoUEFuo8OcbrGLiVLkevNgj84TE4lnHvnumwFjhJO5Eu135g==} + engines: {node: ^20.19.0 || >=22.12.0} + hasBin: true + rollup-plugin-visualizer@7.0.1: resolution: {integrity: sha512-UJUT4+1Ho4OcWmPYU3sYXgUqI8B8Ayfe06MX7y0qCJ1K8aGoKtR/NDd/2nZqM7ADkrzny+I99Ul7GgyoiVNAgg==} engines: {node: '>=22'} @@ -20294,6 +20558,80 @@ packages: uploadthing: optional: true + unstorage@2.0.0-alpha.7: + resolution: {integrity: sha512-ELPztchk2zgFJnakyodVY3vJWGW9jy//keJ32IOJVGUMyaPydwcA1FtVvWqT0TNRch9H+cMNEGllfVFfScImog==} + peerDependencies: + '@azure/app-configuration': ^1.11.0 + '@azure/cosmos': ^4.9.1 + '@azure/data-tables': ^13.3.2 + '@azure/identity': ^4.13.0 + '@azure/keyvault-secrets': ^4.10.0 + '@azure/storage-blob': ^12.31.0 + '@capacitor/preferences': ^6 || ^7 || ^8 + '@deno/kv': '>=0.13.0' + '@netlify/blobs': ^6.5.0 || ^7.0.0 || ^8.1.0 || ^9.0.0 || ^10.0.0 + '@planetscale/database': ^1.19.0 + '@upstash/redis': ^1.36.2 + '@vercel/blob': '>=0.27.3' + '@vercel/functions': ^2.2.12 || ^3.0.0 + '@vercel/kv': ^1.0.1 + aws4fetch: ^1.0.20 + chokidar: ^4 || ^5 + db0: '>=0.3.4' + idb-keyval: ^6.2.2 + ioredis: ^5.9.3 + lru-cache: ^11.2.6 + mongodb: ^6 || ^7 + ofetch: '*' + uploadthing: ^7.7.4 + peerDependenciesMeta: + '@azure/app-configuration': + optional: true + '@azure/cosmos': + optional: true + '@azure/data-tables': + optional: true + '@azure/identity': + optional: true + '@azure/keyvault-secrets': + optional: true + '@azure/storage-blob': + optional: true + '@capacitor/preferences': + optional: true + '@deno/kv': + optional: true + '@netlify/blobs': + optional: true + '@planetscale/database': + optional: true + '@upstash/redis': + optional: true + '@vercel/blob': + optional: true + '@vercel/functions': + optional: true + '@vercel/kv': + optional: true + aws4fetch: + optional: true + chokidar: + optional: true + db0: + optional: true + idb-keyval: + optional: true + ioredis: + optional: true + lru-cache: + optional: true + mongodb: + optional: true + ofetch: + optional: true + uploadthing: + optional: true + until-async@3.0.2: resolution: {integrity: sha512-IiSk4HlzAMqTUseHHe3VhIGyuFmN90zMTpD3Z3y8jeQbzLIq500MVM7Jq2vUAnTKAFPJrqwkzr6PoTcPhGcOiw==} @@ -24005,6 +24343,12 @@ snapshots: tslib: 2.8.1 optional: true + '@emnapi/core@1.11.1': + dependencies: + '@emnapi/wasi-threads': 1.2.2 + tslib: 2.8.1 + optional: true + '@emnapi/runtime@1.10.0': dependencies: tslib: 2.8.1 @@ -24020,6 +24364,11 @@ snapshots: tslib: 2.8.1 optional: true + '@emnapi/wasi-threads@1.2.2': + dependencies: + tslib: 2.8.1 + optional: true + '@esbuild/aix-ppc64@0.23.1': optional: true @@ -26421,6 +26770,13 @@ snapshots: '@tybys/wasm-util': 0.10.2 optional: true + '@napi-rs/wasm-runtime@1.1.6(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.1)': + dependencies: + '@emnapi/core': 1.11.1 + '@emnapi/runtime': 1.11.1 + '@tybys/wasm-util': 0.10.3 + optional: true + '@nestjs/cli@10.4.9(@swc/core@1.15.3(@swc/helpers@0.5.21))(lightningcss@1.32.0)': dependencies: '@angular-devkit/core': 17.3.11(chokidar@3.6.0) @@ -26927,7 +27283,7 @@ snapshots: transitivePeerDependencies: - magicast - '@nuxt/nitro-server@3.21.7(@upstash/redis@1.38.0)(@vercel/functions@3.6.0(@aws-sdk/credential-provider-web-identity@3.972.49))(aws4fetch@1.0.20)(db0@0.3.4)(ioredis@5.11.1)(magicast@0.5.3)(nuxt@3.21.7(@parcel/watcher@2.5.6)(@types/node@22.19.19)(@upstash/redis@1.38.0)(@vercel/functions@3.6.0(@aws-sdk/credential-provider-web-identity@3.972.49))(@vue/compiler-sfc@3.5.38)(aws4fetch@1.0.20)(cac@6.7.14)(commander@13.1.0)(db0@0.3.4)(ioredis@5.11.1)(less@4.4.0)(lightningcss@1.32.0)(magicast@0.5.3)(optionator@0.9.4)(oxlint@1.56.0)(rollup-plugin-visualizer@7.0.1(rollup@4.62.0))(rollup@4.62.0)(sass@1.90.0)(srvx@0.11.16)(terser@5.48.0)(tsx@4.22.0)(typescript@5.9.3)(vite@7.3.5(@types/node@22.19.19)(jiti@2.7.0)(less@4.4.0)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.48.0)(tsx@4.22.0)(yaml@2.9.0))(yaml@2.9.0))(oxc-parser@0.132.0)(srvx@0.11.16)(typescript@5.9.3)': + '@nuxt/nitro-server@3.21.7(@upstash/redis@1.38.0)(@vercel/functions@3.6.0(@aws-sdk/credential-provider-web-identity@3.972.49))(aws4fetch@1.0.20)(db0@0.3.4)(ioredis@5.11.1)(magicast@0.5.3)(nuxt@3.21.7(@parcel/watcher@2.5.6)(@types/node@22.19.19)(@upstash/redis@1.38.0)(@vercel/functions@3.6.0(@aws-sdk/credential-provider-web-identity@3.972.49))(@vue/compiler-sfc@3.5.38)(aws4fetch@1.0.20)(cac@6.7.14)(commander@13.1.0)(db0@0.3.4)(ioredis@5.11.1)(less@4.4.0)(lightningcss@1.32.0)(magicast@0.5.3)(optionator@0.9.4)(oxlint@1.56.0)(rolldown@1.1.3)(rollup-plugin-visualizer@7.0.1(rolldown@1.1.3)(rollup@4.62.0))(rollup@4.62.0)(sass@1.90.0)(srvx@0.11.16)(terser@5.48.0)(tsx@4.22.0)(typescript@5.9.3)(vite@7.3.5(@types/node@22.19.19)(jiti@2.7.0)(less@4.4.0)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.48.0)(tsx@4.22.0)(yaml@2.9.0))(yaml@2.9.0))(oxc-parser@0.132.0)(rolldown@1.1.3)(srvx@0.11.16)(typescript@5.9.3)': dependencies: '@nuxt/devalue': 2.0.2 '@nuxt/kit': 3.21.7(magicast@0.5.3) @@ -26944,8 +27300,8 @@ snapshots: impound: 1.1.5 klona: 2.0.6 mocked-exports: 0.1.1 - nitropack: 2.13.4(@upstash/redis@1.38.0)(@vercel/functions@3.6.0(@aws-sdk/credential-provider-web-identity@3.972.49))(aws4fetch@1.0.20)(oxc-parser@0.132.0)(srvx@0.11.16) - nuxt: 3.21.7(@parcel/watcher@2.5.6)(@types/node@22.19.19)(@upstash/redis@1.38.0)(@vercel/functions@3.6.0(@aws-sdk/credential-provider-web-identity@3.972.49))(@vue/compiler-sfc@3.5.38)(aws4fetch@1.0.20)(cac@6.7.14)(commander@13.1.0)(db0@0.3.4)(ioredis@5.11.1)(less@4.4.0)(lightningcss@1.32.0)(magicast@0.5.3)(optionator@0.9.4)(oxlint@1.56.0)(rollup-plugin-visualizer@7.0.1(rollup@4.62.0))(rollup@4.62.0)(sass@1.90.0)(srvx@0.11.16)(terser@5.48.0)(tsx@4.22.0)(typescript@5.9.3)(vite@7.3.5(@types/node@22.19.19)(jiti@2.7.0)(less@4.4.0)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.48.0)(tsx@4.22.0)(yaml@2.9.0))(yaml@2.9.0) + nitropack: 2.13.4(@upstash/redis@1.38.0)(@vercel/functions@3.6.0(@aws-sdk/credential-provider-web-identity@3.972.49))(aws4fetch@1.0.20)(oxc-parser@0.132.0)(rolldown@1.1.3)(srvx@0.11.16) + nuxt: 3.21.7(@parcel/watcher@2.5.6)(@types/node@22.19.19)(@upstash/redis@1.38.0)(@vercel/functions@3.6.0(@aws-sdk/credential-provider-web-identity@3.972.49))(@vue/compiler-sfc@3.5.38)(aws4fetch@1.0.20)(cac@6.7.14)(commander@13.1.0)(db0@0.3.4)(ioredis@5.11.1)(less@4.4.0)(lightningcss@1.32.0)(magicast@0.5.3)(optionator@0.9.4)(oxlint@1.56.0)(rolldown@1.1.3)(rollup-plugin-visualizer@7.0.1(rolldown@1.1.3)(rollup@4.62.0))(rollup@4.62.0)(sass@1.90.0)(srvx@0.11.16)(terser@5.48.0)(tsx@4.22.0)(typescript@5.9.3)(vite@7.3.5(@types/node@22.19.19)(jiti@2.7.0)(less@4.4.0)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.48.0)(tsx@4.22.0)(yaml@2.9.0))(yaml@2.9.0) ohash: 2.0.11 pathe: 2.0.3 pkg-types: 2.3.1 @@ -27026,7 +27382,7 @@ snapshots: '@nuxt/ui-templates@1.3.4': {} - '@nuxt/vite-builder@3.21.7(@types/node@22.19.19)(less@4.4.0)(lightningcss@1.32.0)(magicast@0.5.3)(nuxt@3.21.7(@parcel/watcher@2.5.6)(@types/node@22.19.19)(@upstash/redis@1.38.0)(@vercel/functions@3.6.0(@aws-sdk/credential-provider-web-identity@3.972.49))(@vue/compiler-sfc@3.5.38)(aws4fetch@1.0.20)(cac@6.7.14)(commander@13.1.0)(db0@0.3.4)(ioredis@5.11.1)(less@4.4.0)(lightningcss@1.32.0)(magicast@0.5.3)(optionator@0.9.4)(oxlint@1.56.0)(rollup-plugin-visualizer@7.0.1(rollup@4.62.0))(rollup@4.62.0)(sass@1.90.0)(srvx@0.11.16)(terser@5.48.0)(tsx@4.22.0)(typescript@5.9.3)(vite@7.3.5(@types/node@22.19.19)(jiti@2.7.0)(less@4.4.0)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.48.0)(tsx@4.22.0)(yaml@2.9.0))(yaml@2.9.0))(optionator@0.9.4)(oxlint@1.56.0)(rollup-plugin-visualizer@7.0.1(rollup@4.62.0))(rollup@4.62.0)(sass@1.90.0)(terser@5.48.0)(tsx@4.22.0)(typescript@5.9.3)(vue@3.5.38(typescript@5.9.3))(yaml@2.9.0)': + '@nuxt/vite-builder@3.21.7(@types/node@22.19.19)(less@4.4.0)(lightningcss@1.32.0)(magicast@0.5.3)(nuxt@3.21.7(@parcel/watcher@2.5.6)(@types/node@22.19.19)(@upstash/redis@1.38.0)(@vercel/functions@3.6.0(@aws-sdk/credential-provider-web-identity@3.972.49))(@vue/compiler-sfc@3.5.38)(aws4fetch@1.0.20)(cac@6.7.14)(commander@13.1.0)(db0@0.3.4)(ioredis@5.11.1)(less@4.4.0)(lightningcss@1.32.0)(magicast@0.5.3)(optionator@0.9.4)(oxlint@1.56.0)(rolldown@1.1.3)(rollup-plugin-visualizer@7.0.1(rolldown@1.1.3)(rollup@4.62.0))(rollup@4.62.0)(sass@1.90.0)(srvx@0.11.16)(terser@5.48.0)(tsx@4.22.0)(typescript@5.9.3)(vite@7.3.5(@types/node@22.19.19)(jiti@2.7.0)(less@4.4.0)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.48.0)(tsx@4.22.0)(yaml@2.9.0))(yaml@2.9.0))(optionator@0.9.4)(oxlint@1.56.0)(rolldown@1.1.3)(rollup-plugin-visualizer@7.0.1(rolldown@1.1.3)(rollup@4.62.0))(rollup@4.62.0)(sass@1.90.0)(terser@5.48.0)(tsx@4.22.0)(typescript@5.9.3)(vue@3.5.38(typescript@5.9.3))(yaml@2.9.0)': dependencies: '@nuxt/kit': 3.21.7(magicast@0.5.3) '@rollup/plugin-replace': 6.0.3(rollup@4.62.0) @@ -27045,7 +27401,7 @@ snapshots: magic-string: 0.30.21 mlly: 1.8.2 mocked-exports: 0.1.1 - nuxt: 3.21.7(@parcel/watcher@2.5.6)(@types/node@22.19.19)(@upstash/redis@1.38.0)(@vercel/functions@3.6.0(@aws-sdk/credential-provider-web-identity@3.972.49))(@vue/compiler-sfc@3.5.38)(aws4fetch@1.0.20)(cac@6.7.14)(commander@13.1.0)(db0@0.3.4)(ioredis@5.11.1)(less@4.4.0)(lightningcss@1.32.0)(magicast@0.5.3)(optionator@0.9.4)(oxlint@1.56.0)(rollup-plugin-visualizer@7.0.1(rollup@4.62.0))(rollup@4.62.0)(sass@1.90.0)(srvx@0.11.16)(terser@5.48.0)(tsx@4.22.0)(typescript@5.9.3)(vite@7.3.5(@types/node@22.19.19)(jiti@2.7.0)(less@4.4.0)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.48.0)(tsx@4.22.0)(yaml@2.9.0))(yaml@2.9.0) + nuxt: 3.21.7(@parcel/watcher@2.5.6)(@types/node@22.19.19)(@upstash/redis@1.38.0)(@vercel/functions@3.6.0(@aws-sdk/credential-provider-web-identity@3.972.49))(@vue/compiler-sfc@3.5.38)(aws4fetch@1.0.20)(cac@6.7.14)(commander@13.1.0)(db0@0.3.4)(ioredis@5.11.1)(less@4.4.0)(lightningcss@1.32.0)(magicast@0.5.3)(optionator@0.9.4)(oxlint@1.56.0)(rolldown@1.1.3)(rollup-plugin-visualizer@7.0.1(rolldown@1.1.3)(rollup@4.62.0))(rollup@4.62.0)(sass@1.90.0)(srvx@0.11.16)(terser@5.48.0)(tsx@4.22.0)(typescript@5.9.3)(vite@7.3.5(@types/node@22.19.19)(jiti@2.7.0)(less@4.4.0)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.48.0)(tsx@4.22.0)(yaml@2.9.0))(yaml@2.9.0) nypm: 0.6.7 ohash: 2.0.11 pathe: 2.0.3 @@ -27062,7 +27418,8 @@ snapshots: vue: 3.5.38(typescript@5.9.3) vue-bundle-renderer: 2.2.0 optionalDependencies: - rollup-plugin-visualizer: 7.0.1(rollup@4.62.0) + rolldown: 1.1.3 + rollup-plugin-visualizer: 7.0.1(rolldown@1.1.3)(rollup@4.62.0) transitivePeerDependencies: - '@biomejs/biome' - '@types/node' @@ -28291,6 +28648,8 @@ snapshots: '@oxc-project/types@0.132.0': {} + '@oxc-project/types@0.137.0': {} + '@oxc-transform/binding-android-arm-eabi@0.132.0': optional: true @@ -28961,6 +29320,55 @@ snapshots: dependencies: '@redis/client': 1.6.1 + '@rolldown/binding-android-arm64@1.1.3': + optional: true + + '@rolldown/binding-darwin-arm64@1.1.3': + optional: true + + '@rolldown/binding-darwin-x64@1.1.3': + optional: true + + '@rolldown/binding-freebsd-x64@1.1.3': + optional: true + + '@rolldown/binding-linux-arm-gnueabihf@1.1.3': + optional: true + + '@rolldown/binding-linux-arm64-gnu@1.1.3': + optional: true + + '@rolldown/binding-linux-arm64-musl@1.1.3': + optional: true + + '@rolldown/binding-linux-ppc64-gnu@1.1.3': + optional: true + + '@rolldown/binding-linux-s390x-gnu@1.1.3': + optional: true + + '@rolldown/binding-linux-x64-gnu@1.1.3': + optional: true + + '@rolldown/binding-linux-x64-musl@1.1.3': + optional: true + + '@rolldown/binding-openharmony-arm64@1.1.3': + optional: true + + '@rolldown/binding-wasm32-wasi@1.1.3': + dependencies: + '@emnapi/core': 1.11.1 + '@emnapi/runtime': 1.11.1 + '@napi-rs/wasm-runtime': 1.1.6(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.1) + optional: true + + '@rolldown/binding-win32-arm64-msvc@1.1.3': + optional: true + + '@rolldown/binding-win32-x64-msvc@1.1.3': + optional: true + '@rolldown/pluginutils@1.0.0-beta.27': {} '@rolldown/pluginutils@1.0.1': {} @@ -29718,6 +30126,28 @@ snapshots: '@opentelemetry/api': 1.9.1 typescript: 5.9.3 + '@sveltejs/kit@2.60.1(@opentelemetry/api@1.9.1)(@sveltejs/vite-plugin-svelte@5.1.1(svelte@5.55.7)(vite@7.3.5(@types/node@22.19.19)(jiti@2.7.0)(less@4.4.0)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.48.0)(tsx@4.22.0)(yaml@2.9.0)))(svelte@5.55.7)(typescript@5.8.3)(vite@7.3.5(@types/node@22.19.19)(jiti@2.7.0)(less@4.4.0)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.48.0)(tsx@4.22.0)(yaml@2.9.0))': + dependencies: + '@standard-schema/spec': 1.1.0 + '@sveltejs/acorn-typescript': 1.0.9(acorn@8.17.0) + '@sveltejs/vite-plugin-svelte': 5.1.1(svelte@5.55.7)(vite@7.3.5(@types/node@22.19.19)(jiti@2.7.0)(less@4.4.0)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.48.0)(tsx@4.22.0)(yaml@2.9.0)) + '@types/cookie': 0.6.0 + acorn: 8.17.0 + cookie: 0.6.0 + devalue: 5.8.1 + esm-env: 1.2.2 + kleur: 4.1.5 + magic-string: 0.30.21 + mrmime: 2.0.1 + set-cookie-parser: 3.1.0 + sirv: 3.0.2 + svelte: 5.55.7 + vite: 7.3.5(@types/node@22.19.19)(jiti@2.7.0)(less@4.4.0)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.48.0)(tsx@4.22.0)(yaml@2.9.0) + optionalDependencies: + '@opentelemetry/api': 1.9.1 + typescript: 5.8.3 + optional: true + '@sveltejs/package@2.5.7(svelte@5.55.7)(typescript@5.9.3)': dependencies: chokidar: 5.0.0 @@ -29738,6 +30168,16 @@ snapshots: transitivePeerDependencies: - supports-color + '@sveltejs/vite-plugin-svelte-inspector@4.0.1(@sveltejs/vite-plugin-svelte@5.1.1(svelte@5.55.7)(vite@7.3.5(@types/node@22.19.19)(jiti@2.7.0)(less@4.4.0)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.48.0)(tsx@4.22.0)(yaml@2.9.0)))(svelte@5.55.7)(vite@7.3.5(@types/node@22.19.19)(jiti@2.7.0)(less@4.4.0)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.48.0)(tsx@4.22.0)(yaml@2.9.0))': + dependencies: + '@sveltejs/vite-plugin-svelte': 5.1.1(svelte@5.55.7)(vite@7.3.5(@types/node@22.19.19)(jiti@2.7.0)(less@4.4.0)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.48.0)(tsx@4.22.0)(yaml@2.9.0)) + debug: 4.4.3(supports-color@8.1.1) + svelte: 5.55.7 + vite: 7.3.5(@types/node@22.19.19)(jiti@2.7.0)(less@4.4.0)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.48.0)(tsx@4.22.0)(yaml@2.9.0) + transitivePeerDependencies: + - supports-color + optional: true + '@sveltejs/vite-plugin-svelte@5.1.1(svelte@5.55.7)(vite@6.4.3(@types/node@22.19.19)(jiti@2.7.0)(less@4.4.0)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.48.0)(tsx@4.22.0)(yaml@2.9.0))': dependencies: '@sveltejs/vite-plugin-svelte-inspector': 4.0.1(@sveltejs/vite-plugin-svelte@5.1.1(svelte@5.55.7)(vite@6.4.3(@types/node@22.19.19)(jiti@2.7.0)(less@4.4.0)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.48.0)(tsx@4.22.0)(yaml@2.9.0)))(svelte@5.55.7)(vite@6.4.3(@types/node@22.19.19)(jiti@2.7.0)(less@4.4.0)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.48.0)(tsx@4.22.0)(yaml@2.9.0)) @@ -29751,6 +30191,20 @@ snapshots: transitivePeerDependencies: - supports-color + '@sveltejs/vite-plugin-svelte@5.1.1(svelte@5.55.7)(vite@7.3.5(@types/node@22.19.19)(jiti@2.7.0)(less@4.4.0)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.48.0)(tsx@4.22.0)(yaml@2.9.0))': + dependencies: + '@sveltejs/vite-plugin-svelte-inspector': 4.0.1(@sveltejs/vite-plugin-svelte@5.1.1(svelte@5.55.7)(vite@7.3.5(@types/node@22.19.19)(jiti@2.7.0)(less@4.4.0)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.48.0)(tsx@4.22.0)(yaml@2.9.0)))(svelte@5.55.7)(vite@7.3.5(@types/node@22.19.19)(jiti@2.7.0)(less@4.4.0)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.48.0)(tsx@4.22.0)(yaml@2.9.0)) + debug: 4.4.3(supports-color@8.1.1) + deepmerge: 4.3.1 + kleur: 4.1.5 + magic-string: 0.30.21 + svelte: 5.55.7 + vite: 7.3.5(@types/node@22.19.19)(jiti@2.7.0)(less@4.4.0)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.48.0)(tsx@4.22.0)(yaml@2.9.0) + vitefu: 1.1.3(vite@7.3.5(@types/node@22.19.19)(jiti@2.7.0)(less@4.4.0)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.48.0)(tsx@4.22.0)(yaml@2.9.0)) + transitivePeerDependencies: + - supports-color + optional: true + '@swc/cli@0.8.1(@swc/core@1.15.3(@swc/helpers@0.5.21))(chokidar@5.0.0)': dependencies: '@swc/core': 1.15.3(@swc/helpers@0.5.21) @@ -30042,6 +30496,11 @@ snapshots: tslib: 2.8.1 optional: true + '@tybys/wasm-util@0.10.3': + dependencies: + tslib: 2.8.1 + optional: true + '@types/aria-query@5.0.4': {} '@types/aws-lambda@8.10.162': {} @@ -33780,6 +34239,13 @@ snapshots: env-paths@2.2.1: {} + env-runner@0.1.14: + dependencies: + crossws: 0.4.6(srvx@0.11.16) + exsolve: 1.0.8 + httpxy: 0.5.3 + srvx: 0.11.16 + environment@1.1.0: {} err-code@2.0.3: {} @@ -34056,6 +34522,57 @@ snapshots: etag@1.8.1: {} + eve@0.16.2(9f588373a316ebe0585ec16084e36741): + dependencies: + ai: link:packages/ai + nitro: 3.0.260610-beta(@upstash/redis@1.38.0)(@vercel/functions@3.6.0(@aws-sdk/credential-provider-web-identity@3.972.49))(aws4fetch@1.0.20)(chokidar@5.0.0)(dotenv@17.4.2)(giget@3.3.0)(ioredis@5.11.1)(jiti@2.7.0)(lru-cache@11.5.1)(rollup@4.62.0)(vite@7.3.5(@types/node@22.19.19)(jiti@2.7.0)(less@4.4.0)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.48.0)(tsx@4.22.0)(yaml@2.9.0)) + optionalDependencies: + '@opentelemetry/api': 1.9.1 + '@sveltejs/kit': 2.60.1(@opentelemetry/api@1.9.1)(@sveltejs/vite-plugin-svelte@5.1.1(svelte@5.55.7)(vite@7.3.5(@types/node@22.19.19)(jiti@2.7.0)(less@4.4.0)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.48.0)(tsx@4.22.0)(yaml@2.9.0)))(svelte@5.55.7)(typescript@5.8.3)(vite@7.3.5(@types/node@22.19.19)(jiti@2.7.0)(less@4.4.0)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.48.0)(tsx@4.22.0)(yaml@2.9.0)) + next: 15.5.18(@opentelemetry/api@1.9.1)(@playwright/test@1.60.0)(react-dom@19.0.0-rc-cc1ec60d0d-20240607(react@19.0.0-rc-cc1ec60d0d-20240607))(react@19.0.0-rc-cc1ec60d0d-20240607)(sass@1.90.0) + react: 19.2.6 + svelte: 5.55.7 + vite: 7.3.5(@types/node@22.19.19)(jiti@2.7.0)(less@4.4.0)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.48.0)(tsx@4.22.0)(yaml@2.9.0) + vue: 3.5.38(typescript@5.8.3) + 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 + event-target-shim@5.0.1: {} eventemitter3@4.0.7: {} @@ -34975,6 +35492,13 @@ snapshots: ufo: 1.6.4 uncrypto: 0.1.3 + h3@2.0.1-rc.22(crossws@0.4.6(srvx@0.11.16)): + dependencies: + rou3: 0.8.1 + srvx: 0.11.16 + optionalDependencies: + crossws: 0.4.6(srvx@0.11.16) + hachure-fill@0.5.2: {} handle-thing@2.0.1: {} @@ -37795,7 +38319,63 @@ snapshots: - babel-plugin-macros optional: true - nitropack@2.13.4(@upstash/redis@1.38.0)(@vercel/functions@3.6.0(@aws-sdk/credential-provider-web-identity@3.972.49))(aws4fetch@1.0.20)(oxc-parser@0.132.0)(srvx@0.11.16): + nf3@0.3.17: {} + + nitro@3.0.260610-beta(@upstash/redis@1.38.0)(@vercel/functions@3.6.0(@aws-sdk/credential-provider-web-identity@3.972.49))(aws4fetch@1.0.20)(chokidar@5.0.0)(dotenv@17.4.2)(giget@3.3.0)(ioredis@5.11.1)(jiti@2.7.0)(lru-cache@11.5.1)(rollup@4.62.0)(vite@7.3.5(@types/node@22.19.19)(jiti@2.7.0)(less@4.4.0)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.48.0)(tsx@4.22.0)(yaml@2.9.0)): + dependencies: + consola: 3.4.2 + crossws: 0.4.6(srvx@0.11.16) + db0: 0.3.4 + env-runner: 0.1.14 + h3: 2.0.1-rc.22(crossws@0.4.6(srvx@0.11.16)) + hookable: 6.1.1 + nf3: 0.3.17 + ocache: 0.1.5 + ofetch: 2.0.0-alpha.3 + ohash: 2.0.11 + rolldown: 1.1.3 + srvx: 0.11.16 + unenv: 2.0.0-rc.24 + unstorage: 2.0.0-alpha.7(@upstash/redis@1.38.0)(@vercel/functions@3.6.0(@aws-sdk/credential-provider-web-identity@3.972.49))(aws4fetch@1.0.20)(chokidar@5.0.0)(db0@0.3.4)(ioredis@5.11.1)(lru-cache@11.5.1)(ofetch@2.0.0-alpha.3) + optionalDependencies: + dotenv: 17.4.2 + giget: 3.3.0 + jiti: 2.7.0 + rollup: 4.62.0 + vite: 7.3.5(@types/node@22.19.19)(jiti@2.7.0)(less@4.4.0)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.48.0)(tsx@4.22.0)(yaml@2.9.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' + - aws4fetch + - better-sqlite3 + - chokidar + - drizzle-orm + - idb-keyval + - ioredis + - lru-cache + - miniflare + - mongodb + - mysql2 + - sqlite3 + - uploadthing + - wrangler + + nitropack@2.13.4(@upstash/redis@1.38.0)(@vercel/functions@3.6.0(@aws-sdk/credential-provider-web-identity@3.972.49))(aws4fetch@1.0.20)(oxc-parser@0.132.0)(rolldown@1.1.3)(srvx@0.11.16): dependencies: '@cloudflare/kv-asset-handler': 0.4.2 '@rollup/plugin-alias': 6.0.0(rollup@4.62.0) @@ -37848,7 +38428,7 @@ snapshots: pretty-bytes: 7.1.0 radix3: 1.1.2 rollup: 4.62.0 - rollup-plugin-visualizer: 7.0.1(rollup@4.62.0) + rollup-plugin-visualizer: 7.0.1(rolldown@1.1.3)(rollup@4.62.0) scule: 1.3.0 semver: 7.8.4 serve-placeholder: 2.0.2 @@ -37860,7 +38440,7 @@ snapshots: uncrypto: 0.1.3 unctx: 2.5.0 unenv: 2.0.0-rc.24 - unimport: 6.3.0(oxc-parser@0.132.0) + unimport: 6.3.0(oxc-parser@0.132.0)(rolldown@1.1.3) unplugin-utils: 0.3.1 unstorage: 1.17.5(@upstash/redis@1.38.0)(@vercel/functions@3.6.0(@aws-sdk/credential-provider-web-identity@3.972.49))(aws4fetch@1.0.20)(db0@0.3.4)(ioredis@5.11.1) untyped: 2.0.0 @@ -38084,16 +38664,16 @@ snapshots: dependencies: boolbase: 1.0.0 - nuxt@3.21.7(@parcel/watcher@2.5.6)(@types/node@22.19.19)(@upstash/redis@1.38.0)(@vercel/functions@3.6.0(@aws-sdk/credential-provider-web-identity@3.972.49))(@vue/compiler-sfc@3.5.38)(aws4fetch@1.0.20)(cac@6.7.14)(commander@13.1.0)(db0@0.3.4)(ioredis@5.11.1)(less@4.4.0)(lightningcss@1.32.0)(magicast@0.5.3)(optionator@0.9.4)(oxlint@1.56.0)(rollup-plugin-visualizer@7.0.1(rollup@4.62.0))(rollup@4.62.0)(sass@1.90.0)(srvx@0.11.16)(terser@5.48.0)(tsx@4.22.0)(typescript@5.9.3)(vite@7.3.5(@types/node@22.19.19)(jiti@2.7.0)(less@4.4.0)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.48.0)(tsx@4.22.0)(yaml@2.9.0))(yaml@2.9.0): + nuxt@3.21.7(@parcel/watcher@2.5.6)(@types/node@22.19.19)(@upstash/redis@1.38.0)(@vercel/functions@3.6.0(@aws-sdk/credential-provider-web-identity@3.972.49))(@vue/compiler-sfc@3.5.38)(aws4fetch@1.0.20)(cac@6.7.14)(commander@13.1.0)(db0@0.3.4)(ioredis@5.11.1)(less@4.4.0)(lightningcss@1.32.0)(magicast@0.5.3)(optionator@0.9.4)(oxlint@1.56.0)(rolldown@1.1.3)(rollup-plugin-visualizer@7.0.1(rolldown@1.1.3)(rollup@4.62.0))(rollup@4.62.0)(sass@1.90.0)(srvx@0.11.16)(terser@5.48.0)(tsx@4.22.0)(typescript@5.9.3)(vite@7.3.5(@types/node@22.19.19)(jiti@2.7.0)(less@4.4.0)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.48.0)(tsx@4.22.0)(yaml@2.9.0))(yaml@2.9.0): dependencies: '@dxup/nuxt': 0.4.1(magicast@0.5.3)(typescript@5.9.3) '@nuxt/cli': 3.35.2(@nuxt/schema@3.21.7)(cac@6.7.14)(commander@13.1.0)(magicast@0.5.3) '@nuxt/devtools': 3.2.4(vite@7.3.5(@types/node@22.19.19)(jiti@2.7.0)(less@4.4.0)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.48.0)(tsx@4.22.0)(yaml@2.9.0))(vue@3.5.38(typescript@5.9.3)) '@nuxt/kit': 3.21.7(magicast@0.5.3) - '@nuxt/nitro-server': 3.21.7(@upstash/redis@1.38.0)(@vercel/functions@3.6.0(@aws-sdk/credential-provider-web-identity@3.972.49))(aws4fetch@1.0.20)(db0@0.3.4)(ioredis@5.11.1)(magicast@0.5.3)(nuxt@3.21.7(@parcel/watcher@2.5.6)(@types/node@22.19.19)(@upstash/redis@1.38.0)(@vercel/functions@3.6.0(@aws-sdk/credential-provider-web-identity@3.972.49))(@vue/compiler-sfc@3.5.38)(aws4fetch@1.0.20)(cac@6.7.14)(commander@13.1.0)(db0@0.3.4)(ioredis@5.11.1)(less@4.4.0)(lightningcss@1.32.0)(magicast@0.5.3)(optionator@0.9.4)(oxlint@1.56.0)(rollup-plugin-visualizer@7.0.1(rollup@4.62.0))(rollup@4.62.0)(sass@1.90.0)(srvx@0.11.16)(terser@5.48.0)(tsx@4.22.0)(typescript@5.9.3)(vite@7.3.5(@types/node@22.19.19)(jiti@2.7.0)(less@4.4.0)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.48.0)(tsx@4.22.0)(yaml@2.9.0))(yaml@2.9.0))(oxc-parser@0.132.0)(srvx@0.11.16)(typescript@5.9.3) + '@nuxt/nitro-server': 3.21.7(@upstash/redis@1.38.0)(@vercel/functions@3.6.0(@aws-sdk/credential-provider-web-identity@3.972.49))(aws4fetch@1.0.20)(db0@0.3.4)(ioredis@5.11.1)(magicast@0.5.3)(nuxt@3.21.7(@parcel/watcher@2.5.6)(@types/node@22.19.19)(@upstash/redis@1.38.0)(@vercel/functions@3.6.0(@aws-sdk/credential-provider-web-identity@3.972.49))(@vue/compiler-sfc@3.5.38)(aws4fetch@1.0.20)(cac@6.7.14)(commander@13.1.0)(db0@0.3.4)(ioredis@5.11.1)(less@4.4.0)(lightningcss@1.32.0)(magicast@0.5.3)(optionator@0.9.4)(oxlint@1.56.0)(rolldown@1.1.3)(rollup-plugin-visualizer@7.0.1(rolldown@1.1.3)(rollup@4.62.0))(rollup@4.62.0)(sass@1.90.0)(srvx@0.11.16)(terser@5.48.0)(tsx@4.22.0)(typescript@5.9.3)(vite@7.3.5(@types/node@22.19.19)(jiti@2.7.0)(less@4.4.0)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.48.0)(tsx@4.22.0)(yaml@2.9.0))(yaml@2.9.0))(oxc-parser@0.132.0)(rolldown@1.1.3)(srvx@0.11.16)(typescript@5.9.3) '@nuxt/schema': 3.21.7 '@nuxt/telemetry': 2.8.0(@nuxt/kit@3.21.7(magicast@0.5.3)) - '@nuxt/vite-builder': 3.21.7(@types/node@22.19.19)(less@4.4.0)(lightningcss@1.32.0)(magicast@0.5.3)(nuxt@3.21.7(@parcel/watcher@2.5.6)(@types/node@22.19.19)(@upstash/redis@1.38.0)(@vercel/functions@3.6.0(@aws-sdk/credential-provider-web-identity@3.972.49))(@vue/compiler-sfc@3.5.38)(aws4fetch@1.0.20)(cac@6.7.14)(commander@13.1.0)(db0@0.3.4)(ioredis@5.11.1)(less@4.4.0)(lightningcss@1.32.0)(magicast@0.5.3)(optionator@0.9.4)(oxlint@1.56.0)(rollup-plugin-visualizer@7.0.1(rollup@4.62.0))(rollup@4.62.0)(sass@1.90.0)(srvx@0.11.16)(terser@5.48.0)(tsx@4.22.0)(typescript@5.9.3)(vite@7.3.5(@types/node@22.19.19)(jiti@2.7.0)(less@4.4.0)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.48.0)(tsx@4.22.0)(yaml@2.9.0))(yaml@2.9.0))(optionator@0.9.4)(oxlint@1.56.0)(rollup-plugin-visualizer@7.0.1(rollup@4.62.0))(rollup@4.62.0)(sass@1.90.0)(terser@5.48.0)(tsx@4.22.0)(typescript@5.9.3)(vue@3.5.38(typescript@5.9.3))(yaml@2.9.0) + '@nuxt/vite-builder': 3.21.7(@types/node@22.19.19)(less@4.4.0)(lightningcss@1.32.0)(magicast@0.5.3)(nuxt@3.21.7(@parcel/watcher@2.5.6)(@types/node@22.19.19)(@upstash/redis@1.38.0)(@vercel/functions@3.6.0(@aws-sdk/credential-provider-web-identity@3.972.49))(@vue/compiler-sfc@3.5.38)(aws4fetch@1.0.20)(cac@6.7.14)(commander@13.1.0)(db0@0.3.4)(ioredis@5.11.1)(less@4.4.0)(lightningcss@1.32.0)(magicast@0.5.3)(optionator@0.9.4)(oxlint@1.56.0)(rolldown@1.1.3)(rollup-plugin-visualizer@7.0.1(rolldown@1.1.3)(rollup@4.62.0))(rollup@4.62.0)(sass@1.90.0)(srvx@0.11.16)(terser@5.48.0)(tsx@4.22.0)(typescript@5.9.3)(vite@7.3.5(@types/node@22.19.19)(jiti@2.7.0)(less@4.4.0)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.48.0)(tsx@4.22.0)(yaml@2.9.0))(yaml@2.9.0))(optionator@0.9.4)(oxlint@1.56.0)(rolldown@1.1.3)(rollup-plugin-visualizer@7.0.1(rolldown@1.1.3)(rollup@4.62.0))(rollup@4.62.0)(sass@1.90.0)(terser@5.48.0)(tsx@4.22.0)(typescript@5.9.3)(vue@3.5.38(typescript@5.9.3))(yaml@2.9.0) '@unhead/vue': 2.1.15(vue@3.5.38(typescript@5.9.3)) '@vue/shared': 3.5.38 c12: 3.3.4(magicast@0.5.3) @@ -38124,7 +38704,7 @@ snapshots: oxc-minify: 0.132.0 oxc-parser: 0.132.0 oxc-transform: 0.132.0 - oxc-walker: 1.0.0(oxc-parser@0.132.0) + oxc-walker: 1.0.0(oxc-parser@0.132.0)(rolldown@1.1.3) pathe: 2.0.3 perfect-debounce: 2.1.0 pkg-types: 2.3.1 @@ -38137,7 +38717,7 @@ snapshots: ultrahtml: 1.6.0 uncrypto: 0.1.3 unctx: 2.5.0 - unimport: 6.3.0(oxc-parser@0.132.0) + unimport: 6.3.0(oxc-parser@0.132.0)(rolldown@1.1.3) unplugin: 3.0.0 unplugin-vue-router: 0.19.2(@vue/compiler-sfc@3.5.38)(vue-router@4.6.4(vue@3.5.38(typescript@5.9.3)))(vue@3.5.38(typescript@5.9.3)) untyped: 2.0.0 @@ -38259,6 +38839,10 @@ snapshots: obug@2.1.3: {} + ocache@0.1.5: + dependencies: + ohash: 2.0.11 + ofetch@1.5.1: dependencies: destr: 2.0.5 @@ -38472,11 +39056,12 @@ snapshots: '@oxc-transform/binding-win32-ia32-msvc': 0.132.0 '@oxc-transform/binding-win32-x64-msvc': 0.132.0 - oxc-walker@1.0.0(oxc-parser@0.132.0): + oxc-walker@1.0.0(oxc-parser@0.132.0)(rolldown@1.1.3): dependencies: magic-regexp: 0.11.0 optionalDependencies: oxc-parser: 0.132.0 + rolldown: 1.1.3 oxfmt@0.41.0: dependencies: @@ -39834,13 +40419,35 @@ snapshots: robust-predicates@3.0.3: {} - rollup-plugin-visualizer@7.0.1(rollup@4.62.0): + rolldown@1.1.3: + dependencies: + '@oxc-project/types': 0.137.0 + '@rolldown/pluginutils': 1.0.1 + optionalDependencies: + '@rolldown/binding-android-arm64': 1.1.3 + '@rolldown/binding-darwin-arm64': 1.1.3 + '@rolldown/binding-darwin-x64': 1.1.3 + '@rolldown/binding-freebsd-x64': 1.1.3 + '@rolldown/binding-linux-arm-gnueabihf': 1.1.3 + '@rolldown/binding-linux-arm64-gnu': 1.1.3 + '@rolldown/binding-linux-arm64-musl': 1.1.3 + '@rolldown/binding-linux-ppc64-gnu': 1.1.3 + '@rolldown/binding-linux-s390x-gnu': 1.1.3 + '@rolldown/binding-linux-x64-gnu': 1.1.3 + '@rolldown/binding-linux-x64-musl': 1.1.3 + '@rolldown/binding-openharmony-arm64': 1.1.3 + '@rolldown/binding-wasm32-wasi': 1.1.3 + '@rolldown/binding-win32-arm64-msvc': 1.1.3 + '@rolldown/binding-win32-x64-msvc': 1.1.3 + + rollup-plugin-visualizer@7.0.1(rolldown@1.1.3)(rollup@4.62.0): dependencies: open: 11.0.0 picomatch: 4.0.4 source-map: 0.7.6 yargs: 18.0.0 optionalDependencies: + rolldown: 1.1.3 rollup: 4.62.0 rollup@3.30.0: @@ -41694,7 +42301,7 @@ snapshots: transitivePeerDependencies: - rollup - unimport@6.3.0(oxc-parser@0.132.0): + unimport@6.3.0(oxc-parser@0.132.0)(rolldown@1.1.3): dependencies: acorn: 8.17.0 escape-string-regexp: 5.0.0 @@ -41712,6 +42319,7 @@ snapshots: unplugin-utils: 0.3.1 optionalDependencies: oxc-parser: 0.132.0 + rolldown: 1.1.3 unist-util-find-after@5.0.0: dependencies: @@ -41819,6 +42427,17 @@ snapshots: db0: 0.3.4 ioredis: 5.11.1 + unstorage@2.0.0-alpha.7(@upstash/redis@1.38.0)(@vercel/functions@3.6.0(@aws-sdk/credential-provider-web-identity@3.972.49))(aws4fetch@1.0.20)(chokidar@5.0.0)(db0@0.3.4)(ioredis@5.11.1)(lru-cache@11.5.1)(ofetch@2.0.0-alpha.3): + optionalDependencies: + '@upstash/redis': 1.38.0 + '@vercel/functions': 3.6.0(@aws-sdk/credential-provider-web-identity@3.972.49) + aws4fetch: 1.0.20 + chokidar: 5.0.0 + db0: 0.3.4 + ioredis: 5.11.1 + lru-cache: 11.5.1 + ofetch: 2.0.0-alpha.3 + until-async@3.0.2: {} untun@0.1.3: @@ -42186,6 +42805,11 @@ snapshots: optionalDependencies: vite: 6.4.3(@types/node@22.19.19)(jiti@2.7.0)(less@4.4.0)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.48.0)(tsx@4.22.0)(yaml@2.9.0) + vitefu@1.1.3(vite@7.3.5(@types/node@22.19.19)(jiti@2.7.0)(less@4.4.0)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.48.0)(tsx@4.22.0)(yaml@2.9.0)): + optionalDependencies: + vite: 7.3.5(@types/node@22.19.19)(jiti@2.7.0)(less@4.4.0)(lightningcss@1.32.0)(sass@1.90.0)(terser@5.48.0)(tsx@4.22.0)(yaml@2.9.0) + optional: true + vitest@3.2.4(@edge-runtime/vm@5.0.0)(@types/debug@4.1.13)(@types/node@22.19.19)(jiti@2.7.0)(jsdom@26.1.0)(less@4.4.0)(lightningcss@1.32.0)(msw@2.14.6(@types/node@22.19.19)(typescript@5.8.3))(sass@1.90.0)(terser@5.43.1)(tsx@4.19.2)(yaml@2.9.0): dependencies: '@types/chai': 5.2.3 diff --git a/tsconfig.json b/tsconfig.json index c294ad1ddb83..41d0ef6018ab 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -87,6 +87,9 @@ { "path": "packages/harness-deepagents" }, + { + "path": "packages/harness-eve" + }, { "path": "packages/harness-opencode" },