const normalizeLine = line => {
  if (line.startsWith('Error: ')) {
    return line.slice('Error: '.length);
  }
  if (line.startsWith('VError: ')) {
    return line.slice('VError: '.length);
  }
  return line;
};
const getCombinedMessage = (error, message) => {
  const stringifiedError = normalizeLine(`${error}`);
  if (message) {
    return `${message}: ${stringifiedError}`;
  }
  return stringifiedError;
};
const NewLine$2 = '\n';
const getNewLineIndex$1 = (string, startIndex = undefined) => {
  return string.indexOf(NewLine$2, startIndex);
};
const mergeStacks = (parent, child) => {
  if (!child) {
    return parent;
  }
  const parentNewLineIndex = getNewLineIndex$1(parent);
  const childNewLineIndex = getNewLineIndex$1(child);
  if (childNewLineIndex === -1) {
    return parent;
  }
  const parentFirstLine = parent.slice(0, parentNewLineIndex);
  const childRest = child.slice(childNewLineIndex);
  const childFirstLine = normalizeLine(child.slice(0, childNewLineIndex));
  if (parentFirstLine.includes(childFirstLine)) {
    return parentFirstLine + childRest;
  }
  return child;
};
class VError extends Error {
  constructor(error, message) {
    const combinedMessage = getCombinedMessage(error, message);
    super(combinedMessage);
    this.name = 'VError';
    if (error instanceof Error) {
      this.stack = mergeStacks(this.stack, error.stack);
    }
    if (error.codeFrame) {
      // @ts-ignore
      this.codeFrame = error.codeFrame;
    }
    if (error.code) {
      // @ts-ignore
      this.code = error.code;
    }
  }
}

const isMessagePort = value => {
  return value && value instanceof MessagePort;
};
const isMessagePortMain = value => {
  return value && value.constructor && value.constructor.name === 'MessagePortMain';
};
const isOffscreenCanvas = value => {
  return typeof OffscreenCanvas !== 'undefined' && value instanceof OffscreenCanvas;
};
const isInstanceOf = (value, constructorName) => {
  return value?.constructor?.name === constructorName;
};
const isSocket = value => {
  return isInstanceOf(value, 'Socket');
};
const transferrables = [isMessagePort, isMessagePortMain, isOffscreenCanvas, isSocket];
const isTransferrable = value => {
  for (const fn of transferrables) {
    if (fn(value)) {
      return true;
    }
  }
  return false;
};
const walkValue = (value, transferrables, isTransferrable) => {
  if (!value) {
    return;
  }
  if (isTransferrable(value)) {
    transferrables.push(value);
    return;
  }
  if (Array.isArray(value)) {
    for (const item of value) {
      walkValue(item, transferrables, isTransferrable);
    }
    return;
  }
  if (typeof value === 'object') {
    for (const property of Object.values(value)) {
      walkValue(property, transferrables, isTransferrable);
    }
    return;
  }
};
const getTransferrables = value => {
  const transferrables = [];
  walkValue(value, transferrables, isTransferrable);
  return transferrables;
};
const attachEvents = that => {
  const handleMessage = (...args) => {
    const data = that.getData(...args);
    that.dispatchEvent(new MessageEvent('message', {
      data
    }));
  };
  that.onMessage(handleMessage);
  const handleClose = event => {
    that.dispatchEvent(new Event('close'));
  };
  that.onClose(handleClose);
};
class Ipc extends EventTarget {
  constructor(rawIpc) {
    super();
    this._rawIpc = rawIpc;
    attachEvents(this);
  }
}
const E_INCOMPATIBLE_NATIVE_MODULE = 'E_INCOMPATIBLE_NATIVE_MODULE';
const E_MODULES_NOT_SUPPORTED_IN_ELECTRON = 'E_MODULES_NOT_SUPPORTED_IN_ELECTRON';
const ERR_MODULE_NOT_FOUND = 'ERR_MODULE_NOT_FOUND';
const NewLine$1 = '\n';
const joinLines$1 = lines => {
  return lines.join(NewLine$1);
};
const RE_AT = /^\s+at/;
const RE_AT_PROMISE_INDEX = /^\s*at async Promise.all \(index \d+\)$/;
const isNormalStackLine = line => {
  return RE_AT.test(line) && !RE_AT_PROMISE_INDEX.test(line);
};
const getDetails = lines => {
  const index = lines.findIndex(isNormalStackLine);
  if (index === -1) {
    return {
      actualMessage: joinLines$1(lines),
      rest: []
    };
  }
  let lastIndex = index - 1;
  while (++lastIndex < lines.length) {
    if (!isNormalStackLine(lines[lastIndex])) {
      break;
    }
  }
  return {
    actualMessage: lines[index - 1],
    rest: lines.slice(index, lastIndex)
  };
};
const splitLines$1 = lines => {
  return lines.split(NewLine$1);
};
const RE_MESSAGE_CODE_BLOCK_START = /^Error: The module '.*'$/;
const RE_MESSAGE_CODE_BLOCK_END = /^\s* at/;
const isMessageCodeBlockStartIndex = line => {
  return RE_MESSAGE_CODE_BLOCK_START.test(line);
};
const isMessageCodeBlockEndIndex = line => {
  return RE_MESSAGE_CODE_BLOCK_END.test(line);
};
const getMessageCodeBlock = stderr => {
  const lines = splitLines$1(stderr);
  const startIndex = lines.findIndex(isMessageCodeBlockStartIndex);
  const endIndex = startIndex + lines.slice(startIndex).findIndex(isMessageCodeBlockEndIndex, startIndex);
  const relevantLines = lines.slice(startIndex, endIndex);
  const relevantMessage = relevantLines.join(' ').slice('Error: '.length);
  return relevantMessage;
};
const isModuleNotFoundMessage = line => {
  return line.includes('[ERR_MODULE_NOT_FOUND]');
};
const getModuleNotFoundError = stderr => {
  const lines = splitLines$1(stderr);
  const messageIndex = lines.findIndex(isModuleNotFoundMessage);
  const message = lines[messageIndex];
  return {
    code: ERR_MODULE_NOT_FOUND,
    message
  };
};
const isModuleNotFoundError = stderr => {
  if (!stderr) {
    return false;
  }
  return stderr.includes('ERR_MODULE_NOT_FOUND');
};
const isModulesSyntaxError = stderr => {
  if (!stderr) {
    return false;
  }
  return stderr.includes('SyntaxError: Cannot use import statement outside a module');
};
const RE_NATIVE_MODULE_ERROR = /^innerError Error: Cannot find module '.*.node'/;
const RE_NATIVE_MODULE_ERROR_2 = /was compiled against a different Node.js version/;
const isUnhelpfulNativeModuleError = stderr => {
  return RE_NATIVE_MODULE_ERROR.test(stderr) && RE_NATIVE_MODULE_ERROR_2.test(stderr);
};
const getNativeModuleErrorMessage = stderr => {
  const message = getMessageCodeBlock(stderr);
  return {
    code: E_INCOMPATIBLE_NATIVE_MODULE,
    message: `Incompatible native node module: ${message}`
  };
};
const getModuleSyntaxError = () => {
  return {
    code: E_MODULES_NOT_SUPPORTED_IN_ELECTRON,
    message: `ES Modules are not supported in electron`
  };
};
const getHelpfulChildProcessError = (stdout, stderr) => {
  if (isUnhelpfulNativeModuleError(stderr)) {
    return getNativeModuleErrorMessage(stderr);
  }
  if (isModulesSyntaxError(stderr)) {
    return getModuleSyntaxError();
  }
  if (isModuleNotFoundError(stderr)) {
    return getModuleNotFoundError(stderr);
  }
  const lines = splitLines$1(stderr);
  const {
    actualMessage,
    rest
  } = getDetails(lines);
  return {
    code: '',
    message: actualMessage,
    stack: rest
  };
};
class IpcError extends VError {
  // @ts-ignore
  constructor(betterMessage, stdout = '', stderr = '') {
    if (stdout || stderr) {
      // @ts-ignore
      const {
        code,
        message,
        stack
      } = getHelpfulChildProcessError(stdout, stderr);
      const cause = new Error(message);
      // @ts-ignore
      cause.code = code;
      cause.stack = stack;
      super(cause, betterMessage);
    } else {
      super(betterMessage);
    }
    // @ts-ignore
    this.name = 'IpcError';
    // @ts-ignore
    this.stdout = stdout;
    // @ts-ignore
    this.stderr = stderr;
  }
}
const readyMessage = 'ready';
const getData$2 = event => {
  return event.data;
};
const listen$7 = () => {
  // @ts-ignore
  if (typeof WorkerGlobalScope === 'undefined') {
    throw new TypeError('module is not in web worker scope');
  }
  return globalThis;
};
const signal$8 = global => {
  global.postMessage(readyMessage);
};
class IpcChildWithModuleWorker extends Ipc {
  getData(event) {
    return getData$2(event);
  }
  send(message) {
    // @ts-ignore
    this._rawIpc.postMessage(message);
  }
  sendAndTransfer(message) {
    const transfer = getTransferrables(message);
    // @ts-ignore
    this._rawIpc.postMessage(message, transfer);
  }
  dispose() {
    // ignore
  }
  onClose(callback) {
    // ignore
  }
  onMessage(callback) {
    this._rawIpc.addEventListener('message', callback);
  }
}
const wrap$f = global => {
  return new IpcChildWithModuleWorker(global);
};
const IpcChildWithModuleWorker$1 = {
  __proto__: null,
  listen: listen$7,
  signal: signal$8,
  wrap: wrap$f
};
const addListener = (emitter, type, callback) => {
  if ('addEventListener' in emitter) {
    emitter.addEventListener(type, callback);
  } else {
    emitter.on(type, callback);
  }
};
const removeListener = (emitter, type, callback) => {
  if ('removeEventListener' in emitter) {
    emitter.removeEventListener(type, callback);
  } else {
    emitter.off(type, callback);
  }
};
const getFirstEvent = (eventEmitter, eventMap) => {
  const {
    promise,
    resolve
  } = Promise.withResolvers();
  const listenerMap = Object.create(null);
  const cleanup = value => {
    for (const event of Object.keys(eventMap)) {
      removeListener(eventEmitter, event, listenerMap[event]);
    }
    resolve(value);
  };
  for (const [event, type] of Object.entries(eventMap)) {
    const listener = event => {
      cleanup({
        event,
        type
      });
    };
    addListener(eventEmitter, event, listener);
    listenerMap[event] = listener;
  }
  return promise;
};
const Message$1 = 3;
const create$5$1 = async ({
  isMessagePortOpen,
  messagePort
}) => {
  if (!isMessagePort(messagePort)) {
    throw new IpcError('port must be of type MessagePort');
  }
  if (isMessagePortOpen) {
    return messagePort;
  }
  const eventPromise = getFirstEvent(messagePort, {
    message: Message$1
  });
  messagePort.start();
  const {
    event,
    type
  } = await eventPromise;
  if (type !== Message$1) {
    throw new IpcError('Failed to wait for ipc message');
  }
  if (event.data !== readyMessage) {
    throw new IpcError('unexpected first message');
  }
  return messagePort;
};
const signal$1 = messagePort => {
  messagePort.start();
};
class IpcParentWithMessagePort extends Ipc {
  getData = getData$2;
  send(message) {
    this._rawIpc.postMessage(message);
  }
  sendAndTransfer(message) {
    const transfer = getTransferrables(message);
    this._rawIpc.postMessage(message, transfer);
  }
  dispose() {
    this._rawIpc.close();
  }
  onMessage(callback) {
    this._rawIpc.addEventListener('message', callback);
  }
  onClose(callback) {}
}
const wrap$5 = messagePort => {
  return new IpcParentWithMessagePort(messagePort);
};
const IpcParentWithMessagePort$1 = {
  __proto__: null,
  create: create$5$1,
  signal: signal$1,
  wrap: wrap$5
};

