'use strict';

var crypto$1 = require('crypto');
var fs = require('fs');
var path = require('path');

function _interopNamespace(e) {
  if (e && e.__esModule) return e;
  var n = Object.create(null);
  if (e) {
    Object.keys(e).forEach(function (k) {
      if (k !== 'default') {
        var d = Object.getOwnPropertyDescriptor(e, k);
        Object.defineProperty(n, k, d.get ? d : {
          enumerable: true,
          get: function () { return e[k]; }
        });
      }
    });
  }
  n.default = e;
  return Object.freeze(n);
}

var fs__namespace = /*#__PURE__*/_interopNamespace(fs);
var path__namespace = /*#__PURE__*/_interopNamespace(path);

// src/chat/server/utils.ts
var BodyParseError = class extends Error {
  statusCode;
  constructor(message, statusCode) {
    super(message);
    this.name = "BodyParseError";
    this.statusCode = statusCode;
  }
};
function readBody(req, maxSize = 1048576) {
  return new Promise((resolve2, reject) => {
    let body = "";
    let size = 0;
    let exceeded = false;
    req.on("data", (chunk) => {
      if (exceeded) return;
      const str = chunk.toString();
      size += Buffer.byteLength(str);
      if (size > maxSize) {
        exceeded = true;
        reject(new BodyParseError("Request body too large", 413));
        return;
      }
      body += str;
    });
    req.on("end", () => {
      if (exceeded) return;
      try {
        resolve2(JSON.parse(body || "{}"));
      } catch {
        reject(new BodyParseError("Invalid JSON in request body", 400));
      }
    });
    if ("once" in req && typeof req.once === "function") {
      req.once(
        "error",
        () => reject(new BodyParseError("Request error", 500))
      );
    }
  });
}
function json(res, data, status = 200) {
  res.writeHead(status, { "Content-Type": "application/json" });
  res.end(JSON.stringify(data));
}

// src/chat/server/routes/sessions.ts
var sessionRoutes = async (method, path2, req, res, ctx) => {
  const { runtime, maxBodySize } = ctx;
  const sessionMatch = path2.match(/^\/sessions\/([^/]+)$/);
  const contextStatsMatch = path2.match(/^\/sessions\/([^/]+)\/context-stats$/);
  if (method === "POST" && path2 === "/sessions/create") {
    const body = await readBody(req, maxBodySize);
    const session = await runtime.createSession({
      title: body.title || `Chat ${(/* @__PURE__ */ new Date()).toLocaleTimeString()}`,
      config: body.config || {
        model: "",
        backend: ""
      },
      ...body.tags ? { tags: body.tags } : {},
      ...body.custom ? { custom: body.custom } : {}
    });
    json(res, session);
    return true;
  }
  if (method === "GET" && contextStatsMatch) {
    const id = decodeURIComponent(contextStatsMatch[1]);
    const stats = await runtime.getContextStats(id);
    json(res, stats ?? null);
    return true;
  }
  if (method === "GET" && sessionMatch) {
    const id = decodeURIComponent(sessionMatch[1]);
    const session = await runtime.getSession(id);
    if (!session) {
      json(res, { error: "Not found" }, 404);
      return true;
    }
    json(res, session);
    return true;
  }
  if (method === "DELETE" && sessionMatch) {
    const id = decodeURIComponent(sessionMatch[1]);
    await runtime.deleteSession(id);
    json(res, { ok: true });
    return true;
  }
  if (method === "GET" && path2 === "/sessions") {
    const sessions = await runtime.listSessions();
    json(res, sessions);
    return true;
  }
  return false;
};

// src/chat/backends/transport.ts
var SSEChatTransport = class {
  res;
  _open;
  _heartbeatTimer;
  constructor(res, options) {
    this.res = res;
    this._open = true;
    res.writeHead(200, {
      "Content-Type": "text/event-stream",
      "Cache-Control": "no-cache",
      "Connection": "keep-alive",
      "X-Accel-Buffering": "no"
    });
    if (options?.request) {
      options.request.on("close", () => {
        this._cleanup();
      });
    }
    const heartbeatMs = options?.heartbeatMs;
    if (heartbeatMs && heartbeatMs > 0) {
      this._heartbeatTimer = setInterval(() => {
        if (!this.isOpen) {
          this._clearHeartbeat();
          return;
        }
        this.res.write(": heartbeat\n\n");
      }, heartbeatMs);
    }
  }
  get isOpen() {
    return this._open && !this.res.writableEnded;
  }
  send(event) {
    if (!this.isOpen) return;
    this.res.write(`data: ${JSON.stringify(event)}

`);
  }
  close() {
    if (!this.isOpen) return;
    this._open = false;
    this._clearHeartbeat();
    this.res.write(`data: [DONE]

`);
    this.res.end();
  }
  error(err) {
    if (!this.isOpen) return;
    this._open = false;
    this._clearHeartbeat();
    const errorEvent = {
      type: "error",
      error: err.message,
      recoverable: false
    };
    this.res.write(`data: ${JSON.stringify(errorEvent)}

`);
    this.res.end();
  }
  _cleanup() {
    this._open = false;
    this._clearHeartbeat();
  }
  _clearHeartbeat() {
    if (this._heartbeatTimer !== void 0) {
      clearInterval(this._heartbeatTimer);
      this._heartbeatTimer = void 0;
    }
  }
};
async function streamToTransport(events, transport) {
  try {
    let accumulatedText = "";
    for await (const event of events) {
      if (!transport.isOpen) break;
      transport.send(event);
      if (event.type === "message:delta") {
        accumulatedText += event.text;
      }
    }
    if (transport.isOpen) {
      transport.send({ type: "done", finalOutput: accumulatedText || void 0 });
    }
    transport.close();
  } catch (err) {
    transport.error(err instanceof Error ? err : new Error(String(err)));
  }
}

// src/errors.ts
var AgentSDKError = class extends Error {
  /** @internal Marker for cross-bundle identity checks */
  _agentSDKError = true;
  /** Machine-readable error code. Prefer values from the ErrorCode enum. */
  code;
  /** Whether this error is safe to retry */
  retryable;
  /** HTTP status code hint for error classification */
  httpStatus;
  constructor(message, options) {
    super(message, options);
    this.name = "AgentSDKError";
    this.code = options?.code;
    this.retryable = options?.retryable ?? false;
    this.httpStatus = options?.httpStatus;
  }
  /** Check if an error is an AgentSDKError (works across bundled copies) */
  static is(error) {
    return error instanceof Error && "_agentSDKError" in error && error._agentSDKError === true;
  }
};

// src/chat/errors.ts
var ChatError = class extends AgentSDKError {
  code;
  retryable;
  retryAfter;
  timestamp;
  constructor(message, options) {
    super(message, {
      cause: options.cause,
      code: options.code,
      retryable: options.retryable
    });
    this.name = "ChatError";
    this.code = options.code;
    this.retryable = options.retryable ?? false;
    this.retryAfter = options.retryAfter;
    this.timestamp = (/* @__PURE__ */ new Date()).toISOString();
  }
};

// src/chat/server/request-context.ts
async function resolveRequestContext(providerId, deps) {
  const provider = await deps.providerStore.get(providerId);
  if (!provider) {
    throw new ChatError(`Provider "${providerId}" not found`, {
      code: "PROVIDER_NOT_FOUND" /* PROVIDER_NOT_FOUND */
    });
  }
  const credentials = await deps.tokenStore.load(provider.backend);
  if (!credentials) {
    throw new ChatError(
      `Authentication required for backend "${provider.backend}"`,
      {
        code: "AUTH_REQUIRED" /* AUTH_REQUIRED */
      }
    );
  }
  return {
    backend: provider.backend,
    credentials,
    model: provider.model,
    provider
  };
}

// src/chat/server/routes/messages.ts
var messageRoutes = async (method, path2, req, res, ctx) => {
  const { runtime, maxBodySize, heartbeatMs, hooks, transportFactory } = ctx;
  if (method === "POST" && path2 === "/send") {
    const body = await readBody(req, maxBodySize);
    const sessionId = body.sessionId;
    const message = body.message || body.content;
    if (!sessionId || !message) {
      json(res, { error: "sessionId and message are required" }, 400);
      return true;
    }
    let model;
    let reqBackend;
    let reqCredentials;
    const hasProviderInfra = !!(ctx.providerStore && ctx.tokenStore);
    if (hasProviderInfra) {
      const providerId = body.providerId;
      if (!providerId || typeof providerId !== "string") {
        json(res, { error: "providerId is required" }, 400);
        return true;
      }
      try {
        const reqCtx = await resolveRequestContext(providerId, {
          providerStore: ctx.providerStore,
          tokenStore: ctx.tokenStore
        });
        model = reqCtx.model;
        reqBackend = reqCtx.backend;
        reqCredentials = reqCtx.credentials;
      } catch (err) {
        if (err instanceof ChatError && err.code === "PROVIDER_NOT_FOUND" /* PROVIDER_NOT_FOUND */) {
          json(res, { error: err.message }, 404);
          return true;
        }
        if (err instanceof ChatError && err.code === "AUTH_REQUIRED" /* AUTH_REQUIRED */) {
          json(res, { error: err.message }, 401);
          return true;
        }
        throw err;
      }
    }
    const bodyModel = body.model;
    if (hooks?.onModelSwitch && bodyModel && typeof bodyModel === "string") {
      try {
        await hooks.onModelSwitch(bodyModel);
      } catch (err) {
        json(res, { error: err instanceof Error ? err.message : String(err) }, 403);
        return true;
      }
    }
    if (hooks?.onBeforeSend) {
      try {
        await hooks.onBeforeSend(sessionId, message);
      } catch (err) {
        json(res, { error: err instanceof Error ? err.message : String(err) }, 403);
        return true;
      }
    }
    model = bodyModel || model;
    if (!model) {
      json(res, { error: "model is required (via body.model or providerId)" }, 400);
      return true;
    }
    const transport = transportFactory ? transportFactory(req, res) : new SSEChatTransport(res, {
      heartbeatMs,
      request: req
    });
    try {
      if (!reqBackend || !reqCredentials) {
        json(res, { error: "backend and credentials are required (configure providerStore + tokenStore)" }, 400);
        return true;
      }
      const opts = { model, backend: reqBackend, credentials: reqCredentials };
      const stream = runtime.send(sessionId, message, opts);
      await streamToTransport(stream, transport);
    } catch (err) {
      transport.error(err instanceof Error ? err : new Error(String(err)));
    }
    return true;
  }
  if (method === "POST" && path2 === "/abort") {
    runtime.abort();
    json(res, { ok: true });
    return true;
  }
  return false;
};

