import {
  downloadIcon,
  findIconFile,
  isIconUrl,
  validateIconValue
} from "./chunk-7HDFEBAE.js";
import {
  __require
} from "./chunk-NIGTGIJC.js";
import {
  sanitizeProviderSourceTools
} from "./chunk-5N6QO6NC.js";

// ../packages/sources/dist/index.js
import { createHash, pbkdf2Sync, randomBytes, createCipheriv, createDecipheriv } from "crypto";
import { readFileSync as readFileSync2, writeFileSync, renameSync, unlinkSync, existsSync as existsSync2, mkdirSync } from "fs";
import { join, dirname } from "path";
import { homedir as homedir2 } from "os";
import { execSync } from "child_process";
import { readFileSync, existsSync } from "fs";
import { homedir, userInfo, platform } from "os";
import { existsSync as existsSync3, mkdirSync as mkdirSync2, readFileSync as readFileSync4, writeFileSync as writeFileSync2, readdirSync, rmSync } from "fs";
import { join as join3, basename } from "path";
import { randomUUID } from "crypto";
import { readFileSync as readFileSync3 } from "fs";
import { join as join2 } from "path";
function getBuiltinSources(_workspaceId, _workspaceRootPath) {
  return [];
}
function createSourceActivationPlan(options) {
  const requestedSourceSlugs = unique(options.requestedSourceSlugs);
  const states = new Map(options.sourceStates.map((source) => [source.sourceSlug, source]));
  const activeSourceSlugs = [];
  const authRequiredSourceSlugs = [];
  const missingSourceSlugs = [];
  const blockedSourceSlugs = [];
  const timelineItems = [];
  for (const sourceSlug of requestedSourceSlugs) {
    const state = states.get(sourceSlug) ?? {
      sourceSlug,
      enabled: false,
      authenticated: false,
      status: "missing"
    };
    if (state.enabled && state.authenticated && state.status === "active") {
      activeSourceSlugs.push(sourceSlug);
    } else if (state.enabled && !state.authenticated) {
      authRequiredSourceSlugs.push(sourceSlug);
    } else if (state.status === "missing") {
      missingSourceSlugs.push(sourceSlug);
    } else {
      blockedSourceSlugs.push(sourceSlug);
    }
    timelineItems.push({
      type: "source_state_changed",
      source: {
        sourceSlug: state.sourceSlug,
        status: state.status,
        enabled: state.enabled,
        authenticated: state.authenticated,
        ...state.reason ? { reason: state.reason } : {}
      }
    });
  }
  return {
    requestedSourceSlugs,
    activeSourceSlugs,
    authRequiredSourceSlugs,
    missingSourceSlugs,
    blockedSourceSlugs,
    timelineItems
  };
}
function unique(values) {
  return [...new Set(values)];
}
function createSourceToolAssemblyPlan(options) {
  const bySlug = new Map(options.sources.map((source) => [source.config.slug, source]));
  const tools = [];
  const blockedSourceSlugs = [];
  for (const sourceSlug of unique2(options.activeSourceSlugs)) {
    const source = bySlug.get(sourceSlug);
    if (!source) {
      blockedSourceSlugs.push(sourceSlug);
      continue;
    }
    const descriptor = descriptorFromSource(
      source,
      options.credentialRefs?.[sourceSlug],
      options.allowedStdioEnvKeys ?? []
    );
    if (descriptor) {
      tools.push(descriptor);
    } else {
      blockedSourceSlugs.push(sourceSlug);
    }
  }
  return { tools, blockedSourceSlugs };
}
function descriptorFromSource(source, credentialRef, allowedStdioEnvKeys) {
  const { config } = source;
  if (config.type === "api" && config.api) {
    return {
      kind: "api-source",
      sourceSlug: config.slug,
      baseUrl: config.api.baseUrl,
      authType: config.api.authType,
      ...headersOption("defaultHeaders", config.api.defaultHeaders),
      ...credentialRef ? { credentialRef } : {}
    };
  }
  if (config.type === "local" && config.local) {
    return {
      kind: "local-source",
      sourceSlug: config.slug,
      path: config.local.path,
      ...config.local.format ? { format: config.local.format } : {}
    };
  }
  if (config.type === "mcp" && config.mcp) {
    const transport = config.mcp.transport ?? "http";
    return {
      kind: "mcp-server",
      sourceSlug: config.slug,
      transport,
      ...config.mcp.url ? { url: config.mcp.url } : {},
      ...config.mcp.command ? { command: config.mcp.command } : {},
      ...config.mcp.args ? { args: [...config.mcp.args] } : {},
      ...transport === "stdio" ? { env: scrubEnv(config.mcp.env, allowedStdioEnvKeys) } : {},
      ...transport !== "stdio" ? headersOption("headers", config.mcp.headers) : {},
      ...credentialRef ? { credentialRef } : {}
    };
  }
  return void 0;
}
function scrubEnv(env, allowedKeys) {
  if (!env) return void 0;
  const allowed = new Set(allowedKeys);
  const scrubbed = Object.fromEntries(
    Object.entries(env).filter(([key]) => allowed.has(key))
  );
  return Object.keys(scrubbed).length > 0 ? scrubbed : void 0;
}
function headersOption(key, headers) {
  const scrubbed = scrubCredentialHeaders(headers);
  return scrubbed ? { [key]: scrubbed } : {};
}
function scrubCredentialHeaders(headers) {
  if (!headers) return void 0;
  const scrubbed = Object.fromEntries(
    Object.entries(headers).filter(([key]) => !isCredentialHeader(key))
  );
  return Object.keys(scrubbed).length > 0 ? scrubbed : void 0;
}
function isCredentialHeader(key) {
  const normalized = key.toLowerCase();
  return normalized === "authorization" || normalized === "proxy-authorization" || normalized === "cookie" || normalized === "set-cookie" || normalized.includes("api-key") || normalized.includes("apikey") || normalized.includes("token") || normalized.includes("secret");
}
function unique2(values) {
  return [...new Set(values)];
}
function createSourceRuntimeAssemblyPlan(options) {
  const activation = createSourceActivationPlan({
    requestedSourceSlugs: options.requestedSourceSlugs,
    sourceStates: options.sourceStates
  });
  const toolAssembly = createSourceToolAssemblyPlan({
    activeSourceSlugs: activation.activeSourceSlugs,
    sources: options.sources,
    credentialRefs: options.credentialRefs,
    allowedStdioEnvKeys: options.allowedStdioEnvKeys
  });
  return {
    requestedSourceSlugs: activation.requestedSourceSlugs,
    activeSourceSlugs: activation.activeSourceSlugs,
    authRequiredSourceSlugs: activation.authRequiredSourceSlugs,
    missingSourceSlugs: activation.missingSourceSlugs,
    blockedSourceSlugs: unique3([
      ...activation.blockedSourceSlugs,
      ...toolAssembly.blockedSourceSlugs
    ]),
    toolBlockedSourceSlugs: toolAssembly.blockedSourceSlugs,
    sourceTools: toolAssembly.tools,
    timelineItems: activation.timelineItems
  };
}
function unique3(values) {
  return [...new Set(values)];
}
function createSourceRuntimeProviderOptions(options) {
  const plan = createSourceRuntimeAssemblyPlan(options);
  const sourceTools = sanitizeProviderSourceTools(
    scrubProviderCredentialHeaders(plan.sourceTools)
  );
  return {
    extensions: {
      sources: {
        enabledSourceSlugs: plan.activeSourceSlugs
      }
    },
    sourceTools,
    timelineItems: plan.timelineItems,
    capabilityDegradation: {
      authRequiredSourceSlugs: plan.authRequiredSourceSlugs,
      missingSourceSlugs: plan.missingSourceSlugs,
      blockedSourceSlugs: plan.blockedSourceSlugs,
      toolBlockedSourceSlugs: plan.toolBlockedSourceSlugs
    }
  };
}
function scrubProviderCredentialHeaders(sourceTools) {
  return sourceTools.map((sourceTool) => {
    if (sourceTool.kind === "api-source" && sourceTool.defaultHeaders) {
      const defaultHeaders = scrubCredentialHeaders2(sourceTool.defaultHeaders);
      return {
        ...sourceTool,
        ...Object.keys(defaultHeaders).length > 0 ? { defaultHeaders } : { defaultHeaders: void 0 }
      };
    }
    if (sourceTool.kind === "mcp-server" && sourceTool.headers) {
      const headers = scrubCredentialHeaders2(sourceTool.headers);
      return {
        ...sourceTool,
        ...Object.keys(headers).length > 0 ? { headers } : { headers: void 0 }
      };
    }
    return sourceTool;
  });
}
function scrubCredentialHeaders2(headers) {
  const scrubbed = {};
  for (const [key, value] of Object.entries(headers)) {
    if (isCredentialHeader2(key)) continue;
    scrubbed[key] = value;
  }
  return scrubbed;
}
function isCredentialHeader2(key) {
  const normalized = key.toLowerCase();
  return normalized === "authorization" || normalized === "proxy-authorization" || normalized === "cookie" || normalized === "set-cookie" || normalized.includes("api-key") || normalized.includes("apikey") || normalized.includes("token") || normalized.includes("secret");
}
var SOURCE_TYPES = /* @__PURE__ */ new Set(["api", "mcp", "local"]);
var API_AUTH_TYPES = /* @__PURE__ */ new Set(["bearer", "header", "query", "basic", "oauth", "none"]);
var MCP_TRANSPORTS = /* @__PURE__ */ new Set(["http", "sse", "stdio"]);
var CREDENTIAL_HEADER_NAMES = /* @__PURE__ */ new Set(["authorization", "x-api-key", "api-key", "x-auth-token"]);
function validateSourceConfig(config) {
  const errors = [];
  const warnings = [];
  const record = asRecord(config);
  const file = sourceConfigFile(record);
  if (!record) {
    errors.push(issue(file, "", "Source config must be an object", "error"));
    return result(errors, warnings);
  }
  validateRequiredString(record, "id", file, errors);
  validateRequiredString(record, "name", file, errors);
  validateRequiredString(record, "slug", file, errors);
  validateRequiredString(record, "provider", file, errors);
  if (typeof record.enabled !== "boolean") {
    errors.push(issue(file, "enabled", "Source config requires boolean enabled", "error"));
  }
  if (!SOURCE_TYPES.has(record.type)) {
    errors.push(issue(file, "type", `Invalid source type: ${String(record.type)}`, "error", "Use one of: api, mcp, local."));
    return result(errors, warnings);
  }
  if (record.type === "api") validateApiSource(record, file, errors, warnings);
  if (record.type === "mcp") validateMcpSource(record, file, errors);
  if (record.type === "local") validateLocalSource(record, file, errors);
  return result(errors, warnings);
}
function validateApiSource(config, file, errors, warnings) {
  if (!config.api || typeof config.api !== "object") {
    errors.push(issue(file, "api", "API sources require an api config block", "error"));
    return;
  }
  if (!isNonEmptyString(config.api.baseUrl) || !isValidUrl(config.api.baseUrl)) {
    errors.push(issue(file, "api.baseUrl", "API sources require a valid baseUrl", "error"));
  }
  if (!API_AUTH_TYPES.has(config.api.authType)) {
    errors.push(issue(file, "api.authType", `Invalid API authType: ${String(config.api.authType)}`, "error"));
  }
  for (const key of Object.keys(config.api.defaultHeaders ?? {})) {
    if (CREDENTIAL_HEADER_NAMES.has(key.toLowerCase())) {
      warnings.push(issue(
        file,
        `api.defaultHeaders.${key}`,
        "Source config should not store credential-bearing headers",
        "warning",
        "Store secret values in the source credential gateway and keep only credentialRef metadata in runtime descriptors."
      ));
    }
  }
}
function validateMcpSource(config, file, errors) {
  if (!config.mcp || typeof config.mcp !== "object") {
    errors.push(issue(file, "mcp", "MCP sources require an mcp config block", "error"));
    return;
  }
  const transport = config.mcp.transport ?? "http";
  if (!MCP_TRANSPORTS.has(transport)) {
    errors.push(issue(file, "mcp.transport", `Invalid MCP transport: ${String(transport)}`, "error"));
    return;
  }
  if (transport === "stdio") {
    if (!isNonEmptyString(config.mcp.command)) {
      errors.push(issue(
        file,
        "mcp.command",
        "Stdio MCP sources require a command",
        "error",
        "Set mcp.command to the executable used to start the MCP server."
      ));
    }
    return;
  }
  if (!isNonEmptyString(config.mcp.url) || !isValidUrl(config.mcp.url)) {
    errors.push(issue(file, "mcp.url", "HTTP/SSE MCP sources require a valid url", "error"));
  }
}
function validateLocalSource(config, file, errors) {
  if (!config.local || typeof config.local !== "object") {
    errors.push(issue(file, "local", "Local sources require a local config block", "error"));
    return;
  }
  if (!isNonEmptyString(config.local.path)) {
    errors.push(issue(file, "local.path", "Local sources require a path", "error"));
  }
}
function validateRequiredString(record, key, file, errors) {
  if (!isNonEmptyString(record[key])) {
    errors.push(issue(file, key, `Source config requires ${key}`, "error"));
  }
}
function sourceConfigFile(record) {
  const slug = typeof record?.slug === "string" && record.slug.length > 0 ? record.slug : "<unknown>";
  return `sources/${slug}/config.json`;
}
function issue(file, path, message, severity, suggestion) {
  return {
    file,
    path,
    message,
    severity,
    ...suggestion ? { suggestion } : {}
  };
}
function result(errors, warnings) {
  return {
    valid: errors.length === 0,
    errors,
    warnings
  };
}
function asRecord(value) {
  return value && typeof value === "object" && !Array.isArray(value) ? value : void 0;
}
function isNonEmptyString(value) {
  return typeof value === "string" && value.trim().length > 0;
}
function isValidUrl(value) {
  try {
    new URL(value);
    return true;
  } catch {
    return false;
  }
}
function providerAuthToSourceStatus(detection) {
  if (detection.mode === "provider-owned") {
    return {
      mode: detection.mode,
      configured: detection.configured,
      description: detection.configured ? `Provider auth configured (${detection.method ?? "unknown method"})` : detection.error ?? "Provider auth not configured"
    };
  }
  if (detection.mode === "managed") {
    return {
      mode: detection.mode,
      configured: detection.configured,
      description: detection.configured ? `Credentials available via env vars (${detection.method ?? "unknown method"})` : detection.error ?? "No credentials found in env vars"
    };
  }
  return {
    mode: detection.mode,
    configured: true,
    description: "No auth required"
  };
}
var StubSourceOAuthProvider = class {
  async authenticate() {
    return {
      success: false,
      error: "Source OAuth not available \u2014 no host OAuthProvider injected. The host application must provide a SourceOAuthProvider implementation."
    };
  }
  async refresh() {
    return {
      success: false,
      error: "Source OAuth refresh not available \u2014 no host OAuthProvider injected."
    };
  }
};
function inferGoogleServiceFromUrl(baseUrl) {
  if (!baseUrl) return void 0;
  let hostname;
  let pathname;
  try {
    const parsed = new URL(baseUrl);
    hostname = parsed.hostname.toLowerCase();
    pathname = parsed.pathname.toLowerCase();
  } catch {
    return void 0;
  }
  if (hostname === "calendar.googleapis.com") return "calendar";
  if (hostname === "drive.googleapis.com") return "drive";
  if (hostname === "gmail.googleapis.com") return "gmail";
  if (hostname === "docs.googleapis.com") return "docs";
  if (hostname === "sheets.googleapis.com") return "sheets";
  if (hostname === "youtube.googleapis.com") return "youtube";
  if (hostname === "searchconsole.googleapis.com" || hostname === "webmasters.googleapis.com") return "searchconsole";
  if (hostname === "www.googleapis.com" || hostname === "googleapis.com") {
    if (pathname.startsWith("/calendar/")) return "calendar";
    if (pathname.startsWith("/drive/")) return "drive";
    if (pathname.startsWith("/gmail/")) return "gmail";
    if (pathname.startsWith("/v1/documents") || pathname.startsWith("/documents/")) return "docs";
    if (pathname.startsWith("/v4/spreadsheets") || pathname.startsWith("/spreadsheets/")) return "sheets";
    if (pathname.startsWith("/youtube/")) return "youtube";
    if (pathname.startsWith("/webmasters/")) return "searchconsole";
  }
  return void 0;
}
function inferSlackServiceFromUrl(baseUrl) {
  if (!baseUrl) return void 0;
  let hostname;
  try {
    const parsed = new URL(baseUrl);
    hostname = parsed.hostname.toLowerCase();
  } catch {
    return void 0;
  }
  if (hostname === "slack.com" || hostname === "api.slack.com") {
    return "full";
  }
  return void 0;
}
function inferMicrosoftServiceFromUrl(baseUrl) {
  if (!baseUrl) return void 0;
  let hostname;
  let pathname;
  try {
    const parsed = new URL(baseUrl);
    hostname = parsed.hostname.toLowerCase();
    pathname = parsed.pathname.toLowerCase();
  } catch {
    return void 0;
  }
  if (hostname === "graph.microsoft.com") {
    if (pathname.includes("/me/messages") || pathname.includes("/me/mailfolders") || pathname.includes("/mail")) {
      return "outlook";
    }
    if (pathname.includes("/me/calendar") || pathname.includes("/me/events")) {
      return "microsoft-calendar";
    }
    if (pathname.includes("/me/drive") || pathname.includes("/drives")) {
      return "onedrive";
    }
    if (pathname.includes("/teams") || pathname.includes("/chats")) {
      return "teams";
    }
    if (pathname.includes("/sites")) {
      return "sharepoint";
    }
    return void 0;
  }
  if (hostname === "outlook.office.com" || hostname === "outlook.office365.com") {
    return "outlook";
  }
  return void 0;
}
var API_OAUTH_PROVIDERS = ["google", "microsoft", "slack"];
function isApiOAuthProvider(provider) {
  return API_OAUTH_PROVIDERS.includes(provider);
}
function hasRenewEndpoint(source) {
  return source.config.type === "api" && !!source.config.api?.renewEndpoint?.path;
}
function debug(message, ...args) {
  console.debug(`[sources] ${message}`, ...args);
}
var MAX_DOWNLOAD_SIZE = 50 * 1024 * 1024;
function buildAuthorizationHeader(authScheme, token) {
  const scheme = authScheme ?? "Bearer";
  return scheme ? `${scheme} ${token}` : token;
}
function getMachineId() {
  const os = platform();
  try {
    if (os === "darwin") {
      const out = execSync(
        "ioreg -rd1 -c IOPlatformExpertDevice | grep IOPlatformUUID",
        { encoding: "utf-8", timeout: 3e3 }
      );
      const match = out.match(/"IOPlatformUUID"\s*=\s*"([^"]+)"/);
      if (match?.[1]) return match[1];
    }
    if (os === "linux") {
      for (const path of ["/var/lib/dbus/machine-id", "/etc/machine-id"]) {
        if (existsSync(path)) {
          const id = readFileSync(path, "utf-8").trim();
          if (id.length > 0) return id;
        }
      }
    }
    if (os === "win32") {
      const out = execSync(
        "reg query HKLM\\SOFTWARE\\Microsoft\\Cryptography /v MachineGuid",
        { encoding: "utf-8", timeout: 3e3 }
      );
      const match = out.match(/MachineGuid\s+REG_SZ\s+(.+)/);
      if (match?.[1]) return match[1].trim();
    }
  } catch {
  }
  const info = userInfo();
  return `${info.username}:${homedir()}`;
}
var MAGIC = Buffer.from("WEFT01\0\0");
var HEADER_SIZE = 64;
var SALT_SIZE = 32;
var IV_SIZE = 12;
var TAG_SIZE = 16;
var PBKDF2_ITERATIONS = 1e5;
var KEY_LENGTH = 32;
var DEFAULT_PATH = join(homedir2(), ".weft", "credentials.enc");
var EncryptedCredentialBackend = class {
  filePath;
  cache = null;
  constructor(filePath) {
    this.filePath = filePath ?? DEFAULT_PATH;
  }
  async get(id) {
    const store = this.load();
    return store[credentialKey(id)] ?? null;
  }
  async set(id, credential) {
    const store = this.load();
    store[credentialKey(id)] = credential;
    this.save(store);
  }
  async delete(id) {
    const store = this.load();
    const key = credentialKey(id);
    if (!(key in store)) return false;
    delete store[key];
    this.save(store);
    return true;
  }
  async list(filter) {
    const store = this.load();
    return Object.keys(store).map(parseCredentialKey).filter((id) => {
      if (!id) return false;
      if (filter?.type && id.type !== filter.type) return false;
      if (filter?.workspaceId && id.workspaceId !== filter.workspaceId) return false;
      if (filter?.sourceId && id.sourceId !== filter.sourceId) return false;
      return true;
    });
  }
  load() {
    if (this.cache) return this.cache;
    if (!existsSync2(this.filePath)) {
      this.cache = {};
      return this.cache;
    }
    const buf = readFileSync2(this.filePath);
    if (buf.length < HEADER_SIZE + IV_SIZE + TAG_SIZE) {
      this.cache = {};
      return this.cache;
    }
    const magic = buf.subarray(0, MAGIC.length);
    if (!magic.equals(MAGIC)) {
      throw new Error("Corrupt credential file: invalid magic bytes");
    }
    const salt = buf.subarray(12, 12 + SALT_SIZE);
    const payload = buf.subarray(HEADER_SIZE);
    const iv = payload.subarray(0, IV_SIZE);
    const tag = payload.subarray(IV_SIZE, IV_SIZE + TAG_SIZE);
    const ciphertext = payload.subarray(IV_SIZE + TAG_SIZE);
    const key = deriveKey(salt);
    const decipher = createDecipheriv("aes-256-gcm", key, iv);
    decipher.setAuthTag(tag);
    let plaintext;
    try {
      plaintext = Buffer.concat([decipher.update(ciphertext), decipher.final()]);
    } catch {
      throw new Error("Failed to decrypt credential file \u2014 machine identity may have changed");
    }
    try {
      this.cache = JSON.parse(plaintext.toString("utf-8"));
    } catch {
      throw new Error("Corrupt credential file: invalid JSON payload");
    }
    return this.cache;
  }
  save(store) {
    this.cache = store;
    const dir = dirname(this.filePath);
    if (!existsSync2(dir)) mkdirSync(dir, { recursive: true, mode: 448 });
    const salt = randomBytes(SALT_SIZE);
    const key = deriveKey(salt);
    const iv = randomBytes(IV_SIZE);
    const cipher = createCipheriv("aes-256-gcm", key, iv);
    const plaintext = Buffer.from(JSON.stringify(store), "utf-8");
    const ciphertext = Buffer.concat([cipher.update(plaintext), cipher.final()]);
    const tag = cipher.getAuthTag();
    const header = Buffer.alloc(HEADER_SIZE);
    MAGIC.copy(header, 0);
    header.writeUInt32LE(0, 8);
    salt.copy(header, 12);
    const output = Buffer.concat([header, iv, tag, ciphertext]);
    const tmpPath = `${this.filePath}.tmp`;
    writeFileSync(tmpPath, output, { mode: 384 });
    try {
      renameSync(tmpPath, this.filePath);
    } catch (err) {
      try {
        unlinkSync(tmpPath);
      } catch {
      }
      throw err;
    }
  }
};
function deriveKey(salt) {
  const machineId = getMachineId();
  const seed = createHash("sha256").update(`${machineId}:weft-credentials-v1`).digest();
  return pbkdf2Sync(seed, salt, PBKDF2_ITERATIONS, KEY_LENGTH, "sha256");
}
function credentialKey(id) {
  return `${id.type}::${id.workspaceId}::${id.sourceId}`;
}
function parseCredentialKey(key) {
  const parts = key.split("::");
  if (parts.length < 3) return null;
  const type = parts[0];
  if (!["source_oauth", "source_bearer", "source_apikey", "source_basic"].includes(type)) return null;
  return { type, workspaceId: parts[1], sourceId: parts.slice(2).join("::") };
}
var InMemoryCredentialManager = class {
  store = /* @__PURE__ */ new Map();
  key(id) {
    return `${id.type}::${id.workspaceId}::${id.sourceId}`;
  }
  async get(id) {
    return this.store.get(this.key(id)) ?? null;
  }
  async set(id, credential) {
    this.store.set(this.key(id), credential);
  }
  async delete(id) {
    return this.store.delete(this.key(id));
  }
};
var instance = null;
function getCredentialManager(options) {
  if (!instance) {
    const backend = options?.backend ?? "encrypted";
    if (backend === "memory") {
      instance = new InMemoryCredentialManager();
    } else {
      instance = new EncryptedCredentialBackend(options?.filePath);
    }
  }
  return instance;
}
function resetCredentialManager() {
  instance = null;
}
function readJsonFileSync(filePath) {
  const content = readFileSync3(filePath, "utf-8");
  return JSON.parse(content);
}
function expandPath(path) {
  if (path.startsWith("~")) {
    const { homedir: homedir3 } = __require("os");
    return path.replace("~", homedir3());
  }
  return path;
}
function toPortablePath(path) {
  const { homedir: homedir3 } = __require("os");
  const home = homedir3();
  if (path.startsWith(home)) {
    return "~" + path.slice(home.length);
  }
  return path;
}
function getWorkspaceSourcesPath(workspaceRootPath) {
  return join2(workspaceRootPath, "sources");
}
function getSourcePath(workspaceRootPath, sourceSlug) {
  return join3(getWorkspaceSourcesPath(workspaceRootPath), sourceSlug);
}
function ensureSourcesDir(workspaceRootPath) {
  const dir = getWorkspaceSourcesPath(workspaceRootPath);
  if (!existsSync3(dir)) {
    mkdirSync2(dir, { recursive: true });
  }
}
function loadSourceConfig(workspaceRootPath, sourceSlug) {
  const configPath = join3(getSourcePath(workspaceRootPath, sourceSlug), "config.json");
  if (!existsSync3(configPath)) return null;
  try {
    const config = readJsonFileSync(configPath);
    if (config.type === "local" && config.local?.path) {
      config.local.path = expandPath(config.local.path);
    }
    return config;
  } catch {
    return null;
  }
}
function markSourceAuthenticated(workspaceRootPath, sourceSlug) {
  const config = loadSourceConfig(workspaceRootPath, sourceSlug);
  if (!config) {
    debug(`[markSourceAuthenticated] Source ${sourceSlug} not found`);
    return false;
  }
  config.isAuthenticated = true;
  config.connectionStatus = "connected";
  config.connectionError = void 0;
  saveSourceConfig(workspaceRootPath, config);
  debug(`[markSourceAuthenticated] Marked ${sourceSlug} as authenticated`);
  return true;
}
function saveSourceConfig(workspaceRootPath, config) {
  const validation = validateSourceConfig(config);
  if (!validation.valid) {
    const errorMessages = validation.errors.map((e) => `${e.path}: ${e.message}`).join(", ");
    debug("[saveSourceConfig] Validation failed:", errorMessages);
    throw new Error(`Invalid source config: ${errorMessages}`);
  }
  const dir = getSourcePath(workspaceRootPath, config.slug);
  if (!existsSync3(dir)) {
    mkdirSync2(dir, { recursive: true });
  }
  const storageConfig = { ...config, updatedAt: Date.now() };
  if (storageConfig.type === "local" && storageConfig.local?.path) {
    storageConfig.local = {
      ...storageConfig.local,
      path: toPortablePath(storageConfig.local.path)
    };
  }
  writeFileSync2(join3(dir, "config.json"), JSON.stringify(storageConfig, null, 2));
}
function parseGuideMarkdown(raw) {
  const guide = { raw };
  const sectionRegex = /^## (Scope|Guidelines|Context|API Notes|Cache)\n([\s\S]*?)(?=\n## |\Z)/gim;
  let match;
  while ((match = sectionRegex.exec(raw)) !== null) {
    const sectionName = (match[1] ?? "").toLowerCase().replace(/\s+/g, "");
    const content = (match[2] ?? "").trim();
    switch (sectionName) {
      case "scope":
        guide.scope = content;
        break;
      case "guidelines":
        guide.guidelines = content;
        break;
      case "context":
        guide.context = content;
        break;
      case "apinotes":
        guide.apiNotes = content;
        break;
      case "cache":
        const jsonMatch = content.match(/```json\n([\s\S]*?)\n```/);
        if (jsonMatch && jsonMatch[1]) {
          try {
            guide.cache = JSON.parse(jsonMatch[1]);
          } catch {
          }
        }
        break;
    }
  }
  return guide;
}
function loadSourceGuide(workspaceRootPath, sourceSlug) {
  const guidePath = join3(getSourcePath(workspaceRootPath, sourceSlug), "guide.md");
  if (!existsSync3(guidePath)) return null;
  try {
    const raw = readFileSync4(guidePath, "utf-8");
    return parseGuideMarkdown(raw);
  } catch {
    return null;
  }
}
function saveSourceGuide(workspaceRootPath, sourceSlug, guide) {
  const dir = getSourcePath(workspaceRootPath, sourceSlug);
  if (!existsSync3(dir)) {
    mkdirSync2(dir, { recursive: true });
  }
  writeFileSync2(join3(dir, "guide.md"), guide.raw);
}
function loadSource(workspaceRootPath, sourceSlug) {
  const folderPath = getSourcePath(workspaceRootPath, sourceSlug);
  const config = loadSourceConfig(workspaceRootPath, sourceSlug);
  if (!config) return null;
  const workspaceId = basename(workspaceRootPath);
  const iconPath = findIconFile(folderPath);
  return {
    config,
    guide: loadSourceGuide(workspaceRootPath, sourceSlug),
    folderPath,
    workspaceRootPath,
    workspaceId,
    iconPath
  };
}
function loadWorkspaceSources(workspaceRootPath) {
  ensureSourcesDir(workspaceRootPath);
  const sources = [];
  const sourcesDir = getWorkspaceSourcesPath(workspaceRootPath);
  if (!existsSync3(sourcesDir)) return sources;
  const entries = readdirSync(sourcesDir, { withFileTypes: true });
  for (const entry of entries) {
    if (entry.isDirectory()) {
      const source = loadSource(workspaceRootPath, entry.name);
      if (source) {
        sources.push(source);
      }
    }
  }
  return sources;
}
function loadAllSources(workspaceRootPath) {
  const workspaceId = basename(workspaceRootPath);
  const userSources = loadWorkspaceSources(workspaceRootPath);
  const builtinSources = getBuiltinSources(workspaceId, workspaceRootPath);
  return [...userSources, ...builtinSources];
}
function generateSourceSlug(workspaceRootPath, name) {
  let slug = name.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "").substring(0, 50);
  if (!slug) {
    slug = "source";
  }
  const sourcesDir = getWorkspaceSourcesPath(workspaceRootPath);
  const existingSlugs = /* @__PURE__ */ new Set();
  if (existsSync3(sourcesDir)) {
    const entries = readdirSync(sourcesDir, { withFileTypes: true });
    for (const entry of entries) {
      if (entry.isDirectory()) {
        existingSlugs.add(entry.name);
      }
    }
  }
  if (!existingSlugs.has(slug)) {
    return slug;
  }
  let counter = 2;
  while (existingSlugs.has(`${slug}-${counter}`)) {
    counter++;
  }
  return `${slug}-${counter}`;
}
async function createSource(workspaceRootPath, input) {
  const slug = generateSourceSlug(workspaceRootPath, input.name);
  const now = Date.now();
  const config = {
    // ID format: {slug}_{random} for easy identification (e.g., "linear_a1b2c3d4")
    id: `${slug}_${randomUUID().slice(0, 8)}`,
    name: input.name,
    slug,
    enabled: input.enabled ?? true,
    provider: input.provider,
    type: input.type,
    createdAt: now,
    updatedAt: now
  };
  switch (input.type) {
    case "mcp":
      if (input.mcp) {
        config.mcp = input.mcp;
      }
      break;
    case "api":
      if (input.api) {
        config.api = input.api;
      }
      break;
    case "local":
      if (input.local) {
        config.local = input.local;
      }
      break;
  }
  if (input.icon) {
    const validatedIcon = validateIconValue(input.icon, "Sources");
    if (validatedIcon) {
      config.icon = validatedIcon;
    }
  }
  saveSourceConfig(workspaceRootPath, config);
  const sourcePath = getSourcePath(workspaceRootPath, slug);
  if (config.icon && isIconUrl(config.icon)) {
    const iconPath = await downloadIcon(sourcePath, config.icon, "Sources");
    if (iconPath) {
      debug(`[createSource] Icon downloaded for ${slug}: ${iconPath}`);
    }
  } else if (!config.icon) {
    const { deriveServiceUrl, getHighQualityLogoUrl } = await import("./logo-V5LSKQOU-PFP7HYFV.js");
    const { downloadIcon: downloadIcon2 } = await import("./icon-GXJSPBS6-AGOZHZ4I.js");
    const serviceUrl = deriveServiceUrl(input);
    if (serviceUrl) {
      const logoUrl = await getHighQualityLogoUrl(serviceUrl, input.provider);
      if (logoUrl) {
        const iconPath = await downloadIcon2(sourcePath, logoUrl, `createSource:${slug}`);
        if (iconPath) {
          config.icon = logoUrl;
          saveSourceConfig(workspaceRootPath, config);
        }
      }
    }
  }
  const guideContent = `# ${input.name}

## Guidelines

(Add usage guidelines here)

## Context

(Add context about this source)
`;
  saveSourceGuide(workspaceRootPath, slug, { raw: guideContent });
  return config;
}
function deleteSource(workspaceRootPath, sourceSlug) {
  const dir = getSourcePath(workspaceRootPath, sourceSlug);
  if (existsSync3(dir)) {
    rmSync(dir, { recursive: true });
  }
}
function sourceExists(workspaceRootPath, sourceSlug) {
  return existsSync3(join3(getSourcePath(workspaceRootPath, sourceSlug), "config.json"));
}
function isMultiHeaderCredential(cred) {
  return typeof cred === "object" && cred !== null && !("username" in cred && "password" in cred);
}
var SourceCredentialManager = class {
  // Track in-flight refresh promises to prevent concurrent refreshes for the same source
  // This prevents race conditions (especially important for Microsoft which rotates refresh tokens)
  pendingRefreshes = /* @__PURE__ */ new Map();
  /**
   * Host-injected OAuth provider for source-level auth flows.
   * Weft does NOT own browser callback servers or PKCE — the host app
   * (Electron, web UI, CLI) provides the actual implementation.
   * Defaults to StubSourceOAuthProvider which returns errors for all operations.
   */
  oauthProvider;
  /**
   * Construct a SourceCredentialManager with optional OAuth provider injection.
   *
   * If no provider is given, defaults to StubSourceOAuthProvider which
   * returns errors for all OAuth operations. The host app should either:
   * 1. Pass its SourceOAuthProvider at construction
   * 2. Call setOAuthProvider() after construction
   */
  constructor(oauthProvider) {
    this.oauthProvider = oauthProvider ?? new StubSourceOAuthProvider();
  }
  /**
   * Set the host OAuth provider. Called by the host application at startup
   * to inject its OAuth implementation (browser callbacks, PKCE, etc).
   *
   * This can also be used to replace the provider at runtime (e.g. when
   * the Electron app initializes after the credential manager singleton
   * has already been created).
   */
  setOAuthProvider(provider) {
    this.oauthProvider = provider;
  }
  // ============================================================
  // Core CRUD Operations
  // ============================================================
  /**
   * Save credential for a source
   */
  async save(source, credential) {
    const credentialId = this.getCredentialId(source);
    const manager = getCredentialManager();
    await manager.set(credentialId, credential);
    debug(`[SourceCredentialManager] Saved ${credentialId.type} for ${source.config.slug}`);
  }
  /**
   * Load credential for a source
   *
   * For MCP sources, tries both OAuth and bearer credentials as fallback
   * (credentials may have been stored via different auth modes)
   */
  async load(source) {
    const manager = getCredentialManager();
    if (source.config.type === "mcp" && source.config.mcp?.transport !== "stdio" && source.config.mcp?.authType !== "none") {
      return this.loadMcpCredential(source);
    }
    const credentialId = this.getCredentialId(source);
    const cred = await manager.get(credentialId);
    if (cred) {
      debug(`[SourceCredentialManager] Found ${credentialId.type} for ${source.config.slug}`);
    }
    return cred;
  }
  /**
   * Load MCP credential with fallback (OAuth -> bearer)
   */
  async loadMcpCredential(source) {
    const manager = getCredentialManager();
    const baseId = {
      workspaceId: source.workspaceId,
      sourceId: source.config.slug
    };
    const oauthCreds = await manager.get({ type: "source_oauth", ...baseId });
    if (oauthCreds?.value) {
      debug(`[SourceCredentialManager] Found source_oauth for ${source.config.slug}`);
      return oauthCreds;
    }
    const bearerCreds = await manager.get({ type: "source_bearer", ...baseId });
    if (bearerCreds?.value) {
      debug(`[SourceCredentialManager] Found source_bearer for ${source.config.slug}`);
      return bearerCreds;
    }
    debug(`[SourceCredentialManager] No credential found for MCP source ${source.config.slug}`);
    return null;
  }
  /**
   * Delete credential for a source
   */
  async delete(source) {
    const credentialId = this.getCredentialId(source);
    const manager = getCredentialManager();
    const deleted = await manager.delete(credentialId);
    if (deleted) {
      debug(`[SourceCredentialManager] Deleted ${credentialId.type} for ${source.config.slug}`);
    }
    return deleted;
  }
  /**
   * Get token value for a source (convenience method)
   * Returns null if no credential exists or if expired
   */
  async getToken(source) {
    const cred = await this.load(source);
    if (!cred?.value) return null;
    if (this.isExpired(cred)) {
      debug(`[SourceCredentialManager] Token expired for ${source.config.slug}`);
      return null;
    }
    return cred.value;
  }
  /**
   * Get API credential for a source (handles basic auth and multi-header JSON parsing)
   */
  async getApiCredential(source) {
    const cred = await this.load(source);
    const headerNames = source.config.api?.headerNames || source.config.mcp?.headerNames;
    debug(`[SourceCredentialManager] getApiCredential for ${source.config.slug}: cred.value exists=${!!cred?.value}, headerNames=${JSON.stringify(headerNames)}`);
    if (!cred?.value) return null;
    if (headerNames?.length) {
      debug(`[SourceCredentialManager] Attempting multi-header parse for ${source.config.slug}, raw value length=${cred.value.length}`);
      try {
        const parsed = JSON.parse(cred.value);
        debug(`[SourceCredentialManager] Parsed JSON keys: ${Object.keys(parsed).join(", ")}`);
        const hasAllHeaders = headerNames.every((h) => h in parsed);
        debug(`[SourceCredentialManager] hasAllHeaders=${hasAllHeaders}`);
        if (hasAllHeaders) {
          return parsed;
        }
      } catch (e) {
        debug(`[SourceCredentialManager] JSON parse failed: ${e}`);
      }
    }
    if (source.config.api?.authType === "basic") {
      try {
        const parsed = JSON.parse(cred.value);
        if (parsed.username && parsed.password) {
          return parsed;
        }
      } catch {
      }
    }
    return cred.value;
  }
  // ============================================================
  // Credential ID Resolution
  // ============================================================
  /**
   * Get the credential ID for a source
   *
   * Determines the correct credential type based on:
   * - Source type (mcp, api, local)
   * - Auth type (oauth, bearer, header, etc.)
   */
  getCredentialId(source) {
    const mcp = source.config.mcp;
    const api = source.config.api;
    let type;
    if (source.config.type === "mcp") {
      type = mcp?.authType === "bearer" ? "source_bearer" : "source_oauth";
    } else if (source.config.type === "api") {
      if (isApiOAuthProvider(source.config.provider)) {
        type = "source_oauth";
      } else if (api?.authType === "oauth") {
        type = "source_oauth";
      } else if (api?.authType === "bearer") {
        type = "source_bearer";
      } else if (api?.authType === "basic") {
        type = "source_basic";
      } else {
        type = "source_apikey";
      }
    } else {
      type = "source_oauth";
    }
    return {
      type,
      workspaceId: source.workspaceId,
      sourceId: source.config.slug
    };
  }
  // ============================================================
  // Expiry Checking
  // ============================================================
  /**
   * Check if a credential is expired
   */
  isExpired(credential) {
    if (!credential.expiresAt) return false;
    return Date.now() > credential.expiresAt;
  }
  /**
   * Check if a credential needs refresh (within 5 min of expiry)
   */
  needsRefresh(credential) {
    if (!credential.expiresAt) return false;
    const fiveMinutes = 5 * 60 * 1e3;
    return Date.now() > credential.expiresAt - fiveMinutes;
  }
  /**
   * Mark a source as needing re-authentication.
   * Called when token is missing/expired or token refresh fails.
   * Updates config.json so the UI shows "needs auth" and the agent gets proper context.
   */
  markSourceNeedsReauth(source, errorMessage) {
    try {
      const config = loadSourceConfig(source.workspaceRootPath, source.config.slug);
      if (config) {
        config.isAuthenticated = false;
        config.connectionStatus = "needs_auth";
        config.connectionError = errorMessage;
        saveSourceConfig(source.workspaceRootPath, config);
        debug(`[SourceCredentialManager] Marked ${source.config.slug} as needing re-auth: ${errorMessage}`);
      }
    } catch (error) {
      debug(`[SourceCredentialManager] Failed to mark ${source.config.slug} as needing re-auth:`, error);
    }
  }
  /**
   * Check if source has valid (non-expired) credentials
   */
  async hasValidCredentials(source) {
    const token = await this.getToken(source);
    return token !== null;
  }
  // ============================================================
  // OAuth Provider Detection & Routing
  // ============================================================
  /**
   * Detect the OAuth provider for a source.
   */
  detectProvider(source) {
    if (source.config.provider === "google") return "google";
    if (source.config.provider === "slack") return "slack";
    if (source.config.provider === "microsoft") return "microsoft";
    if (source.config.api?.authType === "oauth") return "generic";
    return "mcp";
  }
  /**
   * Check whether a source uses OAuth authentication.
   * Used to gate authenticate() and refresh() before delegating to oauthProvider.
   */
  isOAuthSource(source) {
    if (source.config.provider === "google") return true;
    if (source.config.provider === "slack") return true;
    if (source.config.provider === "microsoft") return true;
    if (source.config.api?.authType === "oauth") return true;
    if (source.config.type === "mcp" && source.config.mcp?.authType === "oauth") return true;
    return false;
  }
  /**
   * Build SourceOAuthOptions from source config for a given provider.
   *
   * Extracts provider-specific fields (service, scopes, clientId, etc.)
   * and maps them to the generalized SourceOAuthOptions interface.
   * The host app's SourceOAuthProvider implementation interprets these
   * fields as needed for its provider-specific OAuth flow.
   */
  buildOAuthOptions(source, provider, options) {
    const oauthOptions = {
      callbackPort: options?.callbackPort,
      callbackUrl: options?.callbackUrl,
      sessionContext: options?.sessionContext
    };
    const api = source.config.api;
    switch (provider) {
      case "google": {
        let service;
        let scopes;
        if (api?.googleScopes && api.googleScopes.length > 0) {
          scopes = api.googleScopes;
        } else if (api?.googleService) {
          service = api.googleService;
        } else {
          service = inferGoogleServiceFromUrl(api?.baseUrl);
        }
        oauthOptions.service = service;
        oauthOptions.scopes = scopes;
        oauthOptions.appType = "electron";
        oauthOptions.clientId = api?.googleOAuthClientId;
        oauthOptions.clientSecret = api?.googleOAuthClientSecret;
        break;
      }
      case "slack": {
        let service;
        let scopes;
        if (api?.slackUserScopes && api.slackUserScopes.length > 0) {
          scopes = api.slackUserScopes;
        } else if (api?.slackService) {
          service = api.slackService;
        } else {
          service = inferSlackServiceFromUrl(api?.baseUrl) || "full";
        }
        oauthOptions.service = service;
        oauthOptions.scopes = scopes;
        oauthOptions.appType = "electron";
        break;
      }
      case "microsoft": {
        let service;
        let scopes;
        if (api?.microsoftScopes && api.microsoftScopes.length > 0) {
          scopes = api.microsoftScopes;
        } else if (api?.microsoftService) {
          service = api.microsoftService;
        } else {
          service = inferMicrosoftServiceFromUrl(api?.baseUrl);
        }
        oauthOptions.service = service;
        oauthOptions.scopes = scopes;
        oauthOptions.appType = "electron";
        break;
      }
      case "generic": {
        const oauthConfig = api?.oauth;
        if (oauthConfig) {
          oauthOptions.service = api?.baseUrl;
          oauthOptions.clientId = oauthConfig.clientId;
          oauthOptions.clientSecret = oauthConfig.clientSecret;
        } else {
          oauthOptions.service = api?.baseUrl;
        }
        break;
      }
      case "mcp": {
        oauthOptions.service = source.config.mcp?.url;
        break;
      }
    }
    return oauthOptions;
  }
  // ============================================================
  // OAuth Authentication (Delegated to Host App)
  // ============================================================
  /**
   * Authenticate a source via OAuth, delegating to the host app.
   *
   * The host app's SourceOAuthProvider handles the entire flow:
   * browser redirect, callback server, token exchange, and credential storage.
   * On success, the source is marked as authenticated in config.json.
   *
   * This replaces the previous prepare/exchange split (prepareOAuth + exchangeAndStore).
   * If a host app needs prepare/exchange separation, it implements that in its
   * SourceOAuthProvider.
   */
  async authenticate(source, options) {
    if (!this.isOAuthSource(source)) {
      return {
        success: false,
        error: `Source ${source.config.slug} does not use OAuth authentication`
      };
    }
    const provider = this.detectProvider(source);
    const oauthOptions = this.buildOAuthOptions(source, provider, options);
    const result2 = await this.oauthProvider.authenticate(
      source.config.slug,
      provider,
      oauthOptions
    );
    if (result2.success) {
      if (result2.accessToken) {
        await this.save(source, {
          value: result2.accessToken,
          refreshToken: result2.refreshToken,
          expiresAt: result2.expiresAt
        });
        debug(`[SourceCredentialManager] Saved token from host OAuthProvider for ${source.config.slug}`);
      }
      markSourceAuthenticated(source.workspaceRootPath, source.config.slug);
      debug(`[SourceCredentialManager] OAuth authenticate complete for ${source.config.slug}`);
    } else {
      debug(`[SourceCredentialManager] OAuth authenticate failed for ${source.config.slug}: ${result2.error}`);
    }
    return { success: result2.success, error: result2.error, email: result2.email };
  }
  // ============================================================
  // Token Refresh (Delegated to Host App for OAuth)
  // ============================================================
  /**
   * Refresh token for a source
   *
   * Returns the new access token, or null if refresh fails.
   * On success, credentials are automatically updated by the host app.
   *
   * Uses promise deduplication to prevent concurrent refresh requests for the same source.
   * This is important because:
   * - Multiple API calls may hit refresh simultaneously when token is expiring
   * - Microsoft rotates refresh tokens, so concurrent refreshes could cause token invalidation
   */
  async refresh(source) {
    const key = source.config.slug;
    const pending = this.pendingRefreshes.get(key);
    if (pending) {
      debug(`[SourceCredentialManager] Reusing pending refresh for ${key}`);
      return pending;
    }
    const refreshPromise = this.doRefresh(source).finally(() => {
      this.pendingRefreshes.delete(key);
    });
    this.pendingRefreshes.set(key, refreshPromise);
    return refreshPromise;
  }
  /**
   * Internal refresh implementation.
   *
   * Routes to:
   * 1. refreshApiRenew — for custom API renew endpoints (non-OAuth)
   * 2. oauthProvider.refresh() — for all OAuth providers (Google, Slack, Microsoft, Generic, MCP)
   *
   * The host app's refresh() implementation handles token refresh and credential storage.
   * After successful refresh, we reload the credential to return the new access token.
   */
  async doRefresh(source) {
    const cred = await this.load(source);
    if (!cred) {
      debug(`[SourceCredentialManager] No credential for ${source.config.slug}`);
      return null;
    }
    if (hasRenewEndpoint(source)) {
      return this.refreshApiRenew(source, cred);
    }
    if (!cred.refreshToken) {
      debug(`[SourceCredentialManager] No refresh token for ${source.config.slug}`);
      return null;
    }
    if (!this.isOAuthSource(source)) {
      debug(`[SourceCredentialManager] Source ${source.config.slug} is not an OAuth source`);
      return null;
    }
    const provider = this.detectProvider(source);
    try {
      const result2 = await this.oauthProvider.refresh(
        source.config.slug,
        provider,
        cred.refreshToken
      );
      if (!result2.success) {
        debug(`[SourceCredentialManager] OAuth refresh failed for ${source.config.slug}: ${result2.error}`);
        this.markSourceNeedsReauth(source, `Token refresh failed: ${result2.error ?? "unknown error"}`);
        return null;
      }
      if (result2.accessToken) {
        await this.save(source, {
          value: result2.accessToken,
          refreshToken: result2.refreshToken ?? cred.refreshToken,
          expiresAt: result2.expiresAt
        });
        debug(`[SourceCredentialManager] Saved refreshed token from host OAuthProvider for ${source.config.slug}`);
        return result2.accessToken;
      }
      const updatedCred = await this.load(source);
      if (updatedCred?.value) {
        debug(`[SourceCredentialManager] Refreshed token for ${source.config.slug}`);
        return updatedCred.value;
      }
      debug(`[SourceCredentialManager] No credential found after refresh for ${source.config.slug}`);
      this.markSourceNeedsReauth(source, "Token not found after refresh");
      return null;
    } catch (error) {
      const errorMsg = error instanceof Error ? error.message : String(error);
      debug(`[SourceCredentialManager] OAuth refresh error for ${source.config.slug}:`, error);
      this.markSourceNeedsReauth(source, `Token refresh failed: ${errorMsg}`);
      return null;
    }
  }
  /**
   * Refresh token via a custom API renew endpoint (non-OAuth).
   * Uses the current access token for renewal — no separate refresh token needed.
   */
  async refreshApiRenew(source, cred) {
    const renewConfig = source.config.api?.renewEndpoint;
    if (!renewConfig?.path) return null;
    const baseUrl = source.config.api.baseUrl;
    const authScheme = source.config.api.authScheme;
    const currentToken = cred.value;
    try {
      const url = renewConfig.path.startsWith("http") ? renewConfig.path : new URL(renewConfig.path, baseUrl.endsWith("/") ? baseUrl : `${baseUrl}/`).toString();
      const headers = {
        "Content-Type": "application/json",
        ...source.config.api.defaultHeaders,
        ...substituteTokenInHeaders(renewConfig.headers, currentToken)
      };
      if (!renewConfig.headers?.["Authorization"] && !renewConfig.headers?.["authorization"]) {
        headers["Authorization"] = buildAuthorizationHeader(authScheme, currentToken);
      }
      const method = renewConfig.method ?? "POST";
      const fetchOptions = { method, headers };
      if (renewConfig.body && method !== "GET") {
        fetchOptions.body = JSON.stringify(substituteTokenInBody(renewConfig.body, currentToken));
      }
      const response = await fetch(url, fetchOptions);
      if (!response.ok) {
        const errorText = await response.text().catch(() => "");
        throw new Error(`Renew endpoint returned ${response.status}: ${errorText.slice(0, 200)}`);
      }
      const json = await response.json();
      const tokenField = renewConfig.tokenField ?? "access_token";
      const newToken = json[tokenField];
      if (typeof newToken !== "string" || !newToken) {
        throw new Error(`Renew response missing "${tokenField}" field`);
      }
      const expiresInField = renewConfig.expiresInField ?? "expires_in";
      const expiresInRaw = json[expiresInField];
      let expiresAt;
      if (typeof expiresInRaw === "number" && expiresInRaw > 0) {
        expiresAt = Date.now() + expiresInRaw * 1e3;
      } else if (renewConfig.fallbackTtlSecs) {
        expiresAt = Date.now() + renewConfig.fallbackTtlSecs * 1e3;
      }
      await this.save(source, {
        ...cred,
        value: newToken,
        ...expiresAt !== void 0 ? { expiresAt } : {}
      });
      debug(`[SourceCredentialManager] Refreshed token via renew endpoint for ${source.config.slug}`);
      return newToken;
    } catch (error) {
      const errorMsg = error instanceof Error ? error.message : String(error);
      debug(`[SourceCredentialManager] Renew endpoint refresh failed for ${source.config.slug}:`, error);
      this.markSourceNeedsReauth(source, `Token refresh failed: ${errorMsg}`);
      return null;
    }
  }
};
function substituteTokenInBody(obj, token) {
  const result2 = {};
  for (const [key, value] of Object.entries(obj)) {
    if (typeof value === "string") {
      result2[key] = value.replace(/\{\{token\}\}/g, token);
    } else if (Array.isArray(value)) {
      result2[key] = value.map(
        (item) => typeof item === "string" ? item.replace(/\{\{token\}\}/g, token) : item && typeof item === "object" ? substituteTokenInBody(item, token) : item
      );
    } else if (value && typeof value === "object") {
      result2[key] = substituteTokenInBody(value, token);
    } else {
      result2[key] = value;
    }
  }
  return result2;
}
function substituteTokenInHeaders(headers, token) {
  if (!headers) return {};
  const result2 = {};
  for (const [key, value] of Object.entries(headers)) {
    result2[key] = value.replace(/\{\{token\}\}/g, token);
  }
  return result2;
}

export {
  getBuiltinSources,
  createSourceActivationPlan,
  createSourceToolAssemblyPlan,
  createSourceRuntimeAssemblyPlan,
  createSourceRuntimeProviderOptions,
  validateSourceConfig,
  providerAuthToSourceStatus,
  StubSourceOAuthProvider,
  getMachineId,
  EncryptedCredentialBackend,
  InMemoryCredentialManager,
  getCredentialManager,
  resetCredentialManager,
  loadSourceConfig,
  saveSourceConfig,
  loadSource,
  loadAllSources,
  createSource,
  deleteSource,
  sourceExists,
  isMultiHeaderCredential,
  SourceCredentialManager
};