class CommandNotFoundError extends Error {
  constructor(command) {
    super(`Command not found ${command}`);
    this.name = 'CommandNotFoundError';
  }
}
const commands = Object.create(null);
const register = commandMap => {
  Object.assign(commands, commandMap);
};
const getCommand = key => {
  return commands[key];
};
const execute = (command, ...args) => {
  const fn = getCommand(command);
  if (!fn) {
    throw new CommandNotFoundError(command);
  }
  return fn(...args);
};

const Two$1 = '2.0';
const callbacks = Object.create(null);
const get$1 = id => {
  return callbacks[id];
};
const remove = id => {
  delete callbacks[id];
};
class JsonRpcError extends Error {
  constructor(message) {
    super(message);
    this.name = 'JsonRpcError';
  }
}
const NewLine = '\n';
const DomException = 'DOMException';
const ReferenceError$1 = 'ReferenceError';
const SyntaxError$1 = 'SyntaxError';
const TypeError$1 = 'TypeError';
const getErrorConstructor = (message, type) => {
  if (type) {
    switch (type) {
      case DomException:
        return DOMException;
      case ReferenceError$1:
        return ReferenceError;
      case SyntaxError$1:
        return SyntaxError;
      case TypeError$1:
        return TypeError;
      default:
        return Error;
    }
  }
  if (message.startsWith('TypeError: ')) {
    return TypeError;
  }
  if (message.startsWith('SyntaxError: ')) {
    return SyntaxError;
  }
  if (message.startsWith('ReferenceError: ')) {
    return ReferenceError;
  }
  return Error;
};
const constructError = (message, type, name) => {
  const ErrorConstructor = getErrorConstructor(message, type);
  if (ErrorConstructor === DOMException && name) {
    return new ErrorConstructor(message, name);
  }
  if (ErrorConstructor === Error) {
    const error = new Error(message);
    if (name && name !== 'VError') {
      error.name = name;
    }
    return error;
  }
  return new ErrorConstructor(message);
};
const joinLines = lines => {
  return lines.join(NewLine);
};
const splitLines = lines => {
  return lines.split(NewLine);
};
const getCurrentStack = () => {
  const stackLinesToSkip = 3;
  const currentStack = joinLines(splitLines(new Error().stack || '').slice(stackLinesToSkip));
  return currentStack;
};
const getNewLineIndex = (string, startIndex = undefined) => {
  return string.indexOf(NewLine, startIndex);
};
const getParentStack = error => {
  let parentStack = error.stack || error.data || error.message || '';
  if (parentStack.startsWith('    at')) {
    parentStack = error.message + NewLine + parentStack;
  }
  return parentStack;
};
const MethodNotFound = -32601;
const Custom = -32001;
const restoreJsonRpcError = error => {
  const currentStack = getCurrentStack();
  if (error && error instanceof Error) {
    if (typeof error.stack === 'string') {
      error.stack = error.stack + NewLine + currentStack;
    }
    return error;
  }
  if (error && error.code && error.code === MethodNotFound) {
    const restoredError = new JsonRpcError(error.message);
    const parentStack = getParentStack(error);
    restoredError.stack = parentStack + NewLine + currentStack;
    return restoredError;
  }
  if (error && error.message) {
    const restoredError = constructError(error.message, error.type, error.name);
    if (error.data) {
      if (error.data.stack && error.data.type && error.message) {
        restoredError.stack = error.data.type + ': ' + error.message + NewLine + error.data.stack + NewLine + currentStack;
      } else if (error.data.stack) {
        restoredError.stack = error.data.stack;
      }
      if (error.data.codeFrame) {
        // @ts-ignore
        restoredError.codeFrame = error.data.codeFrame;
      }
      if (error.data.code) {
        // @ts-ignore
        restoredError.code = error.data.code;
      }
      if (error.data.type) {
        // @ts-ignore
        restoredError.name = error.data.type;
      }
    } else {
      if (error.stack) {
        const lowerStack = restoredError.stack || '';
        // @ts-ignore
        const indexNewLine = getNewLineIndex(lowerStack);
        const parentStack = getParentStack(error);
        // @ts-ignore
        restoredError.stack = parentStack + lowerStack.slice(indexNewLine);
      }
      if (error.codeFrame) {
        // @ts-ignore
        restoredError.codeFrame = error.codeFrame;
      }
    }
    return restoredError;
  }
  if (typeof error === 'string') {
    return new Error(`JsonRpc Error: ${error}`);
  }
  return new Error(`JsonRpc Error: ${error}`);
};
const unwrapJsonRpcResult = responseMessage => {
  if ('error' in responseMessage) {
    const restoredError = restoreJsonRpcError(responseMessage.error);
    throw restoredError;
  }
  if ('result' in responseMessage) {
    return responseMessage.result;
  }
  throw new JsonRpcError('unexpected response message');
};
const warn = (...args) => {
  console.warn(...args);
};
const resolve = (id, response) => {
  const fn = get$1(id);
  if (!fn) {
    console.log(response);
    warn(`callback ${id} may already be disposed`);
    return;
  }
  fn(response);
  remove(id);
};
const E_COMMAND_NOT_FOUND = 'E_COMMAND_NOT_FOUND';
const getErrorType = prettyError => {
  if (prettyError && prettyError.type) {
    return prettyError.type;
  }
  if (prettyError && prettyError.constructor && prettyError.constructor.name) {
    return prettyError.constructor.name;
  }
  return undefined;
};
const isAlreadyStack = line => {
  return line.trim().startsWith('at ');
};
const getStack = prettyError => {
  const stackString = prettyError.stack || '';
  const newLineIndex = stackString.indexOf('\n');
  if (newLineIndex !== -1 && !isAlreadyStack(stackString.slice(0, newLineIndex))) {
    return stackString.slice(newLineIndex + 1);
  }
  return stackString;
};
const getErrorProperty = (error, prettyError) => {
  if (error && error.code === E_COMMAND_NOT_FOUND) {
    return {
      code: MethodNotFound,
      data: error.stack,
      message: error.message
    };
  }
  return {
    code: Custom,
    data: {
      code: prettyError.code,
      codeFrame: prettyError.codeFrame,
      name: prettyError.name,
      stack: getStack(prettyError),
      type: getErrorType(prettyError)
    },
    message: prettyError.message
  };
};
const create$1$1 = (id, error) => {
  return {
    error,
    id,
    jsonrpc: Two$1
  };
};
const getErrorResponse = (id, error, preparePrettyError, logError) => {
  const prettyError = preparePrettyError(error);
  logError(error, prettyError);
  const errorProperty = getErrorProperty(error, prettyError);
  return create$1$1(id, errorProperty);
};
const create$5 = (message, result) => {
  return {
    id: message.id,
    jsonrpc: Two$1,
    result: result ?? null
  };
};
const getSuccessResponse = (message, result) => {
  const resultProperty = result ?? null;
  return create$5(message, resultProperty);
};
const getErrorResponseSimple = (id, error) => {
  return {
    error: {
      code: Custom,
      data: error,
      // @ts-ignore
      message: error.message
    },
    id,
    jsonrpc: Two$1
  };
};
const getResponse = async (message, ipc, execute, preparePrettyError, logError, requiresSocket) => {
  try {
    const result = requiresSocket(message.method) ? await execute(message.method, ipc, ...message.params) : await execute(message.method, ...message.params);
    return getSuccessResponse(message, result);
  } catch (error) {
    if (ipc.canUseSimpleErrorResponse) {
      return getErrorResponseSimple(message.id, error);
    }
    return getErrorResponse(message.id, error, preparePrettyError, logError);
  }
};
const defaultPreparePrettyError = error => {
  return error;
};
const defaultLogError = () => {
  // ignore
};
const defaultRequiresSocket = () => {
  return false;
};
const defaultResolve = resolve;

// TODO maybe remove this in v6 or v7, only accept options object to simplify the code
const normalizeParams = args => {
  if (args.length === 1) {
    const options = args[0];
    return {
      execute: options.execute,
      ipc: options.ipc,
      logError: options.logError || defaultLogError,
      message: options.message,
      preparePrettyError: options.preparePrettyError || defaultPreparePrettyError,
      requiresSocket: options.requiresSocket || defaultRequiresSocket,
      resolve: options.resolve || defaultResolve
    };
  }
  return {
    execute: args[2],
    ipc: args[0],
    logError: args[5],
    message: args[1],
    preparePrettyError: args[4],
    requiresSocket: args[6],
    resolve: args[3]
  };
};
const handleJsonRpcMessage = async (...args) => {
  const options = normalizeParams(args);
  const {
    execute,
    ipc,
    logError,
    message,
    preparePrettyError,
    requiresSocket,
    resolve
  } = options;
  if ('id' in message) {
    if ('method' in message) {
      const response = await getResponse(message, ipc, execute, preparePrettyError, logError, requiresSocket);
      try {
        ipc.send(response);
      } catch (error) {
        const errorResponse = getErrorResponse(message.id, error, preparePrettyError, logError);
        ipc.send(errorResponse);
      }
      return;
    }
    resolve(message.id, message);
    return;
  }
  if ('method' in message) {
    await getResponse(message, ipc, execute, preparePrettyError, logError, requiresSocket);
    return;
  }
  throw new JsonRpcError('unexpected message');
};

const Two = '2.0';

const create$4 = (method, params) => {
  return {
    jsonrpc: Two,
    method,
    params
  };
};

const create$3 = (id, method, params) => {
  const message = {
    id,
    jsonrpc: Two,
    method,
    params
  };
  return message;
};

let id = 0;
const create$2 = () => {
  return ++id;
};

const registerPromise = map => {
  const id = create$2();
  const {
    promise,
    resolve
  } = Promise.withResolvers();
  map[id] = resolve;
  return {
    id,
    promise
  };
};