// src/chat/server/routes/config.ts
var configRoutes = async (method, path2, req, res, ctx) => {
  const { runtime, maxBodySize, hooks, providerStore } = ctx;
  if (method === "GET" && path2 === "/models") {
    let models = await runtime.listModels();
    if (models.length === 0 && providerStore && ctx.tokenStore) {
      const providers = await providerStore.list();
      for (const p of providers) {
        const token = await ctx.tokenStore.load(p.backend);
        if (token) {
          models = await runtime.listModels({ backend: p.backend, credentials: token });
          break;
        }
      }
    }
    if (hooks?.filterModels) models = hooks.filterModels(models);
    json(res, models);
    return true;
  }
  if (method === "GET" && path2 === "/backends") {
    const backends = await runtime.listBackends();
    json(res, backends);
    return true;
  }
  if (method === "POST" && path2 === "/model/switch") {
    const body = await readBody(req, maxBodySize);
    if (!body.model || typeof body.model !== "string") {
      json(res, { error: "model is required" }, 400);
      return true;
    }
    if (hooks?.onModelSwitch) {
      try {
        await hooks.onModelSwitch(body.model);
      } catch (err) {
        json(res, { error: err instanceof Error ? err.message : String(err) }, 403);
        return true;
      }
    }
    json(res, { ok: true });
    return true;
  }
  if (method === "POST" && path2 === "/provider/switch") {
    const body = await readBody(req, maxBodySize);
    if (!body.providerId || typeof body.providerId !== "string") {
      json(res, { error: "providerId is required" }, 400);
      return true;
    }
    if (!providerStore) {
      json(res, { error: "No provider store configured" }, 400);
      return true;
    }
    const provider = await providerStore.get(body.providerId);
    if (!provider) {
      json(res, { error: `Provider "${body.providerId}" not found` }, 404);
      return true;
    }
    if (hooks?.onProviderSwitch) {
      try {
        await hooks.onProviderSwitch({ providerId: body.providerId, backend: provider.backend });
      } catch (err) {
        json(res, { error: err instanceof Error ? err.message : String(err) }, 400);
        return true;
      }
    }
    json(res, { ok: true });
    return true;
  }
  return false;
};
var providerRoutes = async (method, path2, req, res, ctx) => {
  const { providerStore, maxBodySize } = ctx;
  if (!providerStore) return false;
  const idMatch = path2.match(/^\/providers\/([^/]+)$/);
  if (method === "GET" && path2 === "/providers") {
    const providers = await providerStore.list();
    json(res, providers);
    return true;
  }
  if (method === "GET" && idMatch) {
    const id = decodeURIComponent(idMatch[1]);
    const provider = await providerStore.get(id);
    if (!provider) {
      json(res, { error: "Provider not found" }, 404);
      return true;
    }
    json(res, provider);
    return true;
  }
  if (method === "POST" && path2 === "/providers") {
    const body = await readBody(req, maxBodySize);
    const backend = body.backend;
    const model = body.model;
    const label = body.label;
    if (!backend || typeof backend !== "string") {
      json(res, { error: "backend is required" }, 400);
      return true;
    }
    if (!model || typeof model !== "string") {
      json(res, { error: "model is required" }, 400);
      return true;
    }
    if (!label || typeof label !== "string") {
      json(res, { error: "label is required" }, 400);
      return true;
    }
    const config = {
      id: crypto$1.randomUUID(),
      backend,
      model,
      label,
      createdAt: Date.now()
    };
    await providerStore.create(config);
    json(res, config, 201);
    return true;
  }
  if (method === "PUT" && idMatch) {
    const id = decodeURIComponent(idMatch[1]);
    const existing = await providerStore.get(id);
    if (!existing) {
      json(res, { error: "Provider not found" }, 404);
      return true;
    }
    const body = await readBody(req, maxBodySize);
    const changes = {};
    if (body.backend && typeof body.backend === "string") changes.backend = body.backend;
    if (body.model && typeof body.model === "string") changes.model = body.model;
    if (body.label && typeof body.label === "string") changes.label = body.label;
    await providerStore.update(id, changes);
    const updated = await providerStore.get(id);
    json(res, updated);
    return true;
  }
  if (method === "DELETE" && idMatch) {
    const id = decodeURIComponent(idMatch[1]);
    await providerStore.delete(id);
    json(res, { ok: true });
    return true;
  }
  return false;
};

// src/chat/server/handler.ts
var ROUTE_PIPELINE = [
  sessionRoutes,
  messageRoutes,
  configRoutes,
  providerRoutes
];
function createChatHandler(runtime, options) {
  const prefix = options?.prefix ?? "";
  const state = {};
  const ctx = {
    runtime,
    maxBodySize: options?.maxBodySize ?? 1048576,
    heartbeatMs: options?.heartbeatMs,
    hooks: options?.hooks,
    providerStore: options?.providerStore,
    tokenStore: options?.tokenStore,
    transportFactory: options?.transportFactory,
    state
  };
  return async (req, res) => {
    const url = req.url || "";
    const method = req.method || "GET";
    const rawPath = prefix ? url.slice(prefix.length) : url;
    const path2 = rawPath.split("?")[0];
    try {
      for (const route of ROUTE_PIPELINE) {
        if (await route(method, path2, req, res, ctx)) return;
      }
      json(res, { error: "Not found" }, 404);
    } catch (err) {
      if (err instanceof BodyParseError) {
        json(res, { error: err.message }, err.statusCode);
      } else {
        const message = err instanceof Error ? err.message : String(err);
        if (ctx.hooks?.onError) {
          ctx.hooks.onError(err instanceof Error ? err : new Error(message), { route: path2, method });
        }
        json(res, { error: message }, 500);
      }
    }
  };
}

// src/chat/server/auth-handler.ts
function createAuthHandler(options) {
  const { tokenStore, onAuth } = options;
  const prefix = options.prefix ?? "";
  const maxBodySize = options.maxBodySize ?? 1048576;
  let pendingCopilot = null;
  let pendingClaude = null;
  return async (req, res) => {
    const url = req.url || "";
    const method = req.method || "GET";
    const rawPath = prefix ? url.slice(prefix.length) : url;
    const path2 = rawPath.split("?")[0];
    try {
      if (method === "POST" && path2 === "/auth/start") {
        const body = await readBody(req, maxBodySize);
        const provider = body.provider;
        if (!provider || !isValidProvider(provider)) {
          json(res, { error: "provider is required (copilot, claude, vercel-ai)" }, 400);
          return;
        }
        pendingCopilot = null;
        pendingClaude = null;
        if (provider === "copilot") {
          if (!options.createCopilotAuth) {
            json(res, { error: "Copilot auth not configured" }, 400);
            return;
          }
          const auth = options.createCopilotAuth();
          const flow = await auth.startDeviceFlow();
          pendingCopilot = { waitForToken: flow.waitForToken };
          json(res, { userCode: flow.userCode, verificationUrl: flow.verificationUrl });
          return;
        }
        if (provider === "claude") {
          if (!options.createClaudeAuth) {
            json(res, { error: "Claude auth not configured" }, 400);
            return;
          }
          const auth = options.createClaudeAuth();
          const flow = auth.startOAuthFlow();
          pendingClaude = { completeAuth: flow.completeAuth };
          json(res, { authorizeUrl: flow.authorizeUrl });
          return;
        }
        json(res, { ready: true });
        return;
      }
      if (method === "POST" && path2 === "/auth/copilot/poll") {
        if (!pendingCopilot) {
          json(res, { error: "No active Copilot flow" }, 400);
          return;
        }
        const token = await pendingCopilot.waitForToken();
        pendingCopilot = null;
        await tokenStore.save("copilot", token);
        if (onAuth) await onAuth("copilot", token);
        json(res, { ok: true, login: token.login });
        return;
      }
      if (method === "POST" && path2 === "/auth/claude/complete") {
        if (!pendingClaude) {
          json(res, { error: "No active Claude flow" }, 400);
          return;
        }
        const body = await readBody(req, maxBodySize);
        const code = body.code;
        if (!code || typeof code !== "string") {
          json(res, { error: "code is required" }, 400);
          return;
        }
        const token = await pendingClaude.completeAuth(code);
        pendingClaude = null;
        await tokenStore.save("claude", token);
        if (onAuth) await onAuth("claude", token);
        json(res, { ok: true });
        return;
      }
      if (method === "POST" && path2 === "/auth/vercel/complete") {
        const body = await readBody(req, maxBodySize);
        const apiKey = body.apiKey;
        if (!apiKey || typeof apiKey !== "string") {
          json(res, { error: "apiKey is required" }, 400);
          return;
        }
        const token = {
          accessToken: apiKey,
          tokenType: "bearer",
          obtainedAt: Date.now()
        };
        const storeToken = body.baseUrl ? { ...token, baseUrl: body.baseUrl } : token;
        await tokenStore.save("vercel-ai", storeToken);
        if (onAuth) await onAuth("vercel-ai", storeToken);
        json(res, { ok: true });
        return;
      }
      if (method === "GET" && path2 === "/tokens/saved") {
        const saved = await tokenStore.list();
        json(res, { saved });
        return;
      }
      if (method === "POST" && path2 === "/tokens/use") {
        const body = await readBody(req, maxBodySize);
        const provider = body.provider;
        if (!provider || !isValidProvider(provider)) {
          json(res, { error: "provider is required (copilot, claude, vercel-ai)" }, 400);
          return;
        }
        const token = await tokenStore.load(provider);
        if (!token) {
          json(res, { error: `No saved token for ${provider}` }, 404);
          return;
        }
        if (onAuth) await onAuth(provider, token);
        json(res, { ok: true, provider });
        return;
      }
      if (method === "POST" && path2 === "/tokens/clear") {
        await tokenStore.clearAll();
        if (options.onLogout) await options.onLogout();
        json(res, { ok: true });
        return;
      }
      if (method === "POST" && path2 === "/auth/dispose") {
        pendingCopilot = null;
        pendingClaude = null;
        if (options.onLogout) await options.onLogout();
        json(res, { ok: true });
        return;
      }
      json(res, { error: "Not found" }, 404);
    } catch (err) {
      if (err instanceof BodyParseError) {
        json(res, { error: err.message }, err.statusCode);
      } else {
        const message = err instanceof Error ? err.message : String(err);
        json(res, { error: message }, 500);
      }
    }
  };
}
function isValidProvider(p) {
  return p === "copilot" || p === "claude" || p === "vercel-ai";
}

