eve
Version:
Filesystem-first framework for durable backend AI agents that run anywhere.
1,527 lines (1,502 loc) • 59.9 kB
JavaScript
import { createSubscriber } from "svelte/reactivity";
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 ?? stringifyUnknown(eve