const invokeHelper = async (callbacks, ipc, method, params, useSendAndTransfer) => {
  const {
    id,
    promise
  } = registerPromise(callbacks);
  const message = create$3(id, method, params);
  if (useSendAndTransfer && ipc.sendAndTransfer) {
    ipc.sendAndTransfer(message);
  } else {
    ipc.send(message);
  }
  const responseMessage = await promise;
  return unwrapJsonRpcResult(responseMessage);
};
const createRpc = ipc => {
  const callbacks = Object.create(null);
  ipc._resolve = (id, response) => {
    const fn = callbacks[id];
    if (!fn) {
      console.warn(`callback ${id} may already be disposed`);
      return;
    }
    fn(response);
    delete callbacks[id];
  };
  const rpc = {
    async dispose() {
      await ipc?.dispose();
    },
    invoke(method, ...params) {
      return invokeHelper(callbacks, ipc, method, params, false);
    },
    invokeAndTransfer(method, ...params) {
      return invokeHelper(callbacks, ipc, method, params, true);
    },
    // @ts-ignore
    ipc,
    /**
     * @deprecated
     */
    send(method, ...params) {
      const message = create$4(method, params);
      ipc.send(message);
    }
  };
  return rpc;
};

const requiresSocket = () => {
  return false;
};
const preparePrettyError = error => {
  return error;
};
const logError = () => {
  // handled by renderer worker
};
const handleMessage = event => {
  const actualRequiresSocket = event?.target?.requiresSocket || requiresSocket;
  const actualExecute = event?.target?.execute || execute;
  return handleJsonRpcMessage(event.target, event.data, actualExecute, event.target._resolve, preparePrettyError, logError, actualRequiresSocket);
};

const handleIpc = ipc => {
  if ('addEventListener' in ipc) {
    ipc.addEventListener('message', handleMessage);
  } else if ('on' in ipc) {
    // deprecated
    ipc.on('message', handleMessage);
  }
};

const listen$1 = async (module, options) => {
  const rawIpc = await module.listen(options);
  if (module.signal) {
    module.signal(rawIpc);
  }
  const ipc = module.wrap(rawIpc);
  return ipc;
};

const create$1 = async ({
  commandMap,
  isMessagePortOpen = true,
  messagePort
}) => {
  // TODO create a commandMap per rpc instance
  register(commandMap);
  const rawIpc = await IpcParentWithMessagePort$1.create({
    isMessagePortOpen,
    messagePort
  });
  const ipc = IpcParentWithMessagePort$1.wrap(rawIpc);
  handleIpc(ipc);
  const rpc = createRpc(ipc);
  messagePort.start();
  return rpc;
};

const create = async ({
  commandMap
}) => {
  // TODO create a commandMap per rpc instance
  register(commandMap);
  const ipc = await listen$1(IpcChildWithModuleWorker$1);
  handleIpc(ipc);
  const rpc = createRpc(ipc);
  return rpc;
};

const requiredChatViewEventTypes = new Set(['chat-session-created', 'chat-session-deleted', 'chat-session-title-updated', 'chat-message-added', 'chat-message-updated', 'chat-session-messages-replaced']);
const isRequiredChatViewEvent = event => {
  return requiredChatViewEventTypes.has(event.type);
};
const isDebugChatViewEvent = event => {
  return !isRequiredChatViewEvent(event);
};
const filterDebugChatViewEvents = events => {
  return events.filter(isDebugChatViewEvent);
};

class CompositeChatSessionStorage {
  constructor(sessionStorage, debugEventStorage) {
    this.sessionStorage = sessionStorage;
    this.debugEventStorage = debugEventStorage;
  }
  async appendDebugEvent(event) {
    await this.debugEventStorage.appendEvent(event);
  }
  async appendEvent(event) {
    if (isRequiredChatViewEvent(event)) {
      await this.sessionStorage.appendEvent(event);
      return;
    }
    await this.debugEventStorage.appendEvent(event);
  }
  async clear() {
    await Promise.all([this.sessionStorage.clear(), this.debugEventStorage.clear()]);
  }
  async deleteSession(id) {
    await Promise.all([this.sessionStorage.deleteSession(id), this.debugEventStorage.deleteSession(id)]);
  }
  async getEvents(sessionId) {
    const debugEvents = await this.debugEventStorage.getEvents(sessionId);
    if (debugEvents.length > 0) {
      return debugEvents;
    }
    const legacyEvents = await this.sessionStorage.getEvents(sessionId);
    return filterDebugChatViewEvents(legacyEvents);
  }
  async getSession(id) {
    return this.sessionStorage.getSession(id);
  }
  async listSessions() {
    return this.sessionStorage.listSessions();
  }
  async setSession(session) {
    await this.sessionStorage.setSession(session);
  }
}

const instanceOfAny = (object, constructors) => constructors.some(c => object instanceof c);
let idbProxyableTypes;
let cursorAdvanceMethods;
// This is a function to prevent it throwing up in node environments.
function getIdbProxyableTypes() {
  return idbProxyableTypes || (idbProxyableTypes = [IDBDatabase, IDBObjectStore, IDBIndex, IDBCursor, IDBTransaction]);
}
// This is a function to prevent it throwing up in node environments.
function getCursorAdvanceMethods() {
  return cursorAdvanceMethods || (cursorAdvanceMethods = [IDBCursor.prototype.advance, IDBCursor.prototype.continue, IDBCursor.prototype.continuePrimaryKey]);
}
const transactionDoneMap = new WeakMap();
const transformCache = new WeakMap();
const reverseTransformCache = new WeakMap();
function promisifyRequest(request) {
  const promise = new Promise((resolve, reject) => {
    const unlisten = () => {
      request.removeEventListener('success', success);
      request.removeEventListener('error', error);
    };
    const success = () => {
      resolve(wrap(request.result));
      unlisten();
    };
    const error = () => {
      reject(request.error);
      unlisten();
    };
    request.addEventListener('success', success);
    request.addEventListener('error', error);
  });
  // This mapping exists in reverseTransformCache but doesn't exist in transformCache. This
  // is because we create many promises from a single IDBRequest.
  reverseTransformCache.set(promise, request);
  return promise;
}
function cacheDonePromiseForTransaction(tx) {
  // Early bail if we've already created a done promise for this transaction.
  if (transactionDoneMap.has(tx)) return;
  const done = new Promise((resolve, reject) => {
    const unlisten = () => {
      tx.removeEventListener('complete', complete);
      tx.removeEventListener('error', error);
      tx.removeEventListener('abort', error);
    };
    const complete = () => {
      resolve();
      unlisten();
    };
    const error = () => {
      reject(tx.error || new DOMException('AbortError', 'AbortError'));
      unlisten();
    };
    tx.addEventListener('complete', complete);
    tx.addEventListener('error', error);
    tx.addEventListener('abort', error);
  });
  // Cache it for later retrieval.
  transactionDoneMap.set(tx, done);
}
let idbProxyTraps = {
  get(target, prop, receiver) {
    if (target instanceof IDBTransaction) {
      // Special handling for transaction.done.
      if (prop === 'done') return transactionDoneMap.get(target);
      // Make tx.store return the only store in the transaction, or undefined if there are many.
      if (prop === 'store') {
        return receiver.objectStoreNames[1] ? undefined : receiver.objectStore(receiver.objectStoreNames[0]);
      }
    }
    // Else transform whatever we get back.
    return wrap(target[prop]);
  },
  set(target, prop, value) {
    target[prop] = value;
    return true;
  },
  has(target, prop) {
    if (target instanceof IDBTransaction && (prop === 'done' || prop === 'store')) {
      return true;
    }
    return prop in target;
  }
};
function replaceTraps(callback) {
  idbProxyTraps = callback(idbProxyTraps);
}
function wrapFunction(func) {
  // Due to expected object equality (which is enforced by the caching in `wrap`), we
  // only create one new func per func.
  // Cursor methods are special, as the behaviour is a little more different to standard IDB. In
  // IDB, you advance the cursor and wait for a new 'success' on the IDBRequest that gave you the
  // cursor. It's kinda like a promise that can resolve with many values. That doesn't make sense
  // with real promises, so each advance methods returns a new promise for the cursor object, or
  // undefined if the end of the cursor has been reached.
  if (getCursorAdvanceMethods().includes(func)) {
    return function (...args) {
      // Calling the original function with the proxy as 'this' causes ILLEGAL INVOCATION, so we use
      // the original object.
      func.apply(unwrap(this), args);
      return wrap(this.request);
    };
  }
  return function (...args) {
    // Calling the original function with the proxy as 'this' causes ILLEGAL INVOCATION, so we use
    // the original object.
    return wrap(func.apply(unwrap(this), args));
  };
}
function transformCachableValue(value) {
  if (typeof value === 'function') return wrapFunction(value);
  // This doesn't return, it just creates a 'done' promise for the transaction,
  // which is later returned for transaction.done (see idbObjectHandler).
  if (value instanceof IDBTransaction) cacheDonePromiseForTransaction(value);
  if (instanceOfAny(value, getIdbProxyableTypes())) return new Proxy(value, idbProxyTraps);
  // Return the same value back if we're not going to transform it.
  return value;
}
function wrap(value) {
  // We sometimes generate multiple promises from a single IDBRequest (eg when cursoring), because
  // IDB is weird and a single IDBRequest can yield many responses, so these can't be cached.
  if (value instanceof IDBRequest) return promisifyRequest(value);
  // If we've already transformed this value before, reuse the transformed value.
  // This is faster, but it also provides object equality.
  if (transformCache.has(value)) return transformCache.get(value);
  const newValue = transformCachableValue(value);
  // Not all types are transformed.
  // These may be primitive types, so they can't be WeakMap keys.
  if (newValue !== value) {
    transformCache.set(value, newValue);
    reverseTransformCache.set(newValue, value);
  }
  return newValue;
}
const unwrap = value => reverseTransformCache.get(value);

/**
 * Open a database.
 *
 * @param name Name of the database.
 * @param version Schema version.
 * @param callbacks Additional callbacks.
 */