// src/chat/server/cors.ts
function corsMiddleware(options) {
  const origin = options?.origin ?? "*";
  const methods = options?.methods ?? ["GET", "POST", "DELETE", "OPTIONS"];
  const headers = options?.headers ?? ["Content-Type"];
  const maxAge = options?.maxAge ?? 86400;
  const methodsValue = methods.join(", ");
  const headersValue = headers.join(", ");
  return (req, res) => {
    let originValue;
    if (Array.isArray(origin)) {
      const reqOrigin = typeof req.headers?.origin === "string" ? req.headers.origin : "";
      originValue = origin.includes(reqOrigin) ? reqOrigin : origin[0];
      res.setHeader("Vary", "Origin");
    } else {
      originValue = origin;
    }
    res.setHeader("Access-Control-Allow-Origin", originValue);
    res.setHeader("Access-Control-Allow-Methods", methodsValue);
    res.setHeader("Access-Control-Allow-Headers", headersValue);
    if (req.method === "OPTIONS") {
      res.setHeader("Access-Control-Max-Age", String(maxAge));
      res.writeHead(204);
      res.end();
      return true;
    }
    return false;
  };
}

// src/chat/types.ts
function createChatId() {
  return crypto.randomUUID();
}
var UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
function toChatId(value) {
  if (!UUID_RE.test(value)) {
    throw new TypeError(`Invalid ChatId: "${value}" is not a valid UUID`);
  }
  return value;
}

// src/chat/bridge.ts
function chatEventToAgentEvent(event) {
  switch (event.type) {
    case "message:delta":
      return { type: "text_delta", text: event.text };
    case "thinking:start":
      return { type: "thinking_start" };
    case "thinking:delta":
      return { type: "thinking_delta", text: event.text };
    case "thinking:end":
      return { type: "thinking_end" };
    case "tool:start":
      return {
        type: "tool_call_start",
        toolCallId: event.toolCallId,
        toolName: event.toolName,
        args: event.args
      };
    case "tool:complete":
      return {
        type: "tool_call_end",
        toolCallId: event.toolCallId,
        toolName: event.toolName,
        result: event.result
      };
    case "error":
      return { type: "error", error: event.error, recoverable: event.recoverable, code: event.code };
    default:
      return null;
  }
}

// src/chat/context.ts
function estimateTokens(message, options) {
  const ratio = options?.charsPerToken ?? 4;
  let charCount = 0;
  charCount += message.role.length + 4;
  for (const part of message.parts) {
    charCount += estimatePartChars(part);
  }
  return Math.ceil(charCount / ratio);
}
function estimatePartChars(part) {
  switch (part.type) {
    case "text":
      return part.text.length;
    case "reasoning":
      return part.text.length;
    case "tool_call":
      return JSON.stringify(part.args).length + part.name.length + 20 + (part.result !== void 0 ? JSON.stringify(part.result).length : 0);
    case "source":
      return (part.title?.length ?? 0) + part.url.length + 10;
    case "file":
      return part.name.length + part.data.length + 20;
  }
}
var ContextWindowManager = class {
  config;
  constructor(config) {
    this.config = {
      maxTokens: config.maxTokens,
      reservedTokens: config.reservedTokens ?? 0,
      strategy: config.strategy ?? "truncate-oldest",
      estimation: config.estimation,
      summarizer: config.summarizer
    };
  }
  /** Available token budget after reserving tokens */
  get availableBudget() {
    return Math.max(0, this.config.maxTokens - this.config.reservedTokens);
  }
  /**
   * Estimate tokens for a single message.
   * @param message - Message to estimate
   * @returns Estimated token count
   */
  estimateMessageTokens(message) {
    return estimateTokens(message, this.config.estimation);
  }
  /**
   * Fit messages within the token budget using the configured strategy.
   * @param messages - All messages to consider
   * @returns Result with fitted messages and metadata
   */
  fitMessages(messages) {
    if (messages.length === 0) {
      return { messages: [], totalTokens: 0, removedCount: 0, wasTruncated: false };
    }
    const budget = this.availableBudget;
    const tokenCounts = messages.map((m) => this.estimateMessageTokens(m));
    const totalTokens = tokenCounts.reduce((a, b) => a + b, 0);
    if (totalTokens <= budget) {
      return {
        messages: [...messages],
        totalTokens,
        removedCount: 0,
        wasTruncated: false
      };
    }
    switch (this.config.strategy) {
      case "truncate-oldest":
        return this.truncateOldest(messages, tokenCounts, budget);
      case "sliding-window":
        return this.slidingWindow(messages, tokenCounts, budget);
      case "summarize-placeholder":
        return this.summarizePlaceholder(messages, tokenCounts, budget);
    }
  }
  /**
   * Async variant of fitMessages that supports async summarization.
   * When strategy is "summarize-placeholder" and a summarizer is configured,
   * calls the summarizer with removed messages and replaces the placeholder text.
   * Falls back to static placeholder if summarizer throws.
   * For other strategies, behaves identically to fitMessages().
   */
  async fitMessagesAsync(messages) {
    const result = this.fitMessages(messages);
    if (this.config.strategy !== "summarize-placeholder" || !result.wasTruncated || !this.config.summarizer) {
      return result;
    }
    const keptIds = new Set(result.messages.map((m) => m.id));
    const removed = messages.filter((m) => !keptIds.has(m.id));
    if (removed.length === 0) return result;
    let summaryText;
    try {
      summaryText = await this.config.summarizer(removed);
    } catch {
      return result;
    }
    const updatedMessages = result.messages.map((m) => {
      if (m.metadata?.isSummary === true) {
        return {
          ...m,
          parts: [{ type: "text", text: summaryText, status: "complete" }]
        };
      }
      return m;
    });
    return { ...result, messages: updatedMessages };
  }
  /**
   * Trim messages using real token usage data from the previous API call.
   * Uses average-based algorithm: `avgTokensPerMessage = lastPromptTokens / messageCount`.
   * Removes oldest non-system messages until freed budget brings usage under modelContextWindow.
   *
   * @param messages - All messages in the session
   * @param lastPromptTokens - Real prompt tokens from the last API response
   * @param modelContextWindow - Model's total context window size in tokens
   * @returns Result with fitted messages and metadata
   */
  fitMessagesWithUsage(messages, lastPromptTokens, modelContextWindow) {
    if (messages.length === 0) {
      return { messages: [], totalTokens: 0, removedCount: 0, wasTruncated: false };
    }
    const budget = modelContextWindow - this.config.reservedTokens;
    if (budget <= 0 || lastPromptTokens <= budget) {
      return {
        messages: [...messages],
        totalTokens: lastPromptTokens,
        removedCount: 0,
        wasTruncated: false
      };
    }
    const avgTokensPerMessage = lastPromptTokens / messages.length;
    const tokensToFree = lastPromptTokens - budget;
    const messagesToRemove = Math.ceil(tokensToFree / avgTokensPerMessage);
    const nonSystemIndices = [];
    for (let i = 0; i < messages.length; i++) {
      if (messages[i].role === "system") ; else {
        nonSystemIndices.push(i);
      }
    }
    const removableCount = Math.min(messagesToRemove, nonSystemIndices.length);
    const removedIndices = new Set(nonSystemIndices.slice(0, removableCount));
    const result = [];
    for (let i = 0; i < messages.length; i++) {
      if (!removedIndices.has(i)) {
        result.push(messages[i]);
      }
    }
    const estimatedTokens = Math.round(
      lastPromptTokens * (result.length / messages.length)
    );
    return {
      messages: result,
      totalTokens: estimatedTokens,
      removedCount: removableCount,
      wasTruncated: removableCount > 0
    };
  }
  /**
   * Truncate oldest: keeps system messages, removes oldest non-system messages first.
   * Always keeps the most recent user message.
   */
  truncateOldest(messages, tokenCounts, budget) {
    const systemIndices = [];
    const nonSystemIndices = [];
    for (let i = 0; i < messages.length; i++) {
      if (messages[i].role === "system") {
        systemIndices.push(i);
      } else {
        nonSystemIndices.push(i);
      }
    }
    let usedTokens = systemIndices.reduce(
      (sum, i) => sum + tokenCounts[i],
      0
    );
    const includedNonSystem = [];
    for (let i = nonSystemIndices.length - 1; i >= 0; i--) {
      const idx = nonSystemIndices[i];
      if (usedTokens + tokenCounts[idx] <= budget) {
        includedNonSystem.unshift(idx);
        usedTokens += tokenCounts[idx];
      }
    }
    const includedSet = /* @__PURE__ */ new Set([...systemIndices, ...includedNonSystem]);
    const result = [];
    let resultTokens = 0;
    for (let i = 0; i < messages.length; i++) {
      if (includedSet.has(i)) {
        result.push(messages[i]);
        resultTokens += tokenCounts[i];
      }
    }
    return {
      messages: result,
      totalTokens: resultTokens,
      removedCount: messages.length - result.length,
      wasTruncated: true
    };
  }
  /**
   * Sliding window: keeps the most recent messages that fit within budget.
   */
  slidingWindow(messages, tokenCounts, budget) {
    const result = [];
    let usedTokens = 0;
    for (let i = messages.length - 1; i >= 0; i--) {
      if (usedTokens + tokenCounts[i] <= budget) {
        result.unshift(messages[i]);
        usedTokens += tokenCounts[i];
      } else {
        break;
      }
    }
    return {
      messages: result,
      totalTokens: usedTokens,
      removedCount: messages.length - result.length,
      wasTruncated: true
    };
  }
  /**
   * Summarize placeholder: replaces truncated messages with a placeholder,
   * preserving system messages and recent context.
   */
  summarizePlaceholder(messages, tokenCounts, budget) {
    const systemMessages = [];
    const nonSystem = [];
    for (let i = 0; i < messages.length; i++) {
      if (messages[i].role === "system") {
        systemMessages.push({ msg: messages[i], tokens: tokenCounts[i] });
      } else {
        nonSystem.push({ msg: messages[i], tokens: tokenCounts[i], idx: i });
      }
    }
    let usedTokens = systemMessages.reduce((s, m) => s + m.tokens, 0);
    const placeholderTokens = 20;
    usedTokens += placeholderTokens;
    const recentKept = [];
    for (let i = nonSystem.length - 1; i >= 0; i--) {
      if (usedTokens + nonSystem[i].tokens <= budget) {
        recentKept.unshift(nonSystem[i]);
        usedTokens += nonSystem[i].tokens;
      } else {
        break;
      }
    }
    const removedCount = messages.length - systemMessages.length - recentKept.length;
    const result = [];
    for (const sm of systemMessages) {
      result.push(sm.msg);
    }
    if (removedCount > 0) {
      result.push({
        id: "context-placeholder",
        role: "system",
        parts: [{ type: "text", text: `[${removedCount} earlier message${removedCount === 1 ? "" : "s"} omitted for context window]`, status: "complete" }],
        metadata: { isSummary: true },
        createdAt: (/* @__PURE__ */ new Date()).toISOString(),
        status: "complete"
      });
    }
    for (const m of recentKept) {
      result.push(m.msg);
    }
    return {
      messages: result,
      totalTokens: usedTokens,
      removedCount,
      wasTruncated: true
    };
  }
};

