UNPKG

eve

Version:

Filesystem-first framework for durable backend AI agents that run anywhere.

1,527 lines (1,502 loc) 59.6 kB
import { computed, onScopeDispose, shallowRef } from "vue"; import { z } from "#compiled/zod/index.js"; //#region src/protocol/routes.ts const EVE_ROUTE_PREFIX = "/eve/v1"; const EVE_HEALTH_ROUTE_PATH = `${EVE_ROUTE_PREFIX}/health`; const EVE_INFO_ROUTE_PATH = `${EVE_ROUTE_PREFIX}/info`; const EVE_CREATE_SESSION_ROUTE_PATH = `${EVE_ROUTE_PREFIX}/session`; const EVE_CONTINUE_SESSION_ROUTE_PATTERN = `${EVE_ROUTE_PREFIX}/session/:sessionId`; const EVE_MESSAGE_STREAM_ROUTE_PATTERN = `${EVE_ROUTE_PREFIX}/session/:sessionId/stream`; const EVE_CANCEL_TURN_ROUTE_PATTERN = `${EVE_ROUTE_PREFIX}/session/:sessionId/cancel`; const EVE_DEV_DISPATCH_SCHEDULE_ROUTE_PATTERN = `${EVE_ROUTE_PREFIX}/dev/schedules/:scheduleId`; const EVE_DEV_RUNTIME_ARTIFACTS_ROUTE_PATH = `${EVE_ROUTE_PREFIX}/dev/runtime-artifacts`; const EVE_DEV_RUNTIME_ARTIFACTS_REBUILD_ROUTE_PATH = `${EVE_DEV_RUNTIME_ARTIFACTS_ROUTE_PATH}/rebuild`; const EVE_CONNECTION_CALLBACK_ROUTE_PATTERN = `${EVE_ROUTE_PREFIX}/connections/:name/callback/:token`; const EVE_CALLBACK_ROUTE_PATTERN = `${EVE_ROUTE_PREFIX}/callback/:token`; function createEveMessageStreamRoutePath(sessionId) { return `${EVE_ROUTE_PREFIX}/session/${encodeURIComponent(sessionId)}/stream`; } function createEveContinueSessionRoutePath(sessionId) { return `${EVE_ROUTE_PREFIX}/session/${encodeURIComponent(sessionId)}`; } function createEveCancelTurnRoutePath(sessionId) { return `${EVE_ROUTE_PREFIX}/session/${encodeURIComponent(sessionId)}/cancel`; } //#endregion //#region src/client/agent-info-error.ts var AgentInfoResponseError = class extends Error { issues; constructor(issues = []) { const detail = issues.length === 0 ? "" : ` (${issues.join("; ")})`; super(`The server returned an unrecognized response from the eve agent info route.${detail}`); this.name = "AgentInfoResponseError"; this.issues = issues; } }; //#endregion //#region src/internal/http/basic-auth.ts function encodeBasicCredentials(username, password) { const bytes = new TextEncoder().encode(`${username}:${password}`); const binaryString = Array.from(bytes, (byte) => String.fromCodePoint(byte)).join(""); return btoa(binaryString); } //#endregion //#region src/client/agent-info-schema.ts const source = z.object({ exportName: z.string().optional(), logicalPath: z.string(), sourceId: z.string().optional(), sourceKind: z.string() }); const entry = source.extend({ name: z.string() }); const modelRouting = z.discriminatedUnion("kind", [z.object({ kind: z.literal("gateway"), target: z.string(), byok: z.string().optional() }), z.object({ kind: z.literal("external"), provider: z.string() })]); const modelEndpoint = z.union([ z.object({ kind: z.literal("external"), provider: z.string() }), z.object({ kind: z.literal("gateway"), connected: z.literal(true), credential: z.enum(["api-key", "oidc"]) }), z.object({ kind: z.literal("gateway"), connected: z.literal(false) }) ]); const tool = entry.extend({ description: z.string(), hasAuth: z.boolean(), hasExecute: z.boolean(), hasModelOutputProjection: z.boolean(), hasOutputSchema: z.boolean(), inputSchema: z.unknown(), origin: z.enum(["authored", "framework"]), outputSchema: z.unknown().optional(), replacesFrameworkTool: z.boolean(), requiresApproval: z.boolean() }); const frameworkTool = tool.extend({ disabledByAuthor: z.boolean(), replacedByAuthoredTool: z.boolean(), status: z.enum([ "active", "disabled", "replaced" ]) }); const dynamicResolver = source.extend({ eventNames: z.array(z.string()), origin: z.enum(["authored", "framework"]), slug: z.string() }); const skill = entry.extend({ description: z.string(), license: z.string().optional(), markdown: z.string(), metadata: z.record(z.string(), z.string()).optional() }); const instructions = entry.extend({ markdown: z.string() }); const schedule = entry.extend({ cron: z.string(), hasRun: z.boolean(), markdown: z.string().optional() }); const subagent = entry.extend({ description: z.string(), entryPath: z.string(), nodeId: z.string(), rootPath: z.string(), summary: z.object({ channels: z.number(), connections: z.number(), hooks: z.number(), instructions: z.boolean(), schedules: z.number(), skills: z.number(), tools: z.number() }) }); const channel = entry.extend({ adapterKind: z.string().optional(), method: z.string(), origin: z.enum(["authored", "framework"]), urlPath: z.string() }); const frameworkChannel = channel.extend({ disabledByAuthor: z.boolean(), replacedByAuthoredChannel: z.boolean(), status: z.enum([ "active", "disabled", "replaced" ]) }); const connection = source.extend({ connectionName: z.string(), description: z.string(), hasApproval: z.boolean(), hasAuthorization: z.boolean(), hasHeaders: z.boolean(), protocol: z.string(), toolFilter: z.unknown().optional(), url: z.string() }); const hook = source.extend({ eventNames: z.array(z.string()), slug: z.string() }); const sandbox = source.extend({ backendKind: z.string().optional(), description: z.string().optional(), hasBootstrap: z.boolean(), hasOnSession: z.boolean(), revalidationKey: z.string().optional(), sourceHash: z.string().optional() }); const AgentInfoResultSchema = z.object({ agent: z.object({ agentRoot: z.string(), appRoot: z.string(), configSource: source.optional(), description: z.string().optional(), model: z.object({ contextWindowTokens: z.number().optional(), id: z.string(), providerOptions: z.unknown().optional(), reasoning: z.enum([ "provider-default", "none", "minimal", "low", "medium", "high", "xhigh" ]).optional().catch(void 0), source: source.optional(), routing: modelRouting.optional(), endpoint: modelEndpoint.optional() }), name: z.string(), outputSchema: z.unknown().optional() }), capabilities: z.object({ devRoutes: z.boolean() }), channels: z.object({ authored: z.array(channel), available: z.array(channel), disabledFramework: z.array(z.string()), framework: z.array(frameworkChannel) }), connections: z.array(connection), diagnostics: z.object({ discoveryErrors: z.number(), discoveryWarnings: z.number() }), hooks: z.array(hook), instructions: z.object({ dynamic: z.array(dynamicResolver), static: instructions.nullable() }), kind: z.literal("eve-agent-info"), mode: z.enum(["development", "production"]), sandbox: sandbox.nullable(), schedules: z.array(schedule), skills: z.object({ dynamic: z.array(dynamicResolver), static: z.array(skill) }), subagents: z.object({ local: z.array(subagent), total: z.number() }), tools: z.object({ authored: z.array(tool), available: z.array(tool), disabledFramework: z.array(z.string()), dynamic: z.array(dynamicResolver), framework: z.array(frameworkTool), reserved: z.array(z.string()) }), version: z.literal(1), workflow: z.object({ enabled: z.boolean(), toolName: z.string() }), workspace: z.object({ resourceRoot: z.unknown(), rootEntries: z.array(z.string()) }) }); //#endregion //#region src/shared/guards.ts function isObject(value) { return typeof value === "object" && value !== null && !Array.isArray(value); } //#endregion //#region src/client/client-error.ts var ClientError = class extends Error { status; body; headers; constructor(status, body, headers) { let message = body || `Server returned ${status}.`; try { const parsed = JSON.parse(body); if (isObject(parsed) && typeof parsed.error === "string") message = parsed.error; } catch {} super(message); this.name = "ClientError"; this.status = status; this.body = body; this.headers = Object.freeze(Object.fromEntries(new Headers(headers).entries())); } }; //#endregion //#region src/shared/errors.ts function toErrorMessage(error) { if (error instanceof Error) return error.message; if (typeof error === "string") return error; if (error === null || error === void 0) return String(error); if (isObject(error)) { if (typeof error.message === "string" && error.message.length > 0) return error.message; return safeJsonStringify(error); } return String(error); } function toError(raw) { if (raw instanceof Error) return raw; const error = new Error(toErrorMessage(raw)); if (!isObject(raw)) return error; if (typeof raw.name === "string" && raw.name.length > 0) error.name = raw.name; if (typeof raw.stack === "string" && raw.stack.length > 0) error.stack = raw.stack; if ("cause" in raw && raw.cause !== void 0 && raw.cause !== raw) error.cause = raw.cause; return error; } function safeJsonStringify(value) { try { return JSON.stringify(value) ?? String(value); } catch { return String(value); } } //#endregion //#region src/protocol/message.ts const EVE_SESSION_ID_HEADER = "x-eve-session-id"; const textEncoder = new TextEncoder(); function isCurrentTurnBoundaryEvent(event) { return event.type === "session.completed" || event.type === "session.failed" || event.type === "session.waiting"; } //#endregion //#region src/protocol/cancel-turn.ts const CancelTurnResponseSchema = z.object({ ok: z.literal(true), sessionId: z.string().min(1), status: z.enum(["accepted", "no_active_turn"]) }); //#endregion //#region src/client/output-schema.ts function extractCompletedResult(events) { let result; for (const event of events) if (isResultCompletedEvent(event)) result = event.data.result; return result; } function isResultCompletedEvent(event) { return event.type === "result.completed"; } //#endregion //#region src/client/session-utils.ts function createInitialSessionState() { return { streamIndex: 0 }; } function advanceSession(input) { const boundaryEvent = findBoundaryEvent(input.events); const streamIndex = input.session.streamIndex + input.events.length; if (boundaryEvent?.type === "session.waiting" || input.preserveCompletedSessions === true && boundaryEvent?.type === "session.completed") return { continuationToken: boundaryEvent?.type === "session.waiting" ? boundaryEvent.data.continuationToken : input.continuationToken ?? input.session.continuationToken, sessionId: input.sessionId, streamIndex }; if (boundaryEvent === void 0) return { continuationToken: input.continuationToken ?? input.session.continuationToken, sessionId: input.sessionId, streamIndex }; return createInitialSessionState(); } function extractCompletedMessage(events) { let lastMessage; for (const event of events) if (isFinalMessageCompleted(event)) lastMessage = event.data.message ?? void 0; return lastMessage; } function deriveResultStatus(events) { const boundary = findBoundaryEvent(events); if (boundary?.type === "session.waiting") return "waiting"; if (boundary?.type === "session.failed") return "failed"; return "completed"; } function extractInputRequests(events) { const requests = []; for (const event of events) if (event.type === "input.requested") requests.push(...event.data.requests); return requests; } function findBoundaryEvent(events) { for (let i = events.length - 1; i >= 0; i--) { const event = events[i]; if (event !== void 0 && isCurrentTurnBoundaryEvent(event)) return event; } } function isFinalMessageCompleted(event) { return event.type === "message.completed" && event.data.finishReason !== "tool-calls"; } //#endregion //#region src/client/message-response.ts var MessageResponse = class { continuationToken; sessionId; #consumed = false; #createStream; constructor(input) { this.continuationToken = input.continuationToken; this.sessionId = input.sessionId; this.#createStream = input.createStream; } async result() { const events = []; for await (const event of this) events.push(event); return { data: extractCompletedResult(events), events, inputRequests: extractInputRequests(events), message: extractCompletedMessage(events), sessionId: this.sessionId, status: deriveResultStatus(events) }; } [Symbol.asyncIterator]() { if (this.#consumed) throw new Error("MessageResponse has already been consumed."); this.#consumed = true; return this.#createStream(); } }; //#endregion //#region src/client/ndjson.ts function isStreamDisconnectError(error) { if (error instanceof DOMException) return error.name === "AbortError"; if (!(error instanceof Error)) return false; const errorCode = "code" in error && typeof error.code === "string" ? error.code : void 0; return error.name === "AbortError" || error.message === "terminated" || errorCode === "UND_ERR_SOCKET" || error instanceof TypeError && /^(?:failed to fetch|fetch failed)$/i.test(error.message) || /abort|cancel|disconnect|premature close|socket|terminated/i.test(error.message); } async function* readNdjsonStream(body) { const reader = body.getReader(); const decoder = new TextDecoder(); let buffer = ""; let reachedEof = false; try { while (true) { const result = await reader.read(); if (result.done) { reachedEof = true; buffer += decoder.decode(); break; } if (result.value) buffer += decoder.decode(result.value, { stream: true }); let newlineIndex = buffer.indexOf("\n"); while (newlineIndex !== -1) { const line = buffer.slice(0, newlineIndex).trim(); buffer = buffer.slice(newlineIndex + 1); if (line.length > 0) yield JSON.parse(line); newlineIndex = buffer.indexOf("\n"); } } const trailing = buffer.trim(); if (trailing.length > 0) yield JSON.parse(trailing); } finally { if (!reachedEof) await reader.cancel().catch(() => {}); reader.releaseLock(); } } //#endregion //#region src/client/url.ts function createClientUrl(host, routePath, searchParams) { const normalizedRoute = routePath.startsWith("/") ? routePath : `/${routePath}`; if (isAbsoluteUrl(host)) { const url = new URL(host); url.pathname = `${trimTrailingSlash(url.pathname)}${normalizedRoute}`; mergeSearchParams(url.searchParams, searchParams); url.hash = ""; return url.toString(); } const url = new URL(host, "http://eve.local"); const basePath = trimTrailingSlash(url.pathname); mergeSearchParams(url.searchParams, searchParams); return `${basePath}${normalizedRoute}${formatSearch(url.searchParams)}`; } function isAbsoluteUrl(value) { return /^[a-z][a-z\d+\-.]*:/i.test(value); } function trimTrailingSlash(value) { if (value === "/") return ""; return value.endsWith("/") ? value.slice(0, -1) : value; } function mergeSearchParams(target, searchParams) { if (searchParams === void 0) return; for (const [name, value] of Object.entries(searchParams)) target.set(name, value); } function formatSearch(searchParams) { const value = searchParams.toString(); return value.length === 0 ? "" : `?${value}`; } //#endregion //#region src/client/open-stream.ts const DEFAULT_STREAM_RECONNECT_POLICY = { retryableErrorStatuses: new Set([ 404, 409, 425, 500, 502, 503, 504 ]), streamIdleReconnectPolicy: { baseDelayMs: 250, maxAttempts: 5, maxDelayMs: 4e3 }, streamOpenReconnectPolicy: { baseDelayMs: 250, maxAttempts: 12, maxDelayMs: 5e3 } }; const NO_STREAM_RECONNECT_POLICY = { ...DEFAULT_STREAM_RECONNECT_POLICY, streamIdleReconnectPolicy: { ...DEFAULT_STREAM_RECONNECT_POLICY.streamIdleReconnectPolicy, maxAttempts: 0 }, streamOpenReconnectPolicy: { ...DEFAULT_STREAM_RECONNECT_POLICY.streamOpenReconnectPolicy, maxAttempts: 1 } }; function resolveRetryPolicy(policy, defaults) { return { ...defaults, ...policy }; } function resolveStreamReconnectPolicy(policy) { if (policy && "reconnect" in policy && policy.reconnect === false) return NO_STREAM_RECONNECT_POLICY; const configured = policy; return { retryableErrorStatuses: configured?.retryableErrorStatuses ? new Set(configured.retryableErrorStatuses) : DEFAULT_STREAM_RECONNECT_POLICY.retryableErrorStatuses, streamIdleReconnectPolicy: resolveRetryPolicy(configured?.streamIdleReconnectPolicy, DEFAULT_STREAM_RECONNECT_POLICY.streamIdleReconnectPolicy), streamOpenReconnectPolicy: resolveRetryPolicy(configured?.streamOpenReconnectPolicy, DEFAULT_STREAM_RECONNECT_POLICY.streamOpenReconnectPolicy) }; } async function* followStreamIterable(input) { const retryPolicy = resolveStreamReconnectPolicy(input.streamReconnectPolicy); const idleRetryPolicy = retryPolicy.streamIdleReconnectPolicy; let startIndex = input.startIndex; let reconnectDelayMs = idleRetryPolicy.baseDelayMs; let idleReconnects = 0; let initialConnection = true; while (true) { let body; try { body = await openStreamBody({ ...input, retryPolicy, startIndex }); } catch (error) { if (input.signal?.aborted) return; throw error; } let deliveredEvent = false; try { for await (const event of readNdjsonStream(body)) { startIndex += 1; deliveredEvent = true; reconnectDelayMs = idleRetryPolicy.baseDelayMs; idleReconnects = 0; yield event; } } catch (error) { if (!isStreamDisconnectError(error)) throw error; } if (input.signal?.aborted || input.startIndex < 0 || idleRetryPolicy.maxAttempts === 0) return; if (!deliveredEvent && !initialConnection && (idleReconnects += 1) >= idleRetryPolicy.maxAttempts) return; initialConnection = false; await sleep$1(reconnectDelayMs, input.signal); if (input.signal?.aborted) return; reconnectDelayMs = Math.min(reconnectDelayMs * 2, idleRetryPolicy.maxDelayMs); } } async function openStreamBody(input) { const retryPolicy = input.retryPolicy ?? DEFAULT_STREAM_RECONNECT_POLICY; const openRetryPolicy = retryPolicy.streamOpenReconnectPolicy; let lastStatus; let lastBody; let lastHeaders; let retryDelayMs = openRetryPolicy.baseDelayMs; for (let attempt = 0; attempt < openRetryPolicy.maxAttempts; attempt += 1) { const url = createClientUrl(input.host, createEveMessageStreamRoutePath(input.sessionId), input.startIndex !== 0 ? { startIndex: String(input.startIndex) } : void 0); const headers = await input.resolveHeaders(); let response; try { response = await fetch(url, { headers, redirect: input.redirect, signal: input.signal ?? null }); } catch (error) { if (input.signal?.aborted || !isStreamDisconnectError(error) || attempt === openRetryPolicy.maxAttempts - 1) throw error; await sleep$1(retryDelayMs, input.signal); retryDelayMs = Math.min(retryDelayMs * 2, openRetryPolicy.maxDelayMs); continue; } if (response.ok) { if (!response.body) throw new ClientError(response.status, "Response body is null.", response.headers); return response.body; } lastStatus = response.status; lastBody = await response.text(); lastHeaders = response.headers; if (!retryPolicy.retryableErrorStatuses.has(response.status)) throw new ClientError(response.status, lastBody, response.headers); if (attempt < openRetryPolicy.maxAttempts - 1) { await sleep$1(retryDelayMs, input.signal); retryDelayMs = Math.min(retryDelayMs * 2, openRetryPolicy.maxDelayMs); } } throw new ClientError(lastStatus ?? 0, lastBody ?? "Failed to open message stream.", lastHeaders); } async function sleep$1(ms, signal) { if (signal?.aborted) return; await new Promise((resolve) => { const onAbort = () => { clearTimeout(timer); resolve(); }; const timer = setTimeout(() => { signal?.removeEventListener("abort", onAbort); resolve(); }, ms); signal?.addEventListener("abort", onAbort, { once: true }); }); } //#endregion //#region src/shared/json.ts const INVALID_JSON_VALUE_CANDIDATE = Symbol("invalid-json-value-candidate"); const JSON_VALUE_ERROR_MESSAGE = "Expected a JSON-serializable value."; const JSON_OBJECT_ERROR_MESSAGE = "Expected a JSON-serializable object."; function parseJsonValue(value) { const normalized = normalizeJsonValueCandidate(value); if (normalized === INVALID_JSON_VALUE_CANDIDATE) throw new TypeError(JSON_VALUE_ERROR_MESSAGE); return normalized; } function parseJsonObject(value) { const normalized = parseJsonValue(value); if (!isJsonObjectValue(normalized)) throw new TypeError(JSON_OBJECT_ERROR_MESSAGE); return normalized; } function normalizeJsonValueCandidate(value, seen = /* @__PURE__ */ new WeakSet()) { if (value === null || typeof value === "boolean" || typeof value === "string") return value; if (typeof value === "number") return Number.isFinite(value) ? value : INVALID_JSON_VALUE_CANDIDATE; if (Array.isArray(value)) { const normalizedItems = []; for (const item of value) { const normalizedItem = normalizeJsonValueCandidate(item, seen); if (normalizedItem === INVALID_JSON_VALUE_CANDIDATE) return INVALID_JSON_VALUE_CANDIDATE; normalizedItems.push(normalizedItem); } return normalizedItems; } if (typeof value !== "object" || value === void 0) return INVALID_JSON_VALUE_CANDIDATE; if (!isPlainObject(value)) return INVALID_JSON_VALUE_CANDIDATE; if (seen.has(value)) return INVALID_JSON_VALUE_CANDIDATE; seen.add(value); const normalized = {}; for (const [key, entry] of Object.entries(value)) { if (entry === void 0) continue; const normalizedEntry = normalizeJsonValueCandidate(entry, seen); if (normalizedEntry === INVALID_JSON_VALUE_CANDIDATE) return INVALID_JSON_VALUE_CANDIDATE; normalized[key] = normalizedEntry; } seen.delete(value); return normalized; } function isJsonObjectValue(value) { return value !== null && !Array.isArray(value) && typeof value === "object"; } function isPlainObject(value) { const prototype = Object.getPrototypeOf(value); return prototype === null || prototype === Object.prototype; } //#endregion //#region src/shared/tool-schema.ts const JSON_SCHEMA_TARGET = "draft-07"; function serializeOutputSchema(source) { return serializeSchema(source, "output"); } const UNSPECIFIED_INPUT_SCHEMA = z.fromJSONSchema({}); function serializeSchema(source, direction) { if (source === null || source === void 0) return source; return toJsonObject(source, direction); } function toJsonObject(source, direction) { const { $schema: _schemaVersion, ...canonical } = isStandardJsonSchema(source) ? parseJsonObject(source["~standard"].jsonSchema[direction]({ target: JSON_SCHEMA_TARGET })) : parseJsonObject(source); return canonical; } function isStandardJsonSchema(value) { return value !== null && typeof value === "object" && "~standard" in value; } //#endregion //#region src/client/session.ts const DELIVER_RETRY_ATTEMPTS = 10; const DELIVER_RETRY_DELAY_MS = 200; var ClientSession = class { #context; #state; constructor(context, state) { this.#context = context; this.#state = state; } get state() { return this.#state; } async send(input) { const payload = normalizeSendTurnInput(input); const state = this.#state; const { continuationToken, sessionId } = await this.#postTurn(payload, state); if (this.#state === state) this.#state = { ...state, sessionId }; return new MessageResponse({ continuationToken, createStream: () => this.#createEventStream(sessionId, continuationToken, state, payload), sessionId }); } async cancel(options) { const sessionId = this.#state.sessionId; if (!sessionId) throw new Error("Session has no session ID. Send a message first."); const url = createClientUrl(this.#context.host, createEveCancelTurnRoutePath(sessionId)); const headers = await this.#context.resolveHeaders(); headers.set("content-type", "application/json"); const response = await fetch(url, withRedirectPolicy$1({ headers, method: "POST", body: options ? JSON.stringify(options) : void 0 }, this.#context.redirect)); const body = await response.text(); if (!response.ok) throw new ClientError(response.status, body, response.headers); let payload; try { payload = JSON.parse(body); } catch { throw new Error(`Cancel route returned invalid JSON (${response.status}).`); } const result = CancelTurnResponseSchema.safeParse(payload); if (!result.success || result.data.sessionId !== sessionId) throw new Error(`Cancel route returned an invalid response (${response.status}).`); return { sessionId: result.data.sessionId, status: result.data.status }; } stream(options) { const sessionId = this.#state.sessionId; if (!sessionId) throw new Error("Session has no session ID. Send a message first."); return this.#streamAndAdvance(sessionId, options); } async #postTurn(input, session) { const routePath = session.sessionId ? createEveContinueSessionRoutePath(session.sessionId) : EVE_CREATE_SESSION_ROUTE_PATH; const url = createClientUrl(this.#context.host, routePath); const headers = await this.#context.resolveHeaders(input.headers); headers.set("content-type", "application/json"); const body = createHandleMessageBody({ input, outputSchema: serializeOutputSchema(input.outputSchema), session }); if (body === null) throw new Error("Session.send requires a non-empty message, inputResponses, or both."); const response = await postTurnWithRetry({ body: JSON.stringify(body), headers, mustDeliver: (input.inputResponses?.length ?? 0) > 0, redirect: this.#context.redirect, signal: input.signal, url }); const payload = await response.json(); const sessionId = (typeof payload.sessionId === "string" ? payload.sessionId : void 0) ?? response.headers.get("x-eve-session-id")?.trim() ?? session.sessionId; if (!sessionId) throw new Error("Message route did not return a session id."); return { continuationToken: typeof payload.continuationToken === "string" ? payload.continuationToken : void 0, sessionId }; } async *#createEventStream(sessionId, continuationToken, initialState, input) { const events = []; try { for await (const event of followStreamIterable({ host: this.#context.host, resolveHeaders: () => this.#context.resolveHeaders(input.headers), redirect: this.#context.redirect, streamReconnectPolicy: input.streamReconnectPolicy, sessionId, signal: input.signal, startIndex: initialState.sessionId === sessionId ? initialState.streamIndex : 0 })) { events.push(event); yield event; if (isCurrentTurnBoundaryEvent(event)) break; } } finally { this.#state = advanceSession({ continuationToken, events, preserveCompletedSessions: this.#context.preserveCompletedSessions, sessionId, session: initialState }); } } async *#streamAndAdvance(sessionId, options) { const initialState = this.#state; const streamIndex = options?.startIndex ?? initialState.streamIndex; const events = []; try { for await (const event of followStreamIterable({ host: this.#context.host, resolveHeaders: () => this.#context.resolveHeaders(), redirect: this.#context.redirect, streamReconnectPolicy: options?.streamReconnectPolicy, sessionId, signal: options?.signal, startIndex: streamIndex })) { events.push(event); yield event; } } finally { if (streamIndex >= 0) this.#state = advanceSession({ continuationToken: initialState.continuationToken, events, preserveCompletedSessions: this.#context.preserveCompletedSessions, session: { ...initialState, sessionId, streamIndex }, sessionId }); } } }; async function postTurnWithRetry(input) { const attempts = input.mustDeliver ? DELIVER_RETRY_ATTEMPTS : 1; let lastStatus; let lastBody; let lastHeaders; for (let attempt = 0; attempt < attempts; attempt += 1) { const response = await fetch(input.url, { body: input.body, headers: input.headers, method: "POST", redirect: input.redirect, signal: input.signal ?? null }); if (response.ok) return response; lastStatus = response.status; lastBody = await response.text(); lastHeaders = response.headers; if (!isRetryableDeliveryFailure(response.status, lastBody)) throw new ClientError(response.status, lastBody, response.headers); if (attempt < attempts - 1) await sleep(DELIVER_RETRY_DELAY_MS); } throw new ClientError(lastStatus ?? 0, lastBody ?? "Failed to deliver session turn.", lastHeaders); } function isRetryableDeliveryFailure(status, body) { return status === 500 && /target session was not found/i.test(body); } async function sleep(ms) { await new Promise((resolve) => setTimeout(resolve, ms)); } function normalizeSendTurnInput(input) { return typeof input === "string" ? { message: input } : input; } function createHandleMessageBody(input) { const body = {}; if (input.input.message !== void 0) body.message = input.input.message; if (input.input.inputResponses !== void 0 && input.input.inputResponses.length > 0) body.inputResponses = input.input.inputResponses; if (input.input.clientContext !== void 0) body.clientContext = input.input.clientContext; if (input.outputSchema !== void 0) body.outputSchema = input.outputSchema; if (input.session.continuationToken !== void 0) body.continuationToken = input.session.continuationToken; if (Object.keys(body).length === 0) return null; if (input.session.continuationToken === void 0 && body.message === void 0) return null; if (input.session.continuationToken !== void 0 && body.message === void 0 && body.inputResponses === void 0) return null; return body; } function withRedirectPolicy$1(init, redirect) { return redirect === void 0 ? init : { ...init, redirect }; } //#endregion //#region src/client/types.ts const VERCEL_TRUSTED_OIDC_IDP_TOKEN_HEADER = "x-vercel-trusted-oidc-idp-token"; //#endregion //#region src/client/client.ts var Client = class { #auth; #headers; #host; #preserveCompletedSessions; #redirect; constructor(options) { this.#host = options.host; this.#auth = options.auth; this.#headers = options.headers; this.#preserveCompletedSessions = options.preserveCompletedSessions ?? false; this.#redirect = options.redirect; } async health() { const url = createClientUrl(this.#host, EVE_HEALTH_ROUTE_PATH); const headers = await this.#resolveHeaders(); const response = await fetch(url, withRedirectPolicy({ headers }, this.#redirect)); if (!response.ok) { const body = await response.text(); throw new ClientError(response.status, body, response.headers); } return await response.json(); } async info() { const response = await this.fetch(EVE_INFO_ROUTE_PATH); if (!response.ok) { const body = await response.text(); throw new ClientError(response.status, body, response.headers); } let payload; try { payload = await response.json(); } catch { throw new AgentInfoResponseError(); } const result = AgentInfoResultSchema.safeParse(payload); if (!result.success) throw new AgentInfoResponseError(result.error.issues.slice(0, 5).map((issue) => { const path = issue.path.join("."); return path.length === 0 ? issue.message : `${path}: ${issue.message}`; })); return result.data; } async fetch(path, init = {}) { const url = createClientUrl(this.#host, path); const headers = await this.#resolveHeaders(headersInitToRecord(init.headers)); return await fetch(url, withRedirectPolicy({ ...init, headers }, this.#redirect)); } session(state) { let resolved; if (typeof state === "string") resolved = { continuationToken: state, streamIndex: 0 }; else if (state) resolved = state; else resolved = createInitialSessionState(); return new ClientSession({ host: this.#host, preserveCompletedSessions: this.#preserveCompletedSessions, redirect: this.#redirect, resolveHeaders: (perRequest) => this.#resolveHeaders(perRequest) }, resolved); } async #resolveHeaders(perRequest) { const headers = new Headers(); const [baseHeaders, authHeaders] = await Promise.all([resolveHeadersValue(this.#headers), this.#resolveAuthHeaders()]); for (const [key, value] of Object.entries(baseHeaders)) headers.set(key, value); if (perRequest) for (const [key, value] of Object.entries(perRequest)) headers.set(key, value); for (const [key, value] of Object.entries(authHeaders)) headers.set(key, value); return headers; } async #resolveAuthHeaders() { const auth = this.#auth; if (!auth) return {}; if ("vercelOidc" in auth) { const token = (await resolveTokenValue(auth.vercelOidc.token)).trim(); if (token.length === 0) return {}; return { authorization: `Bearer ${token}`, [VERCEL_TRUSTED_OIDC_IDP_TOKEN_HEADER]: token }; } if ("bearer" in auth) { const token = (await resolveTokenValue(auth.bearer)).trim(); return token.length === 0 ? {} : { authorization: `Bearer ${token}` }; } if ("basic" in auth) { const password = await resolveTokenValue(auth.basic.password); return { authorization: `Basic ${encodeBasicCredentials(auth.basic.username, password)}` }; } return {}; } }; async function resolveTokenValue(value) { return typeof value === "function" ? value() : value; } async function resolveHeadersValue(value) { if (value === void 0) return {}; return typeof value === "function" ? await value() : value; } function headersInitToRecord(headers) { if (headers === void 0) return {}; return Object.fromEntries(new Headers(headers).entries()); } function withRedirectPolicy(init, redirect) { return redirect === void 0 ? init : { ...init, redirect }; } //#endregion //#region src/client/eve-agent-store.ts var EveAgentStore = class { #createSession; #optimistic; #reducer; #subscribers = /* @__PURE__ */ new Set(); #abortController; #callbacks = {}; #data; #error; #events; #operationId = 0; #pendingMessageSubmission; #projectionEvents; #session; #snapshot; #status = "ready"; constructor(init) { this.#createSession = init.session ? void 0 : () => new Client({ auth: init.auth, headers: init.headers, host: init.host ?? "" }).session(init.initialSession); this.#events = [...init.initialEvents ?? []]; this.#projectionEvents = [...this.#events]; this.#optimistic = init.optimistic ?? true; this.#reducer = init.reducer; this.#session = init.session ?? this.#createOwnedSession(); this.#data = this.#reduceProjectionEvents(this.#projectionEvents); this.#snapshot = this.#createSnapshot(); } get snapshot() { return this.#snapshot; } setCallbacks(callbacks) { this.#callbacks = callbacks; } subscribe(callback) { this.#subscribers.add(callback); return () => { this.#subscribers.delete(callback); }; } async send(input) { if (this.#status === "streaming" || this.#status === "submitted") throw new Error("eve session is already processing a turn."); const operationId = this.#startOperation(); const abortController = new AbortController(); this.#abortController = abortController; this.#error = void 0; this.#status = "submitted"; this.#publish(); try { const preparedInput = await this.#callbacks.prepareSend?.(input) ?? input; if (!this.#isCurrentOperation(operationId)) return; this.#projectOptimisticMessage(preparedInput); this.#projectInputResponses(preparedInput); this.#publish(); const response = await this.#session.send({ ...preparedInput, signal: createAbortSignal(preparedInput.signal, abortController.signal) }); let sawEvent = false; for await (const event of response) { if (!this.#isCurrentOperation(operationId)) return; if (!sawEvent) { sawEvent = true; this.#status = "streaming"; } this.#events = [...this.#events, event]; this.#applyServerEvent(event); this.#callbacks.onEvent?.(event); this.#applyTerminalStreamFailure(event); this.#publish(); } if (!this.#isCurrentOperation(operationId)) return; this.#status = this.#error === void 0 ? "ready" : "error"; } catch (error) { if (!this.#isCurrentOperation(operationId)) return; if (isAbortError(error)) { this.#status = "ready"; this.#failPendingMessageSubmission(toError(error)); } else { this.#error = toError(error); this.#status = "error"; this.#failPendingMessageSubmission(this.#error); this.#callbacks.onError?.(this.#error); } } finally { if (this.#isCurrentOperation(operationId)) { this.#abortController = void 0; this.#callbacks.onSessionChange?.(this.#session.state); this.#publish(); this.#callbacks.onFinish?.(this.#snapshot); } } } stop() { this.#abortController?.abort(); } reset() { this.#invalidateOperation(); this.stop(); this.#abortController = void 0; this.#session = this.#createSession?.() ?? this.#session; this.#events = []; this.#pendingMessageSubmission = void 0; this.#projectionEvents = []; this.#data = this.#reducer.initial(); this.#error = void 0; this.#status = "ready"; this.#callbacks.onSessionChange?.(this.#session.state); this.#publish(); } #createOwnedSession() { if (!this.#createSession) throw new Error("Cannot create an owned eve session from an external session."); return this.#createSession(); } #startOperation() { this.#operationId += 1; return this.#operationId; } #invalidateOperation() { this.#operationId += 1; } #isCurrentOperation(operationId) { return this.#operationId === operationId; } #projectOptimisticMessage(input) { if (!this.#optimistic || input.message === void 0) return; const id = createSubmissionId(); const pending = { createdAt: Date.now(), id, message: summarizeUserContent(input.message) }; this.#pendingMessageSubmission = pending; this.#appendProjectionEvent({ data: { createdAt: pending.createdAt, message: pending.message, submissionId: pending.id }, type: "client.message.submitted" }); } #projectInputResponses(input) { if (input.inputResponses === void 0 || input.inputResponses.length === 0) return; this.#appendProjectionEvent({ data: { createdAt: Date.now(), responses: input.inputResponses }, type: "client.input.responded" }); } #applyServerEvent(event) { if (event.type === "message.received" && this.#pendingMessageSubmission !== void 0) { const submissionId = this.#pendingMessageSubmission.id; this.#pendingMessageSubmission = void 0; this.#replaceProjectionEvent((candidate) => candidate.type === "client.message.submitted" && candidate.data.submissionId === submissionId, event); return; } this.#appendProjectionEvent(event); } #applyTerminalStreamFailure(event) { const error = toTerminalStreamFailureError(event); if (error === void 0) return; this.#status = "error"; this.#failPendingMessageSubmission(error); if (this.#error === void 0) { this.#error = error; this.#callbacks.onError?.(error); } } #failPendingMessageSubmission(error) { const pending = this.#pendingMessageSubmission; if (pending === void 0) return; this.#pendingMessageSubmission = void 0; this.#replaceProjectionEvent((event) => event.type === "client.message.submitted" && event.data.submissionId === pending.id, { data: { createdAt: pending.createdAt, error: { message: error.message }, message: pending.message, submissionId: pending.id }, type: "client.message.failed" }); } #appendProjectionEvent(event) { this.#projectionEvents = [...this.#projectionEvents, event]; this.#data = this.#reducer.reduce(this.#data, event); } #replaceProjectionEvent(predicate, replacement) { let replaced = false; this.#projectionEvents = this.#projectionEvents.map((event) => { if (!replaced && predicate(event)) { replaced = true; return replacement; } return event; }); if (!replaced) this.#projectionEvents = [...this.#projectionEvents, replacement]; this.#data = this.#reduceProjectionEvents(this.#projectionEvents); } #reduceProjectionEvents(events) { let data = this.#reducer.initial(); for (const event of events) data = this.#reducer.reduce(data, event); return data; } #createSnapshot() { return { data: this.#data, error: this.#error, events: this.#events, session: this.#session.state, status: this.#status }; } #publish() { this.#snapshot = this.#createSnapshot(); for (const subscriber of this.#subscribers) subscriber(); } }; let submissionSequence = 0; function createSubmissionId() { const randomUUID = globalThis.crypto?.randomUUID; if (randomUUID !== void 0) return randomUUID.call(globalThis.crypto); submissionSequence += 1; return `submission_${submissionSequence.toString()}`; } function createAbortSignal(first, second) { return first ? AbortSignal.any([first, second]) : second; } function summarizeUserContent(message) { if (typeof message === "string") return message; const parts = []; for (const part of message) { if (part.type === "text") { parts.push(part.text); continue; } if (part.type === "file") parts.push(part.filename ? `[file: ${part.filename}]` : "[file]"); } return parts.join("\n"); } function isAbortError(error) { return error instanceof Error && error.name === "AbortError"; } function toTerminalStreamFailureError(event) { if (event.type !== "session.failed") return; const error = new Error(event.data.message); error.name = event.data.code; return error; } //#endregion //#region src/client/agent-host.ts const AGENT_NAME_PATTERN = /^[a-z0-9][a-z0-9_-]*$/; const EVE_NAMED_AGENT_ROUTE_PREFIX = "/eve/agents"; function resolveEveAgentHost(input) { if (input.agent === void 0) return input.host ?? ""; if (input.host !== void 0) throw new Error("useEveAgent cannot combine agent and host. Use one target option."); assertValidAgentName(input.agent); return `${EVE_NAMED_AGENT_ROUTE_PREFIX}/${input.agent}`; } function assertValidAgentName(name) { if (!AGENT_NAME_PATTERN.test(name)) throw new Error(`eve agent name ${JSON.stringify(name)} is invalid. Use lowercase letters, numbers, underscores, or hyphens, starting with a letter or number.`); } //#endregion //#region src/client/authorization-message-parts.ts function createAuthorizationRequiredPart(event) { const displayName = event.data.authorization?.displayName ?? formatAuthorizationDisplayName(event.data.name); return { authorization: event.data.authorization, description: normalizeAuthorizationDescription(event.data.description, event.data.name, displayName), displayName, name: event.data.name, state: "required", stepIndex: event.data.stepIndex, turnId: event.data.turnId, type: "authorization" }; } function createAuthorizationCompletedPart(event, existing) { const displayName = event.data.authorization?.displayName ?? existing?.displayName ?? formatAuthorizationDisplayName(event.data.name); return { authorization: existing?.authorization || event.data.authorization ? { ...existing?.authorization, ...event.data.authorization } : void 0, description: existing?.description ?? buildCompletedAuthorizationDescription(displayName, event.data.outcome, event.data.reason), displayName, name: event.data.name, outcome: event.data.outcome, reason: event.data.reason, state: "completed", stepIndex: existing?.stepIndex ?? event.data.stepIndex, turnId: existing?.turnId ?? event.data.turnId, type: "authorization" }; } function buildCompletedAuthorizationDescription(displayName, outcome, reason) { if (outcome === "authorized") return `${displayName} connected.`; return `${displayName} authorization ${outcome}${reason !== void 0 ? ` (${reason})` : ""}.`; } function normalizeAuthorizationDescription(description, name, displayName) { if (description === `Authorization required for ${name}`) return `Authorization required for ${displayName}`; return description; } function formatAuthorizationDisplayName(name) { if (name.length === 0) return name; return `${name.charAt(0).toUpperCase()}${name.slice(1)}`; } //#endregion //#region src/client/message-action-parts.ts function toMessageInputRequest(request) { return { allowFreeform: request.allowFreeform, display: request.display, options: request.options, prompt: request.prompt, requestId: request.requestId }; } function createToolMetadata(descriptor, extra) { return { eve: { inputRequest: extra?.inputRequest, kind: descriptor.kind, name: descriptor.name } }; } function mergeToolMetadata(current, next) { const kind = next.eve?.kind ?? current?.eve?.kind ?? "unknown"; const name = next.eve?.name ?? current?.eve?.name ?? "unknown"; return { eve: { ...current?.eve, ...next.eve, inputRequest: next.eve?.inputRequest ?? current?.eve?.inputRequest, inputResponse: next.eve?.inputResponse ?? current?.eve?.inputResponse, kind, name } }; } function approvedApproval(part) { if (!part?.approval?.id) return; return { approved: true, id: part.approval.id, isAutomatic: part.approval.isAutomatic, reason: part.approval.reason }; } function normalizeActionRequest(action) { switch (action.kind) { case "load-skill": return { kind: "load-skill", name: "load_skill", toolName: "eve:load-skill" }; case "tool-call": return { kind: "tool-call", name: action.toolName, toolName: action.toolName }; case "subagent-call": return { kind: "subagent-call", name: action.subagentName, toolName: `eve:subagent:${action.subagentName}` }; case "remote-agent-call": return { kind: "subagent-call", name: action.remoteAgentName, toolName: `eve:subagent:${action.remoteAgentName}` }; } } function normalizeActionResult(result) { switch (result.kind) { case "load-skill-result": return { kind: "load-skill", name: result.name ?? "load_skill", toolName: "eve:load-skill" }; case "tool-result": return { kind: "tool-call", name: result.toolName, toolName: result.toolName }; case "subagent-result": return { kind: "subagent-call", name: result.subagentName, toolName: `eve:subagent:${result.subagentName}` }; } } function stringifyUnknown(value) { if (typeof value === "string") return value; try { return JSON.stringify(value); } catch { return "Action failed."; } } //#endregion //#region src/client/message-reducer.ts function defaultMessageReducer() { return { initial() { return { messages: [] }; }, reduce(data, event) { return reduceMessageData(data, event); } }; } function reduceMessageData(data, event) { switch (event.type) { case "client.message.submitted": return upsertMessage(data, { id: optimisticUserMessageId(event.data.submissionId), metadata: { optimistic: true, status: "submitted" }, parts: [{ type: "text", text: event.data.message }], role: "user" }); case "client.message.failed": return upsertMessage(data, { id: optimisticUserMessageId(event.data.submissionId), metadata: { optimistic: true, status: "failed" }, parts: [{ type: "text", text: event.data.message }], role: "user" }); case "client.input.responded": { let next = data; for (const response of event.data.responses) next = respondToInputRequest(next, response); return next; } case "message.received": return upsertMessage(data, { id: `${event.data.turnId}:user`, metadata: { status: "complete", turnId: event.data.turnId }, parts: projectReceivedParts(event.data.parts, event.data.message), role: "user" }); case "step.started": return updateAssistantMessage(data, event.data.turnId, (message) => ensureStepStartPart(message, event.data.stepIndex)); case "reasoning.appended": return updateAssistantMessage(data, event.data.turnId, (message) => upsertRun(ensureStepStartPart(message, event.data.stepIndex), { state: "streaming", stepIndex: event.data.stepIndex, text: event.data.reasoningSoFar, type: "reasoning" })); case "reasoning.completed": return updateAssistantMessage(data, event.data.turnId, (message) => upsertRun(ensureStepStartPart(message, event.data.stepIndex), { state: "done", stepIndex: event.data.stepIndex, text: event.data.reasoning, type: "reasoning" })); case "actions.requested": { let next = data; for (const action of event.data.actions) { const descriptor = normalizeActionRequest(action); next = updateAssistantMessage(next, event.data.turnId, (message) => upsertPart(ensureStepStartPart(message, event.data.stepIndex), { input: "input" in action ? action.input : void 0, state: "input-available", stepIndex: event.data.stepIndex, toolCallId: action.callId, toolMetadata: createToolMetadata(descriptor), toolName: descriptor.toolName, type: "dynamic-tool" })); } return next; } case "input.requested": { let next = data; for (const request of event.data.requests) { const descriptor = normalizeActionRequest(request.action); next = updateAssistantMessage(next, event.data.turnId, (message) => upsertPart(ensureStepStartPart(message, event.data.stepIndex), { approval: { id: request.requestId }, input: request.action.input, state: "approval-requested", stepIndex: event.data.stepIndex, toolCallId: request.action.callId, toolMetadata: createToolMetadata(descriptor, { inputRequest: toMessageInputRequest(request) }), toolName: descriptor.toolName, type: "dynamic-tool" })); } return next; } case "action.result": { const descriptor = normalizeActionResult(event.data.result); const existing = findToolPart(data, event.data.result.callId); const denied = event.data.error?.code === "TOOL_EXECUTION_DENIED"; const failed = event.data.status === "failed" && !denied; const approvalId = existing?.approval?.id ?? event.data.result.callId; const toolMetadata = mergeToolMetadata(existing?.toolMetadata, createToolMetadata(descriptor)); const resultPartBase = { input: existing?.input, stepIndex: event.data.stepIndex, toolCallId: event.data.result.callId, toolMetadata, toolName: existing?.toolName ?? descriptor.toolName, type: "dynamic-tool" }; let nextPart; if (denied) nextPart = { ...resultPartBase, approval: { approved: false, id: approvalId, reason: event.data.error?.message }, state: "output-denied" }; else if (failed) nextPart = { ...resultPartBase, approval: approvedApproval(existing), errorText: event.data.error?.message ?? stringifyUnkno