function openDB(name, version, {
  blocked,
  upgrade,
  blocking,
  terminated
} = {}) {
  const request = indexedDB.open(name, version);
  const openPromise = wrap(request);
  if (upgrade) {
    request.addEventListener('upgradeneeded', event => {
      upgrade(wrap(request.result), event.oldVersion, event.newVersion, wrap(request.transaction), event);
    });
  }
  if (blocked) {
    request.addEventListener('blocked', event => blocked(
    // Casting due to https://github.com/microsoft/TypeScript-DOM-lib-generator/pull/1405
    event.oldVersion, event.newVersion, event));
  }
  openPromise.then(db => {
    if (terminated) db.addEventListener('close', () => terminated());
    if (blocking) {
      db.addEventListener('versionchange', event => blocking(event.oldVersion, event.newVersion, event));
    }
  }).catch(() => {});
  return openPromise;
}
const readMethods = ['get', 'getKey', 'getAll', 'getAllKeys', 'count'];
const writeMethods = ['put', 'add', 'delete', 'clear'];
const cachedMethods = new Map();
function getMethod(target, prop) {
  if (!(target instanceof IDBDatabase && !(prop in target) && typeof prop === 'string')) {
    return;
  }
  if (cachedMethods.get(prop)) return cachedMethods.get(prop);
  const targetFuncName = prop.replace(/FromIndex$/, '');
  const useIndex = prop !== targetFuncName;
  const isWrite = writeMethods.includes(targetFuncName);
  if (
  // Bail if the target doesn't exist on the target. Eg, getAll isn't in Edge.
  !(targetFuncName in (useIndex ? IDBIndex : IDBObjectStore).prototype) || !(isWrite || readMethods.includes(targetFuncName))) {
    return;
  }
  const method = async function (storeName, ...args) {
    // isWrite ? 'readwrite' : undefined gzipps better, but fails in Edge :(
    const tx = this.transaction(storeName, isWrite ? 'readwrite' : 'readonly');
    let target = tx.store;
    if (useIndex) target = target.index(args.shift());
    // Must reject if op rejects.
    // If it's a write operation, must reject if tx.done rejects.
    // Must reject with op rejection first.
    // Must resolve with op value.
    // Must handle both promises (no unhandled rejections)
    return (await Promise.all([target[targetFuncName](...args), isWrite && tx.done]))[0];
  };
  cachedMethods.set(prop, method);
  return method;
}
replaceTraps(oldTraps => ({
  ...oldTraps,
  get: (target, prop, receiver) => getMethod(target, prop) || oldTraps.get(target, prop, receiver),
  has: (target, prop) => !!getMethod(target, prop) || oldTraps.has(target, prop)
}));
const advanceMethodProps = ['continue', 'continuePrimaryKey', 'advance'];
const methodMap = {};
const advanceResults = new WeakMap();
const ittrProxiedCursorToOriginalProxy = new WeakMap();
const cursorIteratorTraps = {
  get(target, prop) {
    if (!advanceMethodProps.includes(prop)) return target[prop];
    let cachedFunc = methodMap[prop];
    if (!cachedFunc) {
      cachedFunc = methodMap[prop] = function (...args) {
        advanceResults.set(this, ittrProxiedCursorToOriginalProxy.get(this)[prop](...args));
      };
    }
    return cachedFunc;
  }
};
async function* iterate(...args) {
  // tslint:disable-next-line:no-this-assignment
  let cursor = this;
  if (!(cursor instanceof IDBCursor)) {
    cursor = await cursor.openCursor(...args);
  }
  if (!cursor) return;
  cursor = cursor;
  const proxiedCursor = new Proxy(cursor, cursorIteratorTraps);
  ittrProxiedCursorToOriginalProxy.set(proxiedCursor, cursor);
  // Map this double-proxy back to the original, so other cursor methods work.
  reverseTransformCache.set(proxiedCursor, unwrap(cursor));
  while (cursor) {
    yield proxiedCursor;
    // If one of the advancing methods was not called, call continue().
    cursor = await (advanceResults.get(proxiedCursor) || cursor.continue());
    advanceResults.delete(proxiedCursor);
  }
}
function isIteratorProp(target, prop) {
  return prop === Symbol.asyncIterator && instanceOfAny(target, [IDBIndex, IDBObjectStore, IDBCursor]) || prop === 'iterate' && instanceOfAny(target, [IDBIndex, IDBObjectStore]);
}
replaceTraps(oldTraps => ({
  ...oldTraps,
  get(target, prop, receiver) {
    if (isIteratorProp(target, prop)) return iterate;
    return oldTraps.get(target, prop, receiver);
  },
  has(target, prop) {
    return isIteratorProp(target, prop) || oldTraps.has(target, prop);
  }
}));

const chatSessionStorageDatabaseName = 'lvce-chat-view-sessions';
const chatSessionStorageDatabaseVersion = 2;
const chatSessionStorageEventStoreName = 'chat-view-events';
const chatSessionStorageSessionIdIndexName = 'sessionId';
const chatSessionStorageStoreName = 'chat-sessions';

/* eslint-disable @typescript-eslint/prefer-readonly-parameter-types */
/* cspell:ignore IDBP */

const toChatViewEvent = event => {
  const {
    eventId: _eventId,
    ...chatViewEvent
  } = event;
  return chatViewEvent;
};
const now$1 = () => {
  return new Date().toISOString();
};
const isSameMessage$1 = (a, b) => {
  return a.id === b.id && a.inProgress === b.inProgress && a.role === b.role && a.text === b.text && a.time === b.time && JSON.stringify(a.toolCalls || []) === JSON.stringify(b.toolCalls || []);
};
const canAppendMessages$1 = (previousMessages, nextMessages) => {
  if (nextMessages.length < previousMessages.length) {
    return false;
  }
  return previousMessages.every((message, index) => isSameMessage$1(message, nextMessages[index]));
};
const canUpdateMessages$1 = (previousMessages, nextMessages) => {
  if (previousMessages.length !== nextMessages.length) {
    return false;
  }
  for (let i = 0; i < previousMessages.length; i += 1) {
    const previous = previousMessages[i];
    const next = nextMessages[i];
    if (previous.id !== next.id || previous.role !== next.role) {
      return false;
    }
  }
  return true;
};
const getMutationEvents$1 = (previous, next) => {
  const timestamp = now$1();
  const events = [];
  if (!previous) {
    events.push({
      sessionId: next.id,
      timestamp,
      title: next.title,
      type: 'chat-session-created'
    });
    for (const message of next.messages) {
      events.push({
        message,
        sessionId: next.id,
        timestamp,
        type: 'chat-message-added'
      });
    }
    return events;
  }
  if (previous.title !== next.title) {
    events.push({
      sessionId: next.id,
      timestamp,
      title: next.title,
      type: 'chat-session-title-updated'
    });
  }
  if (canAppendMessages$1(previous.messages, next.messages)) {
    for (let i = previous.messages.length; i < next.messages.length; i += 1) {
      events.push({
        message: next.messages[i],
        sessionId: next.id,
        timestamp,
        type: 'chat-message-added'
      });
    }
    return events;
  }
  if (canUpdateMessages$1(previous.messages, next.messages)) {
    for (let i = 0; i < previous.messages.length; i += 1) {
      const previousMessage = previous.messages[i];
      const nextMessage = next.messages[i];
      if (!isSameMessage$1(previousMessage, nextMessage)) {
        events.push({
          inProgress: nextMessage.inProgress,
          messageId: nextMessage.id,
          sessionId: next.id,
          text: nextMessage.text,
          time: nextMessage.time,
          timestamp,
          toolCalls: nextMessage.toolCalls,
          type: 'chat-message-updated'
        });
      }
    }
    return events;
  }
  events.push({
    messages: [...next.messages],
    sessionId: next.id,
    timestamp,
    type: 'chat-session-messages-replaced'
  });
  return events;
};
const getUpdatedMessage$1 = (message, event) => {
  if (message.id !== event.messageId) {
    return message;
  }
  return {
    ...message,
    ...(event.inProgress === undefined ? {} : {
      inProgress: event.inProgress
    }),
    text: event.text,
    time: event.time,
    ...(event.toolCalls === undefined ? {} : {
      toolCalls: event.toolCalls
    })
  };
};
const applyReplayEvent$1 = (state, event) => {
  switch (event.type) {
    case 'chat-message-added':
      return {
        ...state,
        messages: [...state.messages, event.message]
      };
    case 'chat-message-updated':
      return {
        ...state,
        messages: state.messages.map(message => getUpdatedMessage$1(message, event))
      };
    case 'chat-session-created':
      return {
        ...state,
        deleted: false,
        title: event.title
      };
    case 'chat-session-deleted':
      return {
        ...state,
        deleted: true
      };
    case 'chat-session-messages-replaced':
      return {
        ...state,
        messages: [...event.messages]
      };
    case 'chat-session-title-updated':
      return {
        ...state,
        title: event.title
      };
  }
  return state;
};
const replaySession$1 = (id, summary, events) => {
  // TODO simplify this maybe. store session info
  // as it should be displayed in list
  // e.g. id, title, timestamp
  if (!summary && events.length === 0) {
    return undefined;
  }
  let state = {
    deleted: false,
    messages: summary?.messages ? [...summary.messages] : [],
    title: summary?.title || ''
  };
  for (const event of events) {
    if (event.sessionId !== id) {
      continue;
    }
    state = applyReplayEvent$1(state, event);
  }
  if (state.deleted) {
    return undefined;
  }
  return {
    id,
    messages: state.messages,
    title: state.title
  };
};
class IndexedDbChatSessionStorage {
  constructor(options = {}) {
    this.state = {
      databaseName: options.databaseName || chatSessionStorageDatabaseName,
      databasePromise: undefined,
      databaseVersion: options.databaseVersion || chatSessionStorageDatabaseVersion,
      eventStoreName: options.eventStoreName || chatSessionStorageEventStoreName,
      storeName: options.storeName || chatSessionStorageStoreName
    };
  }
  openDatabase = async () => {
    if (this.state.databasePromise) {
      return this.state.databasePromise;
    }
    const databasePromise = openDB(this.state.databaseName, this.state.databaseVersion, {
      upgrade: (database, _oldVersion, _newVersion, transaction) => {
        if (!database.objectStoreNames.contains(this.state.storeName)) {
          database.createObjectStore(this.state.storeName, {
            keyPath: 'id'
          });
        }
        if (database.objectStoreNames.contains(this.state.eventStoreName)) {
          const eventStore = transaction.objectStore(this.state.eventStoreName);
          if (!eventStore.indexNames.contains('sessionId')) {
            eventStore.createIndex('sessionId', 'sessionId', {
              unique: false
            });
          }
        } else {
          const eventStore = database.createObjectStore(this.state.eventStoreName, {
            autoIncrement: true,
            keyPath: 'eventId'
          });
          eventStore.createIndex('sessionId', 'sessionId', {
            unique: false
          });
        }
      }
    });
    this.state.databasePromise = databasePromise;
    return databasePromise;
  };
  listSummaries = async () => {
    const database = await this.openDatabase();
    const summaries = await database.getAll(this.state.storeName);
    return summaries;
  };
  getSummary = async id => {
    const database = await this.openDatabase();
    const summary = await database.get(this.state.storeName, id);
    return summary;
  };
  getEventsBySessionId = async sessionId => {
    const database = await this.openDatabase();
    const events = await database.getAllFromIndex(this.state.eventStoreName, 'sessionId', IDBKeyRange.only(sessionId));
    return events.map(toChatViewEvent);
  };
  listEventsInternal = async () => {
    const database = await this.openDatabase();
    const events = await database.getAll(this.state.eventStoreName);
    return events.map(toChatViewEvent);
  };
  appendEvents = async events => {
    if (events.length === 0) {
      return;
    }
    const database = await this.openDatabase();
    const transaction = database.transaction([this.state.storeName, this.state.eventStoreName], 'readwrite');
    const summaryStore = transaction.objectStore(this.state.storeName);
    const eventStore = transaction.objectStore(this.state.eventStoreName);
    for (const event of events) {
      await eventStore.add(event);
      if (event.type === 'chat-session-created' || event.type === 'chat-session-title-updated') {
        await summaryStore.put({
          id: event.sessionId,
          title: event.title
        });
      }
      if (event.type === 'chat-session-deleted') {
        await summaryStore.delete(event.sessionId);
      }
    }
    await transaction.done;
  };
  async appendEvent(event) {
    await this.appendEvents([event]);
  }
  async clear() {
    const database = await this.openDatabase();
    const transaction = database.transaction([this.state.storeName, this.state.eventStoreName], 'readwrite');
    await Promise.all([transaction.objectStore(this.state.storeName).clear(), transaction.objectStore(this.state.eventStoreName).clear()]);
    await transaction.done;
  }
  deleteSessionGetKeys = async (database, id) => {
    const readTransaction = database.transaction(this.state.eventStoreName, 'readonly');
    const readEventStore = readTransaction.objectStore(this.state.eventStoreName);
    const readEventIndex = readEventStore.index('sessionId');
    const eventKeys = await readEventIndex.getAllKeys(IDBKeyRange.only(id));
    await readTransaction.done;
    return eventKeys;
  };
  deleteSessionRemoveEntries = async (database, id, eventKeys) => {
    const writeTransaction = database.transaction([this.state.storeName, this.state.eventStoreName], 'readwrite');
    const summaryStore = writeTransaction.objectStore(this.state.storeName);
    const eventStore = writeTransaction.objectStore(this.state.eventStoreName);
    const deletePromises = [summaryStore.delete(id)];
    for (const key of eventKeys) {
      deletePromises.push(eventStore.delete(key));
    }
    await Promise.all([...deletePromises, writeTransaction.done]);
  };
  async deleteSession(id) {
    const database = await this.openDatabase();
    const eventKeys = await this.deleteSessionGetKeys(database, id);
    await this.deleteSessionRemoveEntries(database, id, eventKeys);
  }
  async getEvents(sessionId) {
    if (sessionId) {
      return this.getEventsBySessionId(sessionId);
    }
    return this.listEventsInternal();
  }
  async getSession(id) {
    const [summary, events] = await Promise.all([this.getSummary(id), this.getEventsBySessionId(id)]);
    return replaySession$1(id, summary, events);
  }
  async listSessions() {
    const summaries = await this.listSummaries();
    const sessions = [];
    for (const summary of summaries) {
      const events = await this.getEventsBySessionId(summary.id);
      const session = replaySession$1(summary.id, summary, events);
      if (!session) {
        continue;
      }
      sessions.push(session);
    }
    return sessions;
  }
  async setSession(session) {
    const previous = await this.getSession(session.id);
    const events = getMutationEvents$1(previous, session);
    await this.appendEvents(events);
    if (events.length === 0) {
      const database = await this.openDatabase();
      const transaction = database.transaction(this.state.storeName, 'readwrite');
      const summaryStore = transaction.objectStore(this.state.storeName);
      await summaryStore.put({
        id: session.id,
        timestamp: session.timestamp,
        title: session.title
      });
      await transaction.done;
    }
  }
}