// src/chat/state.ts
var StateMachine = class {
  constructor(initial, transitions) {
    this.initial = initial;
    this.transitions = transitions;
    this._current = initial;
  }
  _current;
  /** Current state */
  get current() {
    return this._current;
  }
  /**
   * Check whether transitioning to `next` is allowed from current state
   * @param next - Target state to check
   * @returns True if transition is allowed
   */
  canTransition(next) {
    const allowed = this.transitions[this._current];
    return allowed !== void 0 && allowed.includes(next);
  }
  /**
   * Transition to `next` state.
   * @throws ChatError(INVALID_TRANSITION) if the transition is not allowed
   */
  transition(next) {
    if (!this.canTransition(next)) {
      throw new ChatError(
        `Invalid transition: ${this._current} \u2192 ${next}`,
        { code: "INVALID_TRANSITION" /* INVALID_TRANSITION */ }
      );
    }
    this._current = next;
  }
  /** Reset to initial state */
  reset() {
    this._current = this.initial;
  }
};
var RUNTIME_TRANSITIONS = {
  idle: ["streaming", "disposed"],
  streaming: ["idle", "error", "disposed"],
  error: ["idle", "disposed"],
  disposed: []
};
var ChatReentrancyGuard = class {
  _acquired = false;
  /** Whether the guard is currently held */
  get isAcquired() {
    return this._acquired;
  }
  /**
   * Acquire the guard. Throws if already acquired.
   * @throws ChatError with code REENTRANCY
   */
  acquire() {
    if (this._acquired) {
      throw new ChatError(
        "Concurrent operation detected: a send is already in progress",
        { code: "REENTRANCY" /* REENTRANCY */ }
      );
    }
    this._acquired = true;
  }
  /** Release the guard. Safe to call even if not acquired. */
  release() {
    this._acquired = false;
  }
};
var ChatAbortController = class {
  _controller;
  _onExternalAbort;
  _externalSignal;
  constructor(externalSignal) {
    this._controller = new AbortController();
    this._externalSignal = externalSignal;
    if (externalSignal) {
      if (externalSignal.aborted) {
        this._controller.abort(externalSignal.reason);
      } else {
        this._onExternalAbort = () => {
          this._controller.abort(externalSignal.reason);
        };
        externalSignal.addEventListener("abort", this._onExternalAbort, { once: true });
      }
    }
  }
  /** The AbortSignal for this controller */
  get signal() {
    return this._controller.signal;
  }
  /** Whether the operation has been aborted */
  get isAborted() {
    return this._controller.signal.aborted;
  }
  /**
   * Abort the operation.
   * @param reason - Optional abort reason
   */
  abort(reason) {
    this._controller.abort(reason);
  }
  /** Clean up external signal listener to prevent memory leaks */
  dispose() {
    if (this._onExternalAbort && this._externalSignal) {
      this._externalSignal.removeEventListener("abort", this._onExternalAbort);
    }
  }
};

// src/chat/accumulator.ts
var MessageAccumulator = class {
  messageId;
  parts = [];
  status = "pending";
  currentTextPart = null;
  currentReasoningPart = null;
  toolCallParts = /* @__PURE__ */ new Map();
  _finalized = false;
  constructor(messageId) {
    this.messageId = messageId ?? createChatId();
  }
  /** Get current message ID */
  get id() {
    return this.messageId;
  }
  /**
   * Apply an AgentEvent to accumulate into the message
   * @param event - AgentEvent to process
   * @throws Error if accumulator is already finalized
   */
  apply(event) {
    if (this._finalized) throw new Error("Cannot apply events to finalized accumulator");
    if (this.status === "pending") {
      this.status = "streaming";
    }
    switch (event.type) {
      case "text_delta":
        this.handleTextDelta(event.text);
        break;
      case "thinking_start":
        this.finalizeCurrentText();
        this.currentReasoningPart = { type: "reasoning", text: "", status: "streaming" };
        this.parts.push(this.currentReasoningPart);
        break;
      case "thinking_delta":
        if (this.currentReasoningPart) {
          this.currentReasoningPart.text += event.text;
        }
        break;
      case "thinking_end":
        if (this.currentReasoningPart) {
          this.currentReasoningPart.status = "complete";
          this.currentReasoningPart = null;
        }
        break;
      case "tool_call_start": {
        this.finalizeCurrentText();
        const toolPart = {
          type: "tool_call",
          toolCallId: event.toolCallId,
          name: event.toolName,
          args: event.args,
          status: "running"
        };
        this.toolCallParts.set(event.toolCallId, toolPart);
        this.parts.push(toolPart);
        break;
      }
      case "tool_call_end": {
        const existing = this.toolCallParts.get(event.toolCallId);
        if (existing) {
          existing.result = event.result;
          existing.status = "complete";
        }
        break;
      }
      case "error":
        this.status = "error";
        break;
    }
  }
  handleTextDelta(text) {
    if (!this.currentTextPart) {
      this.currentTextPart = { type: "text", text: "", status: "streaming" };
      this.parts.push(this.currentTextPart);
    }
    this.currentTextPart.text += text;
  }
  finalizeCurrentText() {
    if (this.currentTextPart) {
      this.currentTextPart.status = "complete";
      this.currentTextPart = null;
    }
  }
  /**
   * Get a snapshot of the current accumulated message (for streaming UI)
   * @returns ChatMessage with current parts and "streaming" status
   */
  snapshot() {
    const now = (/* @__PURE__ */ new Date()).toISOString();
    return {
      id: this.messageId,
      role: "assistant",
      parts: this.parts.map((p) => ({ ...p })),
      status: this.status === "pending" ? "pending" : "streaming",
      createdAt: now,
      updatedAt: now
    };
  }
  /**
   * Finalize the accumulator and return the complete ChatMessage
   * @returns Completed ChatMessage with all parts finalized
   * @throws Error if accumulator is already finalized
   */
  finalize() {
    if (this._finalized) throw new Error("Accumulator already finalized");
    this._finalized = true;
    this.finalizeCurrentText();
    if (this.currentReasoningPart) {
      this.currentReasoningPart.status = "complete";
      this.currentReasoningPart = null;
    }
    for (const [, toolPart] of this.toolCallParts) {
      if (toolPart.status === "running" || toolPart.status === "pending") {
        toolPart.status = "error";
      }
    }
    if (this.status !== "error" && this.status !== "cancelled") {
      this.status = "complete";
    }
    const now = (/* @__PURE__ */ new Date()).toISOString();
    return {
      id: this.messageId,
      role: "assistant",
      parts: this.parts,
      status: this.status,
      createdAt: now,
      updatedAt: now
    };
  }
  /** Check if the accumulator has been finalized */
  get finalized() {
    return this._finalized;
  }
};

// src/chat/watchdog.ts
async function* withStreamWatchdog(source, config) {
  const { timeoutMs, signal } = config;
  const iterator = source[Symbol.asyncIterator]();
  let aborted = false;
  if (signal?.aborted) {
    iterator.return?.();
    return;
  }
  const onAbort = () => {
    aborted = true;
    iterator.return?.();
  };
  signal?.addEventListener("abort", onAbort, { once: true });
  try {
    while (true) {
      if (aborted) break;
      const timeout = new CancellableTimeout(timeoutMs);
      try {
        const result = await Promise.race([
          iterator.next(),
          timeout.promise
        ]);
        timeout.cancel();
        if (result.done) break;
        yield result.value;
      } catch (err) {
        timeout.cancel();
        throw err;
      }
    }
  } finally {
    signal?.removeEventListener("abort", onAbort);
    iterator.return?.();
  }
}
var CancellableTimeout = class {
  promise;
  _timer;
  _cancelled = false;
  constructor(ms) {
    this.promise = new Promise((_, reject) => {
      this._timer = setTimeout(() => {
        if (!this._cancelled) {
          reject(
            new ChatError(
              `Stream timed out after ${ms}ms of inactivity`,
              { code: "TIMEOUT" /* TIMEOUT */ }
            )
          );
        }
      }, ms);
    });
    this.promise.catch(() => {
    });
  }
  cancel() {
    this._cancelled = true;
    if (this._timer !== void 0) {
      clearTimeout(this._timer);
      this._timer = void 0;
    }
  }
};

// src/chat/listener-set.ts
var ListenerSet = class {
  _listeners = /* @__PURE__ */ new Set();
  /** Add a listener. Returns an unsubscribe function. */
  add(callback) {
    this._listeners.add(callback);
    return () => {
      this._listeners.delete(callback);
    };
  }
  /** Notify all listeners with the given arguments. Errors are isolated per listener. */
  notify(...args) {
    for (const cb of this._listeners) {
      try {
        cb(...args);
      } catch {
      }
    }
  }
  /** Remove all listeners. */
  clear() {
    this._listeners.clear();
  }
  /** Current number of listeners. */
  get size() {
    return this._listeners.size;
  }
};

