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 waitForFirstMessage = async port => {
  const {
    promise,
    resolve
  } = Promise.withResolvers();
  port.addEventListener('message', resolve, {
    once: true
  });
  const event = await promise;
  // @ts-ignore
  return event.data;
};
const listen$6 = async () => {
  const parentIpcRaw = listen$7();
  signal$8(parentIpcRaw);
  const parentIpc = wrap$f(parentIpcRaw);
  const firstMessage = await waitForFirstMessage(parentIpc);
  if (firstMessage.method !== 'initialize') {
    throw new IpcError('unexpected first message');
  }
  const type = firstMessage.params[0];
  if (type === 'message-port') {
    parentIpc.send({
      id: firstMessage.id,
      jsonrpc: '2.0',
      result: null
    });
    parentIpc.dispose();
    const port = firstMessage.params[1];
    return port;
  }
  return globalThis;
};
class IpcChildWithModuleWorkerAndMessagePort extends Ipc {
  getData(event) {
    return getData$2(event);
  }
  send(message) {
    this._rawIpc.postMessage(message);
  }
  sendAndTransfer(message) {
    const transfer = getTransferrables(message);
    this._rawIpc.postMessage(message, transfer);
  }
  dispose() {
    if (this._rawIpc.close) {
      this._rawIpc.close();
    }
  }
  onClose(callback) {
    // ignore
  }
  onMessage(callback) {
    this._rawIpc.addEventListener('message', callback);
    this._rawIpc.start();
  }
}
const wrap$e = port => {
  return new IpcChildWithModuleWorkerAndMessagePort(port);
};
const IpcChildWithModuleWorkerAndMessagePort$1 = {
  __proto__: null,
  listen: listen$6,
  wrap: wrap$e
};
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$3 = id => {
  return callbacks[id];
};
const remove$1 = 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$3(id);
  if (!fn) {
    console.log(response);
    warn(`callback ${id} may already be disposed`);
    return;
  }
  fn(response);
  remove$1(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$9 = (message, result) => {
  return {
    id: message.id,
    jsonrpc: Two$1,
    result: result ?? null
  };
};
const getSuccessResponse = (message, result) => {
  const resultProperty = result ?? null;
  return create$9(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$8 = (method, params) => {
  return {
    jsonrpc: Two,
    method,
    params
  };
};

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

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

const registerPromise = map => {
  const id = create$6();
  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$7(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$8(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$5 = 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$4 = async ({
  commandMap,
  isMessagePortOpen,
  send
}) => {
  const {
    port1,
    port2
  } = new MessageChannel();
  await send(port1);
  return create$5({
    commandMap,
    isMessagePortOpen,
    messagePort: port2
  });
};

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

const ExtensionHostWorker = 44;
const RendererWorker = 1;

const createMockRpc = ({
  commandMap
}) => {
  const invocations = [];
  const invoke = (method, ...params) => {
    invocations.push([method, ...params]);
    const command = commandMap[method];
    if (!command) {
      throw new Error(`command ${method} not found`);
    }
    return command(...params);
  };
  const mockRpc = {
    invocations,
    invoke,
    invokeAndTransfer: invoke
  };
  return mockRpc;
};

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

/* eslint-disable  @typescript-eslint/explicit-function-return-type */
const create$2 = rpcId => {
  return {
    async dispose() {
      const rpc = get$2(rpcId);
      await rpc.dispose();
    },
    // @ts-ignore
    invoke(method, ...params) {
      const rpc = get$2(rpcId);
      // @ts-ignore
      return rpc.invoke(method, ...params);
    },
    // @ts-ignore
    invokeAndTransfer(method, ...params) {
      const rpc = get$2(rpcId);
      // @ts-ignore
      return rpc.invokeAndTransfer(method, ...params);
    },
    registerMockRpc(commandMap) {
      const mockRpc = createMockRpc({
        commandMap
      });
      set$3(rpcId, mockRpc);
      // @ts-ignore
      mockRpc[Symbol.dispose] = () => {
        remove(rpcId);
      };
      // @ts-ignore
      return mockRpc;
    },
    set(rpc) {
      set$3(rpcId, rpc);
    }
  };
};

const {
  set: set$2
} = create$2(ExtensionHostWorker);

const {
  invoke,
  invokeAndTransfer,
  set: set$1
} = create$2(RendererWorker);
const sendMessagePortToExtensionHostWorker$1 = async (port, rpcId = 0) => {
  const command = 'HandleMessagePort.handleMessagePort2';
  await invokeAndTransfer('SendMessagePortToExtensionHostWorker.sendMessagePortToExtensionHostWorker', port, command, rpcId);
};
const getPreference = async key => {
  return await invoke('Preferences.get', key);
};
const openExternal = async uri => {
  await invoke('Open.openExternal', uri);
};

const toCommandId = key => {
  const dotIndex = key.indexOf('.');
  return key.slice(dotIndex + 1);
};
const create$1 = () => {
  const states = Object.create(null);
  const commandMapRef = {};
  return {
    clear() {
      for (const key of Object.keys(states)) {
        delete states[key];
      }
    },
    diff(uid, modules, numbers) {
      const {
        newState,
        oldState
      } = states[uid];
      const diffResult = [];
      for (let i = 0; i < modules.length; i++) {
        const fn = modules[i];
        if (!fn(oldState, newState)) {
          diffResult.push(numbers[i]);
        }
      }
      return diffResult;
    },
    dispose(uid) {
      delete states[uid];
    },
    get(uid) {
      return states[uid];
    },
    getCommandIds() {
      const keys = Object.keys(commandMapRef);
      const ids = keys.map(toCommandId);
      return ids;
    },
    getKeys() {
      return Object.keys(states).map(key => {
        return Number.parseFloat(key);
      });
    },
    registerCommands(commandMap) {
      Object.assign(commandMapRef, commandMap);
    },
    set(uid, oldState, newState) {
      states[uid] = {
        newState,
        oldState
      };
    },
    wrapCommand(fn) {
      const wrapped = async (uid, ...args) => {
        const {
          newState,
          oldState
        } = states[uid];
        const newerState = await fn(newState, ...args);
        if (oldState === newerState || newState === newerState) {
          return;
        }
        const latestOld = states[uid];
        const latestNew = {
          ...latestOld.newState,
          ...newerState
        };
        states[uid] = {
          newState: latestNew,
          oldState: latestOld.oldState
        };
      };
      return wrapped;
    },
    wrapGetter(fn) {
      const wrapped = (uid, ...args) => {
        const {
          newState
        } = states[uid];
        return fn(newState, ...args);
      };
      return wrapped;
    },
    wrapLoadContent(fn) {
      const wrapped = async (uid, ...args) => {
        const {
          newState,
          oldState
        } = states[uid];
        const result = await fn(newState, ...args);
        const {
          error,
          state
        } = result;
        if (oldState === state || newState === state) {
          return {
            error
          };
        }
        const latestOld = states[uid];
        const latestNew = {
          ...latestOld.newState,
          ...state
        };
        states[uid] = {
          newState: latestNew,
          oldState: latestOld.oldState
        };
        return {
          error
        };
      };
      return wrapped;
    }
  };
};
const terminate = () => {
  globalThis.close();
};

const clearInput = async state => {
  return {
    ...state,
    composerValue: ''
  };
};

const emptyObject = {};
const RE_PLACEHOLDER = /\{(PH\d+)\}/g;
const i18nString = (key, placeholders = emptyObject) => {
  if (placeholders === emptyObject) {
    return key;
  }
  const replacer = (match, rest) => {
    return placeholders[rest];
  };
  return key.replaceAll(RE_PLACEHOLDER, replacer);
};

const chatTitle = () => {
  return i18nString('Chat');
};
const chats = () => {
  return i18nString('Chats');
};
const newChat = () => {
  return i18nString('New Chat');
};
const backToChats = () => {
  return i18nString('Back to chats');
};
const settings = () => {
  return i18nString('Settings');
};
const closeChat = () => {
  return i18nString('Close Chat');
};
const clickToOpenNewChat = () => {
  return i18nString('Click the + button to open a new chat.');
};
const startConversation = () => {
  return i18nString('Start a conversation by typing below.');
};
const composePlaceholder = () => {
  return i18nString('Type your message. Enter to send, Shift+Enter for newline.');
};
const openRouterApiKeyPlaceholder = () => {
  return i18nString('Enter OpenRouter API key');
};
const sendMessage = () => {
  return i18nString('Send message');
};
const save = () => {
  return i18nString('Save');
};
const getOpenRouterApiKey = () => {
  return i18nString('Get API Key');
};
const deleteChatSession$1 = () => {
  return i18nString('Delete chat session');
};
const defaultSessionTitle = () => {
  return i18nString('Chat 1');
};
const dummyChatA = () => {
  return i18nString('Dummy Chat A');
};
const dummyChatB = () => {
  return i18nString('Dummy Chat B');
};
const dummyChatC = () => {
  return i18nString('Dummy Chat C');
};
const unknownViewMode = () => {
  return i18nString('Unknown view mode');
};

/* eslint-disable @cspell/spellchecker */

const getDefaultModels = () => {
  const defaultModelId = 'test';
  return [{
    id: defaultModelId,
    name: 'test',
    provider: 'test'
  }, {
    id: 'codex-5.3',
    name: 'Codex 5.3',
    provider: 'openRouter'
  }, {
    id: 'claude-code',
    name: 'Claude Code',
    provider: 'openRouter'
  }, {
    id: 'claude-haiku',
    name: 'Claude Haiku',
    provider: 'openRouter'
  }, {
    id: 'openRouter/openai/gpt-4o-mini',
    name: 'GPT-4o Mini',
    provider: 'openRouter'
  }, {
    id: 'openRouter/anthropic/claude-3.5-haiku',
    name: 'Claude 3.5 Haiku',
    provider: 'openRouter'
  }, {
    id: 'openRouter/google/gemini-2.0-flash-001',
    name: 'Gemini 2.0 Flash',
    provider: 'openRouter'
  }, {
    id: 'openRouter/openai/gpt-oss-20b:free',
    name: 'GPT OSS 20B (Free)',
    provider: 'openRouter'
  }, {
    id: 'openRouter/openai/gpt-oss-120b:free',
    name: 'GPT OSS 120B (Free)',
    provider: 'openRouter'
  }, {
    id: 'openRouter/meta-llama/llama-3.3-70b-instruct:free',
    name: 'Llama 3.3 70B Instruct (Free)',
    provider: 'openRouter'
  }, {
    id: 'openRouter/google/gemma-3-27b-it:free',
    name: 'Gemma 3 27B IT (Free)',
    provider: 'openRouter'
  }, {
    id: 'openRouter/qwen/qwen3-coder:free',
    name: 'Qwen3 Coder (Free)',
    provider: 'openRouter'
  }, {
    id: 'openRouter/mistralai/mistral-small-3.1-24b-instruct:free',
    name: 'Mistral Small 3.1 24B Instruct (Free)',
    provider: 'openRouter'
  }];
};
const createDefaultState = () => {
  const defaultSessionId = 'session-1';
  const defaultModelId = 'test';
  return {
    assetDir: '',
    composerValue: '',
    errorCount: 0,
    focus: 'composer',
    focused: false,
    headerHeight: 50,
    height: 0,
    initial: true,
    inputSource: 'script',
    lastSubmittedSessionId: '',
    listItemHeight: 40,
    models: getDefaultModels(),
    nextMessageId: 1,
    openRouterApiBaseUrl: 'https://openrouter.ai/api/v1',
    openRouterApiKey: '',
    openRouterApiKeyInput: '',
    openRouterApiKeysSettingsUrl: 'https://openrouter.ai/settings/keys',
    platform: 0,
    renamingSessionId: '',
    selectedModelId: defaultModelId,
    selectedSessionId: defaultSessionId,
    sessions: [{
      id: defaultSessionId,
      messages: [],
      title: defaultSessionTitle()
    }],
    tokensMax: 0,
    tokensUsed: 0,
    uid: 0,
    usageOverviewEnabled: false,
    viewMode: 'list',
    warningCount: 0,
    width: 0,
    x: 0,
    y: 0
  };
};

const {
  get: get$1,
  getCommandIds,
  registerCommands,
  set,
  wrapCommand,
  wrapGetter
} = create$1();

const create = (uid, x, y, width, height, platform, assetDir) => {
  const state = {
    ...createDefaultState(),
    assetDir,
    height,
    platform,
    uid,
    width,
    x,
    y
  };
  set(uid, state, state);
};

const isEqual$1 = (oldState, newState) => {
  return oldState.initial === newState.initial;
};

const diffFocus = (oldState, newState) => {
  if (!newState.focused) {
    return true;
  }
  return oldState.focus === newState.focus && oldState.focused === newState.focused;
};

const isEqual = (oldState, newState) => {
  return oldState.composerValue === newState.composerValue && oldState.initial === newState.initial && oldState.renamingSessionId === newState.renamingSessionId && oldState.selectedModelId === newState.selectedModelId && oldState.selectedSessionId === newState.selectedSessionId && oldState.sessions === newState.sessions && oldState.tokensMax === newState.tokensMax && oldState.tokensUsed === newState.tokensUsed && oldState.usageOverviewEnabled === newState.usageOverviewEnabled && oldState.viewMode === newState.viewMode;
};

const RenderItems = 4;
const RenderFocus = 6;
const RenderFocusContext = 7;
const RenderValue = 8;
const RenderCss = 10;
const RenderIncremental = 11;

const diffValue = (oldState, newState) => {
  if (oldState.composerValue === newState.composerValue) {
    return true;
  }
  return newState.inputSource !== 'script';
};

const modules = [isEqual, diffValue, diffFocus, isEqual$1, diffFocus];
const numbers = [RenderIncremental, RenderValue, RenderFocus, RenderCss, RenderFocusContext];

const diff = (oldState, newState) => {
  const diffResult = [];
  for (let i = 0; i < modules.length; i++) {
    const fn = modules[i];
    if (!fn(oldState, newState)) {
      diffResult.push(numbers[i]);
    }
  }
  return diffResult;
};

const diff2 = uid => {
  const {
    newState,
    oldState
  } = get$1(uid);
  const result = diff(oldState, newState);
  return result;
};

const ClientX = 'event.clientX';
const ClientY = 'event.clientY';
const Key = 'event.key';
const ShiftKey = 'event.shiftKey';
const TargetName = 'event.target.name';
const TargetValue = 'event.target.value';

const FocusSelector = 'Viewlet.focusSelector';
const SetCss = 'Viewlet.setCss';
const SetDom2 = 'Viewlet.setDom2';
const SetFocusContext = 'Viewlet.setFocusContext';
const SetValueByName = 'Viewlet.setValueByName';
const SetPatches = 'Viewlet.setPatches';

const FocusChatInput = 8000;

const Button$2 = 'button';

const Button$1 = 1;
const Div = 4;
const Input = 6;
const Span = 8;
const Text = 12;
const Li = 48;
const Ol = 49;
const P = 50;
const TextArea = 62;
const Select$1 = 63;
const Option$1 = 64;
const Reference = 100;

const Enter = 3;

const Shift = 1 << 10 >>> 0;

const mergeClassNames = (...classNames) => {
  return classNames.filter(Boolean).join(' ');
};

const text = data => {
  return {
    childCount: 0,
    text: data,
    type: Text
  };
};

const SetText = 1;
const Replace = 2;
const SetAttribute = 3;
const RemoveAttribute = 4;
const Add = 6;
const NavigateChild = 7;
const NavigateParent = 8;
const RemoveChild = 9;
const NavigateSibling = 10;
const SetReferenceNodeUid = 11;

const isKey = key => {
  return key !== 'type' && key !== 'childCount';
};

const getKeys = node => {
  const keys = Object.keys(node).filter(isKey);
  return keys;
};

const arrayToTree = nodes => {
  const result = [];
  let i = 0;
  while (i < nodes.length) {
    const node = nodes[i];
    const {
      children,
      nodesConsumed
    } = getChildrenWithCount(nodes, i + 1, node.childCount || 0);
    result.push({
      node,
      children
    });
    i += 1 + nodesConsumed;
  }
  return result;
};
const getChildrenWithCount = (nodes, startIndex, childCount) => {
  if (childCount === 0) {
    return {
      children: [],
      nodesConsumed: 0
    };
  }
  const children = [];
  let i = startIndex;
  let remaining = childCount;
  let totalConsumed = 0;
  while (remaining > 0 && i < nodes.length) {
    const node = nodes[i];
    const nodeChildCount = node.childCount || 0;
    const {
      children: nodeChildren,
      nodesConsumed
    } = getChildrenWithCount(nodes, i + 1, nodeChildCount);
    children.push({
      node,
      children: nodeChildren
    });
    const nodeSize = 1 + nodesConsumed;
    i += nodeSize;
    totalConsumed += nodeSize;
    remaining--;
  }
  return {
    children,
    nodesConsumed: totalConsumed
  };
};

const compareNodes = (oldNode, newNode) => {
  const patches = [];
  // Check if node type changed - return null to signal incompatible nodes
  // (caller should handle this with a Replace operation)
  if (oldNode.type !== newNode.type) {
    return null;
  }
  // Handle reference nodes - special handling for uid changes
  if (oldNode.type === Reference) {
    if (oldNode.uid !== newNode.uid) {
      patches.push({
        type: SetReferenceNodeUid,
        uid: newNode.uid
      });
    }
    return patches;
  }
  // Handle text nodes
  if (oldNode.type === Text && newNode.type === Text) {
    if (oldNode.text !== newNode.text) {
      patches.push({
        type: SetText,
        value: newNode.text
      });
    }
    return patches;
  }
  // Compare attributes
  const oldKeys = getKeys(oldNode);
  const newKeys = getKeys(newNode);
  // Check for attribute changes
  for (const key of newKeys) {
    if (oldNode[key] !== newNode[key]) {
      patches.push({
        type: SetAttribute,
        key,
        value: newNode[key]
      });
    }
  }
  // Check for removed attributes
  for (const key of oldKeys) {
    if (!(key in newNode)) {
      patches.push({
        type: RemoveAttribute,
        key
      });
    }
  }
  return patches;
};

const treeToArray = node => {
  const result = [node.node];
  for (const child of node.children) {
    result.push(...treeToArray(child));
  }
  return result;
};

const diffChildren = (oldChildren, newChildren, patches) => {
  const maxLength = Math.max(oldChildren.length, newChildren.length);
  // Track where we are: -1 means at parent, >= 0 means at child index
  let currentChildIndex = -1;
  // Collect indices of children to remove (we'll add these patches at the end in reverse order)
  const indicesToRemove = [];
  for (let i = 0; i < maxLength; i++) {
    const oldNode = oldChildren[i];
    const newNode = newChildren[i];
    if (!oldNode && !newNode) {
      continue;
    }
    if (!oldNode) {
      // Add new node - we should be at the parent
      if (currentChildIndex >= 0) {
        // Navigate back to parent
        patches.push({
          type: NavigateParent
        });
        currentChildIndex = -1;
      }
      // Flatten the entire subtree so renderInternal can handle it
      const flatNodes = treeToArray(newNode);
      patches.push({
        type: Add,
        nodes: flatNodes
      });
    } else if (newNode) {
      // Compare nodes to see if we need any patches
      const nodePatches = compareNodes(oldNode.node, newNode.node);
      // If nodePatches is null, the node types are incompatible - need to replace
      if (nodePatches === null) {
        // Navigate to this child
        if (currentChildIndex === -1) {
          patches.push({
            type: NavigateChild,
            index: i
          });
          currentChildIndex = i;
        } else if (currentChildIndex !== i) {
          patches.push({
            type: NavigateSibling,
            index: i
          });
          currentChildIndex = i;
        }
        // Replace the entire subtree
        const flatNodes = treeToArray(newNode);
        patches.push({
          type: Replace,
          nodes: flatNodes
        });
        // After replace, we're at the new element (same position)
        continue;
      }
      // Check if we need to recurse into children
      const hasChildrenToCompare = oldNode.children.length > 0 || newNode.children.length > 0;
      // Only navigate to this element if we need to do something
      if (nodePatches.length > 0 || hasChildrenToCompare) {
        // Navigate to this child if not already there
        if (currentChildIndex === -1) {
          patches.push({
            type: NavigateChild,
            index: i
          });
          currentChildIndex = i;
        } else if (currentChildIndex !== i) {
          patches.push({
            type: NavigateSibling,
            index: i
          });
          currentChildIndex = i;
        }
        // Apply node patches (these apply to the current element, not children)
        if (nodePatches.length > 0) {
          patches.push(...nodePatches);
        }
        // Compare children recursively
        if (hasChildrenToCompare) {
          diffChildren(oldNode.children, newNode.children, patches);
        }
      }
    } else {
      // Remove old node - collect the index for later removal
      indicesToRemove.push(i);
    }
  }
  // Navigate back to parent if we ended at a child
  if (currentChildIndex >= 0) {
    patches.push({
      type: NavigateParent
    });
    currentChildIndex = -1;
  }
  // Add remove patches in reverse order (highest index first)
  // This ensures indices remain valid as we remove
  for (let j = indicesToRemove.length - 1; j >= 0; j--) {
    patches.push({
      type: RemoveChild,
      index: indicesToRemove[j]
    });
  }
};
const diffTrees = (oldTree, newTree, patches, path) => {
  // At the root level (path.length === 0), we're already AT the element
  // So we compare the root node directly, then compare its children
  if (path.length === 0 && oldTree.length === 1 && newTree.length === 1) {
    const oldNode = oldTree[0];
    const newNode = newTree[0];
    // Compare root nodes
    const nodePatches = compareNodes(oldNode.node, newNode.node);
    // If nodePatches is null, the root node types are incompatible - need to replace
    if (nodePatches === null) {
      const flatNodes = treeToArray(newNode);
      patches.push({
        type: Replace,
        nodes: flatNodes
      });
      return;
    }
    if (nodePatches.length > 0) {
      patches.push(...nodePatches);
    }
    // Compare children
    if (oldNode.children.length > 0 || newNode.children.length > 0) {
      diffChildren(oldNode.children, newNode.children, patches);
    }
  } else {
    // Non-root level or multiple root elements - use the regular comparison
    diffChildren(oldTree, newTree, patches);
  }
};

const removeTrailingNavigationPatches = patches => {
  // Find the last non-navigation patch
  let lastNonNavigationIndex = -1;
  for (let i = patches.length - 1; i >= 0; i--) {
    const patch = patches[i];
    if (patch.type !== NavigateChild && patch.type !== NavigateParent && patch.type !== NavigateSibling) {
      lastNonNavigationIndex = i;
      break;
    }
  }
  // Return patches up to and including the last non-navigation patch
  return lastNonNavigationIndex === -1 ? [] : patches.slice(0, lastNonNavigationIndex + 1);
};

const diffTree = (oldNodes, newNodes) => {
  // Step 1: Convert flat arrays to tree structures
  const oldTree = arrayToTree(oldNodes);
  const newTree = arrayToTree(newNodes);
  // Step 3: Compare the trees
  const patches = [];
  diffTrees(oldTree, newTree, patches, []);
  // Remove trailing navigation patches since they serve no purpose
  return removeTrailingNavigationPatches(patches);
};

const getKeyBindings = () => {
  return [{
    command: 'Chat.handleSubmit',
    key: Enter,
    when: FocusChatInput
  }, {
    command: 'Chat.enterNewLine',
    key: Shift | Enter,
    when: FocusChatInput
  }];
};

const getSelectedSessionId = state => {
  return state.selectedSessionId;
};

const getListIndex = (state, eventX, eventY) => {
  const {
    headerHeight,
    height,
    listItemHeight,
    width,
    x,
    y
  } = state;
  if (eventX < x || eventY < y) {
    return -1;
  }
  if (eventX >= x + width || eventY >= y + height) {
    return -1;
  }
  const listY = eventY - y - headerHeight;
  if (listY < 0) {
    return -1;
  }
  const itemHeight = listItemHeight > 0 ? listItemHeight : 40;
  return Math.floor(listY / itemHeight);
};

const CHAT_LIST_ITEM_CONTEXT_MENU = 'ChatListItemContextMenu';
const handleChatListContextMenu = async (state, eventX, eventY) => {
  const index = getListIndex(state, eventX, eventY);
  if (index === -1) {
    return state;
  }
  const item = state.sessions[index];
  if (!item) {
    return state;
  }
  await invoke('ContextMenu.show', eventX, eventY, CHAT_LIST_ITEM_CONTEXT_MENU, item.id);
  return state;
};

const toError = error => {
  if (error instanceof Error) {
    return error;
  }
  return new Error('IndexedDB request failed');
};

const requestToPromise = async createRequest => {
  const request = createRequest();
  const {
    promise,
    reject,
    resolve
  } = Promise.withResolvers();
  request.addEventListener('success', () => {
    resolve(request.result);
  });
  request.addEventListener('error', () => {
    reject(toError(request.error));
  });
  return promise;
};

const openSessionsDatabase = async (databaseName, databaseVersion, storeName) => {
  const request = indexedDB.open(databaseName, databaseVersion);
  request.addEventListener('upgradeneeded', () => {
    const database = request.result;
    if (!database.objectStoreNames.contains(storeName)) {
      database.createObjectStore(storeName, {
        keyPath: 'id'
      });
    }
  });
  return requestToPromise(() => request);
};

const getDatabase = async (getDatabasePromise, setDatabasePromise, databaseName, databaseVersion, storeName) => {
  const existingDatabasePromise = getDatabasePromise();
  if (existingDatabasePromise) {
    return existingDatabasePromise;
  }
  const nextDatabasePromise = openSessionsDatabase(databaseName, databaseVersion, storeName);
  setDatabasePromise(nextDatabasePromise);
  return nextDatabasePromise;
};

const transactionToPromise = async createTransaction => {
  const transaction = createTransaction();
  const {
    promise,
    reject,
    resolve
  } = Promise.withResolvers();
  transaction.addEventListener('complete', () => {
    resolve();
  });
  transaction.addEventListener('error', () => {
    reject(toError(transaction.error));
  });
  transaction.addEventListener('abort', () => {
    reject(toError(transaction.error));
  });
  return promise;
};

const clear = async (getDatabasePromise, setDatabasePromise, databaseName, databaseVersion, storeName) => {
  const database = await getDatabase(getDatabasePromise, setDatabasePromise, databaseName, databaseVersion, storeName);
  const transaction = database.transaction(storeName, 'readwrite');
  const createTransaction = () => transaction;
  const store = transaction.objectStore(storeName);
  store.clear();
  await transactionToPromise(createTransaction);
};

const deleteSession$1 = async (getDatabasePromise, setDatabasePromise, databaseName, databaseVersion, storeName, id) => {
  const database = await getDatabase(getDatabasePromise, setDatabasePromise, databaseName, databaseVersion, storeName);
  const transaction = database.transaction(storeName, 'readwrite');
  const createTransaction = () => transaction;
  const store = transaction.objectStore(storeName);
  store.delete(id);
  await transactionToPromise(createTransaction);
};

const getSession = async (getDatabasePromise, setDatabasePromise, databaseName, databaseVersion, storeName, id) => {
  const database = await getDatabase(getDatabasePromise, setDatabasePromise, databaseName, databaseVersion, storeName);
  const transaction = database.transaction(storeName, 'readonly');
  const store = transaction.objectStore(storeName);
  const result = await requestToPromise(() => store.get(id));
  return result;
};

const listSessions = async (getDatabasePromise, setDatabasePromise, databaseName, databaseVersion, storeName) => {
  const database = await getDatabase(getDatabasePromise, setDatabasePromise, databaseName, databaseVersion, storeName);
  const transaction = database.transaction(storeName, 'readonly');
  const store = transaction.objectStore(storeName);
  const result = await requestToPromise(() => store.getAll());
  return result;
};

const setSession = async (getDatabasePromise, setDatabasePromise, databaseName, databaseVersion, storeName, session) => {
  const database = await getDatabase(getDatabasePromise, setDatabasePromise, databaseName, databaseVersion, storeName);
  const transaction = database.transaction(storeName, 'readwrite');
  const createTransaction = () => transaction;
  const store = transaction.objectStore(storeName);
  store.put(session);
  await transactionToPromise(createTransaction);
};

class IndexedDbChatSessionStorage {
  constructor(options = {}) {
    this.state = {
      databaseName: options.databaseName || 'lvce-chat-view-sessions',
      databasePromise: undefined,
      databaseVersion: options.databaseVersion || 1,
      storeName: options.storeName || 'chat-sessions'
    };
  }
  getDatabasePromise = () => {
    return this.state.databasePromise;
  };
  setDatabasePromise = databasePromise => {
    this.state.databasePromise = databasePromise;
  };
  async clear() {
    return clear(this.getDatabasePromise, this.setDatabasePromise, this.state.databaseName, this.state.databaseVersion, this.state.storeName);
  }
  async deleteSession(id) {
    return deleteSession$1(this.getDatabasePromise, this.setDatabasePromise, this.state.databaseName, this.state.databaseVersion, this.state.storeName, id);
  }
  async getSession(id) {
    return getSession(this.getDatabasePromise, this.setDatabasePromise, this.state.databaseName, this.state.databaseVersion, this.state.storeName, id);
  }
  async listSessions() {
    return listSessions(this.getDatabasePromise, this.setDatabasePromise, this.state.databaseName, this.state.databaseVersion, this.state.storeName);
  }
  async setSession(session) {
    return setSession(this.getDatabasePromise, this.setDatabasePromise, this.state.databaseName, this.state.databaseVersion, this.state.storeName, session);
  }
}

class InMemoryChatSessionStorage {
  sessions = new Map();
  async clear() {
    this.sessions.clear();
  }
  async deleteSession(id) {
    this.sessions.delete(id);
  }
  async getSession(id) {
    return this.sessions.get(id);
  }
  async listSessions() {
    return [...this.sessions.values()];
  }
  async setSession(session) {
    this.sessions.set(session.id, session);
  }
}

const createDefaultStorage = () => {
  if (typeof indexedDB === 'undefined') {
    return new InMemoryChatSessionStorage();
  }
  return new IndexedDbChatSessionStorage();
};
let chatSessionStorage = createDefaultStorage();
const listChatSessions = async () => {
  const sessions = await chatSessionStorage.listSessions();
  return sessions.map(session => ({
    id: session.id,
    messages: [],
    title: session.title
  }));
};
const getChatSession = async id => {
  const session = await chatSessionStorage.getSession(id);
  if (!session) {
    return undefined;
  }
  return {
    id: session.id,
    messages: [...session.messages],
    title: session.title
  };
};
const saveChatSession = async session => {
  await chatSessionStorage.setSession({
    id: session.id,
    messages: [...session.messages],
    title: session.title
  });
};
const deleteChatSession = async id => {
  await chatSessionStorage.deleteSession(id);
};
const clearChatSessions = async () => {
  await chatSessionStorage.clear();
};

const generateSessionId = () => {
  return crypto.randomUUID();
};

const createSession = async state => {
  const id = generateSessionId();
  const session = {
    id,
    messages: [],
    title: `Chat ${state.sessions.length + 1}`
  };
  await saveChatSession(session);
  return {
    ...state,
    renamingSessionId: '',
    selectedSessionId: id,
    sessions: [...state.sessions, session]
  };
};

const getNextSelectedSessionId = (sessions, deletedId) => {
  if (sessions.length === 0) {
    return '';
  }
  const index = sessions.findIndex(session => session.id === deletedId);
  if (index === -1) {
    return sessions[0].id;
  }
  const nextIndex = Math.min(index, sessions.length - 1);
  return sessions[nextIndex].id;
};

const deleteSession = async (state, id) => {
  const {
    renamingSessionId,
    sessions
  } = state;
  const filtered = sessions.filter(session => session.id !== id);
  if (filtered.length === sessions.length) {
    return state;
  }
  await deleteChatSession(id);
  if (filtered.length === 0) {
    return {
      ...state,
      renamingSessionId: '',
      selectedSessionId: '',
      sessions: [],
      viewMode: 'list'
    };
  }
  const nextSelectedSessionId = getNextSelectedSessionId(filtered, id);
  const loadedSession = await getChatSession(nextSelectedSessionId);
  const hydratedSessions = filtered.map(session => {
    if (session.id !== nextSelectedSessionId) {
      return session;
    }
    if (!loadedSession) {
      return session;
    }
    return loadedSession;
  });
  return {
    ...state,
    renamingSessionId: renamingSessionId === id ? '' : renamingSessionId,
    selectedSessionId: nextSelectedSessionId,
    sessions: hydratedSessions
  };
};

const handleClickOpenRouterApiKeySettings = async state => {
  await openExternal(state.openRouterApiKeysSettingsUrl);
  return state;
};

const openRouterApiKeyRequiredMessage = 'OpenRouter API key is not configured. Enter your OpenRouter API key below and click Save.';
const openRouterRequestFailedMessage = 'OpenRouter request failed. Possible reasons:';
const openRouterRequestFailureReasons = ['ContentSecurityPolicyViolation: Check DevTools for details.', 'OpenRouter server offline: Check DevTools for details.', 'Check your internet connection.'];

const delay = async ms => {
  await new Promise(resolve => setTimeout(resolve, ms));
};

const getMockAiResponse = async userMessage => {
  await delay(800);
  return `Mock AI response: I received "${userMessage}".`;
};

const defaultOpenRouterApiBaseUrl = 'https://openrouter.ai/api/v1';
const getOpenRouterApiEndpoint = openRouterApiBaseUrl => {
  const trimmedBaseUrl = (openRouterApiBaseUrl || defaultOpenRouterApiBaseUrl).replace(/\/+$/, '');
  return `${trimmedBaseUrl}/chat/completions`;
};

const getTextContent = content => {
  if (typeof content === 'string') {
    return content;
  }
  if (!Array.isArray(content)) {
    return '';
  }
  const textParts = [];
  for (const part of content) {
    if (!part || typeof part !== 'object') {
      continue;
    }
    const maybeType = Reflect.get(part, 'type');
    const maybeText = Reflect.get(part, 'text');
    if (maybeType === 'text' && typeof maybeText === 'string') {
      textParts.push(maybeText);
    }
  }
  return textParts.join('\n');
};

const getOpenRouterAssistantText = async (userText, modelId, openRouterApiKey, openRouterApiBaseUrl) => {
  let response;
  try {
    response = await fetch(getOpenRouterApiEndpoint(openRouterApiBaseUrl), {
      body: JSON.stringify({
        messages: [{
          content: userText,
          role: 'user'
        }],
        model: modelId
      }),
      headers: {
        Authorization: `Bearer ${openRouterApiKey}`,
        'Content-Type': 'application/json'
      },
      method: 'POST'
    });
  } catch {
    throw new Error(openRouterRequestFailedMessage);
  }
  if (!response.ok) {
    throw new Error(`Failed to get OpenRouter response: ${response.status}`);
  }
  const parsed = await response.json();
  if (!parsed || typeof parsed !== 'object') {
    return '';
  }
  const choices = Reflect.get(parsed, 'choices');
  if (!Array.isArray(choices)) {
    return '';
  }
  const firstChoice = choices[0];
  if (!firstChoice || typeof firstChoice !== 'object') {
    return '';
  }
  const message = Reflect.get(firstChoice, 'message');
  if (!message || typeof message !== 'object') {
    return '';
  }
  const content = Reflect.get(message, 'content');
  return getTextContent(content);
};

/* eslint-disable @cspell/spellchecker */
const getOpenRouterModelId = selectedModelId => {
  const openRouterPrefix = 'openrouter/';
  if (selectedModelId.toLowerCase().startsWith(openRouterPrefix)) {
    return selectedModelId.slice(openRouterPrefix.length);
  }
  return selectedModelId;
};

/* eslint-disable @cspell/spellchecker */

const isOpenRouterModel = (selectedModelId, models) => {
  const selectedModel = models.find(model => model.id === selectedModelId);
  const normalizedProvider = selectedModel?.provider?.toLowerCase();
  if (normalizedProvider === 'openrouter' || normalizedProvider === 'open-router') {
    return true;
  }
  return selectedModelId.toLowerCase().startsWith('openrouter/');
};

const getAiResponse = async (userText, nextMessageId, selectedModelId, models, openRouterApiKey, openRouterApiBaseUrl) => {
  let text = '';
  const usesOpenRouterModel = isOpenRouterModel(selectedModelId, models);
  if (usesOpenRouterModel) {
    if (openRouterApiKey) {
      try {
        text = await getOpenRouterAssistantText(userText, getOpenRouterModelId(selectedModelId), openRouterApiKey, openRouterApiBaseUrl);
      } catch (error) {
        if (error instanceof Error && error.message) {
          text = error.message;
        } else {
          text = openRouterRequestFailedMessage;
        }
      }
    } else {
      text = openRouterApiKeyRequiredMessage;
    }
  }
  if (!text && !usesOpenRouterModel) {
    text = await getMockAiResponse(userText);
  }
  const assistantTime = new Date().toLocaleTimeString([], {
    hour: '2-digit',
    minute: '2-digit'
  });
  return {
    id: `message-${nextMessageId}`,
    role: 'assistant',
    text,
    time: assistantTime
  };
};

const get = async key => {
  return getPreference(key);
};
const update = async settings => {
  await invoke('Preferences.update', settings);
};

const setOpenRouterApiKey = async (state, openRouterApiKey, persist = true) => {
  if (persist) {
    await update({
      'secrets.openRouterApiKey': openRouterApiKey
    });
  }
  return {
    ...state,
    openRouterApiKey,
    openRouterApiKeyInput: openRouterApiKey
  };
};

const handleClickSaveOpenRouterApiKey = async state => {
  const {
    openRouterApiKeyInput
  } = state;
  const openRouterApiKey = openRouterApiKeyInput.trim();
  if (!openRouterApiKey) {
    return state;
  }
  const updatedState = await setOpenRouterApiKey(state, openRouterApiKey);
  const session = updatedState.sessions.find(item => item.id === updatedState.selectedSessionId);
  if (!session) {
    return updatedState;
  }
  const lastMessage = session.messages.at(-1);
  const shouldRetryOpenRouter = lastMessage?.role === 'assistant' && lastMessage.text === openRouterApiKeyRequiredMessage;
  if (!shouldRetryOpenRouter) {
    return updatedState;
  }
  const previousUserMessage = session.messages.toReversed().find(item => item.role === 'user');
  if (!previousUserMessage) {
    return updatedState;
  }
  const assistantMessage = await getAiResponse(previousUserMessage.text, updatedState.nextMessageId, updatedState.selectedModelId, updatedState.models, openRouterApiKey, updatedState.openRouterApiBaseUrl);
  const updatedSession = {
    ...session,
    messages: [...session.messages.slice(0, -1), assistantMessage]
  };
  await saveChatSession(updatedSession);
  const updatedSessions = updatedState.sessions.map(item => {
    if (item.id !== updatedState.selectedSessionId) {
      return item;
    }
    return updatedSession;
  });
  return {
    ...updatedState,
    nextMessageId: updatedState.nextMessageId + 1,
    sessions: updatedSessions
  };
};

const focusInput = state => {
  return {
    ...state,
    focus: 'composer',
    focused: true
  };
};

const handleSubmit = async state => {
  const {
    composerValue,
    models,
    nextMessageId,
    openRouterApiBaseUrl,
    openRouterApiKey,
    selectedModelId,
    selectedSessionId,
    sessions,
    viewMode
  } = state;
  const userText = composerValue.trim();
  if (!userText) {
    return state;
  }
  const userTime = new Date().toLocaleTimeString([], {
    hour: '2-digit',
    minute: '2-digit'
  });
  const userMessage = {
    id: `message-${nextMessageId}`,
    role: 'user',
    text: userText,
    time: userTime
  };
  let workingSessions = sessions;
  if (viewMode === 'detail') {
    const loadedSession = await getChatSession(selectedSessionId);
    if (loadedSession) {
      workingSessions = sessions.map(session => {
        if (session.id !== selectedSessionId) {
          return session;
        }
        return loadedSession;
      });
    }
  }
  let optimisticState;
  if (viewMode === 'list') {
    const newSessionId = generateSessionId();
    const newSession = {
      id: newSessionId,
      messages: [userMessage],
      title: `Chat ${workingSessions.length + 1}`
    };
    await saveChatSession(newSession);
    optimisticState = focusInput({
      ...state,
      composerValue: '',
      inputSource: 'script',
      lastSubmittedSessionId: newSessionId,
      nextMessageId: nextMessageId + 1,
      selectedSessionId: newSessionId,
      sessions: [...workingSessions, newSession],
      viewMode: 'detail'
    });
  } else {
    const updatedSessions = workingSessions.map(session => {
      if (session.id !== selectedSessionId) {
        return session;
      }
      return {
        ...session,
        messages: [...session.messages, userMessage]
      };
    });
    const selectedSession = updatedSessions.find(session => session.id === selectedSessionId);
    if (selectedSession) {
      await saveChatSession(selectedSession);
    }
    optimisticState = focusInput({
      ...state,
      composerValue: '',
      inputSource: 'script',
      lastSubmittedSessionId: selectedSessionId,
      nextMessageId: nextMessageId + 1,
      sessions: updatedSessions
    });
  }
  set(state.uid, state, optimisticState);
  // @ts-ignore
  await invoke('Chat.rerender');
  const assistantMessage = await getAiResponse(userText, optimisticState.nextMessageId, selectedModelId, models, openRouterApiKey, openRouterApiBaseUrl);
  const updatedSessions = optimisticState.sessions.map(session => {
    if (session.id !== optimisticState.selectedSessionId) {
      return session;
    }
    return {
      ...session,
      messages: [...session.messages, assistantMessage]
    };
  });
  const selectedSession = updatedSessions.find(session => session.id === optimisticState.selectedSessionId);
  if (selectedSession) {
    await saveChatSession(selectedSession);
  }
  return focusInput({
    ...optimisticState,
    nextMessageId: optimisticState.nextMessageId + 1,
    sessions: updatedSessions
  });
};

const handleClickSend = async state => {
  const {
    selectedSessionId,
    sessions,
    viewMode
  } = state;
  const hasSelectedSession = sessions.some(session => session.id === selectedSessionId);
  const submitState = viewMode === 'list' && hasSelectedSession ? {
    ...state,
    viewMode: 'detail'
  } : state;
  return handleSubmit(submitState);
};

const Composer = 'composer';
const Send = 'send';
const Back = 'back';
const Model = 'model';
const CreateSession = 'create-session';
const Settings = 'settings';
const CloseChat = 'close-chat';
const SessionDelete = 'SessionDelete';
const SessionPrefix = 'session:';
const RenamePrefix = 'session-rename:';
const getSessionInputName = sessionId => {
  return `${SessionPrefix}${sessionId}`;
};
const isSessionInputName = name => {
  return name.startsWith(SessionPrefix);
};
const getSessionIdFromInputName = name => {
  return name.slice(SessionPrefix.length);
};
const isRenameInputName = name => {
  return name.startsWith(RenamePrefix);
};
const getRenameIdFromInputName = name => {
  return name.slice(RenamePrefix.length);
};

/* eslint-disable @cspell/spellchecker */
const OpenRouterApiKeyInput = 'open-router-api-key';
const SaveOpenRouterApiKey = 'save-openrouter-api-key';
const OpenOpenRouterApiKeySettings = 'open-openrouter-api-key-settings';

const selectSession = async (state, id) => {
  const exists = state.sessions.some(session => session.id === id);
  if (!exists) {
    return state;
  }
  const loadedSession = await getChatSession(id);
  const sessions = state.sessions.map(session => {
    if (session.id !== id) {
      return session;
    }
    if (!loadedSession) {
      return session;
    }
    return loadedSession;
  });
  return {
    ...state,
    renamingSessionId: '',
    selectedSessionId: id,
    sessions,
    viewMode: 'detail'
  };
};

const startRename = (state, id) => {
  const {
    sessions
  } = state;
  const session = sessions.find(item => item.id === id);
  if (!session) {
    return state;
  }
  return {
    ...state,
    composerValue: session.title,
    inputSource: 'script',
    renamingSessionId: id,
    selectedSessionId: id
  };
};

const selectListIndex = async (state, index) => {
  const {
    sessions
  } = state;
  if (index < 0 || index >= sessions.length) {
    return state;
  }
  const session = sessions[index];
  return selectSession(state, session.id);
};

const handleClickList = async (state, eventX, eventY) => {
  const index = getListIndex(state, eventX, eventY);
  return selectListIndex(state, index);
};

const handleClick = async (state, name, id = '') => {
  if (!name) {
    return state;
  }
  if (name === CreateSession) {
    return createSession(state);
  }
  if (isSessionInputName(name)) {
    const sessionId = getSessionIdFromInputName(name);
    return selectSession(state, sessionId);
  }
  if (isRenameInputName(name)) {
    const sessionId = getRenameIdFromInputName(name);
    return startRename(state, sessionId);
  }
  if (name === SessionDelete) {
    return deleteSession(state, id);
  }
  if (name === Send) {
    return handleClickSend(state);
  }
  if (name === SaveOpenRouterApiKey) {
    return handleClickSaveOpenRouterApiKey(state);
  }
  if (name === OpenOpenRouterApiKeySettings) {
    return handleClickOpenRouterApiKeySettings(state);
  }
  return state;
};

const handleClickBack = async state => {
  return {
    ...state,
    renamingSessionId: '',
    viewMode: 'list'
  };
};

const handleClickClose = async () => {
  // @ts-ignore
  await invoke('Layout.hideSecondarySideBar');
};

const handleClickDelete = async (state, sessionId = '') => {
  return deleteSession(state, sessionId);
};

const handleClickNew = async state => {
  return createSession(state);
};

const handleClickSettings = async () => {
  // TODO
  await invoke('Main.openUri', 'app://settings.json');
};

const handleInput = async (state, name, value, inputSource = 'user') => {
  if (name === OpenRouterApiKeyInput) {
    return {
      ...state,
      openRouterApiKeyInput: value
    };
  }
  if (name !== Composer) {
    return state;
  }
  return {
    ...state,
    composerValue: value,
    inputSource
  };
};

const handleInputFocus = async (state, name) => {
  if (name === Composer) {
    return focusInput(state);
  }
  if (name === Send) {
    return {
      ...state,
      focus: 'send-button',
      focused: true
    };
  }
  if (isSessionInputName(name) || name === SessionDelete) {
    return {
      ...state,
      focus: 'list',
      focused: true
    };
  }
  if (name === CreateSession || name === Settings || name === CloseChat || name === Back) {
    return {
      ...state,
      focus: 'header',
      focused: true
    };
  }
  return {
    ...state,
    focused: false
  };
};

const submitRename = async state => {
  const {
    composerValue,
    renamingSessionId,
    sessions
  } = state;
  const title = composerValue.trim();
  if (!renamingSessionId || !title) {
    return {
      ...state,
      renamingSessionId: ''
    };
  }
  const updatedSessions = sessions.map(session => {
    if (session.id !== renamingSessionId) {
      return session;
    }
    return {
      ...session,
      title
    };
  });
  const renamedSession = updatedSessions.find(session => session.id === renamingSessionId);
  if (renamedSession) {
    await saveChatSession(renamedSession);
  }
  return {
    ...state,
    composerValue: '',
    inputSource: 'script',
    renamingSessionId: '',
    sessions: updatedSessions
  };
};

const handleKeyDown = async (state, key, shiftKey) => {
  const {
    composerValue,
    renamingSessionId,
    selectedSessionId,
    sessions,
    viewMode
  } = state;
  if (key !== 'Enter' || shiftKey) {
    return state;
  }
  if (renamingSessionId) {
    return submitRename(state);
  }
  const hasInput = composerValue.trim().length > 0;
  const hasSelectedSession = sessions.some(session => session.id === selectedSessionId);
  const submitState = viewMode === 'list' && hasInput && hasSelectedSession ? {
    ...state,
    viewMode: 'detail'
  } : state;
  return handleSubmit(submitState);
};

const handleModelChange = async (state, value) => {
  return {
    ...state,
    selectedModelId: value
  };
};

const handleNewline = async state => {
  return handleInput(state, Composer, `${state.composerValue}\n`);
};

const id = 7201;
const sendMessagePortToExtensionHostWorker = async port => {
  await sendMessagePortToExtensionHostWorker$1(port, id);
};

const createExtensionHostRpc = async () => {
  try {
    const rpc = await create$4({
      commandMap: {},
      send: sendMessagePortToExtensionHostWorker
    });
    return rpc;
  } catch (error) {
    throw new VError(error, `Failed to create extension host rpc`);
  }
};

const initialize = async () => {
  const rpc = await createExtensionHostRpc();
  set$2(rpc);
};

const isObject = value => {
  return typeof value === 'object' && value !== null;
};

const getSavedSelectedModelId = savedState => {
  if (!isObject(savedState)) {
    return undefined;
  }
  const {
    selectedModelId
  } = savedState;
  if (typeof selectedModelId !== 'string') {
    return undefined;
  }
  return selectedModelId;
};

const getSavedSelectedSessionId = savedState => {
  if (!isObject(savedState)) {
    return undefined;
  }
  const {
    selectedSessionId
  } = savedState;
  if (typeof selectedSessionId !== 'string') {
    return undefined;
  }
  return selectedSessionId;
};

const getSavedSessions = savedState => {
  if (!isObject(savedState)) {
    return undefined;
  }
  const {
    sessions
  } = savedState;
  if (!Array.isArray(sessions)) {
    return undefined;
  }
  return sessions;
};

const getSavedViewMode = savedState => {
  if (!isObject(savedState)) {
    return undefined;
  }
  const {
    viewMode
  } = savedState;
  if (viewMode !== 'list' && viewMode !== 'detail') {
    return undefined;
  }
  return viewMode;
};

const toSummarySession = session => {
  return {
    id: session.id,
    messages: [],
    title: session.title
  };
};
const loadSelectedSessionMessages = async (sessions, selectedSessionId) => {
  if (!selectedSessionId) {
    return sessions;
  }
  const loadedSession = await getChatSession(selectedSessionId);
  if (!loadedSession) {
    return sessions;
  }
  return sessions.map(session => {
    if (session.id !== selectedSessionId) {
      return session;
    }
    return loadedSession;
  });
};
const loadContent = async (state, savedState) => {
  const savedSelectedModelId = getSavedSelectedModelId(savedState);
  const savedViewMode = getSavedViewMode(savedState);
  let openRouterApiKey = '';
  try {
    const savedOpenRouterApiKey = await get('secrets.openRouterApiKey');
    openRouterApiKey = typeof savedOpenRouterApiKey === 'string' ? savedOpenRouterApiKey : '';
  } catch {
    openRouterApiKey = '';
  }
  const legacySavedSessions = getSavedSessions(savedState);
  const storedSessions = await listChatSessions();
  let sessions = storedSessions;
  if (sessions.length === 0 && legacySavedSessions && legacySavedSessions.length > 0) {
    for (const session of legacySavedSessions) {
      await saveChatSession(session);
    }
    sessions = legacySavedSessions.map(toSummarySession);
  }
  if (sessions.length === 0 && state.sessions.length > 0) {
    for (const session of state.sessions) {
      await saveChatSession(session);
    }
    sessions = state.sessions.map(toSummarySession);
  }
  const preferredSessionId = getSavedSelectedSessionId(savedState) || state.selectedSessionId;
  const preferredModelId = savedSelectedModelId || state.selectedModelId;
  const selectedModelId = state.models.some(model => model.id === preferredModelId) ? preferredModelId : state.models[0]?.id || '';
  const selectedSessionId = sessions.some(session => session.id === preferredSessionId) ? preferredSessionId : sessions[0]?.id || '';
  sessions = await loadSelectedSessionMessages(sessions, selectedSessionId);
  const preferredViewMode = savedViewMode || state.viewMode;
  const viewMode = sessions.length === 0 || !selectedSessionId ? 'list' : preferredViewMode === 'detail' ? 'detail' : 'list';
  return {
    ...state,
    initial: false,
    openRouterApiKey,
    openRouterApiKeyInput: openRouterApiKey,
    selectedModelId,
    selectedSessionId,
    sessions,
    viewMode
  };
};

const openMockSession = async (state, mockSessionId, mockChatMessages) => {
  const {
    sessions: currentSessions
  } = state;
  if (!mockSessionId) {
    return state;
  }
  const existingSession = currentSessions.find(session => session.id === mockSessionId);
  const sessions = existingSession ? currentSessions.map(session => {
    if (session.id !== mockSessionId) {
      return session;
    }
    return {
      ...session,
      messages: mockChatMessages
    };
  }) : [...currentSessions, {
    id: mockSessionId,
    messages: mockChatMessages,
    title: mockSessionId
  }];
  const selectedSession = sessions.find(session => session.id === mockSessionId);
  if (selectedSession) {
    await saveChatSession(selectedSession);
  }
  return {
    ...state,
    renamingSessionId: '',
    selectedSessionId: mockSessionId,
    sessions,
    viewMode: 'detail'
  };
};

// TODO render things like scrollbar height,scrollbar offset, textarea height,
// list height
const css = `
`;
const renderCss = (oldState, newState) => {
  return [SetCss, newState.uid, css];
};

const getFocusSelector = focus => {
  switch (focus) {
    case 'composer':
    case 'input':
      return '[name="composer"]';
    case 'header':
      return '[name="create-session"]';
    case 'list':
      return '[name^="session:"]';
    case 'send-button':
      return '[name="send"]';
    default:
      return '[name="composer"]';
  }
};
const renderFocus = (oldState, newState) => {
  const selector = getFocusSelector(newState.focus);
  return [FocusSelector, selector];
};

const renderFocusContext = (oldState, newState) => {
  return [SetFocusContext, newState.uid, FocusChatInput];
};

const Actions = 'Actions';
const ChatActions = 'ChatActions';
const ChatName = 'ChatName';
const ChatSendArea = 'ChatSendArea';
const SendButtonDisabled = 'SendButtonDisabled';
const ChatSendAreaBottom = 'ChatSendAreaBottom';
const ChatSendAreaContent = 'ChatSendAreaContent';
const Chat = 'Chat';
const ChatHeader = 'ChatHeader';
const Button = 'Button';
const ButtonPrimary = 'ButtonPrimary';
const ButtonSecondary = 'ButtonSecondary';
const IconButton = 'IconButton';
const InputBox = 'InputBox';
const Label = 'Label';
const LabelDetail = 'LabelDetail';
const ChatList = 'ChatList';
const ChatListEmpty = 'ChatListEmpty';
const ChatListItem = 'ChatListItem';
const ChatListItemLabel = 'ChatListItemLabel';
const Markdown = 'Markdown';
const Message = 'Message';
const ChatMessageContent = 'ChatMessageContent';
const MessageUser = 'MessageUser';
const MessageAssistant = 'MessageAssistant';
const MultilineInputBox = 'MultilineInputBox';
const Option = 'Option';
const TokenUsageOverview = 'TokenUsageOverview';
const TokenUsageRing = 'TokenUsageRing';
const TokenUsageRingInner = 'TokenUsageRingInner';
const Select = 'Select';
const Viewlet = 'Viewlet';
const ChatWelcomeMessage = 'ChatWelcomeMessage';

const HandleListContextMenu = 2;
const HandleFocus = 3;
const HandleInput = 4;
const HandleClick = 11;
const HandleKeyDown = 12;
const HandleClickClose = 13;
const HandleClickSettings = 14;
const HandleClickNew = 15;
const HandleClickBack = 16;
const HandleClickList = 17;
const HandleClickDelete = 18;
const HandleSubmit = 19;
const HandleModelChange = 20;

const getModelLabel = model => {
  if (model.provider === 'openRouter') {
    return `${model.name} (OpenRouter)`;
  }
  return model.name;
};
const getModelOptionDOm = (model, selectedModelId) => {
  return [{
    childCount: 1,
    className: Option,
    selected: model.id === selectedModelId,
    type: Option$1,
    value: model.id
  }, text(getModelLabel(model))];
};

const getSendButtonClassName = isSendDisabled => {
  return isSendDisabled ? `${IconButton} ${SendButtonDisabled}` : `${IconButton}`;
};

const getSendButtonDom = isSendDisabled => {
  const sendButtonClassName = getSendButtonClassName(isSendDisabled);
  return [{
    childCount: 1,
    className: sendButtonClassName,
    disabled: isSendDisabled,
    name: Send,
    onClick: HandleSubmit,
    role: Button$2,
    title: sendMessage(),
    type: Button$1
  }, {
    childCount: 0,
    className: 'MaskIcon MaskIconSend',
    type: Div
  }];
};

const clampToPercentage = (tokensUsed, tokensMax) => {
  if (tokensMax <= 0) {
    return 0;
  }
  const percentage = tokensUsed / tokensMax * 100;
  return Math.max(0, Math.min(100, percentage));
};

const getUsageOverviewDom = (tokensUsed, tokensMax) => {
  const usagePercent = clampToPercentage(tokensUsed, tokensMax);
  const usageLabel = `${tokensUsed} / ${tokensMax}`;
  const usageTitle = `${tokensUsed} of ${tokensMax} tokens used (${Math.round(usagePercent)}%)`;
  return [{
    childCount: 3,
    className: TokenUsageOverview,
    type: Div
  }, {
    childCount: 1,
    className: TokenUsageRing,
    style: `background: conic-gradient(var(--vscode-button-background) ${usagePercent}%, var(--vscode-editorWidget-border) 0);`,
    title: usageTitle,
    type: Div
  }, {
    childCount: 0,
    className: TokenUsageRingInner,
    style: 'background: var(--vscode-editor-background);',
    type: Div
  }, {
    childCount: 1,
    className: LabelDetail,
    title: usageTitle,
    type: Span
  }, text(usageLabel)];
};

const getChatSendAreaDom = (composerValue, models, selectedModelId, usageOverviewEnabled, tokensUsed, tokensMax) => {
  const isSendDisabled = composerValue.trim() === '';
  const modelOptions = models.flatMap(model => getModelOptionDOm(model, selectedModelId));
  return [{
    childCount: 1,
    className: ChatSendArea,
    type: Div
  }, {
    childCount: 2,
    className: ChatSendAreaContent,
    type: Div
  }, {
    childCount: 0,
    className: MultilineInputBox,
    name: Composer,
    onFocus: HandleFocus,
    onInput: HandleInput,
    placeholder: composePlaceholder(),
    rows: 4,
    type: TextArea,
    value: composerValue
  }, {
    childCount: usageOverviewEnabled ? 3 : 2,
    className: ChatSendAreaBottom,
    type: Div
  }, {
    childCount: models.length,
    className: Select,
    name: Model,
    onInput: HandleModelChange,
    type: Select$1,
    value: selectedModelId
  }, ...modelOptions, ...(usageOverviewEnabled ? getUsageOverviewDom(tokensUsed, tokensMax) : []), ...getSendButtonDom(isSendDisabled)];
};

const getBackButtonVirtualDom = () => {
  return [{
    childCount: 1,
    className: IconButton,
    name: Back,
    onClick: HandleClickBack,
    role: Button$2,
    title: backToChats(),
    type: Button$1
  }, {
    childCount: 0,
    className: 'MaskIcon MaskIconArrowLeft',
    type: Div
  }];
};

const getHeaderActionVirtualDom = item => {
  return [{
    childCount: 1,
    className: IconButton,
    name: item.name,
    onClick: item.onClick,
    role: Button$2,
    title: item.title,
    type: Button$1
  }, {
    childCount: 0,
    className: item.icon,
    type: Div
  }];
};

const getChatHeaderActionsDom = () => {
  const items = [{
    icon: 'MaskIcon MaskIconAdd',
    name: CreateSession,
    onClick: HandleClickNew,
    title: newChat()
  }, {
    icon: 'MaskIcon MaskIconSettingsGear',
    name: Settings,
    onClick: HandleClickSettings,
    title: settings()
  }, {
    icon: 'MaskIcon MaskIconClose',
    name: CloseChat,
    onClick: HandleClickClose,
    title: closeChat()
  }];
  return [{
    childCount: items.length,
    className: ChatActions,
    type: Div
  }, ...items.flatMap(getHeaderActionVirtualDom)];
};

const getChatHeaderDomDetailMode = selectedSessionTitle => {
  return [{
    childCount: 2,
    className: ChatHeader,
    type: Div
  }, {
    childCount: 2,
    className: ChatName,
    type: Div
  }, ...getBackButtonVirtualDom(), {
    childCount: 1,
    className: Label,
    type: Span
  }, text(selectedSessionTitle), ...getChatHeaderActionsDom()];
};

const getMissingOpenRouterApiKeyDom = openRouterApiKeyInput => {
  return [{
    childCount: 2,
    type: Div
  }, {
    childCount: 0,
    className: InputBox,
    name: OpenRouterApiKeyInput,
    onInput: HandleInput,
    placeholder: openRouterApiKeyPlaceholder(),
    type: Input,
    value: openRouterApiKeyInput
  }, {
    childCount: 2,
    className: Actions,
    type: Div
  }, {
    childCount: 1,
    className: mergeClassNames(Button, ButtonPrimary),
    name: SaveOpenRouterApiKey,
    onClick: HandleClick,
    type: Button$1
  }, text(save()), {
    childCount: 1,
    className: mergeClassNames(Button, ButtonSecondary),
    name: OpenOpenRouterApiKeySettings,
    onClick: HandleClick,
    type: Button$1
  }, text(getOpenRouterApiKey())];
};

const getOpenRouterRequestFailedDom = () => {
  return [{
    childCount: openRouterRequestFailureReasons.length,
    className: Markdown,
    type: Ol
  }, ...openRouterRequestFailureReasons.flatMap(reason => {
    return [{
      childCount: 1,
      type: Li
    }, text(reason)];
  })];
};
const getChatMessageDom = (message, openRouterApiKeyInput) => {
  const roleClassName = message.role === 'user' ? MessageUser : MessageAssistant;
  const isOpenRouterApiKeyMissingMessage = message.role === 'assistant' && message.text === openRouterApiKeyRequiredMessage;
  const isOpenRouterRequestFailedMessage = message.role === 'assistant' && message.text === openRouterRequestFailedMessage;
  const extraChildCount = isOpenRouterApiKeyMissingMessage || isOpenRouterRequestFailedMessage ? 2 : 1;
  return [{
    childCount: 1,
    className: mergeClassNames(Message, roleClassName),
    type: Div
  }, {
    childCount: extraChildCount,
    className: ChatMessageContent,
    type: Div
  }, {
    childCount: 1,
    className: Markdown,
    type: P
  }, text(message.text), ...(isOpenRouterApiKeyMissingMessage ? getMissingOpenRouterApiKeyDom(openRouterApiKeyInput) : []), ...(isOpenRouterRequestFailedMessage ? getOpenRouterRequestFailedDom() : [])];
};

const getEmptyMessagesDom = () => {
  return [{
    childCount: 1,
    className: ChatWelcomeMessage,
    type: Div
  }, text(startConversation())];
};
const getMessagesDom = (messages, openRouterApiKeyInput) => {
  if (messages.length === 0) {
    return getEmptyMessagesDom();
  }
  return [{
    childCount: messages.length,
    className: 'ChatMessages',
    type: Div
  }, ...messages.flatMap(message => getChatMessageDom(message, openRouterApiKeyInput))];
};

const getChatModeDetailVirtualDom = (sessions, selectedSessionId, composerValue, openRouterApiKeyInput, models, selectedModelId, usageOverviewEnabled, tokensUsed, tokensMax) => {
  const selectedSession = sessions.find(session => session.id === selectedSessionId);
  const selectedSessionTitle = selectedSession?.title || chatTitle();
  const messages = selectedSession ? selectedSession.messages : [];
  return [{
    childCount: 3,
    className: mergeClassNames(Viewlet, Chat),
    type: Div
  }, ...getChatHeaderDomDetailMode(selectedSessionTitle), ...getMessagesDom(messages, openRouterApiKeyInput), ...getChatSendAreaDom(composerValue, models, selectedModelId, usageOverviewEnabled, tokensUsed, tokensMax)];
};

const getChatHeaderListModeDom = () => {
  return [{
    childCount: 2,
    className: ChatHeader,
    type: Div
  }, {
    childCount: 1,
    className: Label,
    type: Span
  }, text(chats()), ...getChatHeaderActionsDom()];
};

const getEmptyChatSessionsDom = () => {
  return [{
    childCount: 1,
    className: ChatListEmpty,
    type: Div
  }, {
    childCount: 1,
    className: Label,
    type: Div
  }, text(clickToOpenNewChat())];
};

const getSessionDom = session => {
  const sessionClassName = ChatListItem;
  return [{
    childCount: 2,
    className: sessionClassName,
    type: Div
  }, {
    childCount: 1,
    className: ChatListItemLabel,
    name: getSessionInputName(session.id),
    onContextMenu: HandleListContextMenu,
    tabIndex: 0,
    type: Div
  }, text(session.title), {
    childCount: 1,
    className: ChatActions,
    type: Div
  }, {
    childCount: 1,
    className: IconButton,
    'data-id': session.id,
    name: SessionDelete,
    onClick: HandleClickDelete,
    role: Button$2,
    tabIndex: 0,
    title: deleteChatSession$1(),
    type: Button$1
  }, text('🗑')];
};

const getChatListDom = (sessions, selectedSessionId) => {
  if (sessions.length === 0) {
    return getEmptyChatSessionsDom();
  }
  return [{
    childCount: sessions.length,
    className: ChatList,
    onClick: HandleClickList,
    type: Div
  }, ...sessions.flatMap(getSessionDom)];
};

const getChatModeListVirtualDom = (sessions, selectedSessionId, composerValue, models, selectedModelId, usageOverviewEnabled, tokensUsed, tokensMax) => {
  return [{
    childCount: 3,
    className: mergeClassNames(Viewlet, Chat),
    type: Div
  }, ...getChatHeaderListModeDom(), ...getChatListDom(sessions), ...getChatSendAreaDom(composerValue, models, selectedModelId, usageOverviewEnabled, tokensUsed, tokensMax)];
};

const getChatModeUnsupportedVirtualDom = () => {
  return [{
    childCount: 1,
    type: Div
  }, text(unknownViewMode())];
};

const getChatVirtualDom = (sessions, selectedSessionId, composerValue, openRouterApiKeyInput, viewMode, models, selectedModelId, usageOverviewEnabled, tokensUsed, tokensMax) => {
  switch (viewMode) {
    case 'detail':
      return getChatModeDetailVirtualDom(sessions, selectedSessionId, composerValue, openRouterApiKeyInput, models, selectedModelId, usageOverviewEnabled, tokensUsed, tokensMax);
    case 'list':
      return getChatModeListVirtualDom(sessions, selectedSessionId, composerValue, models, selectedModelId, usageOverviewEnabled, tokensUsed, tokensMax);
    default:
      return getChatModeUnsupportedVirtualDom();
  }
};

const renderItems = (oldState, newState) => {
  const {
    composerValue,
    initial,
    models,
    openRouterApiKeyInput,
    selectedModelId,
    selectedSessionId,
    sessions,
    tokensMax,
    tokensUsed,
    uid,
    usageOverviewEnabled,
    viewMode
  } = newState;
  if (initial) {
    return [SetDom2, uid, []];
  }
  const dom = getChatVirtualDom(sessions, selectedSessionId, composerValue, openRouterApiKeyInput, viewMode, models, selectedModelId, usageOverviewEnabled, tokensUsed, tokensMax);
  return [SetDom2, uid, dom];
};

const renderIncremental = (oldState, newState) => {
  const oldDom = renderItems(oldState, oldState)[2];
  const newDom = renderItems(newState, newState)[2];
  const patches = diffTree(oldDom, newDom);
  return [SetPatches, newState.uid, patches];
};

const renderValue = (oldState, newState) => {
  const {
    composerValue
  } = newState;
  return [SetValueByName, newState.uid, Composer, composerValue];
};

const getRenderer = diffType => {
  switch (diffType) {
    case RenderCss:
      return renderCss;
    case RenderFocus:
      return renderFocus;
    case RenderFocusContext:
      return renderFocusContext;
    case RenderIncremental:
      return renderIncremental;
    case RenderItems:
      return renderItems;
    case RenderValue:
      return renderValue;
    default:
      throw new Error('unknown renderer');
  }
};

const applyRender = (oldState, newState, diffResult) => {
  const commands = [];
  for (const item of diffResult) {
    const fn = getRenderer(item);
    const result = fn(oldState, newState);
    if (result.length > 0) {
      commands.push(result);
    }
  }
  return commands;
};

const render2 = (uid, diffResult) => {
  const {
    newState,
    oldState
  } = get$1(uid);
  set(uid, newState, newState);
  const commands = applyRender(oldState, newState, diffResult);
  return commands;
};

const renderEventListeners = () => {
  return [{
    name: HandleListContextMenu,
    params: ['handleChatListContextMenu', ClientX, ClientY],
    preventDefault: true
  }, {
    name: HandleClick,
    params: ['handleClick', TargetName, 'event.target.dataset.id']
  }, {
    name: HandleClickDelete,
    params: ['handleClickDelete', 'event.target.dataset.id']
  }, {
    name: HandleClickClose,
    params: ['handleClickClose']
  }, {
    name: HandleClickSettings,
    params: ['handleClickSettings']
  }, {
    name: HandleClickNew,
    params: ['handleClickNew']
  }, {
    name: HandleClickBack,
    params: ['handleClickBack']
  }, {
    name: HandleClickList,
    params: ['handleClickList', ClientX, ClientY]
  }, {
    name: HandleInput,
    params: ['handleInput', TargetName, TargetValue]
  }, {
    name: HandleModelChange,
    params: ['handleModelChange', TargetValue]
  }, {
    name: HandleFocus,
    params: ['handleInputFocus', TargetName]
  }, {
    name: HandleKeyDown,
    params: ['handleKeyDown', Key, ShiftKey]
  }, {
    name: HandleSubmit,
    params: ['handleSubmit']
  }];
};

const rerender = state => {
  return structuredClone(state);
};

const reset = async state => {
  await clearChatSessions();
  return {
    ...state,
    composerValue: '',
    openRouterApiKey: '',
    selectedSessionId: '',
    sessions: [],
    viewMode: 'list'
  };
};

const resize = (state, dimensions) => {
  return {
    ...state,
    ...dimensions
  };
};

const saveState = state => {
  const {
    composerValue,
    height,
    nextMessageId,
    renamingSessionId,
    selectedModelId,
    selectedSessionId,
    viewMode,
    width,
    x,
    y
  } = state;
  return {
    composerValue,
    height,
    nextMessageId,
    renamingSessionId,
    selectedModelId,
    selectedSessionId,
    viewMode,
    width,
    x,
    y
  };
};

const dummySessions = [{
  id: 'session-a',
  messages: [],
  title: dummyChatA()
}, {
  id: 'session-b',
  messages: [],
  title: dummyChatB()
}, {
  id: 'session-c',
  messages: [],
  title: dummyChatC()
}];
const setChatList = state => {
  return {
    ...state,
    selectedSessionId: dummySessions[0].id,
    sessions: dummySessions,
    viewMode: 'detail'
  };
};

const commandMap = {
  'Chat.clearInput': wrapCommand(clearInput),
  'Chat.create': create,
  'Chat.diff2': diff2,
  'Chat.enterNewLine': wrapCommand(handleNewline),
  'Chat.getCommandIds': getCommandIds,
  'Chat.getKeyBindings': getKeyBindings,
  'Chat.getSelectedSessionId': wrapGetter(getSelectedSessionId),
  'Chat.handleChatListContextMenu': handleChatListContextMenu,
  'Chat.handleClick': wrapCommand(handleClick),
  'Chat.handleClickBack': wrapCommand(handleClickBack),
  'Chat.handleClickClose': handleClickClose,
  'Chat.handleClickDelete': wrapCommand(handleClickDelete),
  'Chat.handleClickList': wrapCommand(handleClickList),
  'Chat.handleClickNew': wrapCommand(handleClickNew),
  'Chat.handleClickSettings': handleClickSettings,
  'Chat.handleInput': wrapCommand(handleInput),
  'Chat.handleInputFocus': wrapCommand(handleInputFocus),
  'Chat.handleKeyDown': wrapCommand(handleKeyDown),
  'Chat.handleModelChange': wrapCommand(handleModelChange),
  'Chat.handleSubmit': wrapCommand(handleSubmit),
  'Chat.initialize': initialize,
  'Chat.loadContent': wrapCommand(loadContent),
  'Chat.loadContent2': wrapCommand(loadContent),
  'Chat.openMockSession': wrapCommand(openMockSession),
  'Chat.render2': render2,
  'Chat.renderEventListeners': renderEventListeners,
  'Chat.rerender': wrapCommand(rerender),
  'Chat.reset': wrapCommand(reset),
  'Chat.resize': wrapCommand(resize),
  'Chat.saveState': wrapGetter(saveState),
  'Chat.setChatList': wrapCommand(setChatList),
  'Chat.setOpenRouterApiKey': wrapCommand(setOpenRouterApiKey),
  'Chat.terminate': terminate
};

const listen = async () => {
  registerCommands(commandMap);
  const rpc = await create$3({
    commandMap: commandMap
  });
  set$1(rpc);
};

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

main();