const debugEventStorageDatabaseName = 'lvce-chat-debug-events';
const debugEventStorageDatabaseVersion = 1;
const debugEventStorageEventStoreName = 'chat-debug-events';

/* eslint-disable @typescript-eslint/prefer-readonly-parameter-types */
/* cspell:ignore IDBP */

const toDebugEvent = event => {
  const {
    eventId: _eventId,
    ...debugEvent
  } = event;
  return debugEvent;
};
class IndexedDbDebugEventStorage {
  constructor(options = {}) {
    this.state = {
      databaseName: options.databaseName || debugEventStorageDatabaseName,
      databasePromise: undefined,
      databaseVersion: options.databaseVersion || debugEventStorageDatabaseVersion,
      eventStoreName: options.eventStoreName || debugEventStorageEventStoreName,
      sessionIdIndexName: options.sessionIdIndexName || chatSessionStorageSessionIdIndexName
    };
  }
  openDatabase = async () => {
    if (this.state.databasePromise) {
      return this.state.databasePromise;
    }
    const databasePromise = openDB(this.state.databaseName, this.state.databaseVersion, {
      upgrade: (database, _oldVersion, _newVersion, transaction) => {
        if (database.objectStoreNames.contains(this.state.eventStoreName)) {
          const eventStore = transaction.objectStore(this.state.eventStoreName);
          if (!eventStore.indexNames.contains(this.state.sessionIdIndexName)) {
            eventStore.createIndex(this.state.sessionIdIndexName, 'sessionId', {
              unique: false
            });
          }
          return;
        }
        const eventStore = database.createObjectStore(this.state.eventStoreName, {
          autoIncrement: true,
          keyPath: 'eventId'
        });
        eventStore.createIndex(this.state.sessionIdIndexName, 'sessionId', {
          unique: false
        });
      }
    });
    this.state.databasePromise = databasePromise;
    return databasePromise;
  };
  async appendEvent(event) {
    const database = await this.openDatabase();
    await database.add(this.state.eventStoreName, event);
  }
  async clear() {
    const database = await this.openDatabase();
    await database.clear(this.state.eventStoreName);
  }
  deleteSessionGetKeys = async (database, sessionId) => {
    const readTransaction = database.transaction(this.state.eventStoreName, 'readonly');
    const readEventStore = readTransaction.objectStore(this.state.eventStoreName);
    const readEventIndex = readEventStore.index(this.state.sessionIdIndexName);
    const keys = await readEventIndex.getAllKeys(IDBKeyRange.only(sessionId));
    await readTransaction.done;
    return keys;
  };
  deleteSessionRemoveEntries = async (database, keys) => {
    if (keys.length === 0) {
      return;
    }
    const writeTransaction = database.transaction(this.state.eventStoreName, 'readwrite');
    const writeEventStore = writeTransaction.objectStore(this.state.eventStoreName);
    const deletePromises = [];
    for (const key of keys) {
      deletePromises.push(writeEventStore.delete(key));
    }
    await Promise.all([...deletePromises, writeTransaction.done]);
  };
  async deleteSession(sessionId) {
    const database = await this.openDatabase();
    const keys = await this.deleteSessionGetKeys(database, sessionId);
    await this.deleteSessionRemoveEntries(database, keys);
  }
  async getEvents(sessionId) {
    const database = await this.openDatabase();
    if (sessionId) {
      const events = await database.getAllFromIndex(this.state.eventStoreName, this.state.sessionIdIndexName, IDBKeyRange.only(sessionId));
      return events.map(toDebugEvent);
    }
    const events = await database.getAll(this.state.eventStoreName);
    return events.map(toDebugEvent);
  }
}

const now = () => {
  return new Date().toISOString();
};
const isSameMessage = (a, b) => {
  return a.id === b.id && a.inProgress === b.inProgress && a.role === b.role && a.text === b.text && a.time === b.time && JSON.stringify(a.toolCalls || []) === JSON.stringify(b.toolCalls || []);
};
const canAppendMessages = (previousMessages, nextMessages) => {
  if (nextMessages.length < previousMessages.length) {
    return false;
  }
  return previousMessages.every((message, index) => isSameMessage(message, nextMessages[index]));
};
const canUpdateMessages = (previousMessages, nextMessages) => {
  if (previousMessages.length !== nextMessages.length) {
    return false;
  }
  for (let i = 0; i < previousMessages.length; i += 1) {
    const previous = previousMessages[i];
    const next = nextMessages[i];
    if (previous.id !== next.id || previous.role !== next.role) {
      return false;
    }
  }
  return true;
};
const getMutationEvents = (previous, next) => {
  const timestamp = now();
  const events = [];
  if (!previous) {
    events.push({
      sessionId: next.id,
      timestamp,
      title: next.title,
      type: 'chat-session-created'
    });
    for (const message of next.messages) {
      events.push({
        message,
        sessionId: next.id,
        timestamp,
        type: 'chat-message-added'
      });
    }
    return events;
  }
  if (previous.title !== next.title) {
    events.push({
      sessionId: next.id,
      timestamp,
      title: next.title,
      type: 'chat-session-title-updated'
    });
  }
  if (canAppendMessages(previous.messages, next.messages)) {
    for (let i = previous.messages.length; i < next.messages.length; i += 1) {
      events.push({
        message: next.messages[i],
        sessionId: next.id,
        timestamp,
        type: 'chat-message-added'
      });
    }
    return events;
  }
  if (canUpdateMessages(previous.messages, next.messages)) {
    for (let i = 0; i < previous.messages.length; i += 1) {
      const previousMessage = previous.messages[i];
      const nextMessage = next.messages[i];
      if (!isSameMessage(previousMessage, nextMessage)) {
        events.push({
          inProgress: nextMessage.inProgress,
          messageId: nextMessage.id,
          sessionId: next.id,
          text: nextMessage.text,
          time: nextMessage.time,
          timestamp,
          toolCalls: nextMessage.toolCalls,
          type: 'chat-message-updated'
        });
      }
    }
    return events;
  }
  events.push({
    messages: [...next.messages],
    sessionId: next.id,
    timestamp,
    type: 'chat-session-messages-replaced'
  });
  return events;
};
const getUpdatedMessage = (message, event) => {
  if (message.id !== event.messageId) {
    return message;
  }
  return {
    ...message,
    ...(event.inProgress === undefined ? {} : {
      inProgress: event.inProgress
    }),
    text: event.text,
    time: event.time,
    ...(event.toolCalls === undefined ? {} : {
      toolCalls: event.toolCalls
    })
  };
};
const applyReplayEvent = (state, event) => {
  switch (event.type) {
    case 'chat-message-added':
      return {
        ...state,
        messages: [...state.messages, event.message]
      };
    case 'chat-message-updated':
      return {
        ...state,
        messages: state.messages.map(message => getUpdatedMessage(message, event))
      };
    case 'chat-session-created':
      return {
        ...state,
        deleted: false,
        title: event.title
      };
    case 'chat-session-deleted':
      return {
        ...state,
        deleted: true
      };
    case 'chat-session-messages-replaced':
      return {
        ...state,
        messages: [...event.messages]
      };
    case 'chat-session-title-updated':
      return {
        ...state,
        title: event.title
      };
  }
  return state;
};
const replaySession = (id, title, events) => {
  let state = {
    deleted: false,
    messages: [],
    title: title || ''
  };
  for (const event of events) {
    if (event.sessionId !== id) {
      continue;
    }
    state = applyReplayEvent(state, event);
  }
  if (state.deleted || !state.title) {
    return undefined;
  }
  return {
    id,
    messages: state.messages,
    title: state.title
  };
};
class InMemoryChatSessionStorage {
  events = [];
  summaries = new Map();
  async appendEvent(event) {
    this.events.push(event);
    if (event.type === 'chat-session-created' || event.type === 'chat-session-title-updated') {
      this.summaries.set(event.sessionId, event.title);
      return;
    }
    if (event.type === 'chat-session-deleted') {
      this.summaries.delete(event.sessionId);
    }
  }
  async clear() {
    this.events.length = 0;
    this.summaries.clear();
  }
  async deleteSession(id) {
    this.summaries.delete(id);
    for (let index = this.events.length - 1; index >= 0; index -= 1) {
      if (this.events[index].sessionId === id) {
        this.events.splice(index, 1);
      }
    }
  }
  async getEvents(sessionId) {
    if (!sessionId) {
      return [...this.events];
    }
    return this.events.filter(event => event.sessionId === sessionId);
  }
  async getSession(id) {
    return replaySession(id, this.summaries.get(id), this.events);
  }
  async listSessions() {
    const ids = new Set();
    for (const id of this.summaries.keys()) {
      ids.add(id);
    }
    for (const event of this.events) {
      ids.add(event.sessionId);
    }
    const sessions = [];
    for (const id of ids) {
      const session = replaySession(id, this.summaries.get(id), this.events);
      if (!session) {
        continue;
      }
      sessions.push(session);
    }
    return sessions;
  }
  async setSession(session) {
    const previous = await this.getSession(session.id);
    const events = getMutationEvents(previous, session);
    for (const event of events) {
      await this.appendEvent(event);
    }
    this.summaries.set(session.id, session.title);
  }
}