// src/chat/runtime.ts
var ChatRuntime = class {
  _state;
  _guard;
  _backends;
  _sessionStore;
  _contextConfig;
  _middleware;
  _tools = /* @__PURE__ */ new Map();
  _retryConfig;
  _contextStats = /* @__PURE__ */ new Map();
  _sessionUsage = /* @__PURE__ */ new Map();
  _modelContextWindows = /* @__PURE__ */ new Map();
  _onContextTrimmed;
  _streamTimeoutMs;
  _sessionListeners = new ListenerSet();
  _adapterPool = /* @__PURE__ */ new Map();
  _defaultBackend;
  _abortController = null;
  constructor(options) {
    this._state = new StateMachine("idle", RUNTIME_TRANSITIONS);
    this._guard = new ChatReentrancyGuard();
    this._backends = options.backends;
    this._defaultBackend = options.defaultBackend;
    this._sessionStore = options.sessionStore;
    this._contextConfig = options.context;
    this._middleware = [...options.middleware ?? []];
    this._retryConfig = options.retryConfig;
    this._onContextTrimmed = options.onContextTrimmed;
    this._streamTimeoutMs = options.streamTimeoutMs;
    if (!options.backends[options.defaultBackend]) {
      throw new ChatError(
        `Default backend "${options.defaultBackend}" not found in backends map`,
        { code: "INVALID_INPUT" /* INVALID_INPUT */ }
      );
    }
    if (options.tools) {
      for (const tool of options.tools) {
        this._tools.set(tool.name, tool);
      }
    }
  }
  // ── Lifecycle ──────────────────────────────────────────────
  get status() {
    return this._state.current;
  }
  async dispose() {
    if (this._state.current === "disposed") return;
    this._abortController?.abort("Runtime disposed");
    this._abortController?.dispose();
    this._abortController = null;
    this._state.transition("disposed");
    for (const adapter of this._adapterPool.values()) {
      try {
        await adapter.dispose();
      } catch {
      }
    }
    this._adapterPool.clear();
  }
  // ── Sessions ───────────────────────────────────────────────
  async createSession(options) {
    this.assertNotDisposed();
    const config = {
      model: options.config?.model ?? "",
      backend: options.config?.backend ?? this._defaultBackend,
      ...options.config
    };
    const session = await this._sessionStore.createSession({ ...options, config });
    this._notifySessionChange();
    return session;
  }
  async getSession(id) {
    this.assertNotDisposed();
    const cid = toChatId(id);
    return this._sessionStore.getSession(cid);
  }
  async listSessions(options) {
    this.assertNotDisposed();
    return this._sessionStore.listSessions(options);
  }
  async deleteSession(id) {
    this.assertNotDisposed();
    const cid = toChatId(id);
    const session = await this._sessionStore.getSession(cid);
    if (!session) return;
    await this._sessionStore.deleteSession(cid);
    this._contextStats.delete(cid);
    this._sessionUsage.delete(cid);
    this._notifySessionChange();
  }
  // ── Messaging ──────────────────────────────────────────────
  async *send(sessionId, message, options) {
    this.validateSendInput(message, options);
    this._guard.acquire();
    const cid = toChatId(sessionId);
    this._abortController = new ChatAbortController(options?.signal);
    try {
      if (this._state.current === "error") {
        this._state.transition("idle");
      }
      this._state.transition("streaming");
      await this.loadSession(cid);
      const mwCtx = {
        sessionId: cid,
        signal: this._abortController.signal
      };
      const userMessage = await this.applyBeforeSendMiddleware(
        this.createUserMessage(message),
        mwCtx
      );
      if (userMessage === null) {
        this._state.transition("idle");
        return;
      }
      const updatedSession = await this.persistAndReload(cid, userMessage);
      const sessionForAdapter = await this.trimSessionContext(cid, updatedSession, options.model);
      const stream = await this.prepareEventStream(
        cid,
        sessionForAdapter,
        updatedSession,
        message,
        options
      );
      const accumulator = new MessageAccumulator();
      const eventSource = this._streamTimeoutMs ? withStreamWatchdog(stream, { timeoutMs: this._streamTimeoutMs, signal: this._abortController.signal }) : stream;
      for await (const event of eventSource) {
        if (this._abortController.isAborted) break;
        this.feedAccumulator(accumulator, event);
        if (event.type === "usage") {
          this._sessionUsage.set(cid, {
            promptTokens: event.promptTokens,
            completionTokens: event.completionTokens
          });
          this.updateContextStatsWithUsage(cid, event.promptTokens, event.completionTokens, options);
        }
        const processed = await this.applyOnEventMiddleware(event, mwCtx);
        if (processed) yield processed;
      }
      if (this._state.current === "disposed") return;
      await this.finalizeAssistantMessage(cid, accumulator, mwCtx);
      this._state.transition("idle");
    } catch (error) {
      const result = await this.handleSendError(error, cid);
      if (result !== null) throw result;
    } finally {
      this._guard.release();
      this._abortController?.dispose();
      this._abortController = null;
    }
  }
  // ── Send Pipeline Stages ──────────────────────────────────────
  /** Stage 1: Validate send inputs (message content + required fields). */
  validateSendInput(message, options) {
    this.assertNotDisposed();
    if (!message || message.trim().length === 0) {
      throw new ChatError("Message cannot be empty", { code: "INVALID_INPUT" /* INVALID_INPUT */ });
    }
    if (!options.model) {
      throw new ChatError(
        "options.model is required \u2014 caller must specify which model to use",
        { code: "INVALID_INPUT" /* INVALID_INPUT */ }
      );
    }
    if (!options.backend) {
      throw new ChatError(
        "options.backend is required \u2014 caller must specify which backend to use",
        { code: "INVALID_INPUT" /* INVALID_INPUT */ }
      );
    }
    if (!options.credentials) {
      throw new ChatError(
        "options.credentials is required \u2014 caller must provide authentication credentials",
        { code: "INVALID_INPUT" /* INVALID_INPUT */ }
      );
    }
  }
  /** Stage 2: Load session from store. */
  async loadSession(cid) {
    const session = await this._sessionStore.getSession(cid);
    if (!session) {
      throw new ChatError(
        `Session "${cid}" not found`,
        { code: "SESSION_NOT_FOUND" /* SESSION_NOT_FOUND */ }
      );
    }
    return session;
  }
  /** Stage 3: Apply onBeforeSend middleware pipeline. Returns null if middleware rejected the send. */
  async applyBeforeSendMiddleware(userMessage, ctx) {
    let msg = userMessage;
    for (const mw of this._middleware) {
      if (mw.onBeforeSend && msg) {
        msg = await mw.onBeforeSend(msg, ctx);
        if (msg === null) return null;
      }
    }
    return msg;
  }
  /** Stage 4: Persist user message and reload session with full history. */
  async persistAndReload(cid, userMessage) {
    await this._sessionStore.appendMessage(cid, userMessage);
    return await this._sessionStore.getSession(cid);
  }
  /** Stage 5: Auto-trim context window if configured. Returns session snapshot for adapter. */
  async trimSessionContext(cid, session, model) {
    if (!this._contextConfig) return session;
    const ctxManager = new ContextWindowManager(this._contextConfig);
    const lastUsage = this._sessionUsage.get(cid);
    const modelContextWindow = model ? this._modelContextWindows.get(model) : void 0;
    if (lastUsage && modelContextWindow) {
      const result2 = ctxManager.fitMessagesWithUsage(
        session.messages,
        lastUsage.promptTokens,
        modelContextWindow
      );
      this._contextStats.set(cid, {
        totalTokens: result2.totalTokens,
        removedCount: result2.removedCount,
        wasTruncated: result2.wasTruncated,
        availableBudget: Math.max(0, modelContextWindow - result2.totalTokens),
        realPromptTokens: lastUsage.promptTokens,
        realCompletionTokens: lastUsage.completionTokens,
        modelContextWindow
      });
      if (result2.wasTruncated && this._onContextTrimmed) {
        const keptIds = new Set(result2.messages.map((m) => m.id));
        const removed = session.messages.filter((m) => !keptIds.has(m.id));
        if (removed.length > 0) {
          try {
            this._onContextTrimmed(cid, removed);
          } catch {
          }
        }
      }
      return { ...session, messages: result2.messages };
    }
    const result = await ctxManager.fitMessagesAsync(session.messages);
    this._contextStats.set(cid, {
      totalTokens: result.totalTokens,
      removedCount: result.removedCount,
      wasTruncated: result.wasTruncated,
      availableBudget: ctxManager.availableBudget,
      modelContextWindow
    });
    if (result.wasTruncated && this._onContextTrimmed) {
      const keptIds = new Set(result.messages.map((m) => m.id));
      const removed = session.messages.filter((m) => !keptIds.has(m.id));
      if (removed.length > 0) {
        try {
          this._onContextTrimmed(cid, removed);
        } catch {
        }
      }
    }
    return { ...session, messages: result.messages };
  }
  /** Update context stats with real usage data from a usage event. */
  updateContextStatsWithUsage(cid, promptTokens, completionTokens, options) {
    const modelContextWindow = options.model ? this._modelContextWindows.get(options.model) : void 0;
    const existing = this._contextStats.get(cid);
    this._contextStats.set(cid, {
      totalTokens: promptTokens,
      removedCount: existing?.removedCount ?? 0,
      wasTruncated: existing?.wasTruncated ?? false,
      availableBudget: modelContextWindow ? Math.max(0, modelContextWindow - promptTokens) : existing?.availableBudget ?? 0,
      realPromptTokens: promptTokens,
      realCompletionTokens: completionTokens,
      modelContextWindow
    });
  }
  /** Stage 6: Prepare event stream — adapter with retry, tool injection. */
  async prepareEventStream(cid, sessionForAdapter, fullSession, message, options) {
    const adapter = await this.getOrCreateAdapterWithRetry(options.backend, options.credentials);
    const runtimeTools = this._tools.size > 0 ? this.injectToolContext([...this._tools.values()], {
      sessionId: cid,
      custom: fullSession.metadata?.custom
    }) : void 0;
    const streamOptions = {
      signal: this._abortController.signal,
      model: options.model,
      systemPrompt: options.systemPrompt,
      tools: runtimeTools
    };
    return this.createStreamWithRetry(
      adapter,
      sessionForAdapter,
      message,
      streamOptions,
      options.backend,
      options.credentials
    );
  }
  /** Stage 7: Apply onEvent middleware pipeline (sequential transform/suppress). */
  async applyOnEventMiddleware(event, ctx) {
    let processed = event;
    for (const mw of this._middleware) {
      if (mw.onEvent && processed) {
        processed = await mw.onEvent(processed, ctx);
      }
    }
    return processed;
  }
  /** Stage 8: Finalize accumulator, apply afterReceive middleware, persist assistant message. */
  async finalizeAssistantMessage(cid, accumulator, ctx) {
    let assistantMessage = accumulator.finalize();
    for (const mw of this._middleware) {
      if (mw.onAfterReceive) {
        assistantMessage = await mw.onAfterReceive(assistantMessage, ctx);
      }
    }
    await this._sessionStore.appendMessage(cid, assistantMessage);
    this._notifySessionChange();
  }
  /** Stage 9: Error handling — apply onError middleware, transition state. Returns null if suppressed. */
  async handleSendError(error, cid) {
    let processedError = error instanceof Error ? error : new Error(String(error));
    const ctx = {
      sessionId: cid,
      signal: this._abortController?.signal ?? new AbortController().signal
    };
    for (const mw of this._middleware) {
      if (mw.onError) {
        const result = await mw.onError(processedError, ctx);
        if (result === null) {
          if (this._state.canTransition("idle")) {
            this._state.transition("idle");
          }
          return null;
        }
        processedError = result;
      }
    }
    if (this._state.canTransition("error")) {
      this._state.transition("error");
    }
    return processedError;
  }
  abort() {
    this._abortController?.abort("User abort");
  }
  // ── Backend / Model ────────────────────────────────────────
  async listModels(options) {
    this.assertNotDisposed();
    let models = [];
    const firstAdapter = [...this._adapterPool.values()][0];
    if (firstAdapter) {
      try {
        models = await firstAdapter.listModels();
      } catch {
        return [];
      }
    } else if (options?.backend && options?.credentials) {
      try {
        const adapter = await this.getOrCreateAdapter(options.backend, options.credentials);
        models = await adapter.listModels();
      } catch {
        return [];
      }
    }
    for (const model of models) {
      if (model.contextWindow != null) {
        this._modelContextWindows.set(model.id, model.contextWindow);
      }
    }
    return models;
  }
  async listBackends() {
    this.assertNotDisposed();
    return Object.keys(this._backends).map((name) => ({ name }));
  }
  // ── Tools ──────────────────────────────────────────────────
  get registeredTools() {
    return this._tools;
  }
  registerTool(tool) {
    this.assertNotDisposed();
    this._tools.set(tool.name, tool);
  }
  removeTool(name) {
    this.assertNotDisposed();
    this._tools.delete(name);
  }
  // ── Middleware ──────────────────────────────────────────────
  use(middleware) {
    this.assertNotDisposed();
    this._middleware.push(middleware);
  }
  removeMiddleware(middleware) {
    this.assertNotDisposed();
    const idx = this._middleware.indexOf(middleware);
    if (idx >= 0) this._middleware.splice(idx, 1);
  }
  // ── Context Stats ─────────────────────────────────────────
  async getContextStats(sessionId) {
    const cid = toChatId(sessionId);
    return this._contextStats.get(cid) ?? null;
  }
  // ── Session Subscription ──────────────────────────────────
  onSessionChange(callback) {
    return this._sessionListeners.add(callback);
  }
  _notifySessionChange() {
    this._sessionListeners.notify();
  }
  // ── Private Helpers ────────────────────────────────────────
  async getOrCreateAdapter(backend, credentials) {
    const key = this.getPoolKey(backend, credentials);
    const existing = this._adapterPool.get(key);
    if (existing) return existing;
    for (const [oldKey, oldAdapter] of this._adapterPool) {
      if (oldKey.startsWith(backend + ":")) {
        try {
          await oldAdapter.dispose();
        } catch {
        }
        this._adapterPool.delete(oldKey);
      }
    }
    const factory = this._backends[backend];
    if (!factory) {
      throw new ChatError(
        `Backend "${backend}" not found`,
        { code: "INVALID_INPUT" /* INVALID_INPUT */ }
      );
    }
    const adapter = await factory(credentials);
    this._adapterPool.set(key, adapter);
    return adapter;
  }
  getPoolKey(backend, credentials) {
    const token = credentials.accessToken;
    const hash = token.length > 16 ? token.slice(0, 8) + token.slice(-8) : token;
    return `${backend}:${hash}`;
  }
  /** Wrap each tool's execute to inject ToolContext as 2nd argument */
  injectToolContext(tools, context) {
    return tools.map((tool) => ({
      ...tool,
      execute: (params) => tool.execute(params, context)
    }));
  }
  /** Map ChatEvent to AgentEvent for MessageAccumulator */
  feedAccumulator(acc, event) {
    const agentEvent = chatEventToAgentEvent(event);
    if (agentEvent) acc.apply(agentEvent);
  }
  createUserMessage(text) {
    return {
      id: createChatId(),
      role: "user",
      parts: [{ type: "text", text, status: "complete" }],
      createdAt: (/* @__PURE__ */ new Date()).toISOString(),
      status: "complete"
    };
  }
  assertNotDisposed() {
    if (this._state.current === "disposed") {
      throw new ChatError(
        "Runtime is disposed",
        { code: "DISPOSED" /* DISPOSED */ }
      );
    }
  }
  /** Get or create adapter with retry on connection errors */
  async getOrCreateAdapterWithRetry(backend, credentials) {
    const maxAttempts = this._retryConfig?.maxAttempts ?? 1;
    const delayMs = this._retryConfig?.delayMs ?? 0;
    let lastError;
    for (let attempt = 1; attempt <= maxAttempts; attempt++) {
      try {
        return await this.getOrCreateAdapter(backend, credentials);
      } catch (err) {
        lastError = err instanceof Error ? err : new Error(String(err));
        if (attempt < maxAttempts) {
          const key = this.getPoolKey(backend, credentials);
          const old = this._adapterPool.get(key);
          if (old) {
            try {
              await old.dispose();
            } catch {
            }
          }
          this._adapterPool.delete(key);
          await delay(delayMs);
        }
      }
    }
    throw lastError;
  }
  /**
   * Create stream with retry for pre-stream connection errors.
   * Tries to get the first event from the stream; if that fails,
   * retries with a fresh adapter. Once first event is received,
   * the stream is committed (no more retries).
   */
  async createStreamWithRetry(adapter, session, message, options, backend, credentials) {
    const maxAttempts = this._retryConfig?.maxAttempts ?? 1;
    const delayMs = this._retryConfig?.delayMs ?? 0;
    let lastError;
    let currentAdapter = adapter;
    for (let attempt = 1; attempt <= maxAttempts; attempt++) {
      try {
        const stream = currentAdapter.streamMessage(session, message, options);
        const iterator = stream[Symbol.asyncIterator]();
        const first = await iterator.next();
        return (async function* () {
          if (!first.done) yield first.value;
          while (true) {
            const next = await iterator.next();
            if (next.done) break;
            yield next.value;
          }
        })();
      } catch (err) {
        lastError = err instanceof Error ? err : new Error(String(err));
        if (attempt < maxAttempts) {
          try {
            await currentAdapter.dispose();
          } catch {
          }
          const key = this.getPoolKey(backend, credentials);
          this._adapterPool.delete(key);
          await delay(delayMs);
          currentAdapter = await this.getOrCreateAdapter(backend, credentials);
        }
      }
    }
    throw lastError;
  }
};
function delay(ms) {
  return new Promise((resolve2) => setTimeout(resolve2, ms));
}
function createChatRuntime(options) {
  return new ChatRuntime(options);
}
var DEFAULT_PROVIDER_MODELS = {
  copilot: "gpt-5-mini",
  claude: "claude-sonnet-4-5-20250514",
  "vercel-ai": "gpt-4.1-mini"
};
var MIME_TYPES = {
  ".html": "text/html",
  ".css": "text/css",
  ".js": "application/javascript",
  ".mjs": "application/javascript",
  ".json": "application/json",
  ".png": "image/png",
  ".jpg": "image/jpeg",
  ".jpeg": "image/jpeg",
  ".gif": "image/gif",
  ".svg": "image/svg+xml",
  ".ico": "image/x-icon",
  ".woff": "font/woff",
  ".woff2": "font/woff2",
  ".ttf": "font/ttf",
  ".txt": "text/plain",
  ".map": "application/json"
};
function createChatServer(options) {
  const runtime = options.runtime ?? (options.runtimeConfig ? createChatRuntime(options.runtimeConfig) : (() => {
    throw new Error("Either `runtime` or `runtimeConfig` must be provided to createChatServer");
  })());
  const chatPrefix = options.chatPrefix ?? "/api/chat";
  const authPrefix = options.authPrefix ?? "/api/auth";
  const staticPrefix = options.staticPrefix ?? "/";
  const staticDir = options.staticDir ? path__namespace.resolve(options.staticDir) : void 0;
  const healthPath = options.healthPath !== false ? options.healthPath ?? "/api/health" : void 0;
  const authOptions = wrapAuthWithServiceManager(
    wrapAuthWithAutoProviders(options),
    options.serviceManager
  );
  const chatHandler = createChatHandler(runtime, {
    prefix: chatPrefix,
    providerStore: options.providers?.providerStore,
    hooks: options.hooks,
    ...options.chatHandlerOptions
  });
  const authHandler = authOptions ? createAuthHandler(authOptions) : void 0;
  const cors = options.cors !== false ? corsMiddleware(options.cors) : void 0;
  return async (req, res) => {
    const url = req.url || "/";
    const urlPath = url.split("?")[0];
    if (cors) {
      const corsReq = { method: req.method, headers: req.headers };
      const corsRes = {
        setHeader: (name, value) => res.setHeader(name, value),
        writeHead: (statusCode) => res.writeHead(statusCode, {}),
        end: () => res.end()
      };
      if (cors(corsReq, corsRes)) {
        return;
      }
    }
    if (healthPath && urlPath === healthPath) {
      json(res, { ok: true }, 200);
      return;
    }
    if (urlPath.startsWith(chatPrefix + "/") || urlPath === chatPrefix) {
      await chatHandler(req, res);
      return;
    }
    if (authHandler && (urlPath.startsWith(authPrefix + "/") || urlPath === authPrefix)) {
      const authReq = Object.create(req, {
        url: { value: url.replace(authPrefix, ""), enumerable: true }
      });
      await authHandler(authReq, res);
      return;
    }
    if (staticDir && req.method === "GET" && urlPath.startsWith(staticPrefix)) {
      const relativePath = urlPath === staticPrefix || urlPath === staticPrefix + "/" ? "/index.html" : urlPath.slice(staticPrefix.length) || "/index.html";
      const filePath = path__namespace.join(staticDir, relativePath);
      if (!filePath.startsWith(staticDir + path__namespace.sep) && filePath !== staticDir) {
        res.writeHead(403, { "Content-Type": "application/json" });
        res.end(JSON.stringify({ error: "Forbidden" }));
        return;
      }
      try {
        const stat = fs__namespace.statSync(filePath);
        if (stat.isFile()) {
          const ext = path__namespace.extname(filePath).toLowerCase();
          const contentType = MIME_TYPES[ext] || "application/octet-stream";
          const content = fs__namespace.readFileSync(filePath);
          res.writeHead(200, {
            "Content-Type": contentType,
            "Content-Length": String(content.length)
          });
          res.write(content.toString());
          res.end();
          return;
        }
      } catch {
      }
    }
    json(res, { error: "Not found" }, 404);
  };
}
function wrapAuthWithAutoProviders(options) {
  if (!options.auth) return void 0;
  if (!options.autoCreateProviders || !options.providers) return options.auth;
  const providerStore = options.providers.providerStore;
  const modelMap = typeof options.autoCreateProviders === "object" ? options.autoCreateProviders : DEFAULT_PROVIDER_MODELS;
  const userOnAuth = options.auth.onAuth;
  const wrappedOnAuth = async (backend, token) => {
    if (userOnAuth) await userOnAuth(backend, token);
    try {
      const existing = await providerStore.list();
      const hasBackend = existing.some((p) => p.backend === backend);
      if (!hasBackend) {
        const model = modelMap[backend] ?? "default";
        const label = `${backend.charAt(0).toUpperCase() + backend.slice(1)} ${model}`;
        await providerStore.create({
          id: crypto.randomUUID(),
          backend,
          model,
          label,
          createdAt: Date.now()
        });
      }
    } catch {
    }
  };
  return { ...options.auth, onAuth: wrappedOnAuth };
}
function wrapAuthWithServiceManager(authOptions, serviceManager) {
  if (!authOptions || !serviceManager) return authOptions;
  const userOnAuth = authOptions.onAuth;
  const userOnLogout = authOptions.onLogout;
  return {
    ...authOptions,
    onAuth: async (backend, token) => {
      if (userOnAuth) await userOnAuth(backend, token);
      await serviceManager.handleAuth(backend, token);
    },
    onLogout: async () => {
      if (userOnLogout) await userOnLogout();
      await serviceManager.handleLogout();
    }
  };
}
var InMemoryTokenStore = class {
  tokens = /* @__PURE__ */ new Map();
  async save(provider, token) {
    this.tokens.set(provider, { ...token });
  }
  async load(provider) {
    const t = this.tokens.get(provider);
    return t ? { ...t } : null;
  }
  async clear(provider) {
    this.tokens.delete(provider);
  }
  async clearAll() {
    this.tokens.clear();
  }
  async list() {
    return [...this.tokens.keys()];
  }
};
var FileTokenStore = class {
  dir;
  constructor(options) {
    this.dir = options.directory;
  }
  async save(provider, token) {
    fs.mkdirSync(this.dir, { recursive: true });
    fs.writeFileSync(this.filePath(provider), JSON.stringify(token));
  }
  async load(provider) {
    try {
      const data = fs.readFileSync(this.filePath(provider), "utf-8");
      return JSON.parse(data);
    } catch {
      return null;
    }
  }
  async clear(provider) {
    try {
      fs.unlinkSync(this.filePath(provider));
    } catch {
    }
  }
  async clearAll() {
    if (!fs.existsSync(this.dir)) return;
    for (const f of fs.readdirSync(this.dir)) {
      if (f.endsWith("-token.json")) {
        try {
          fs.unlinkSync(path.join(this.dir, f));
        } catch {
        }
      }
    }
  }
  async list() {
    if (!fs.existsSync(this.dir)) return [];
    return fs.readdirSync(this.dir).filter((f) => f.endsWith("-token.json")).map((f) => f.replace(/-token\.json$/, ""));
  }
  filePath(provider) {
    return path.join(this.dir, `${provider}-token.json`);
  }
};
function createProviderHandler(options) {
  const { providerStore } = options;
  return async (req, res) => {
    const url = req.url || "";
    const method = req.method || "GET";
    const path2 = url.split("?")[0];
    const idMatch = path2.match(/^\/providers\/([^/]+)$/);
    try {
      if (method === "GET" && path2 === "/providers") {
        const providers = await providerStore.list();
        json(res, providers);
        return;
      }
      if (method === "GET" && idMatch) {
        const id = decodeURIComponent(idMatch[1]);
        const provider = await providerStore.get(id);
        if (!provider) {
          json(res, { error: "Provider not found" }, 404);
          return;
        }
        json(res, provider);
        return;
      }
      if (method === "POST" && path2 === "/providers") {
        const body = await readBody(req);
        const backend = body.backend;
        const model = body.model;
        const label = body.label;
        if (!backend || typeof backend !== "string") {
          json(res, { error: "backend is required" }, 400);
          return;
        }
        if (!model || typeof model !== "string") {
          json(res, { error: "model is required" }, 400);
          return;
        }
        if (!label || typeof label !== "string") {
          json(res, { error: "label is required" }, 400);
          return;
        }
        const config = {
          id: crypto$1.randomUUID(),
          backend,
          model,
          label,
          createdAt: Date.now()
        };
        await providerStore.create(config);
        json(res, config, 201);
        return;
      }
      if (method === "PUT" && idMatch) {
        const id = decodeURIComponent(idMatch[1]);
        const existing = await providerStore.get(id);
        if (!existing) {
          json(res, { error: "Provider not found" }, 404);
          return;
        }
        const body = await readBody(req);
        const changes = {};
        if (body.backend && typeof body.backend === "string") changes.backend = body.backend;
        if (body.model && typeof body.model === "string") changes.model = body.model;
        if (body.label && typeof body.label === "string") changes.label = body.label;
        await providerStore.update(id, changes);
        const updated = await providerStore.get(id);
        json(res, updated);
        return;
      }
      if (method === "DELETE" && idMatch) {
        const id = decodeURIComponent(idMatch[1]);
        await providerStore.delete(id);
        json(res, { ok: true });
        return;
      }
      json(res, { error: "Not found" }, 404);
    } catch (err) {
      const message = err instanceof Error ? err.message : String(err);
      json(res, { error: message }, 500);
    }
  };
}
var InMemoryProviderStore = class {
  providers = /* @__PURE__ */ new Map();
  async create(config) {
    const id = config.id || crypto$1.randomUUID();
    this.providers.set(id, { ...config, id });
  }
  async get(id) {
    const p = this.providers.get(id);
    return p ? { ...p } : null;
  }
  async update(id, changes) {
    const existing = this.providers.get(id);
    if (!existing) {
      throw new Error(`Provider "${id}" not found`);
    }
    this.providers.set(id, { ...existing, ...changes, id: existing.id, createdAt: existing.createdAt });
  }
  async delete(id) {
    this.providers.delete(id);
  }
  async list() {
    return [...this.providers.values()].map((p) => ({ ...p }));
  }
};
var FileProviderStore = class {
  dir;
  constructor(options) {
    this.dir = options.directory;
  }
  async create(config) {
    const id = config.id || crypto$1.randomUUID();
    const data = { ...config, id };
    fs.mkdirSync(this.dir, { recursive: true });
    fs.writeFileSync(this.filePath(id), JSON.stringify(data));
  }
  async get(id) {
    try {
      const data = fs.readFileSync(this.filePath(id), "utf-8");
      return JSON.parse(data);
    } catch {
      return null;
    }
  }
  async update(id, changes) {
    const existing = await this.get(id);
    if (!existing) {
      throw new Error(`Provider "${id}" not found`);
    }
    const updated = { ...existing, ...changes, id: existing.id, createdAt: existing.createdAt };
    fs.writeFileSync(this.filePath(id), JSON.stringify(updated));
  }
  async delete(id) {
    try {
      fs.unlinkSync(this.filePath(id));
    } catch {
    }
  }
  async list() {
    if (!fs.existsSync(this.dir)) return [];
    return fs.readdirSync(this.dir).filter((f) => f.endsWith("-provider.json")).map((f) => {
      try {
        const data = fs.readFileSync(path.join(this.dir, f), "utf-8");
        return JSON.parse(data);
      } catch {
        return null;
      }
    }).filter((p) => p !== null);
  }
  filePath(id) {
    return path.join(this.dir, `${id}-provider.json`);
  }
};

