"use strict";
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __export = (target, all) => {
  for (var name in all)
    __defProp(target, name, { get: all[name], enumerable: true });
};
var __copyProps = (to, from, except, desc) => {
  if (from && typeof from === "object" || typeof from === "function") {
    for (let key of __getOwnPropNames(from))
      if (!__hasOwnProp.call(to, key) && key !== except)
        __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
  }
  return to;
};
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
var client_exports = {};
__export(client_exports, {
  CopilotClient: () => CopilotClient
});
module.exports = __toCommonJS(client_exports);
var import_node_child_process = require("node:child_process");
var import_node_crypto = require("node:crypto");
var import_node_fs = require("node:fs");
var import_node_module = require("node:module");
var import_node_net = require("node:net");
var import_node_path = require("node:path");
var import_node_url = require("node:url");
var import_node = require("vscode-jsonrpc/node.js");
var import_rpc = require("./generated/rpc.js");
var import_sdkProtocolVersion = require("./sdkProtocolVersion.js");
var import_session = require("./session.js");
var import_sessionFsProvider = require("./sessionFsProvider.js");
var import_telemetry = require("./telemetry.js");
var import_toolSet = require("./toolSet.js");
var import_types = require("./types.js");
const import_meta = {};
const MIN_PROTOCOL_VERSION = 3;
function isZodSchema(value) {
  return value != null && typeof value === "object" && "toJSONSchema" in value && typeof value.toJSONSchema === "function";
}
function toJsonSchema(parameters) {
  if (!parameters) return void 0;
  if (isZodSchema(parameters)) {
    return parameters.toJSONSchema();
  }
  return parameters;
}
function toWireMcpServers(mcpServers) {
  if (!mcpServers) return void 0;
  return Object.fromEntries(
    Object.entries(mcpServers).map(([name, server]) => {
      if ("workingDirectory" in server) {
        const { workingDirectory, ...rest } = server;
        return [name, { ...rest, cwd: workingDirectory }];
      }
      return [name, server];
    })
  );
}
function toWireCustomAgents(agents) {
  if (!agents) return void 0;
  return agents.map((agent) => {
    if (!agent.mcpServers) return agent;
    const { mcpServers, ...rest } = agent;
    return { ...rest, mcpServers: toWireMcpServers(mcpServers) };
  });
}
function toWireLargeOutput(config) {
  if (!config) return void 0;
  const { outputDirectory, ...rest } = config;
  const wire = { ...rest };
  if (outputDirectory !== void 0) {
    wire.outputDir = outputDirectory;
  }
  return wire;
}
function toolFilterListToArray(value) {
  if (value === void 0) {
    return void 0;
  }
  return value instanceof import_toolSet.ToolSet ? value.toArray() : value;
}
function validateToolFilterList(field, list) {
  if (!list) {
    return;
  }
  for (const entry of list) {
    if (entry === "*") {
      throw new Error(
        `Invalid ${field} entry '*': there is no bare wildcard. Use one or more of \`new ToolSet().addBuiltIn('*')\`, \`.addMcp('*')\`, or \`.addCustom('*')\` to target a specific source.`
      );
    }
  }
}
function extractTransformCallbacks(systemMessage) {
  if (!systemMessage || systemMessage.mode !== "customize" || !systemMessage.sections) {
    return { wirePayload: systemMessage, transformCallbacks: void 0 };
  }
  const transformCallbacks = /* @__PURE__ */ new Map();
  const wireSections = {};
  for (const [sectionId, override] of Object.entries(systemMessage.sections)) {
    if (!override) continue;
    if (typeof override.action === "function") {
      transformCallbacks.set(sectionId, override.action);
      wireSections[sectionId] = { action: "transform" };
    } else {
      wireSections[sectionId] = { action: override.action, content: override.content };
    }
  }
  if (transformCallbacks.size === 0) {
    return { wirePayload: systemMessage, transformCallbacks: void 0 };
  }
  const wirePayload = {
    ...systemMessage,
    sections: wireSections
  };
  return { wirePayload, transformCallbacks };
}
function getNodeExecPath() {
  if (process.versions.bun) {
    return "node";
  }
  return process.execPath;
}
function getBundledCliPath() {
  if (typeof import_meta.resolve === "function") {
    const sdkUrl = import_meta.resolve("@github/copilot/sdk");
    const sdkPath = (0, import_node_url.fileURLToPath)(sdkUrl);
    return (0, import_node_path.join)((0, import_node_path.dirname)((0, import_node_path.dirname)(sdkPath)), "index.js");
  }
  const req = (0, import_node_module.createRequire)(__filename);
  const searchPaths = req.resolve.paths("@github/copilot") ?? [];
  for (const base of searchPaths) {
    const candidate = (0, import_node_path.join)(base, "@github", "copilot", "index.js");
    if ((0, import_node_fs.existsSync)(candidate)) {
      return candidate;
    }
  }
  throw new Error(
    `Could not find @github/copilot package. Searched ${searchPaths.length} paths. Ensure it is installed, or pass cliPath/cliUrl to CopilotClient.`
  );
}
class CopilotClient {
  cliStartTimeout = null;
  cliProcess = null;
  connection = null;
  socket = null;
  runtimePort = null;
  actualHost = "localhost";
  state = "disconnected";
  sessions = /* @__PURE__ */ new Map();
  stderrBuffer = "";
  // Captures CLI stderr for error messages
  /** Resolved connection mode chosen in the constructor. */
  connectionConfig;
  /** Resolved path to the runtime executable (only used for child-process kinds). */
  resolvedCliPath;
  /** Resolved environment passed to the spawned runtime. */
  resolvedEnv;
  options;
  isExternalServer = false;
  forceStopping = false;
  /** Token sent in `connect`; auto-generated when the SDK spawns its own CLI in TCP mode. */
  effectiveConnectionToken;
  onListModels;
  onGetTraceContext;
  modelsCache = null;
  modelsCacheLock = Promise.resolve();
  sessionLifecycleHandlers = /* @__PURE__ */ new Set();
  typedLifecycleHandlers = /* @__PURE__ */ new Map();
  _rpc = null;
  _internalRpc = null;
  processExitPromise = null;
  // Rejects when CLI process exits
  negotiatedProtocolVersion = null;
  /** Connection-level session filesystem config, set via constructor option. */
  sessionFsConfig = null;
  /**
   * Typed server-scoped RPC methods.
   * @throws Error if the client is not connected
   */
  get rpc() {
    if (!this.connection) {
      throw new Error("Client is not connected. Call start() first.");
    }
    if (!this._rpc) {
      this._rpc = (0, import_rpc.createServerRpc)(this.connection);
    }
    return this._rpc;
  }
  /**
   * Internal RPC surface (e.g. handshake helpers). Not part of the public API.
   * @internal
   */
  get internalRpc() {
    if (!this.connection) {
      throw new Error("Client is not connected. Call start() first.");
    }
    if (!this._internalRpc) {
      this._internalRpc = (0, import_rpc.createInternalServerRpc)(this.connection);
    }
    return this._internalRpc;
  }
  /**
   * Creates a new CopilotClient instance.
   *
   * @param options - Configuration options for the client
   *
   * @example
   * ```typescript
   * // Default: spawns the bundled runtime over stdio
   * const client = new CopilotClient();
   *
   * // Connect to an existing runtime
   * const client = new CopilotClient({
   *   connection: RuntimeConnection.forUri("localhost:3000"),
   * });
   *
   * // Spawn the runtime over TCP on a chosen port
   * const client = new CopilotClient({
   *   connection: RuntimeConnection.forTcp({ port: 9001 }),
   * });
   *
   * // Use a custom runtime binary
   * const client = new CopilotClient({
   *   connection: RuntimeConnection.forStdio({ path: "/usr/local/bin/copilot" }),
   *   logLevel: "debug",
   * });
   * ```
   */
  constructor(options = {}) {
    const conn = options._internalConnection ?? options.connection ?? { kind: "stdio" };
    if (conn.kind === "uri" && (options.gitHubToken !== void 0 || options.useLoggedInUser !== void 0)) {
      throw new Error(
        "gitHubToken and useLoggedInUser cannot be used with RuntimeConnection.forUri (external server manages its own auth)"
      );
    }
    if (conn.kind === "tcp" && conn.connectionToken !== void 0) {
      if (typeof conn.connectionToken !== "string" || conn.connectionToken.length === 0) {
        throw new Error("connectionToken must be a non-empty string");
      }
    }
    this.connectionConfig = conn;
    if (options.sessionFs) {
      this.validateSessionFsConfig(options.sessionFs);
    }
    if (conn.kind === "uri") {
      const { host, port } = this.parseCliUrl(conn.url);
      this.actualHost = host;
      this.runtimePort = port;
      this.isExternalServer = true;
    } else if (conn.kind === "parent-process") {
      this.isExternalServer = true;
    }
    if (conn.kind === "tcp") {
      this.effectiveConnectionToken = conn.connectionToken ?? (0, import_node_crypto.randomUUID)();
    } else if (conn.kind === "uri") {
      this.effectiveConnectionToken = conn.connectionToken;
    }
    this.onListModels = options.onListModels;
    this.onGetTraceContext = options.onGetTraceContext;
    this.sessionFsConfig = options.sessionFs ?? null;
    const effectiveEnv = options.env ?? process.env;
    this.resolvedEnv = effectiveEnv;
    this.resolvedCliPath = conn.kind === "stdio" || conn.kind === "tcp" ? conn.path ?? effectiveEnv.COPILOT_CLI_PATH ?? getBundledCliPath() : void 0;
    const connArgs = conn.kind === "stdio" || conn.kind === "tcp" ? conn.args ?? [] : [];
    this.connectionExtraArgs = [...connArgs];
    this.options = {
      workingDirectory: options.workingDirectory ?? process.cwd(),
      logLevel: options.logLevel,
      gitHubToken: options.gitHubToken,
      // Default useLoggedInUser to false when gitHubToken is provided, otherwise true.
      useLoggedInUser: options.useLoggedInUser ?? (options.gitHubToken ? false : true),
      telemetry: options.telemetry,
      baseDirectory: options.baseDirectory,
      sessionIdleTimeoutSeconds: options.sessionIdleTimeoutSeconds ?? 0,
      enableRemoteSessions: options.enableRemoteSessions ?? false,
      mode: options.mode ?? "copilot-cli"
    };
    if (this.options.mode === "empty") {
      const hasPersistence = this.options.baseDirectory !== void 0 || this.sessionFsConfig !== null || // External runtimes manage their own persistence layer; the SDK
      // can't enforce it from here.
      conn.kind === "uri" || conn.kind === "parent-process";
      if (!hasPersistence) {
        throw new Error(
          "CopilotClient was created with mode: 'empty' but neither 'baseDirectory' nor 'sessionFs' was set. Empty mode requires an explicit per-session persistence location; pick one."
        );
      }
    }
  }
  connectionExtraArgs = [];
  /**
   * Parse CLI URL into host and port
   * Supports formats: "host:port", "http://host:port", "https://host:port", or just "port"
   */
  parseCliUrl(url) {
    let cleanUrl = url.replace(/^https?:\/\//, "");
    if (/^\d+$/.test(cleanUrl)) {
      return { host: "localhost", port: parseInt(cleanUrl, 10) };
    }
    const parts = cleanUrl.split(":");
    if (parts.length !== 2) {
      throw new Error(
        `Invalid cliUrl format: ${url}. Expected "host:port", "http://host:port", or "port"`
      );
    }
    const host = parts[0] || "localhost";
    const port = parseInt(parts[1], 10);
    if (isNaN(port) || port <= 0 || port > 65535) {
      throw new Error(`Invalid port in cliUrl: ${url}`);
    }
    return { host, port };
  }
  validateSessionFsConfig(config) {
    if (!config.initialCwd) {
      throw new Error("sessionFs.initialCwd is required");
    }
    if (!config.sessionStatePath) {
      throw new Error("sessionFs.sessionStatePath is required");
    }
    if (config.conventions !== "windows" && config.conventions !== "posix") {
      throw new Error("sessionFs.conventions must be either 'windows' or 'posix'");
    }
  }
  setupSessionFs(session, config) {
    if (!this.sessionFsConfig) {
      return;
    }
    if (!config.createSessionFsProvider) {
      throw new Error(
        "createSessionFsProvider is required in session config when sessionFs is enabled in client options."
      );
    }
    const provider = config.createSessionFsProvider(session);
    if (this.sessionFsConfig.capabilities?.sqlite && !provider.sqlite) {
      throw new Error(
        "SessionFsConfig declares capabilities.sqlite but the provider does not implement sqlite."
      );
    }
    session.clientSessionApis.sessionFs = (0, import_sessionFsProvider.createSessionFsAdapter)(provider);
  }
  /**
   * Starts the CLI server and establishes a connection.
   *
   * If connecting to an external server (via cliUrl), only establishes the connection.
   * Otherwise, spawns the CLI server process and then connects.
   *
   * This method is called automatically the first time you create or resume a session.
   *
   * @returns A promise that resolves when the connection is established
   * @throws Error if the server fails to start or the connection fails
   *
   * @example
   * ```typescript
   * const client = new CopilotClient();
   * await client.start();
   * // Now ready to create sessions
   * ```
   */
  async start() {
    if (this.state === "connected") {
      return;
    }
    this.state = "connecting";
    try {
      if (!this.isExternalServer) {
        await this.startCLIServer();
      }
      await this.connectToServer();
      await this.verifyProtocolVersion();
      if (this.sessionFsConfig) {
        await this.connection.sendRequest("sessionFs.setProvider", {
          initialCwd: this.sessionFsConfig.initialCwd,
          sessionStatePath: this.sessionFsConfig.sessionStatePath,
          conventions: this.sessionFsConfig.conventions,
          capabilities: this.sessionFsConfig.capabilities
        });
      }
      this.state = "connected";
    } catch (error) {
      this.state = "error";
      throw error;
    }
  }
  /**
   * Stops the CLI server and closes all active sessions.
   *
   * This method performs graceful cleanup:
   * 1. Closes all active sessions (releases in-memory resources)
   * 2. Closes the JSON-RPC connection
   * 3. Terminates the CLI server process (if spawned by this client)
   *
   * Note: session data on disk is preserved, so sessions can be resumed later.
   * To permanently remove session data before stopping, call
   * {@link deleteSession} for each session first.
   *
   * @returns A promise that resolves with an array of errors encountered during cleanup.
   *          An empty array indicates all cleanup succeeded.
   *
   * @example
   * ```typescript
   * const errors = await client.stop();
   * if (errors.length > 0) {
   *   console.error("Cleanup errors:", errors);
   * }
   * ```
   */
  async stop() {
    const errors = [];
    for (const session of this.sessions.values()) {
      const sessionId = session.sessionId;
      let lastError = null;
      for (let attempt = 1; attempt <= 3; attempt++) {
        try {
          await session.disconnect();
          lastError = null;
          break;
        } catch (error) {
          lastError = error instanceof Error ? error : new Error(String(error));
          if (attempt < 3) {
            const delay = 100 * Math.pow(2, attempt - 1);
            await new Promise((resolve) => setTimeout(resolve, delay));
          }
        }
      }
      if (lastError) {
        errors.push(
          new Error(
            `Failed to disconnect session ${sessionId} after 3 attempts: ${lastError.message}`
          )
        );
      }
    }
    this.sessions.clear();
    if (this.connection) {
      try {
        this.connection.dispose();
      } catch (error) {
        errors.push(
          new Error(
            `Failed to dispose connection: ${error instanceof Error ? error.message : String(error)}`
          )
        );
      }
      this.connection = null;
      this._rpc = null;
    }
    this.modelsCache = null;
    if (this.socket) {
      const socket = this.socket;
      this.socket = null;
      try {
        if (!socket.destroyed) {
          await new Promise((resolve) => {
            socket.once("close", () => resolve());
            socket.end();
          });
        }
      } catch (error) {
        errors.push(
          new Error(
            `Failed to close socket: ${error instanceof Error ? error.message : String(error)}`
          )
        );
      }
    }
    if (this.cliProcess && !this.isExternalServer) {
      const child = this.cliProcess;
      this.cliProcess = null;
      try {
        if (child.exitCode === null && child.signalCode === null) {
          const exited = new Promise((resolve) => {
            child.once("exit", () => resolve());
          });
          child.kill();
          await exited;
        }
      } catch (error) {
        errors.push(
          new Error(
            `Failed to kill CLI process: ${error instanceof Error ? error.message : String(error)}`
          )
        );
      }
    }
    if (this.cliStartTimeout) {
      clearTimeout(this.cliStartTimeout);
      this.cliStartTimeout = null;
    }
    this.state = "disconnected";
    this.runtimePort = null;
    this.stderrBuffer = "";
    this.processExitPromise = null;
    return errors;
  }
  /**
   * Alias for {@link stop} that lets `CopilotClient` participate in `await using`
   * blocks for automatic cleanup.
   *
   * @example
   * ```typescript
   * await using client = new CopilotClient();
   * const session = await client.createSession({ onPermissionRequest: approveAll });
   * await session.sendAndWait("Hello");
   * // client.stop() is called automatically when the block exits.
   * ```
   */
  async [Symbol.asyncDispose]() {
    await this.stop();
  }
  /**
   * Forcefully stops the CLI server without graceful cleanup.
   *
   * Use this when {@link stop} fails or takes too long. This method:
   * - Clears all sessions immediately without destroying them
   * - Force closes the connection
   * - Sends SIGKILL to the CLI process (if spawned by this client)
   *
   * @returns A promise that resolves when the force stop is complete
   *
   * @example
   * ```typescript
   * // If normal stop hangs, force stop
   * const stopPromise = client.stop();
   * const timeout = new Promise((_, reject) =>
   *   setTimeout(() => reject(new Error("Timeout")), 5000)
   * );
   *
   * try {
   *   await Promise.race([stopPromise, timeout]);
   * } catch {
   *   await client.forceStop();
   * }
   * ```
   */
  async forceStop() {
    this.forceStopping = true;
    this.sessions.clear();
    if (this.connection) {
      try {
        this.connection.dispose();
      } catch {
      }
      this.connection = null;
      this._rpc = null;
    }
    this.modelsCache = null;
    if (this.socket) {
      try {
        this.socket.destroy();
      } catch {
      }
      this.socket = null;
    }
    if (this.cliProcess && !this.isExternalServer) {
      try {
        this.cliProcess.kill("SIGKILL");
      } catch {
      }
      this.cliProcess = null;
    }
    if (this.cliStartTimeout) {
      clearTimeout(this.cliStartTimeout);
      this.cliStartTimeout = null;
    }
    this.state = "disconnected";
    this.runtimePort = null;
    this.stderrBuffer = "";
    this.processExitPromise = null;
  }
  /**
   * Creates a new conversation session with the Copilot CLI.
   *
   * Sessions maintain conversation state, handle events, and manage tool execution.
   * If the client is not connected, this method automatically starts the connection.
   *
   * @param config - Optional configuration for the session
   * @returns A promise that resolves with the created session
   * @throws Error if the client fails to start
   *
   * @example
   * ```typescript
   * // Basic session
   * const session = await client.createSession({ onPermissionRequest: approveAll });
   *
   * // Session with model and tools
   * const session = await client.createSession({
   *   onPermissionRequest: approveAll,
   *   model: "gpt-4",
   *   tools: [{
   *     name: "get_weather",
   *     description: "Get weather for a location",
   *     parameters: { type: "object", properties: { location: { type: "string" } } },
   *     handler: async (args) => ({ temperature: 72 })
   *   }]
   * });
   * ```
   */
  /**
   * Normalizes session-level tool filter options. Converts {@link ToolSet}
   * instances to plain string arrays, rejects misuse (bare `"*"`) and the
   * missing-availableTools case in `mode = "empty"`.
   *
   * The SDK always sends `toolFilterPrecedence: "excluded"` so callers can
   * compose include + exclude lists naturally (e.g. "everything matching X
   * except Y") regardless of mode. Allowlist-precedence is intentionally not
   * exposed — it's available on the runtime side as a CLI-only concession to
   * legacy behavior, but SDK consumers always get the composable semantics.
   *
   * @internal
   */
  resolveToolFilterOptions(config) {
    const availableTools = toolFilterListToArray(config.availableTools);
    const excludedTools = toolFilterListToArray(config.excludedTools);
    validateToolFilterList("availableTools", availableTools);
    validateToolFilterList("excludedTools", excludedTools);
    if (this.options.mode === "empty") {
      if (availableTools === void 0) {
        throw new Error(
          "CopilotClient is in mode: 'empty' but the session config did not specify 'availableTools'. Empty mode requires every session to explicitly opt into the tools it wants \u2014 e.g. `new ToolSet().addBuiltIn(BuiltInTools.Isolated)`."
        );
      }
    }
    return { availableTools, excludedTools, toolFilterPrecedence: "excluded" };
  }
  /** Mode-specific defaults spread under the caller's config (app values win). */
  configDefaultsForMode() {
    if (this.options.mode === "empty") {
      return {
        enableSessionTelemetry: false,
        mcpOAuthTokenStorage: "in-memory",
        skipEmbeddingRetrieval: true,
        embeddingCacheStorage: "in-memory",
        enableOnDemandInstructionDiscovery: false,
        enableFileHooks: false,
        enableHostGitOperations: false,
        enableSessionStore: false,
        enableSkills: false
      };
    }
    return {};
  }
  /**
   * Returns the systemMessage config to use, adjusted for the current mode.
   * In empty mode we ensure the environment_context section is removed
   * unless the app has already taken control of it. `append` (and
   * unspecified) mode is promoted to `customize` so we can also strip
   * environment_context; the caller's `content` is preserved verbatim
   * because the runtime appends it as additional instructions in both
   * customize and append modes.
   */
  getSystemMessageConfigForMode(supplied) {
    if (this.options.mode !== "empty") return supplied;
    if (!supplied) {
      return {
        mode: "customize",
        sections: { environment_context: { action: "remove" } }
      };
    }
    switch (supplied.mode) {
      case "replace":
        return supplied;
      case "customize":
        if (supplied.sections?.environment_context) return supplied;
        return {
          ...supplied,
          sections: {
            ...supplied.sections,
            environment_context: { action: "remove" }
          }
        };
      case "append":
      case void 0:
        return {
          mode: "customize",
          content: supplied.content,
          sections: { environment_context: { action: "remove" } }
        };
    }
  }
  /**
   * Mode-specific options applied via session.options.update after create/resume.
   *
   * In empty mode, defaults the four overridable feature flags to safe values
   * (caller values from `config` win). `installedPlugins=[]` is unconditional
   * in empty mode — apps that need custom plugins should switch modes.
   */
  async updateSessionOptionsForMode(session, config) {
    const patch = {};
    if (this.options.mode === "empty") {
      patch.skipCustomInstructions = config.skipCustomInstructions ?? true;
      patch.customAgentsLocalOnly = config.customAgentsLocalOnly ?? true;
      patch.coauthorEnabled = config.coauthorEnabled ?? false;
      patch.manageScheduleEnabled = config.manageScheduleEnabled ?? false;
      patch.installedPlugins = [];
    } else {
      if (config.skipCustomInstructions !== void 0)
        patch.skipCustomInstructions = config.skipCustomInstructions;
      if (config.customAgentsLocalOnly !== void 0)
        patch.customAgentsLocalOnly = config.customAgentsLocalOnly;
      if (config.coauthorEnabled !== void 0)
        patch.coauthorEnabled = config.coauthorEnabled;
      if (config.manageScheduleEnabled !== void 0)
        patch.manageScheduleEnabled = config.manageScheduleEnabled;
    }
    if (Object.keys(patch).length === 0) {
      return;
    }
    try {
      await session.rpc.options.update(patch);
    } catch (e) {
      try {
        await session.disconnect();
      } catch {
      }
      throw e;
    }
  }
  async createSession(config) {
    if (!this.connection) {
      await this.start();
    }
    config = { ...this.configDefaultsForMode(), ...config };
    config.systemMessage = this.getSystemMessageConfigForMode(config.systemMessage);
    const callerSessionId = config.sessionId;
    const useServerGeneratedId = config.cloud != null && callerSessionId == null;
    const localSessionId = useServerGeneratedId ? void 0 : callerSessionId ?? (0, import_node_crypto.randomUUID)();
    const { wirePayload: wireSystemMessage, transformCallbacks } = extractTransformCallbacks(
      config.systemMessage
    );
    const initializeSession = (sessionId) => {
      const s = new import_session.CopilotSession(
        sessionId,
        this.connection,
        void 0,
        this.onGetTraceContext
      );
      s.registerTools(config.tools);
      s.registerCanvases(config.canvases);
      s.registerCommands(config.commands);
      s.registerPermissionHandler(config.onPermissionRequest);
      if (config.onUserInputRequest) {
        s.registerUserInputHandler(config.onUserInputRequest);
      }
      if (config.onElicitationRequest) {
        s.registerElicitationHandler(config.onElicitationRequest);
      }
      if (config.onExitPlanModeRequest) {
        s.registerExitPlanModeHandler(config.onExitPlanModeRequest);
      }
      if (config.onAutoModeSwitchRequest) {
        s.registerAutoModeSwitchHandler(config.onAutoModeSwitchRequest);
      }
      if (config.hooks) {
        s.registerHooks(config.hooks);
      }
      if (transformCallbacks) {
        s.registerTransformCallbacks(transformCallbacks);
      }
      if (config.onEvent) {
        s.on(config.onEvent);
      }
      this.sessions.set(sessionId, s);
      this.setupSessionFs(s, config);
      return s;
    };
    let session;
    let registeredId;
    if (localSessionId !== void 0) {
      session = initializeSession(localSessionId);
      registeredId = localSessionId;
    }
    const toolFilterOptions = this.resolveToolFilterOptions(config);
    try {
      const response = await this.connection.sendRequest("session.create", {
        ...await (0, import_telemetry.getTraceContext)(this.onGetTraceContext),
        model: config.model,
        sessionId: localSessionId,
        clientName: config.clientName,
        reasoningEffort: config.reasoningEffort,
        reasoningSummary: config.reasoningSummary,
        contextTier: config.contextTier,
        tools: config.tools?.map((tool) => ({
          name: tool.name,
          description: tool.description,
          parameters: toJsonSchema(tool.parameters),
          overridesBuiltInTool: tool.overridesBuiltInTool,
          skipPermission: tool.skipPermission
        })),
        canvases: config.canvases?.map((canvas) => canvas.declaration),
        requestCanvasRenderer: config.requestCanvasRenderer,
        requestExtensions: config.requestExtensions,
        extensionSdkPath: config.extensionSdkPath,
        extensionInfo: config.extensionInfo,
        commands: config.commands?.map((cmd) => ({
          name: cmd.name,
          description: cmd.description
        })),
        systemMessage: wireSystemMessage,
        availableTools: toolFilterOptions.availableTools,
        excludedTools: toolFilterOptions.excludedTools,
        toolFilterPrecedence: toolFilterOptions.toolFilterPrecedence,
        provider: config.provider,
        enableSessionTelemetry: config.enableSessionTelemetry,
        modelCapabilities: config.modelCapabilities,
        largeOutput: toWireLargeOutput(config.largeOutput),
        requestPermission: !!config.onPermissionRequest,
        requestUserInput: !!config.onUserInputRequest,
        requestElicitation: !!config.onElicitationRequest,
        ...config.enableMcpApps ? { requestMcpApps: true } : {},
        requestExitPlanMode: !!config.onExitPlanModeRequest,
        requestAutoModeSwitch: !!config.onAutoModeSwitchRequest,
        hooks: !!(config.hooks && Object.values(config.hooks).some(Boolean)),
        workingDirectory: config.workingDirectory,
        streaming: config.streaming,
        includeSubAgentStreamingEvents: config.includeSubAgentStreamingEvents ?? true,
        mcpServers: toWireMcpServers(config.mcpServers),
        mcpOAuthTokenStorage: config.mcpOAuthTokenStorage,
        envValueMode: "direct",
        customAgents: toWireCustomAgents(config.customAgents),
        defaultAgent: config.defaultAgent,
        agent: config.agent,
        configDir: config.configDirectory,
        enableConfigDiscovery: config.enableConfigDiscovery,
        skipEmbeddingRetrieval: config.skipEmbeddingRetrieval,
        embeddingCacheStorage: config.embeddingCacheStorage,
        organizationCustomInstructions: config.organizationCustomInstructions,
        enableOnDemandInstructionDiscovery: config.enableOnDemandInstructionDiscovery,
        enableFileHooks: config.enableFileHooks,
        enableHostGitOperations: config.enableHostGitOperations,
        enableSessionStore: config.enableSessionStore,
        enableSkills: config.enableSkills,
        skillDirectories: config.skillDirectories,
        pluginDirectories: config.pluginDirectories,
        instructionDirectories: config.instructionDirectories,
        disabledSkills: config.disabledSkills,
        infiniteSessions: config.infiniteSessions,
        gitHubToken: config.gitHubToken,
        remoteSession: config.remoteSession,
        cloud: config.cloud
      });
      const {
        sessionId: returnedSessionId,
        workspacePath,
        capabilities
      } = response;
      if (!returnedSessionId) {
        throw new Error("session.create response did not include a sessionId");
      }
      if (localSessionId !== void 0 && localSessionId !== returnedSessionId) {
        throw new Error(
          `session.create returned sessionId ${returnedSessionId} but the caller requested ${localSessionId}`
        );
      }
      if (session === void 0) {
        session = initializeSession(returnedSessionId);
        registeredId = returnedSessionId;
      }
      session["_workspacePath"] = workspacePath;
      session.setCapabilities(capabilities);
      await this.updateSessionOptionsForMode(session, config);
    } catch (e) {
      if (registeredId !== void 0) {
        this.sessions.delete(registeredId);
      }
      throw e;
    }
    return session;
  }
  /**
   * Resumes an existing conversation session by its ID.
   *
   * This allows you to continue a previous conversation, maintaining all
   * conversation history. The session must have been previously created
   * and not deleted.
   *
   * @param sessionId - The ID of the session to resume
   * @param config - Optional configuration for the resumed session
   * @returns A promise that resolves with the resumed session
   * @throws Error if the session does not exist or the client is not connected
   *
   * @example
   * ```typescript
   * // Resume a previous session
   * const session = await client.resumeSession("session-123", { onPermissionRequest: approveAll });
   *
   * // Resume with new tools
   * const session = await client.resumeSession("session-123", {
   *   onPermissionRequest: approveAll,
   *   tools: [myNewTool]
   * });
   * ```
   */
  async resumeSession(sessionId, config) {
    if (!this.connection) {
      await this.start();
    }
    const session = new import_session.CopilotSession(
      sessionId,
      this.connection,
      void 0,
      this.onGetTraceContext
    );
    session.registerTools(config.tools);
    session.registerCanvases(config.canvases);
    session.registerCommands(config.commands);
    session.registerPermissionHandler(config.onPermissionRequest);
    if (config.onUserInputRequest) {
      session.registerUserInputHandler(config.onUserInputRequest);
    }
    if (config.onElicitationRequest) {
      session.registerElicitationHandler(config.onElicitationRequest);
    }
    if (config.onExitPlanModeRequest) {
      session.registerExitPlanModeHandler(config.onExitPlanModeRequest);
    }
    if (config.onAutoModeSwitchRequest) {
      session.registerAutoModeSwitchHandler(config.onAutoModeSwitchRequest);
    }
    if (config.hooks) {
      session.registerHooks(config.hooks);
    }
    config = { ...this.configDefaultsForMode(), ...config };
    config.systemMessage = this.getSystemMessageConfigForMode(config.systemMessage);
    const { wirePayload: wireSystemMessage, transformCallbacks } = extractTransformCallbacks(
      config.systemMessage
    );
    if (transformCallbacks) {
      session.registerTransformCallbacks(transformCallbacks);
    }
    if (config.onEvent) {
      session.on(config.onEvent);
    }
    this.sessions.set(sessionId, session);
    this.setupSessionFs(session, config);
    const toolFilterOptions = this.resolveToolFilterOptions(config);
    try {
      const response = await this.connection.sendRequest("session.resume", {
        ...await (0, import_telemetry.getTraceContext)(this.onGetTraceContext),
        sessionId,
        clientName: config.clientName,
        model: config.model,
        reasoningEffort: config.reasoningEffort,
        reasoningSummary: config.reasoningSummary,
        contextTier: config.contextTier,
        systemMessage: wireSystemMessage,
        availableTools: toolFilterOptions.availableTools,
        excludedTools: toolFilterOptions.excludedTools,
        toolFilterPrecedence: toolFilterOptions.toolFilterPrecedence,
        enableSessionTelemetry: config.enableSessionTelemetry,
        tools: config.tools?.map((tool) => ({
          name: tool.name,
          description: tool.description,
          parameters: toJsonSchema(tool.parameters),
          overridesBuiltInTool: tool.overridesBuiltInTool,
          skipPermission: tool.skipPermission
        })),
        canvases: config.canvases?.map((canvas) => canvas.declaration),
        requestCanvasRenderer: config.requestCanvasRenderer,
        requestExtensions: config.requestExtensions,
        extensionSdkPath: config.extensionSdkPath,
        extensionInfo: config.extensionInfo,
        commands: config.commands?.map((cmd) => ({
          name: cmd.name,
          description: cmd.description
        })),
        provider: config.provider,
        modelCapabilities: config.modelCapabilities,
        largeOutput: toWireLargeOutput(config.largeOutput),
        requestPermission: config.onPermissionRequest !== import_types.defaultJoinSessionPermissionHandler,
        requestUserInput: !!config.onUserInputRequest,
        requestElicitation: !!config.onElicitationRequest,
        ...config.enableMcpApps ? { requestMcpApps: true } : {},
        requestExitPlanMode: !!config.onExitPlanModeRequest,
        requestAutoModeSwitch: !!config.onAutoModeSwitchRequest,
        hooks: !!(config.hooks && Object.values(config.hooks).some(Boolean)),
        workingDirectory: config.workingDirectory,
        configDir: config.configDirectory,
        enableConfigDiscovery: config.enableConfigDiscovery,
        skipEmbeddingRetrieval: config.skipEmbeddingRetrieval,
        embeddingCacheStorage: config.embeddingCacheStorage,
        organizationCustomInstructions: config.organizationCustomInstructions,
        enableOnDemandInstructionDiscovery: config.enableOnDemandInstructionDiscovery,
        enableFileHooks: config.enableFileHooks,
        enableHostGitOperations: config.enableHostGitOperations,
        enableSessionStore: config.enableSessionStore,
        enableSkills: config.enableSkills,
        streaming: config.streaming,
        includeSubAgentStreamingEvents: config.includeSubAgentStreamingEvents ?? true,
        mcpServers: toWireMcpServers(config.mcpServers),
        mcpOAuthTokenStorage: config.mcpOAuthTokenStorage,
        envValueMode: "direct",
        customAgents: toWireCustomAgents(config.customAgents),
        defaultAgent: config.defaultAgent,
        agent: config.agent,
        skillDirectories: config.skillDirectories,
        pluginDirectories: config.pluginDirectories,
        instructionDirectories: config.instructionDirectories,
        disabledSkills: config.disabledSkills,
        infiniteSessions: config.infiniteSessions,
        disableResume: config.suppressResumeEvent,
        continuePendingWork: config.continuePendingWork,
        gitHubToken: config.gitHubToken,
        remoteSession: config.remoteSession,
        openCanvases: config.openCanvases
      });
      const { workspacePath, capabilities, openCanvases } = response;
      session["_workspacePath"] = workspacePath;
      session.setCapabilities(capabilities);
      session.setOpenCanvases(openCanvases ?? []);
      await this.updateSessionOptionsForMode(session, config);
    } catch (e) {
      this.sessions.delete(sessionId);
      throw e;
    }
    return session;
  }
  /**
   * Sends a ping request to the server to verify connectivity.
   *
   * @param message - Optional message to include in the ping
   * @returns A promise that resolves with the ping response containing the message and timestamp
   * @throws Error if the client is not connected
   *
   * @example
   * ```typescript
   * const response = await client.ping("health check");
   * console.log(`Server responded at ${new Date(response.timestamp)}`);
   * ```
   */
  async ping(message) {
    if (!this.connection) {
      throw new Error("Client not connected");
    }
    const result = await this.connection.sendRequest("ping", { message });
    return result;
  }
  /**
   * Get CLI status including version and protocol information
   */
  async getStatus() {
    if (!this.connection) {
      throw new Error("Client not connected");
    }
    const result = await this.connection.sendRequest("status.get", {});
    return result;
  }
  /**
   * Get current authentication status
   */
  async getAuthStatus() {
    if (!this.connection) {
      throw new Error("Client not connected");
    }
    const result = await this.connection.sendRequest("auth.getStatus", {});
    return result;
  }
  /**
   * List available models with their metadata.
   *
   * If an `onListModels` handler was provided in the client options,
   * it is called instead of querying the CLI server.
   *
   * Results are cached after the first successful call to avoid rate limiting.
   * The cache is cleared when the client disconnects.
   *
   * @throws Error if not connected (when no custom handler is set)
   */
  async listModels() {
    await this.modelsCacheLock;
    let resolveLock;
    this.modelsCacheLock = new Promise((resolve) => {
      resolveLock = resolve;
    });
    try {
      if (this.modelsCache !== null) {
        return [...this.modelsCache];
      }
      let models;
      if (this.onListModels) {
        models = await this.onListModels();
      } else {
        if (!this.connection) {
          throw new Error("Client not connected");
        }
        const result = await this.connection.sendRequest("models.list", {});
        const response = result;
        models = response.models;
        for (const model of models) {
          const m = model;
          if (!m.capabilities) {
            m.capabilities = {
              supports: {},
              limits: { max_context_window_tokens: 0 }
            };
          } else {
            if (!m.capabilities.supports) m.capabilities.supports = {};
            if (!m.capabilities.limits) {
              m.capabilities.limits = { max_context_window_tokens: 0 };
            } else if (m.capabilities.limits.max_context_window_tokens === void 0) {
              m.capabilities.limits.max_context_window_tokens = 0;
            }
          }
        }
      }
      this.modelsCache = [...models];
      return [...models];
    } finally {
      resolveLock();
    }
  }
  /**
   * Send the `connect` handshake (carrying the optional token) and verify the
   * server's protocol version. Falls back to `ping` against legacy servers
   * that don't implement `connect`.
   */
  async verifyProtocolVersion() {
    if (!this.connection) {
      throw new Error("Client not connected");
    }
    const maxVersion = (0, import_sdkProtocolVersion.getSdkProtocolVersion)();
    const raceAgainstExit = (p) => this.processExitPromise ? Promise.race([p, this.processExitPromise]) : p;
    let serverVersion;
    try {
      const result = await raceAgainstExit(
        this.internalRpc.connect({ token: this.effectiveConnectionToken })
      );
      serverVersion = result.protocolVersion;
    } catch (err) {
      if (err instanceof import_node.ResponseError && (err.code === import_node.ErrorCodes.MethodNotFound || err.message === "Unhandled method connect")) {
        serverVersion = (await raceAgainstExit(this.ping())).protocolVersion;
      } else {
        throw err;
      }
    }
    if (serverVersion === void 0) {
      throw new Error(
        `SDK protocol version mismatch: SDK supports versions ${MIN_PROTOCOL_VERSION}-${maxVersion}, but server does not report a protocol version. Please update your server to ensure compatibility.`
      );
    }
    if (serverVersion < MIN_PROTOCOL_VERSION || serverVersion > maxVersion) {
      throw new Error(
        `SDK protocol version mismatch: SDK supports versions ${MIN_PROTOCOL_VERSION}-${maxVersion}, but server reports version ${serverVersion}. Please update your SDK or server to ensure compatibility.`
      );
    }
    this.negotiatedProtocolVersion = serverVersion;
  }
  /**
   * Gets the ID of the most recently updated session.
   *
   * This is useful for resuming the last conversation when the session ID
   * was not stored.
   *
   * @returns A promise that resolves with the session ID, or undefined if no sessions exist
   * @throws Error if the client is not connected
   *
   * @example
   * ```typescript
   * const lastId = await client.getLastSessionId();
   * if (lastId) {
   *   const session = await client.resumeSession(lastId, { onPermissionRequest: approveAll });
   * }
   * ```
   */
  async getLastSessionId() {
    if (!this.connection) {
      throw new Error("Client not connected");
    }
    const response = await this.connection.sendRequest("session.getLastId", {});
    return response.sessionId;
  }
  /**
   * Permanently deletes a session and all its data from disk, including
   * conversation history, planning state, and artifacts.
   *
   * Unlike {@link CopilotSession.disconnect}, which only releases in-memory
   * resources and preserves session data for later resumption, this method
   * is irreversible. The session cannot be resumed after deletion.
   *
   * @param sessionId - The ID of the session to delete
   * @returns A promise that resolves when the session is deleted
   * @throws Error if the session does not exist or deletion fails
   *
   * @example
   * ```typescript
   * await client.deleteSession("session-123");
   * ```
   */
  async deleteSession(sessionId) {
    if (!this.connection) {
      throw new Error("Client not connected");
    }
    const response = await this.connection.sendRequest("session.delete", {
      sessionId
    });
    const { success, error } = response;
    if (!success) {
      throw new Error(`Failed to delete session ${sessionId}: ${error || "Unknown error"}`);
    }
    this.sessions.delete(sessionId);
  }
  /**
   * List all available sessions.
   *
   * @param filter - Optional filter to limit returned sessions by context fields
   *
   * @example
   * // List all sessions
   * const sessions = await client.listSessions();
   *
   * @example
   * // List sessions for a specific repository
   * const sessions = await client.listSessions({ repository: "owner/repo" });
   */
  async listSessions(filter) {
    if (!this.connection) {
      throw new Error("Client not connected");
    }
    let wireFilter;
    if (filter) {
      const { workingDirectory, ...rest } = filter;
      wireFilter = { ...rest, cwd: workingDirectory };
    }
    const response = await this.connection.sendRequest("session.list", {
      filter: wireFilter
    });
    const { sessions } = response;
    return sessions.map(CopilotClient.toSessionMetadata);
  }
  /**
   * Gets metadata for a specific session by ID.
   *
   * This provides an efficient O(1) lookup of a single session's metadata
   * instead of listing all sessions. Returns undefined if the session is not found.
   *
   * @param sessionId - The ID of the session to look up
   * @returns A promise that resolves with the session metadata, or undefined if not found
   * @throws Error if the client is not connected
   *
   * @example
   * ```typescript
   * const metadata = await client.getSessionMetadata("session-123");
   * if (metadata) {
   *   console.log(`Session started at: ${metadata.startTime}`);
   * }
   * ```
   */
  async getSessionMetadata(sessionId) {
    if (!this.connection) {
      throw new Error("Client not connected");
    }
    const response = await this.connection.sendRequest("session.getMetadata", { sessionId });
    const { session } = response;
    if (!session) {
      return void 0;
    }
    return CopilotClient.toSessionMetadata(session);
  }
  static toSessionMetadata(raw) {
    const { context } = raw;
    return {
      sessionId: raw.sessionId,
      startTime: new Date(raw.startTime),
      modifiedTime: new Date(raw.modifiedTime),
      summary: raw.summary,
      isRemote: raw.isRemote,
      context: context ? {
        workingDirectory: context.cwd,
        gitRoot: context.gitRoot,
        repository: context.repository,
        branch: context.branch
      } : void 0
    };
  }
  /**
   * Gets the foreground session ID in TUI+server mode.
   *
   * This returns the ID of the session currently displayed in the TUI.
   * Only available when connecting to a server running in TUI+server mode (--ui-server).
   *
   * @returns A promise that resolves with the foreground session ID, or undefined if none
   * @throws Error if the client is not connected
   *
   * @example
   * ```typescript
   * const sessionId = await client.getForegroundSessionId();
   * if (sessionId) {
   *   console.log(`TUI is displaying session: ${sessionId}`);
   * }
   * ```
   */
  async getForegroundSessionId() {
    if (!this.connection) {
      throw new Error("Client not connected");
    }
    const response = await this.connection.sendRequest("session.getForeground", {});
    return response.sessionId;
  }
  /**
   * Sets the foreground session in TUI+server mode.
   *
   * This requests the TUI to switch to displaying the specified session.
   * Only available when connecting to a server running in TUI+server mode (--ui-server).
   *
   * @param sessionId - The ID of the session to display in the TUI
   * @returns A promise that resolves when the session is switched
   * @throws Error if the client is not connected or if the operation fails
   *
   * @example
   * ```typescript
   * // Switch the TUI to display a specific session
   * await client.setForegroundSessionId("session-123");
   * ```
   */
  async setForegroundSessionId(sessionId) {
    if (!this.connection) {
      throw new Error("Client not connected");
    }
    const response = await this.connection.sendRequest("session.setForeground", { sessionId });
    const result = response;
    if (!result.success) {
      throw new Error(result.error || "Failed to set foreground session");
    }
  }
  onLifecycle(eventTypeOrHandler, handler) {
    if (typeof eventTypeOrHandler === "string" && handler) {
      const eventType = eventTypeOrHandler;
      if (!this.typedLifecycleHandlers.has(eventType)) {
        this.typedLifecycleHandlers.set(eventType, /* @__PURE__ */ new Set());
      }
      const storedHandler = handler;
      this.typedLifecycleHandlers.get(eventType).add(storedHandler);
      return () => {
        const handlers = this.typedLifecycleHandlers.get(eventType);
        if (handlers) {
          handlers.delete(storedHandler);
        }
      };
    }
    const wildcardHandler = eventTypeOrHandler;
    this.sessionLifecycleHandlers.add(wildcardHandler);
    return () => {
      this.sessionLifecycleHandlers.delete(wildcardHandler);
    };
  }
  /**
   * Start the CLI server process
   */
  async startCLIServer() {
    return new Promise((resolve, reject) => {
      this.stderrBuffer = "";
      const args = [...this.connectionExtraArgs, "--headless", "--no-auto-update"];
      if (this.options.logLevel) {
        args.push("--log-level", this.options.logLevel);
      }
      if (this.connectionConfig.kind === "stdio") {
        args.push("--stdio");
      } else if (this.connectionConfig.kind === "tcp") {
        const requestedPort = this.connectionConfig.port ?? 0;
        if (requestedPort > 0) {
          args.push("--port", requestedPort.toString());
        }
      }
      if (this.options.gitHubToken) {
        args.push("--auth-token-env", "COPILOT_SDK_AUTH_TOKEN");
      }
      if (!this.options.useLoggedInUser) {
        args.push("--no-auto-login");
      }
      if (this.options.sessionIdleTimeoutSeconds !== void 0 && this.options.sessionIdleTimeoutSeconds > 0) {
        args.push(
          "--session-idle-timeout",
          this.options.sessionIdleTimeoutSeconds.toString()
        );
      }
      if (this.options.enableRemoteSessions) {
        args.push("--remote");
      }
      const envWithoutNodeDebug = { ...this.resolvedEnv };
      delete envWithoutNodeDebug.NODE_DEBUG;
      if (this.options.gitHubToken) {
        envWithoutNodeDebug.COPILOT_SDK_AUTH_TOKEN = this.options.gitHubToken;
      }
      if (this.effectiveConnectionToken) {
        envWithoutNodeDebug.COPILOT_CONNECTION_TOKEN = this.effectiveConnectionToken;
      }
      if (this.options.baseDirectory) {
        envWithoutNodeDebug.COPILOT_HOME = this.options.baseDirectory;
      }
      if (this.options.mode === "empty") {
        envWithoutNodeDebug.COPILOT_DISABLE_KEYTAR = "1";
      }
      if (!this.resolvedCliPath) {
        throw new Error(
          "Path to Copilot CLI is required. Please supply it via `RuntimeConnection.forStdio({ path })` or `RuntimeConnection.forTcp({ path })`, set the COPILOT_CLI_PATH environment variable, or use `RuntimeConnection.forUri(...)` to connect to an already-running runtime."
        );
      }
      if (this.options.telemetry) {
        const t = this.options.telemetry;
        envWithoutNodeDebug.COPILOT_OTEL_ENABLED = "true";
        if (t.otlpEndpoint !== void 0)
          envWithoutNodeDebug.OTEL_EXPORTER_OTLP_ENDPOINT = t.otlpEndpoint;
        if (t.filePath !== void 0)
          envWithoutNodeDebug.COPILOT_OTEL_FILE_EXPORTER_PATH = t.filePath;
        if (t.exporterType !== void 0)
          envWithoutNodeDebug.COPILOT_OTEL_EXPORTER_TYPE = t.exporterType;
        if (t.sourceName !== void 0)
          envWithoutNodeDebug.COPILOT_OTEL_SOURCE_NAME = t.sourceName;
        if (t.captureContent !== void 0)
          envWithoutNodeDebug.OTEL_INSTRUMENTATION_GENAI_CAPTURE_MESSAGE_CONTENT = String(
            t.captureContent
          );
      }
      if (!(0, import_node_fs.existsSync)(this.resolvedCliPath)) {
        throw new Error(
          `Copilot CLI not found at ${this.resolvedCliPath}. Ensure @github/copilot is installed.`
        );
      }
      const stdioConfig = this.connectionConfig.kind === "stdio" ? ["pipe", "pipe", "pipe"] : ["ignore", "pipe", "pipe"];
      const isJsFile = this.resolvedCliPath.endsWith(".js");
      if (isJsFile) {
        this.cliProcess = (0, import_node_child_process.spawn)(getNodeExecPath(), [this.resolvedCliPath, ...args], {
          stdio: stdioConfig,
          cwd: this.options.workingDirectory,
          env: envWithoutNodeDebug,
          windowsHide: true
        });
      } else {
        this.cliProcess = (0, import_node_child_process.spawn)(this.resolvedCliPath, args, {
          stdio: stdioConfig,
          cwd: this.options.workingDirectory,
          env: envWithoutNodeDebug,
          windowsHide: true
        });
      }
      let stdout = "";
      let resolved = false;
      if (this.connectionConfig.kind === "stdio") {
        resolved = true;
        resolve();
      } else {
        this.cliProcess.stdout?.on("data", (data) => {
          stdout += data.toString();
          const match = stdout.match(/listening on port (\d+)/i);
          if (match && !resolved) {
            this.runtimePort = parseInt(match[1], 10);
            resolved = true;
            resolve();
          }
        });
      }
      this.cliProcess.stderr?.on("data", (data) => {
        this.stderrBuffer += data.toString();
        const lines = data.toString().split("\n");
        for (const line of lines) {
          if (line.trim()) {
            process.stderr.write(`[CLI subprocess] ${line}
`);
          }
        }
      });
      this.cliProcess.on("error", (error) => {
        if (!resolved) {
          resolved = true;
          const stderrOutput = this.stderrBuffer.trim();
          if (stderrOutput) {
            reject(
              new Error(
                `Failed to start CLI server: ${error.message}
stderr: ${stderrOutput}`
              )
            );
          } else {
            reject(new Error(`Failed to start CLI server: ${error.message}`));
          }
        }
      });
      this.processExitPromise = new Promise((_, rejectProcessExit) => {
        this.cliProcess.on("exit", (code) => {
          setTimeout(() => {
            const stderrOutput = this.stderrBuffer.trim();
            if (stderrOutput) {
              rejectProcessExit(
                new Error(
                  `CLI server exited with code ${code}
stderr: ${stderrOutput}`
                )
              );
            } else {
              rejectProcessExit(
                new Error(`CLI server exited unexpectedly with code ${code}`)
              );
            }
          }, 50);
        });
      });
      this.processExitPromise.catch(() => {
      });
      this.cliProcess.on("exit", (code) => {
        if (!resolved) {
          resolved = true;
          const stderrOutput = this.stderrBuffer.trim();
          if (stderrOutput) {
            reject(
              new Error(
                `CLI server exited with code ${code}
stderr: ${stderrOutput}`
              )
            );
          } else {
            reject(new Error(`CLI server exited with code ${code}`));
          }
        }
      });
      this.cliStartTimeout = setTimeout(() => {
        if (!resolved) {
          resolved = true;
          reject(new Error("Timeout waiting for CLI server to start"));
        }
      }, 3e4);
    });
  }
  /**
   * Connect to the CLI server (via socket or stdio)
   */
  async connectToServer() {
    switch (this.connectionConfig.kind) {
      case "parent-process":
        return this.connectToParentProcessViaStdio();
      case "stdio":
        return this.connectToChildProcessViaStdio();
      case "tcp":
      case "uri":
        return this.connectViaTcp();
    }
  }
  /**
   * Connect to child via stdio pipes
   */
  async connectToChildProcessViaStdio() {
    if (!this.cliProcess) {
      throw new Error("CLI process not started");
    }
    this.cliProcess.stdin?.on("error", (err) => {
      if (!this.forceStopping) {
        throw err;
      }
    });
    this.connection = (0, import_node.createMessageConnection)(
      new import_node.StreamMessageReader(this.cliProcess.stdout),
      new import_node.StreamMessageWriter(this.cliProcess.stdin)
    );
    this.attachConnectionHandlers();
    this.connection.listen();
  }
  /**
   * Connect to parent via stdio pipes
   */
  async connectToParentProcessViaStdio() {
    if (this.cliProcess) {
      throw new Error("CLI child process was unexpectedly started in parent process mode");
    }
    this.connection = (0, import_node.createMessageConnection)(
      new import_node.StreamMessageReader(process.stdin),
      new import_node.StreamMessageWriter(process.stdout)
    );
    this.attachConnectionHandlers();
    this.connection.listen();
  }
  /**
   * Connect to the CLI server via TCP socket
   */
  async connectViaTcp() {
    if (!this.runtimePort) {
      throw new Error("Server port not available");
    }
    return new Promise((resolve, reject) => {
      this.socket = new import_node_net.Socket();
      const connectionTimeout = setTimeout(() => {
        this.socket?.destroy();
        reject(new Error("Timeout connecting to CLI server"));
      }, 1e4);
      this.socket.connect(this.runtimePort, this.actualHost, () => {
        clearTimeout(connectionTimeout);
        this.connection = (0, import_node.createMessageConnection)(
          new import_node.StreamMessageReader(this.socket),
          new import_node.StreamMessageWriter(this.socket)
        );
        this.attachConnectionHandlers();
        this.connection.listen();
        resolve();
      });
      this.socket.on("error", (error) => {
        clearTimeout(connectionTimeout);
        reject(new Error(`Failed to connect to CLI server: ${error.message}`));
      });
    });
  }
  attachConnectionHandlers() {
    if (!this.connection) {
      return;
    }
    this.connection.onNotification("session.event", (notification) => {
      this.handleSessionEventNotification(notification);
    });
    this.connection.onNotification("session.lifecycle", (notification) => {
      this.handleSessionLifecycleNotification(notification);
    });
    this.connection.onRequest(
      "userInput.request",
      async (params) => await this.handleUserInputRequest(params)
    );
    this.connection.onRequest(
      "exitPlanMode.request",
      async (params) => await this.handleExitPlanModeRequest(params)
    );
    this.connection.onRequest(
      "autoModeSwitch.request",
      async (params) => await this.handleAutoModeSwitchRequest(params)
    );
    this.connection.onRequest(
      "hooks.invoke",
      async (params) => await this.handleHooksInvoke(params)
    );
    this.connection.onRequest(
      "systemMessage.transform",
      async (params) => await this.handleSystemMessageTransform(params)
    );
    const sessions = this.sessions;
    (0, import_rpc.registerClientSessionApiHandlers)(this.connection, (sessionId) => {
      const session = sessions.get(sessionId);
      if (!session) throw new Error(`No session found for sessionId: ${sessionId}`);
      return session.clientSessionApis;
    });
    this.connection.onClose(() => {
      this.state = "disconnected";
    });
    this.connection.onError((_error) => {
      this.state = "disconnected";
    });
  }
  handleSessionEventNotification(notification) {
    if (typeof notification !== "object" || !notification || !("sessionId" in notification) || typeof notification.sessionId !== "string" || !("event" in notification)) {
      return;
    }
    const session = this.sessions.get(notification.sessionId);
    if (session) {
      session._dispatchEvent(notification.event);
    }
  }
  handleSessionLifecycleNotification(notification) {
    if (typeof notification !== "object" || !notification || !("type" in notification) || typeof notification.type !== "string" || !("sessionId" in notification) || typeof notification.sessionId !== "string") {
      return;
    }
    const raw = notification;
    let metadata;
    if (raw.metadata && raw.metadata.startTime && raw.metadata.modifiedTime) {
      metadata = {
        startTime: new Date(raw.metadata.startTime),
        modifiedTime: new Date(raw.metadata.modifiedTime),
        summary: raw.metadata.summary
      };
    }
    const event = {
      type: raw.type,
      sessionId: raw.sessionId,
      metadata
    };
    const typedHandlers = this.typedLifecycleHandlers.get(event.type);
    if (typedHandlers) {
      for (const handler of typedHandlers) {
        try {
          handler(event);
        } catch {
        }
      }
    }
    for (const handler of this.sessionLifecycleHandlers) {
      try {
        handler(event);
      } catch {
      }
    }
  }
  async handleUserInputRequest(params) {
    if (!params || typeof params.sessionId !== "string" || typeof params.question !== "string") {
      throw new Error("Invalid user input request payload");
    }
    const session = this.sessions.get(params.sessionId);
    if (!session) {
      throw new Error(`Session not found: ${params.sessionId}`);
    }
    const result = await session._handleUserInputRequest({
      question: params.question,
      choices: params.choices,
      allowFreeform: params.allowFreeform
    });
    return result;
  }
  async handleExitPlanModeRequest(params) {
    if (!params || typeof params.sessionId !== "string" || typeof params.summary !== "string" || !Array.isArray(params.actions) || typeof params.recommendedAction !== "string") {
      throw new Error("Invalid exit plan mode request payload");
    }
    const session = this.sessions.get(params.sessionId);
    if (!session) {
      throw new Error(`Session not found: ${params.sessionId}`);
    }
    return await session._handleExitPlanModeRequest({
      summary: params.summary,
      planContent: params.planContent,
      actions: params.actions,
      recommendedAction: params.recommendedAction
    });
  }
  async handleAutoModeSwitchRequest(params) {
    if (!params || typeof params.sessionId !== "string") {
      throw new Error("Invalid auto mode switch request payload");
    }
    const session = this.sessions.get(params.sessionId);
    if (!session) {
      throw new Error(`Session not found: ${params.sessionId}`);
    }
    const response = await session._handleAutoModeSwitchRequest({
      errorCode: params.errorCode,
      retryAfterSeconds: params.retryAfterSeconds
    });
    return { response };
  }
  async handleHooksInvoke(params) {
    if (!params || typeof params.sessionId !== "string" || typeof params.hookType !== "string") {
      throw new Error("Invalid hooks invoke payload");
    }
    const session = this.sessions.get(params.sessionId);
    if (!session) {
      throw new Error(`Session not found: ${params.sessionId}`);
    }
    const output = await session._handleHooksInvoke(params.hookType, params.input);
    return { output };
  }
  async handleSystemMessageTransform(params) {
    if (!params || typeof params.sessionId !== "string" || !params.sections || typeof params.sections !== "object") {
      throw new Error("Invalid systemMessage.transform payload");
    }
    const session = this.sessions.get(params.sessionId);
    if (!session) {
      throw new Error(`Session not found: ${params.sessionId}`);
    }
    return await session._handleSystemMessageTransform(params.sections);
  }
}
// Annotate the CommonJS export names for ESM import in node:
0 && (module.exports = {
  CopilotClient
});