class InMemoryDebugEventStorage {
  events = [];
  async appendEvent(event) {
    this.events.push(event);
  }
  async clear() {
    this.events.length = 0;
  }
  async deleteSession(sessionId) {
    for (let index = this.events.length - 1; index >= 0; index -= 1) {
      if (this.events[index].sessionId === sessionId) {
        this.events.splice(index, 1);
      }
    }
  }
  async getEvents(sessionId) {
    if (!sessionId) {
      return [...this.events];
    }
    return this.events.filter(event => event.sessionId === sessionId);
  }
}

const createDefaultStorage = () => {
  if (typeof indexedDB === 'undefined') {
    return new CompositeChatSessionStorage(new InMemoryChatSessionStorage(), new InMemoryDebugEventStorage());
  }
  return new CompositeChatSessionStorage(new IndexedDbChatSessionStorage(), new IndexedDbDebugEventStorage());
};

let chatSessionStorage = createDefaultStorage();

// TODO should store it as an array of listeners per sessionId
const sessionListeners = new Map();
const partialMessages = new Map();
const getChatSessionStorage = () => {
  return chatSessionStorage;
};
const getSessionListeners = () => {
  return sessionListeners;
};
const getPartialMessage = sessionId => {
  return partialMessages.get(sessionId);
};
const setPartialMessageValue = (sessionId, message) => {
  partialMessages.set(sessionId, message);
};
const clearPartialMessageValue = sessionId => {
  return partialMessages.delete(sessionId);
};
const clearAllPartialMessages = () => {
  partialMessages.clear();
};

const rpcs = Object.create(null);
const set = (id, rpc) => {
  rpcs[id] = rpc;
};
const get = id => {
  return rpcs[id];
};

const notifySessionListener = async listener => {
  const rpc = get(listener.rpcId);
  if (!rpc) {
    return;
  }
  await rpc.invoke('handleChatStorageUpdate', listener.uid, listener.sessionId);
};

const notifySessionListeners = async sessionId => {
  const matching = [...getSessionListeners().values()].filter(listener => listener.sessionId === sessionId);
  await Promise.all(matching.map(notifySessionListener));
};

const isDebugAppendCapableStorage = storage => {
  return 'appendDebugEvent' in storage;
};
const appendChatDebugEvent = async event => {
  const storage = getChatSessionStorage();
  if (isDebugAppendCapableStorage(storage)) {
    await storage.appendDebugEvent(event);
  } else {
    await storage.appendEvent(event);
  }
  if (event.sessionId) {
    await notifySessionListeners(event.sessionId);
  }
};

const appendChatViewEvent = async event => {
  await getChatSessionStorage().appendEvent(event);
  if (event.type === 'chat-message-added' || event.type === 'chat-session-deleted' || event.type === 'chat-session-messages-replaced') {
    clearPartialMessageValue(event.sessionId);
  }
  await notifySessionListeners(event.sessionId);
};

const clearPartialMessage = async sessionId => {
  const removed = clearPartialMessageValue(sessionId);
  if (!removed) {
    return;
  }
  await notifySessionListeners(sessionId);
};

const consumeSessionUpdates = () => {
  return [];
};

const deleteChatSession = async id => {
  await getChatSessionStorage().deleteSession(id);
  clearPartialMessageValue(id);
  await notifySessionListeners(id);
};

const getChatSession = async id => {
  const session = await getChatSessionStorage().getSession(id);
  if (!session) {
    return undefined;
  }
  const resultBase = {
    id: session.id,
    messages: [...session.messages],
    title: session.title
  };
  const result = session.projectId ? {
    ...resultBase,
    projectId: session.projectId
  } : resultBase;
  return result;
};

const getChatViewEvents = async sessionId => {
  return getChatSessionStorage().getEvents(sessionId);
};

/* eslint-disable @typescript-eslint/prefer-readonly-parameter-types */

const ensureChatSessionSchema = (database, transaction) => {
  if (!database.objectStoreNames.contains(chatSessionStorageStoreName)) {
    database.createObjectStore(chatSessionStorageStoreName, {
      keyPath: 'id'
    });
  }
  if (database.objectStoreNames.contains(chatSessionStorageEventStoreName)) {
    const eventStore = transaction?.objectStore(chatSessionStorageEventStoreName);
    if (eventStore && !eventStore.indexNames.contains(chatSessionStorageSessionIdIndexName)) {
      eventStore.createIndex(chatSessionStorageSessionIdIndexName, 'sessionId', {
        unique: false
      });
    }
    return;
  }
  const eventStore = database.createObjectStore(chatSessionStorageEventStoreName, {
    autoIncrement: true,
    keyPath: 'eventId'
  });
  eventStore.createIndex(chatSessionStorageSessionIdIndexName, 'sessionId', {
    unique: false
  });
};
const ensureDebugEventSchema = (database, transaction) => {
  if (database.objectStoreNames.contains(debugEventStorageEventStoreName)) {
    const eventStore = transaction?.objectStore(debugEventStorageEventStoreName);
    if (eventStore && !eventStore.indexNames.contains(chatSessionStorageSessionIdIndexName)) {
      eventStore.createIndex(chatSessionStorageSessionIdIndexName, 'sessionId', {
        unique: false
      });
    }
    return;
  }
  const eventStore = database.createObjectStore(debugEventStorageEventStoreName, {
    autoIncrement: true,
    keyPath: 'eventId'
  });
  eventStore.createIndex(chatSessionStorageSessionIdIndexName, 'sessionId', {
    unique: false
  });
};
const openDatabase = async (databaseName, dataBaseVersion) => {
  return openDB(databaseName, dataBaseVersion, {
    upgrade(database, _oldVersion, _newVersion, transaction) {
      if (databaseName === chatSessionStorageDatabaseName) {
        ensureChatSessionSchema(database, transaction);
        return;
      }
      if (databaseName === debugEventStorageDatabaseName) {
        ensureDebugEventSchema(database, transaction);
      }
    }
  });
};

const isMessageEvent = event => {
  return event.type === 'chat-message-added' || event.type === 'chat-message-updated' || event.type === 'chat-session-messages-replaced';
};
const toMessages = events => {
  const messages = [];
  for (const event of events) {
    if (event.type === 'chat-message-added') {
      messages.push({
        message: event.message,
        timestamp: event.timestamp,
        type: 'chat-message-added'
      });
      continue;
    }
    if (event.type === 'chat-message-updated') {
      const existingIndex = messages.findIndex(messageEvent => messageEvent.message?.id === event.messageId);
      if (existingIndex === -1) {
        continue;
      }
      messages[existingIndex] = {
        message: {
          ...messages[existingIndex].message,
          ...(event.inProgress === undefined ? {} : {
            inProgress: event.inProgress
          }),
          text: event.text,
          time: event.time,
          ...(event.toolCalls === undefined ? {} : {
            toolCalls: event.toolCalls
          })
        },
        timestamp: event.timestamp,
        type: 'chat-message-added'
      };
      continue;
    }
    if (event.type === 'chat-session-messages-replaced') {
      messages.length = 0;
      messages.push(...event.messages.map(message => ({
        message,
        timestamp: event.timestamp,
        type: 'chat-message-added'
      })));
    }
  }
  return messages;
};
const mergePartialMessage = (messages, sessionId) => {
  const partialMessage = getPartialMessage(sessionId);
  if (!partialMessage) {
    return messages;
  }
  const partialEvent = {
    message: partialMessage,
    timestamp: partialMessage.time,
    type: 'chat-message-added'
  };
  const existingIndex = messages.findIndex(event => event.message?.id === partialMessage.id);
  if (existingIndex === -1) {
    return [...messages, partialEvent];
  }
  return messages.map((event, index) => {
    if (index !== existingIndex) {
      return event;
    }
    return partialEvent;
  });
};
const isMissingStoreError = error => {
  return typeof error === 'object' && error !== null && 'name' in error && error.name === 'NotFoundError';
};
const getPersistedMessages = async sessionId => {
  const database = await openDatabase(chatSessionStorageDatabaseName, chatSessionStorageDatabaseVersion);
  try {
    const t = database.transaction(chatSessionStorageEventStoreName, 'readonly');
    const store = t.objectStore(chatSessionStorageEventStoreName);
    const index = store.index(chatSessionStorageSessionIdIndexName);
    const events = await index.getAll(sessionId);
    const messageEvents = events.filter(isMessageEvent);
    return toMessages(messageEvents);
  } catch (error) {
    if (isMissingStoreError(error)) {
      return [];
    }
    throw error;
  } finally {
    database.close();
  }
};
const getMessages = async sessionId => {
  if (!sessionId) {
    throw new Error(`session id is required`);
  }
  const messages = await getPersistedMessages(sessionId);
  return mergePartialMessage(messages, sessionId);
};

const listChatSessions = async () => {
  const sessions = await getChatSessionStorage().listSessions();
  return sessions.map(session => {
    const summary = {
      id: session.id,
      messages: [],
      title: session.title
    };
    if (!session.projectId) {
      return summary;
    }
    return {
      ...summary,
      projectId: session.projectId
    };
  });
};

const getEndTime$1 = event => {
  return event.ended ?? event.endTime ?? event.timestamp;
};

const getStartTime$1 = event => {
  return event.started ?? event.startTime ?? event.timestamp;
};

const toTimeNumber$1 = value => {
  if (typeof value === 'number' && Number.isFinite(value)) {
    return value;
  }
  if (typeof value === 'string') {
    const timestamp = Date.parse(value);
    if (!Number.isNaN(timestamp)) {
      return timestamp;
    }
  }
  return undefined;
};

const getDuration$1 = event => {
  const explicitDuration = event.durationMs ?? event.duration;
  if (typeof explicitDuration === 'number' && Number.isFinite(explicitDuration)) {
    return explicitDuration;
  }
  const start = toTimeNumber$1(getStartTime$1(event));
  const end = toTimeNumber$1(getEndTime$1(event));
  if (start === undefined || end === undefined || !Number.isFinite(start) || !Number.isFinite(end)) {
    return 0;
  }
  return Math.max(0, end - start);
};

const isTimeValue = value => {
  return typeof value === 'number' || typeof value === 'string';
};