// src/auth/refresh-manager.ts
var TokenRefreshManager = class {
  currentToken;
  refreshFn;
  threshold;
  maxRetries;
  retryDelayMs;
  minDelayMs;
  timerId = null;
  running = false;
  disposed = false;
  listeners = {
    refreshed: /* @__PURE__ */ new Set(),
    error: /* @__PURE__ */ new Set(),
    expired: /* @__PURE__ */ new Set(),
    disposed: /* @__PURE__ */ new Set()
  };
  constructor(options) {
    this.currentToken = { ...options.token };
    this.refreshFn = options.refresh;
    this.threshold = options.refreshThreshold ?? 0.8;
    this.maxRetries = options.maxRetries ?? 3;
    this.retryDelayMs = options.retryDelayMs ?? 1e3;
    this.minDelayMs = options.minDelayMs ?? 1e3;
  }
  /** Register an event listener */
  on(event, listener) {
    this.listeners[event].add(listener);
    return this;
  }
  /** Remove an event listener */
  off(event, listener) {
    this.listeners[event].delete(listener);
    return this;
  }
  /** Current token managed by this instance */
  get token() {
    return { ...this.currentToken };
  }
  /** Whether the manager is currently running */
  get isRunning() {
    return this.running;
  }
  /** Whether the manager has been disposed */
  get isDisposed() {
    return this.disposed;
  }
  /**
   * Start automatic refresh scheduling.
   * If the token is already expired, emits "expired" immediately.
   * If the token has no expiresIn, does nothing (long-lived token).
   */
  start() {
    if (this.disposed) return;
    if (this.running) return;
    this.running = true;
    this.schedule();
  }
  /** Stop automatic refresh (can be restarted with start()) */
  stop() {
    this.running = false;
    this.clearTimer();
  }
  /**
   * Update the managed token (e.g. after manual refresh).
   * Reschedules automatic refresh if running.
   */
  updateToken(token) {
    if (this.disposed) return;
    this.currentToken = { ...token };
    if (this.running) {
      this.clearTimer();
      this.schedule();
    }
  }
  /** Stop and clean up all resources */
  dispose() {
    if (this.disposed) return;
    this.stop();
    this.disposed = true;
    this.emit("disposed");
    for (const set of Object.values(this.listeners)) {
      set.clear();
    }
  }
  // ─── Private ──────────────────────────────────────────────────
  schedule() {
    if (!this.running || this.disposed) return;
    const delayMs = this.computeRefreshDelay();
    if (delayMs === null) {
      return;
    }
    if (delayMs <= 0) {
      this.timerId = setTimeout(() => {
        this.timerId = null;
        if (!this.running || this.disposed) return;
        void this.performRefresh();
      }, 0);
      return;
    }
    this.timerId = setTimeout(() => {
      this.timerId = null;
      if (!this.running || this.disposed) return;
      void this.performRefresh();
    }, Math.max(delayMs, this.minDelayMs));
  }
  async performRefresh(attempt = 1) {
    if (!this.running || this.disposed) return;
    try {
      const newToken = await this.refreshFn(this.currentToken);
      if (!this.running || this.disposed) return;
      this.currentToken = { ...newToken };
      this.emit("refreshed", newToken);
      this.schedule();
    } catch (err) {
      if (!this.running || this.disposed) return;
      const error = err instanceof Error ? err : new Error(String(err));
      this.emit("error", error, attempt);
      if (attempt < this.maxRetries) {
        const delay2 = this.retryDelayMs * Math.pow(2, attempt - 1);
        this.timerId = setTimeout(() => {
          this.timerId = null;
          if (!this.running || this.disposed) return;
          void this.performRefresh(attempt + 1);
        }, delay2);
      } else {
        if (this.isTokenExpired()) {
          this.running = false;
          this.emit("expired");
        } else {
          const expiresIn = this.currentToken.expiresIn;
          if (expiresIn == null) return;
          const expiresAt = this.currentToken.obtainedAt + expiresIn * 1e3;
          const waitMs = Math.max(expiresAt - Date.now(), this.minDelayMs);
          this.timerId = setTimeout(() => {
            this.timerId = null;
            if (!this.running || this.disposed) return;
            void this.performRefresh();
          }, waitMs);
        }
      }
    }
  }
  computeRefreshDelay() {
    if (this.currentToken.expiresIn == null) return null;
    const lifetimeMs = this.currentToken.expiresIn * 1e3;
    const refreshAtMs = this.currentToken.obtainedAt + lifetimeMs * this.threshold;
    const now = Date.now();
    const delay2 = refreshAtMs - now;
    return delay2;
  }
  isTokenExpired() {
    if (this.currentToken.expiresIn == null) return false;
    const expiresAt = this.currentToken.obtainedAt + this.currentToken.expiresIn * 1e3;
    return Date.now() >= expiresAt;
  }
  clearTimer() {
    if (this.timerId !== null) {
      clearTimeout(this.timerId);
      this.timerId = null;
    }
  }
  emit(event, ...args) {
    for (const listener of this.listeners[event]) {
      try {
        listener(...args);
      } catch {
      }
    }
  }
};