const getTextSize = value => {
  try {
    return JSON.stringify(value).length;
  } catch {
    return 0;
  }
};
const getSize = event => {
  if (typeof event.size === 'number' && event.size > 0) {
    return event.size;
  }
  // TODO maybe flatten tool call event so that
  // its just event.value, similar to how
  // it is for network events
  if (event && 'toolCallResult' in event && typeof event.toolCallResult === 'object' && event.toolCallResult && 'value' in event.toolCallResult && event.toolCallResult.value) {
    return getTextSize(event.toolCallResult.value);
  }
  if (!('value' in event)) {
    return undefined;
  }
  return getTextSize(event.value);
};
const getLightweightEvent = (event, fallbackEventId) => {
  const startTime = getStartTime$1(event);
  const endTime = getEndTime$1(event);
  const {
    requestId,
    timestamp
  } = event;
  const size = getSize(event);
  return {
    duration: getDuration$1(event),
    ...(isTimeValue(endTime) ? {
      endTime
    } : {}),
    eventId: typeof event.eventId === 'number' ? event.eventId : fallbackEventId,
    ...(typeof requestId === 'string' ? {
      requestId
    } : {}),
    ...(typeof size === 'number' ? {
      size
    } : {}),
    ...(isTimeValue(startTime) ? {
      startTime
    } : {}),
    ...(isTimeValue(timestamp) ? {
      timestamp
    } : {}),
    type: event.type
  };
};

const startedEventType$2 = 'tool-execution-started';
const finishedEventType$2 = 'tool-execution-finished';
const mergedEventType$1 = 'tool-execution';
const toTimeNumber = value => {
  if (typeof value === 'number' && Number.isFinite(value)) {
    return value;
  }
  if (typeof value === 'string') {
    const timestamp = Date.parse(value);
    if (!Number.isNaN(timestamp)) {
      return timestamp;
    }
  }
  return undefined;
};
const getStartTime = event => {
  const {
    started,
    startTime,
    timestamp
  } = event;
  if (typeof started === 'number' || typeof started === 'string') {
    return started;
  }
  if (typeof startTime === 'number' || typeof startTime === 'string') {
    return startTime;
  }
  if (typeof timestamp === 'number' || typeof timestamp === 'string') {
    return timestamp;
  }
  return undefined;
};
const getEndTime = event => {
  const {
    ended,
    endTime,
    timestamp
  } = event;
  if (typeof ended === 'number' || typeof ended === 'string') {
    return ended;
  }
  if (typeof endTime === 'number' || typeof endTime === 'string') {
    return endTime;
  }
  if (typeof timestamp === 'number' || typeof timestamp === 'string') {
    return timestamp;
  }
  return undefined;
};
const getDuration = event => {
  const explicitDuration = event.durationMs ?? event.duration;
  if (typeof explicitDuration === 'number' && Number.isFinite(explicitDuration)) {
    return explicitDuration;
  }
  const start = toTimeNumber(getStartTime(event));
  const end = toTimeNumber(getEndTime(event));
  if (start === undefined || end === undefined || !Number.isFinite(start) || !Number.isFinite(end)) {
    return 0;
  }
  return Math.max(0, end - start);
};
const hasMatchingToolName$2 = (startedEvent, finishedEvent) => {
  const startedToolName = startedEvent.toolName;
  const finishedToolName = finishedEvent.toolName;
  if (typeof startedToolName === 'string' && typeof finishedToolName === 'string') {
    return startedToolName === finishedToolName;
  }
  return true;
};
const isMatchingToolExecutionPair$1 = (startedEvent, finishedEvent) => {
  return startedEvent.sessionId === finishedEvent.sessionId && hasMatchingToolName$2(startedEvent, finishedEvent);
};
const mergeToolExecutionEvents$2 = (startedEvent, finishedEvent) => {
  const ended = getEndTime(finishedEvent);
  const started = getStartTime(startedEvent);
  return {
    ...startedEvent,
    ...finishedEvent,
    ...(ended === undefined ? {} : {
      ended
    }),
    ...(typeof startedEvent.eventId === 'number' ? {
      eventId: startedEvent.eventId
    } : {}),
    ...(started === undefined ? {} : {
      started
    }),
    type: mergedEventType$1
  };
};
const withEventIds = events => {
  return events.map((event, index) => {
    return {
      ...event,
      eventId: index + 1
    };
  });
};
const getStatus = event => {
  if (typeof event.statusCode === 'number') {
    return event.statusCode;
  }
  if (event.type === 'tool-calls-finished' && event.toolCallResults && event.toolCallResults[0].type === 'error') {
    if (event.toolCallResults[0].error && event.toolCallResults[0].error.startsWith('Error: File not found')) {
      return 404;
    }
    if (event.toolCallResults[0].error && event.toolCallResults[0].error.startsWith('Invalid argument')) {
      return 400;
    }
    return 500;
  }
  return 200;
};
const getSubType = event => {
  if (typeof event.name === 'string') {
    return event.name;
  }
  return event.type || '';
};
const toLightweightEvent = (event, fallbackEventId) => {
  const startTime = getStartTime(event);
  const endTime = getEndTime(event);
  const {
    requestId,
    timestamp
  } = event;
  const size = getSize(event);
  const subType = getSubType(event);
  return {
    duration: getDuration(event),
    ...(endTime === undefined ? {} : {
      endTime
    }),
    eventId: typeof event.eventId === 'number' ? event.eventId : fallbackEventId,
    ...(typeof requestId === 'string' ? {
      requestId
    } : {}),
    ...(typeof size === 'number' ? {
      size
    } : {}),
    ...(startTime === undefined ? {} : {
      startTime
    }),
    status: getStatus(event),
    ...(typeof timestamp === 'number' || typeof timestamp === 'string' ? {
      timestamp
    } : {}),
    subType,
    type: event.type
  };
};
const listChatViewEventSummaries = events => {
  const eventsWithIds = withEventIds(filterDebugChatViewEvents(events));
  return eventsWithIds.map((event, index) => toLightweightEvent(event, index + 1));
};
const loadSelectedChatViewEvent = (events, eventId, summaryType) => {
  if (eventId < 1) {
    return null;
  }
  const eventsWithIds = withEventIds(filterDebugChatViewEvents(events));
  const event = eventsWithIds[eventId - 1];
  if (!event) {
    return null;
  }
  if (summaryType !== mergedEventType$1 || event.type !== startedEventType$2) {
    return event;
  }
  const nextEvent = eventsWithIds[eventId];
  if (!nextEvent || nextEvent.type !== finishedEventType$2 || !isMatchingToolExecutionPair$1(event, nextEvent)) {
    return event;
  }
  return mergeToolExecutionEvents$2(event, nextEvent);
};

const listChatViewEvents = async sessionId => {
  try {
    const events = await getChatSessionStorage().getEvents(sessionId);
    const normalized = listChatViewEventSummaries(events);
    return {
      events: normalized,
      type: 'success'
    };
  } catch (error) {
    return {
      error,
      type: 'error'
    };
  }
};

const loadSelectedEventOld = async (sessionId, eventId, type) => {
  const events = await getChatSessionStorage().getEvents(sessionId);
  return loadSelectedChatViewEvent(events, eventId, type);
};

const setSession = async session => {
  await getChatSessionStorage().setSession(session);
  await notifySessionListeners(session.id);
};

const setPartialMessage = async ({
  message,
  sessionId
}) => {
  setPartialMessageValue(sessionId, {
    ...message,
    partial: true
  });
  await notifySessionListeners(sessionId);
};

const notifyAllSessionListeners = async () => {
  for (const listener of getSessionListeners().values()) {
    await notifySessionListener(listener);
  }
};

const clearChatSessions = async () => {
  await getChatSessionStorage().clear();
  clearAllPartialMessages();
  await notifyAllSessionListeners();
};

const assertRpcAvailable = rpcId => {
  const rpc = get(rpcId);
  if (!rpc) {
    throw new Error(`No rpc with id ${rpcId} was found`);
  }
};

const getSessionListenerKey = ({
  rpcId,
  uid
}) => {
  return `${rpcId}:${uid}`;
};

const subscribeSessionUpdates = listener => {
  assertRpcAvailable(listener.rpcId);
  getSessionListeners().set(getSessionListenerKey(listener), listener);
};

const unsubscribeSessionUpdates = listener => {
  getSessionListeners().delete(getSessionListenerKey(listener));
};

const waitForSessionUpdates = async () => {
  return [];
};

const createSession = async session => {
  // TODO
  // 1. in chat-session table, insert the new entry

  await getChatSessionStorage().setSession({
    id: session.sessionId,
    messages: [],
    timestamp: session.timestamp || '',
    title: session.title || ''
  });
};

const handleMessagePort = async (port, rpcId) => {
  const rpc = await create$1({
    commandMap: commandMap,
    isMessagePortOpen: true,
    messagePort: port
  });
  if (rpcId) {
    set(rpcId, rpc);
  }
};

const hasMatchingToolName$1 = (startedEvent, finishedEvent) => {
  if (typeof startedEvent.toolName === 'string' && typeof finishedEvent.toolName === 'string') {
    return startedEvent.toolName === finishedEvent.toolName;
  }
  return true;
};

const isMatchingToolExecutionPair = (startedEvent, finishedEvent) => {
  return startedEvent.sessionId === finishedEvent.sessionId && hasMatchingToolName$1(startedEvent, finishedEvent);
};

const startedEventType$1 = 'tool-execution-started';
const finishedEventType$1 = 'tool-execution-finished';
const mergedEventType = 'tool-execution';

const isToolExecutionFinishedEvent = event => {
  return event.type === finishedEventType$1;
};

const isToolExecutionStartedEvent = event => {
  return event.type === startedEventType$1;
};

const getTimestamp$1 = value => {
  return typeof value === 'string' || typeof value === 'number' ? value : undefined;
};

const getEndedTimestamp = event => {
  return getTimestamp$1(event.ended) ?? getTimestamp$1(event.endTime) ?? getTimestamp$1(event.endTimestamp) ?? getTimestamp$1(event.timestamp);
};

const getStartedTimestamp = event => {
  return getTimestamp$1(event.started) ?? getTimestamp$1(event.startTime) ?? getTimestamp$1(event.startTimestamp) ?? getTimestamp$1(event.timestamp);
};

const mergeToolExecutionEvents$1 = (startedEvent, finishedEvent) => {
  const ended = getEndedTimestamp(finishedEvent);
  const {
    eventId
  } = startedEvent;
  const started = getStartedTimestamp(startedEvent);
  const mergedEvent = {
    ...startedEvent,
    ...finishedEvent,
    ...(ended === undefined ? {} : {
      ended
    }),
    ...(eventId === undefined ? {} : {
      eventId
    }),
    ...(started === undefined ? {} : {
      started
    }),
    type: mergedEventType
  };
  return mergedEvent;
};

const collapseToolExecutionEvents = events => {
  const collapsedEvents = [];
  for (let i = 0; i < events.length; i++) {
    const event = events[i];
    if (isToolExecutionStartedEvent(event)) {
      const nextEvent = events[i + 1];
      if (nextEvent && isToolExecutionFinishedEvent(nextEvent) && isMatchingToolExecutionPair(event, nextEvent)) {
        collapsedEvents.push(mergeToolExecutionEvents$1(event, nextEvent));
        i++;
        continue;
      }
    }
    collapsedEvents.push(event);
  }
  return collapsedEvents;
};