// src/chat/server/service-manager.ts
var ServiceManager = class {
  _services = /* @__PURE__ */ new Map();
  _refreshManagers = /* @__PURE__ */ new Map();
  _options;
  constructor(options) {
    this._options = options;
  }
  /**
   * Handle auth event: dispose old service (if any) and create new one.
   * If the token is refreshable and refreshFactory is configured, starts a
   * TokenRefreshManager that auto-refreshes and recreates the service.
   */
  async handleAuth(backend, token) {
    this._stopRefreshManager(backend);
    const old = this._services.get(backend);
    if (old) {
      try {
        await old.dispose();
      } catch {
      }
    }
    const service = await this._options.createService(backend, token);
    this._services.set(backend, service);
    this._startRefreshManager(backend, token);
    return service;
  }
  /**
   * Handle logout: dispose all services, stop all refresh managers, clear cache.
   */
  async handleLogout() {
    for (const backend of [...this._refreshManagers.keys()]) {
      this._stopRefreshManager(backend);
    }
    for (const [, service] of this._services) {
      try {
        await service.dispose();
      } catch {
      }
    }
    this._services.clear();
  }
  /**
   * Dispose the ServiceManager — stops all refresh managers and disposes all services.
   */
  async dispose() {
    await this.handleLogout();
  }
  /** Get cached service for a backend (undefined if not authenticated). */
  getService(backend) {
    return this._services.get(backend);
  }
  /** Check if a service exists for the given backend. */
  hasService(backend) {
    return this._services.has(backend);
  }
  /** Get all backend names with active services. */
  get activeBackends() {
    return [...this._services.keys()];
  }
  /** Get active refresh manager for a backend (for testing/introspection). */
  getRefreshManager(backend) {
    return this._refreshManagers.get(backend);
  }
  // ── Private ─────────────────────────────────────────────────
  _startRefreshManager(backend, token) {
    if (!this._options.refreshFactory) return;
    if (token.expiresIn == null) return;
    const refreshFn = this._options.refreshFactory(backend);
    if (!refreshFn) return;
    const manager = new TokenRefreshManager({
      token,
      refresh: refreshFn,
      refreshThreshold: this._options.refreshOptions?.refreshThreshold,
      maxRetries: this._options.refreshOptions?.maxRetries,
      retryDelayMs: this._options.refreshOptions?.retryDelayMs
    });
    manager.on("refreshed", (newToken) => {
      void this._recreateService(backend, newToken);
    });
    manager.on("expired", () => {
      this._options.onTokenExpired?.(backend);
      void this._logoutBackend(backend);
    });
    this._refreshManagers.set(backend, manager);
    manager.start();
  }
  _stopRefreshManager(backend) {
    const manager = this._refreshManagers.get(backend);
    if (manager) {
      manager.dispose();
      this._refreshManagers.delete(backend);
    }
  }
  async _recreateService(backend, token) {
    const old = this._services.get(backend);
    if (old) {
      try {
        await old.dispose();
      } catch {
      }
    }
    try {
      const service = await this._options.createService(backend, token);
      this._services.set(backend, service);
    } catch {
      this._services.delete(backend);
    }
  }
  async _logoutBackend(backend) {
    this._stopRefreshManager(backend);
    const service = this._services.get(backend);
    if (service) {
      try {
        await service.dispose();
      } catch {
      }
      this._services.delete(backend);
    }
  }
};

// src/chat/server/adapter-pool.ts
var AdapterPool = class {
  _cached = /* @__PURE__ */ new Map();
  _pending = /* @__PURE__ */ new Map();
  _factory;
  _disposed = false;
  constructor(options) {
    this._factory = options.factory;
  }
  /**
   * Get or create an adapter for the given backend.
   * Concurrent calls for the same backend share one creation promise.
   * Failed creations are NOT cached — next call retries.
   */
  async getAdapter(backend) {
    if (this._disposed) {
      throw new Error("AdapterPool is disposed");
    }
    const cached = this._cached.get(backend);
    if (cached) return cached;
    const pending = this._pending.get(backend);
    if (pending) return pending;
    const promise = this._create(backend);
    this._pending.set(backend, promise);
    try {
      const adapter = await promise;
      this._cached.set(backend, adapter);
      return adapter;
    } finally {
      this._pending.delete(backend);
    }
  }
  /**
   * Evict (dispose and remove) the cached adapter for a backend.
   * Use after token rotation to force re-creation on next getAdapter().
   */
  async evict(backend) {
    const cached = this._cached.get(backend);
    if (cached) {
      this._cached.delete(backend);
      try {
        await cached.dispose();
      } catch {
      }
    }
  }
  /** Check if a backend has a cached adapter. */
  has(backend) {
    return this._cached.has(backend);
  }
  /** Get all backend names with cached adapters. */
  get activeBackends() {
    return [...this._cached.keys()];
  }
  /** Dispose all cached adapters and mark pool as unusable. */
  async dispose() {
    this._disposed = true;
    const backends = [...this._cached.keys()];
    for (const backend of backends) {
      await this.evict(backend);
    }
  }
  async _create(backend) {
    return this._factory(backend);
  }
};

exports.AdapterPool = AdapterPool;
exports.BodyParseError = BodyParseError;
exports.DEFAULT_PROVIDER_MODELS = DEFAULT_PROVIDER_MODELS;
exports.FileProviderStore = FileProviderStore;
exports.FileTokenStore = FileTokenStore;
exports.InMemoryProviderStore = InMemoryProviderStore;
exports.InMemoryTokenStore = InMemoryTokenStore;
exports.ServiceManager = ServiceManager;
exports.configRoutes = configRoutes;
exports.corsMiddleware = corsMiddleware;
exports.createAuthHandler = createAuthHandler;
exports.createChatHandler = createChatHandler;
exports.createChatServer = createChatServer;
exports.createProviderHandler = createProviderHandler;
exports.json = json;
exports.messageRoutes = messageRoutes;
exports.providerRoutes = providerRoutes;
exports.readBody = readBody;
exports.resolveRequestContext = resolveRequestContext;
exports.sessionRoutes = sessionRoutes;
//# sourceMappingURL=server.cjs.map
//# sourceMappingURL=server.cjs.map