const filterEventsBySessionId = (events, sessionId) => {
  return events.filter(event => event.sessionId === sessionId);
};

// cspell:ignore IDBP

// eslint-disable-next-line @typescript-eslint/prefer-readonly-parameter-types
const getAllEvents = async store => {
  const all = await store.getAll();
  return all;
};

// cspell:ignore IDBP

const toLightweightEvents = events => {
  const eventsWithIds = events.map((event, index) => {
    return {
      ...event,
      eventId: index + 1
    };
  });
  return collapseToolExecutionEvents(eventsWithIds).map((event, index) => getLightweightEvent(event, index + 1));
};
const getSimpleEventsBySessionId = async (store, sessionId, sessionIdIndexName) => {
  if (store.indexNames.contains(sessionIdIndexName)) {
    const index = store.index(sessionIdIndexName);
    const events = await index.getAll(sessionId);
    return toLightweightEvents(filterDebugChatViewEvents(filterEventsBySessionId(events, sessionId)));
  }
  const all = await getAllEvents(store);
  return toLightweightEvents(filterDebugChatViewEvents(filterEventsBySessionId(all, sessionId)));
};

const isIndexedDbSupported = indexedDbSupportOverride => {
  if (typeof indexedDbSupportOverride === 'boolean') {
    return indexedDbSupportOverride;
  }
  return globalThis.indexedDB != undefined;
};

const shouldUseLegacyFallback$1 = (databaseName, databaseVersion, eventStoreName, sessionIdIndexName) => {
  return databaseName === debugEventStorageDatabaseName && databaseVersion === debugEventStorageDatabaseVersion && eventStoreName === debugEventStorageEventStoreName && sessionIdIndexName === chatSessionStorageSessionIdIndexName;
};
const getEventsFromStore = async (databaseName, databaseVersion, eventStoreName, sessionId, sessionIdIndexName) => {
  const database = await openDatabase(databaseName, databaseVersion);
  try {
    if (!database.objectStoreNames.contains(eventStoreName)) {
      return [];
    }
    const transaction = database.transaction(eventStoreName, 'readonly');
    const store = transaction.objectStore(eventStoreName);
    return getSimpleEventsBySessionId(store, sessionId, sessionIdIndexName);
  } finally {
    database.close();
  }
};
const listChatViewEventsSimple = async ({
  databaseName,
  databaseVersion,
  eventStoreName,
  indexedDbSupportOverride,
  sessionId,
  sessionIdIndexName
}) => {
  if (!isIndexedDbSupported(indexedDbSupportOverride)) {
    return {
      type: 'not-supported'
    };
  }
  if (!sessionId) {
    return {
      events: [],
      type: 'success'
    };
  }
  try {
    const events = await getEventsFromStore(databaseName, databaseVersion, eventStoreName, sessionId, sessionIdIndexName);
    if (events.length > 0 || !shouldUseLegacyFallback$1(databaseName, databaseVersion, eventStoreName, sessionIdIndexName)) {
      return {
        events,
        type: 'success'
      };
    }
    const legacyEvents = await getEventsFromStore(chatSessionStorageDatabaseName, chatSessionStorageDatabaseVersion, chatSessionStorageEventStoreName, sessionId, chatSessionStorageSessionIdIndexName);
    return {
      events: legacyEvents,
      type: 'success'
    };
  } catch (error) {
    return {
      error,
      type: 'error'
    };
  }
};

// cspell:ignore IDBP

const startedEventType = 'tool-execution-started';
const finishedEventType = 'tool-execution-finished';
const getEventsBySessionId = async (store, sessionId, sessionIdIndexName) => {
  if (store.indexNames.contains(sessionIdIndexName)) {
    const index = store.index(sessionIdIndexName);
    const events = await index.getAll(sessionId);
    return filterDebugChatViewEvents(filterEventsBySessionId(events, sessionId));
  }
  const all = await store.getAll();
  return filterDebugChatViewEvents(filterEventsBySessionId(all, sessionId));
};
const getTimestamp = value => {
  return typeof value === 'string' || typeof value === 'number' ? value : undefined;
};
const hasMatchingToolName = (startedEvent, finishedEvent) => {
  if (typeof startedEvent.toolName === 'string' && typeof finishedEvent.toolName === 'string') {
    return startedEvent.toolName === finishedEvent.toolName;
  }
  return true;
};
const mergeToolExecutionEvents = (startedEvent, finishedEvent, eventId) => {
  const ended = getTimestamp(finishedEvent.ended) ?? getTimestamp(finishedEvent.endTime) ?? getTimestamp(finishedEvent.timestamp);
  const started = getTimestamp(startedEvent.started) ?? getTimestamp(startedEvent.startTime) ?? getTimestamp(startedEvent.timestamp);
  return {
    ...startedEvent,
    ...finishedEvent,
    ...(ended === undefined ? {} : {
      ended
    }),
    eventId,
    ...(started === undefined ? {} : {
      started
    }),
    type: 'tool-execution'
  };
};
const getEventDetailsBySessionIdAndEventId = async (store, sessionId, sessionIdIndexName, eventId, summaryType) => {
  if (eventId < 1) {
    return undefined;
  }
  const events = await getEventsBySessionId(store, sessionId, sessionIdIndexName);
  const event = events[eventId - 1];
  if (!event) {
    return undefined;
  }
  if (summaryType !== 'tool-execution') {
    return {
      ...event,
      eventId
    };
  }
  if (event.type !== startedEventType) {
    return {
      ...event,
      eventId
    };
  }
  const nextEvent = events[eventId];
  if (!nextEvent || nextEvent.type !== finishedEventType || nextEvent.sessionId !== sessionId || !hasMatchingToolName(event, nextEvent)) {
    return {
      ...event,
      eventId
    };
  }
  return mergeToolExecutionEvents(event, nextEvent, eventId);
};

const shouldUseLegacyFallback = (databaseName, databaseVersion, eventStoreName, sessionIdIndexName) => {
  return databaseName === debugEventStorageDatabaseName && databaseVersion === debugEventStorageDatabaseVersion && eventStoreName === debugEventStorageEventStoreName && sessionIdIndexName === chatSessionStorageSessionIdIndexName;
};
const loadFromStore = async (databaseName, databaseVersion, eventId, eventStoreName, sessionId, sessionIdIndexName, type) => {
  const database = await openDatabase(databaseName, databaseVersion);
  try {
    if (!database.objectStoreNames.contains(eventStoreName)) {
      return null;
    }
    const transaction = database.transaction(eventStoreName, 'readonly');
    const store = transaction.objectStore(eventStoreName);
    const event = await getEventDetailsBySessionIdAndEventId(store, sessionId, sessionIdIndexName, eventId, type);
    return event ?? null;
  } finally {
    database.close();
  }
};
const loadSelectedEvent = async ({
  databaseName,
  databaseVersion,
  eventId,
  eventStoreName,
  sessionId,
  sessionIdIndexName,
  type
}) => {
  const event = await loadFromStore(databaseName, databaseVersion, eventId, eventStoreName, sessionId, sessionIdIndexName, type);
  if (event || !shouldUseLegacyFallback(databaseName, databaseVersion, eventStoreName, sessionIdIndexName)) {
    return event;
  }
  return loadFromStore(chatSessionStorageDatabaseName, chatSessionStorageDatabaseVersion, eventId, chatSessionStorageEventStoreName, sessionId, chatSessionStorageSessionIdIndexName, type);
};

const setTodosInIndexedDb = async todos => {
  const todoDbName = 'chat-tool-worker';
  const todoStoreName = 'state';
  const todoKey = 'todoList';
  const db = await new Promise((resolve, reject) => {
    const openRequest = indexedDB.open(todoDbName, 1);
    openRequest.onupgradeneeded = () => {
      const nextDb = openRequest.result;
      if (!nextDb.objectStoreNames.contains(todoStoreName)) {
        nextDb.createObjectStore(todoStoreName);
      }
    };
    openRequest.onsuccess = () => {
      resolve(openRequest.result);
    };
    openRequest.onerror = () => {
      reject(openRequest.error ?? new Error('Failed to open IndexedDB'));
    };
  });
  await new Promise((resolve, reject) => {
    const transaction = db.transaction(todoStoreName, 'readwrite');
    transaction.objectStore(todoStoreName).put(todos, todoKey);
    transaction.oncomplete = () => {
      db.close();
      resolve();
    };
    transaction.onerror = () => {
      db.close();
      reject(transaction.error ?? new Error('Failed to persist todo list'));
    };
    transaction.onabort = () => {
      db.close();
      reject(transaction.error ?? new Error('Failed to persist todo list'));
    };
  });
};
const setTodosInCacheStorage = async todos => {
  const todoCacheName = 'chat-tool-worker-todo';
  const todoCacheRequest = new Request('https://chat-tool-worker.local/todo-list');
  const cache = await caches.open(todoCacheName);
  const payload = JSON.stringify({
    todos
  });
  await cache.put(todoCacheRequest, new Response(payload, {
    headers: {
      'content-type': 'application/json'
    }
  }));
};

const commandMap = {
  'ChatStorage.appendDebugEvent': appendChatDebugEvent,
  'ChatStorage.appendEvent': appendChatViewEvent,
  'ChatStorage.clear': clearChatSessions,
  'ChatStorage.clearPartialMessage': clearPartialMessage,
  'ChatStorage.consumeSessionUpdates': consumeSessionUpdates,
  'ChatStorage.createSession': createSession,
  'ChatStorage.deleteSession': deleteChatSession,
  'ChatStorage.getEvents': getChatViewEvents,
  'ChatStorage.getMessages': getMessages,
  'ChatStorage.getSession': getChatSession,
  'ChatStorage.listChatViewEvents': listChatViewEvents,
  'ChatStorage.listChatViewEventsSimple': listChatViewEventsSimple,
  'ChatStorage.listSessions': listChatSessions,
  'ChatStorage.loadSelectedEvent': loadSelectedEventOld,
  'ChatStorage.loadSelectedEventNew': loadSelectedEvent,
  'ChatStorage.setPartialMessage': setPartialMessage,
  'ChatStorage.setSession': setSession,
  'ChatStorage.setTodosInCacheStorage': setTodosInCacheStorage,
  'ChatStorage.setTodosInIndexedDb': setTodosInIndexedDb,
  'ChatStorage.subscribeSessionUpdates': subscribeSessionUpdates,
  'ChatStorage.unsubscribeSessionUpdates': unsubscribeSessionUpdates,
  'ChatStorage.waitForSessionUpdates': waitForSessionUpdates,
  'HandleMessagePort.handleMessagePort': handleMessagePort,
  initialize: (_, port) => handleMessagePort(port)
};

const listen = async () => {
  await create({
    commandMap: commandMap
  });
};

const main = async () => {
  await listen();
};

main();
