// src/providers/registry.ts
import path3 from "path";
import { pathToFileURL } from "url";
import { z as z5 } from "zod";

// src/config.ts
import fs2 from "fs/promises";
import path2 from "path";
import { fileURLToPath } from "url";
import { z as z2 } from "zod";

// src/types.ts
import { z } from "zod";
var providerCapabilitySchema = z.enum([
  "responses",
  "chat",
  "structured",
  "tools",
  "vision",
  "embeddings",
  "streaming",
  "local",
  "image_generation"
]);
var providerTypeSchema = z.enum(["heuristic", "openai", "ollama", "anthropic", "gemini", "openai-compatible", "custom"]);
var webSearchProviderTypeSchema = z.enum(["http-json", "custom"]);

// src/utils.ts
import crypto from "crypto";
import fs from "fs/promises";
import path from "path";
function slugify(value) {
  return value.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 80) || "item";
}
function sha256(value) {
  return crypto.createHash("sha256").update(value).digest("hex");
}
async function ensureDir(dirPath) {
  await fs.mkdir(dirPath, { recursive: true });
}
async function fileExists(filePath) {
  try {
    await fs.access(filePath);
    return true;
  } catch {
    return false;
  }
}
async function readJsonFile(filePath) {
  if (!await fileExists(filePath)) {
    return null;
  }
  const content = await fs.readFile(filePath, "utf8");
  return JSON.parse(content);
}
async function writeJsonFile(filePath, value) {
  await ensureDir(path.dirname(filePath));
  await fs.writeFile(filePath, `${JSON.stringify(value, null, 2)}
`, "utf8");
}
async function appendJsonLine(filePath, value) {
  await ensureDir(path.dirname(filePath));
  await fs.appendFile(filePath, `${JSON.stringify(value)}
`, "utf8");
}
async function writeFileIfChanged(filePath, content) {
  await ensureDir(path.dirname(filePath));
  if (await fileExists(filePath)) {
    const existing = await fs.readFile(filePath, "utf8");
    if (existing === content) {
      return false;
    }
  }
  await fs.writeFile(filePath, content, "utf8");
  return true;
}
function toPosix(value) {
  return value.split(path.sep).join(path.posix.sep);
}
function firstSentences(value, count = 3) {
  const sentences = value.replace(/\s+/g, " ").split(/(?<=[.!?])\s+/).filter(Boolean);
  return sentences.slice(0, count).join(" ").trim();
}
function uniqueBy(items, key) {
  const seen = /* @__PURE__ */ new Set();
  const result = [];
  for (const item of items) {
    const itemKey = key(item);
    if (seen.has(itemKey)) {
      continue;
    }
    seen.add(itemKey);
    result.push(item);
  }
  return result;
}
function extractJson(text) {
  const fencedMatch = text.match(/```json\s*([\s\S]*?)```/i);
  if (fencedMatch) {
    return fencedMatch[1].trim();
  }
  const start = text.indexOf("{");
  const end = text.lastIndexOf("}");
  if (start !== -1 && end !== -1 && end > start) {
    return text.slice(start, end + 1);
  }
  throw new Error("Could not locate JSON object in provider response.");
}
function normalizeWhitespace(value) {
  return value.replace(/\s+/g, " ").trim();
}
function truncate(value, maxLength) {
  if (value.length <= maxLength) {
    return value;
  }
  return `${value.slice(0, maxLength - 3)}...`;
}
async function listFilesRecursive(rootDir) {
  const entries = await fs.readdir(rootDir, { withFileTypes: true }).catch(() => []);
  const files = [];
  for (const entry of entries) {
    const absolutePath = path.join(rootDir, entry.name);
    if (entry.isDirectory()) {
      files.push(...await listFilesRecursive(absolutePath));
      continue;
    }
    if (entry.isFile()) {
      files.push(absolutePath);
    }
  }
  return files;
}

// src/config.ts
var PRIMARY_CONFIG_FILENAME = "swarmvault.config.json";
var PRIMARY_SCHEMA_FILENAME = "swarmvault.schema.md";
var moduleDir = path2.dirname(fileURLToPath(import.meta.url));
var viewerDistDir = path2.basename(moduleDir) === "src" ? path2.resolve(moduleDir, "../../viewer/dist") : path2.resolve(moduleDir, "viewer");
var providerConfigSchema = z2.object({
  type: providerTypeSchema,
  model: z2.string().min(1),
  baseUrl: z2.string().url().optional(),
  apiKeyEnv: z2.string().min(1).optional(),
  headers: z2.record(z2.string(), z2.string()).optional(),
  module: z2.string().min(1).optional(),
  capabilities: z2.array(providerCapabilitySchema).optional(),
  apiStyle: z2.enum(["responses", "chat"]).optional()
});
var scheduleTriggerSchema = z2.object({
  cron: z2.string().min(1).optional(),
  every: z2.string().min(1).optional()
}).refine((value) => Boolean(value.cron || value.every), {
  message: "Schedule triggers require `cron` or `every`."
});
var scheduledTaskSchema = z2.discriminatedUnion("type", [
  z2.object({
    type: z2.literal("compile"),
    approve: z2.boolean().optional()
  }),
  z2.object({
    type: z2.literal("lint"),
    deep: z2.boolean().optional(),
    web: z2.boolean().optional()
  }),
  z2.object({
    type: z2.literal("query"),
    question: z2.string().min(1),
    format: z2.enum(["markdown", "report", "slides", "chart", "image"]).optional(),
    save: z2.boolean().optional()
  }),
  z2.object({
    type: z2.literal("explore"),
    question: z2.string().min(1),
    steps: z2.number().int().positive().optional(),
    format: z2.enum(["markdown", "report", "slides", "chart", "image"]).optional()
  })
]);
var roleExecutorConfigSchema = z2.discriminatedUnion("type", [
  z2.object({
    type: z2.literal("provider"),
    provider: z2.string().min(1)
  }),
  z2.object({
    type: z2.literal("command"),
    command: z2.array(z2.string().min(1)).min(1),
    cwd: z2.string().min(1).optional(),
    env: z2.record(z2.string(), z2.string()).optional(),
    timeoutMs: z2.number().int().positive().optional()
  })
]);
var webSearchProviderConfigSchema = z2.object({
  type: webSearchProviderTypeSchema,
  endpoint: z2.string().url().optional(),
  method: z2.enum(["GET", "POST"]).optional(),
  apiKeyEnv: z2.string().min(1).optional(),
  apiKeyHeader: z2.string().min(1).optional(),
  apiKeyPrefix: z2.string().optional(),
  headers: z2.record(z2.string(), z2.string()).optional(),
  queryParam: z2.string().min(1).optional(),
  limitParam: z2.string().min(1).optional(),
  resultsPath: z2.string().min(1).optional(),
  titleField: z2.string().min(1).optional(),
  urlField: z2.string().min(1).optional(),
  snippetField: z2.string().min(1).optional(),
  module: z2.string().min(1).optional()
});
var vaultConfigSchema = z2.object({
  workspace: z2.object({
    rawDir: z2.string().min(1),
    wikiDir: z2.string().min(1),
    stateDir: z2.string().min(1),
    agentDir: z2.string().min(1),
    inboxDir: z2.string().min(1)
  }),
  providers: z2.record(z2.string(), providerConfigSchema),
  tasks: z2.object({
    compileProvider: z2.string().min(1),
    queryProvider: z2.string().min(1),
    lintProvider: z2.string().min(1),
    visionProvider: z2.string().min(1),
    imageProvider: z2.string().min(1).optional()
  }),
  viewer: z2.object({
    port: z2.number().int().positive()
  }),
  projects: z2.record(
    z2.string(),
    z2.object({
      roots: z2.array(z2.string().min(1)).min(1),
      schemaPath: z2.string().min(1).optional()
    })
  ).optional(),
  agents: z2.array(z2.enum(["codex", "claude", "cursor"])).default(["codex", "claude", "cursor"]),
  schedules: z2.record(z2.string(), z2.object({ enabled: z2.boolean().optional(), when: scheduleTriggerSchema, task: scheduledTaskSchema })).optional(),
  orchestration: z2.object({
    maxParallelRoles: z2.number().int().positive().optional(),
    compilePostPass: z2.boolean().optional(),
    roles: z2.object({
      research: z2.object({ executor: roleExecutorConfigSchema }).optional(),
      audit: z2.object({ executor: roleExecutorConfigSchema }).optional(),
      context: z2.object({ executor: roleExecutorConfigSchema }).optional(),
      safety: z2.object({ executor: roleExecutorConfigSchema }).optional()
    }).optional()
  }).optional(),
  webSearch: z2.object({
    providers: z2.record(z2.string(), webSearchProviderConfigSchema),
    tasks: z2.object({
      deepLintProvider: z2.string().min(1)
    })
  }).optional()
});
function defaultVaultConfig() {
  return {
    workspace: {
      rawDir: "raw",
      wikiDir: "wiki",
      stateDir: "state",
      agentDir: "agent",
      inboxDir: "inbox"
    },
    providers: {
      local: {
        type: "heuristic",
        model: "heuristic-v1",
        capabilities: ["chat", "structured", "vision", "local"]
      }
    },
    tasks: {
      compileProvider: "local",
      queryProvider: "local",
      lintProvider: "local",
      visionProvider: "local",
      imageProvider: "local"
    },
    viewer: {
      port: 4123
    },
    projects: {},
    agents: ["codex", "claude", "cursor"],
    schedules: {},
    orchestration: {
      maxParallelRoles: 2,
      compilePostPass: false,
      roles: {}
    }
  };
}
function defaultVaultSchema() {
  return [
    "# SwarmVault Schema",
    "",
    "Edit this file to teach SwarmVault how this vault should be organized and maintained.",
    "",
    "## Vault Purpose",
    "",
    "- Describe the domain this vault covers.",
    "- Note the intended audience and the kinds of questions the vault should answer well.",
    "",
    "## Naming Conventions",
    "",
    "- Prefer stable, descriptive page titles.",
    "- Keep concept and entity names specific to the domain.",
    "",
    "## Page Structure Rules",
    "",
    "- Source pages should stay grounded in the original material.",
    "- Concept and entity pages should aggregate source-backed claims instead of inventing new ones.",
    "- Preserve contradictions instead of smoothing them away.",
    "",
    "## Categories",
    "",
    "- List domain-specific concept categories here.",
    "- List important entity types here.",
    "",
    "## Relationship Types",
    "",
    "- Mentions",
    "- Supports",
    "- Contradicts",
    "- Depends on",
    "",
    "## Grounding Rules",
    "",
    "- Prefer raw sources over summaries.",
    "- Cite source ids whenever claims are stated.",
    "- Do not treat the wiki as a source of truth when the raw material disagrees.",
    "",
    "## Exclusions",
    "",
    "- List topics, claims, or page types the compiler should avoid generating.",
    ""
  ].join("\n");
}
async function findConfigPath(rootDir) {
  const primaryPath = path2.join(rootDir, PRIMARY_CONFIG_FILENAME);
  return primaryPath;
}
async function findSchemaPath(rootDir) {
  const primaryPath = path2.join(rootDir, PRIMARY_SCHEMA_FILENAME);
  return primaryPath;
}
function resolvePaths(rootDir, config, configPath = path2.join(rootDir, PRIMARY_CONFIG_FILENAME), schemaPath = path2.join(rootDir, PRIMARY_SCHEMA_FILENAME)) {
  const effective = config ?? defaultVaultConfig();
  const rawDir = path2.resolve(rootDir, effective.workspace.rawDir);
  const rawSourcesDir = path2.join(rawDir, "sources");
  const rawAssetsDir = path2.join(rawDir, "assets");
  const wikiDir = path2.resolve(rootDir, effective.workspace.wikiDir);
  const outputsAssetsDir = path2.join(wikiDir, "outputs", "assets");
  const projectsDir = path2.join(wikiDir, "projects");
  const candidatesDir = path2.join(wikiDir, "candidates");
  const stateDir = path2.resolve(rootDir, effective.workspace.stateDir);
  const schedulesDir = path2.join(stateDir, "schedules");
  const agentDir = path2.resolve(rootDir, effective.workspace.agentDir);
  const inboxDir = path2.resolve(rootDir, effective.workspace.inboxDir);
  return {
    rootDir,
    schemaPath,
    rawDir,
    rawSourcesDir,
    rawAssetsDir,
    wikiDir,
    outputsAssetsDir,
    projectsDir,
    candidatesDir,
    candidateConceptsDir: path2.join(candidatesDir, "concepts"),
    candidateEntitiesDir: path2.join(candidatesDir, "entities"),
    stateDir,
    schedulesDir,
    agentDir,
    inboxDir,
    manifestsDir: path2.join(stateDir, "manifests"),
    extractsDir: path2.join(stateDir, "extracts"),
    analysesDir: path2.join(stateDir, "analyses"),
    viewerDistDir,
    graphPath: path2.join(stateDir, "graph.json"),
    searchDbPath: path2.join(stateDir, "search.sqlite"),
    compileStatePath: path2.join(stateDir, "compile-state.json"),
    jobsLogPath: path2.join(stateDir, "jobs.ndjson"),
    sessionsDir: path2.join(stateDir, "sessions"),
    approvalsDir: path2.join(stateDir, "approvals"),
    configPath
  };
}
async function loadVaultConfig(rootDir) {
  const configPath = await findConfigPath(rootDir);
  const schemaPath = await findSchemaPath(rootDir);
  const raw = await readJsonFile(configPath);
  const parsed = vaultConfigSchema.parse(raw ?? defaultVaultConfig());
  return {
    config: parsed,
    paths: resolvePaths(rootDir, parsed, configPath, schemaPath)
  };
}
async function initWorkspace(rootDir) {
  const configPath = await findConfigPath(rootDir);
  const schemaPath = await findSchemaPath(rootDir);
  const config = await fileExists(configPath) ? (await loadVaultConfig(rootDir)).config : defaultVaultConfig();
  const paths = resolvePaths(rootDir, config, configPath, schemaPath);
  const primarySchemaPath = path2.join(rootDir, PRIMARY_SCHEMA_FILENAME);
  await Promise.all([
    ensureDir(paths.rawDir),
    ensureDir(paths.wikiDir),
    ensureDir(paths.outputsAssetsDir),
    ensureDir(paths.projectsDir),
    ensureDir(paths.candidatesDir),
    ensureDir(paths.candidateConceptsDir),
    ensureDir(paths.candidateEntitiesDir),
    ensureDir(paths.stateDir),
    ensureDir(paths.schedulesDir),
    ensureDir(paths.sessionsDir),
    ensureDir(paths.approvalsDir),
    ensureDir(paths.agentDir),
    ensureDir(paths.inboxDir),
    ensureDir(paths.manifestsDir),
    ensureDir(paths.extractsDir),
    ensureDir(paths.analysesDir),
    ensureDir(paths.rawSourcesDir),
    ensureDir(paths.rawAssetsDir)
  ]);
  if (!await fileExists(configPath)) {
    await writeJsonFile(configPath, config);
  }
  if (!await fileExists(primarySchemaPath)) {
    await ensureDir(path2.dirname(primarySchemaPath));
    await fs2.writeFile(primarySchemaPath, defaultVaultSchema(), "utf8");
  }
  return { config, paths };
}

// src/providers/base.ts
import fs3 from "fs/promises";
import { z as z3 } from "zod";
var BaseProviderAdapter = class {
  constructor(id, type, model, capabilities) {
    this.id = id;
    this.type = type;
    this.model = model;
    this.capabilities = new Set(capabilities);
  }
  id;
  type;
  model;
  capabilities;
  async generateImage(_request) {
    throw new Error(`Provider ${this.id} does not support image generation.`);
  }
  async generateStructured(request, schema) {
    const schemaDescription = JSON.stringify(z3.toJSONSchema(schema), null, 2);
    const response = await this.generateText({
      ...request,
      prompt: `${request.prompt}

Return JSON only. Follow this JSON Schema exactly:
${schemaDescription}`
    });
    const parsed = JSON.parse(extractJson(response.text));
    return schema.parse(parsed);
  }
  async encodeAttachments(attachments = []) {
    return Promise.all(
      attachments.map(async (attachment) => ({
        mimeType: attachment.mimeType,
        base64: await fs3.readFile(attachment.filePath, "base64")
      }))
    );
  }
};

// src/providers/anthropic.ts
var AnthropicProviderAdapter = class extends BaseProviderAdapter {
  apiKey;
  headers;
  baseUrl;
  constructor(id, model, options) {
    super(id, "anthropic", model, ["chat", "structured", "tools", "vision", "streaming"]);
    this.apiKey = options.apiKey;
    this.headers = options.headers;
    this.baseUrl = (options.baseUrl ?? "https://api.anthropic.com").replace(/\/+$/, "");
  }
  async generateText(request) {
    const encodedAttachments = await this.encodeAttachments(request.attachments);
    const content = [
      { type: "text", text: request.prompt },
      ...encodedAttachments.map((item) => ({
        type: "image",
        source: {
          type: "base64",
          media_type: item.mimeType,
          data: item.base64
        }
      }))
    ];
    const response = await fetch(`${this.baseUrl}/v1/messages`, {
      method: "POST",
      headers: {
        "content-type": "application/json",
        "anthropic-version": "2023-06-01",
        ...this.apiKey ? { "x-api-key": this.apiKey } : {},
        ...this.headers
      },
      body: JSON.stringify({
        model: this.model,
        max_tokens: request.maxOutputTokens ?? 1200,
        system: request.system,
        messages: [
          {
            role: "user",
            content
          }
        ]
      })
    });
    if (!response.ok) {
      throw new Error(`Provider ${this.id} failed: ${response.status} ${response.statusText}`);
    }
    const payload = await response.json();
    return {
      text: payload.content?.filter((item) => item.type === "text").map((item) => item.text ?? "").join("\n") ?? "",
      usage: payload.usage ? { inputTokens: payload.usage.input_tokens, outputTokens: payload.usage.output_tokens } : void 0
    };
  }
};

// src/providers/gemini.ts
var GeminiProviderAdapter = class extends BaseProviderAdapter {
  apiKey;
  baseUrl;
  constructor(id, model, options) {
    super(id, "gemini", model, ["chat", "structured", "vision", "tools", "streaming"]);
    this.apiKey = options.apiKey;
    this.baseUrl = (options.baseUrl ?? "https://generativelanguage.googleapis.com/v1beta").replace(/\/+$/, "");
  }
  async generateText(request) {
    const encodedAttachments = await this.encodeAttachments(request.attachments);
    const parts = [
      ...request.system ? [{ text: `System instructions:
${request.system}` }] : [],
      { text: request.prompt },
      ...encodedAttachments.map((item) => ({
        inline_data: {
          mime_type: item.mimeType,
          data: item.base64
        }
      }))
    ];
    const response = await fetch(`${this.baseUrl}/models/${this.model}:generateContent`, {
      method: "POST",
      headers: {
        "content-type": "application/json",
        ...this.apiKey ? { "x-goog-api-key": this.apiKey } : {}
      },
      body: JSON.stringify({
        contents: [
          {
            role: "user",
            parts
          }
        ],
        generationConfig: {
          maxOutputTokens: request.maxOutputTokens ?? 1200
        }
      })
    });
    if (!response.ok) {
      throw new Error(`Provider ${this.id} failed: ${response.status} ${response.statusText}`);
    }
    const payload = await response.json();
    const text = payload.candidates?.[0]?.content?.parts?.map((part) => part.text ?? "").join("\n") ?? "";
    return {
      text,
      usage: payload.usageMetadata ? { inputTokens: payload.usageMetadata.promptTokenCount, outputTokens: payload.usageMetadata.candidatesTokenCount } : void 0
    };
  }
};

// src/providers/heuristic.ts
function summarizePrompt(prompt) {
  const cleaned = normalizeWhitespace(prompt);
  if (!cleaned) {
    return "No prompt content provided.";
  }
  return firstSentences(cleaned, 2) || cleaned.slice(0, 280);
}
var HeuristicProviderAdapter = class extends BaseProviderAdapter {
  constructor(id, model) {
    super(id, "heuristic", model, ["chat", "structured", "vision", "local"]);
  }
  async generateText(request) {
    const attachmentHint = request.attachments?.length ? ` Attachments: ${request.attachments.length}.` : "";
    return {
      text: `Heuristic provider response.${attachmentHint} ${summarizePrompt(request.prompt)}`.trim()
    };
  }
};

// src/providers/openai-compatible.ts
import { z as z4 } from "zod";
function buildAuthHeaders(apiKey) {
  return apiKey ? { Authorization: `Bearer ${apiKey}` } : {};
}
var OpenAiCompatibleProviderAdapter = class extends BaseProviderAdapter {
  baseUrl;
  apiKey;
  headers;
  apiStyle;
  constructor(id, type, model, options) {
    super(id, type, model, options.capabilities);
    this.baseUrl = options.baseUrl.replace(/\/+$/, "");
    this.apiKey = options.apiKey;
    this.headers = options.headers;
    this.apiStyle = options.apiStyle ?? "responses";
  }
  async generateText(request) {
    if (this.apiStyle === "chat") {
      return this.generateViaChatCompletions(request);
    }
    return this.generateViaResponses(request);
  }
  async generateStructured(request, schema) {
    if (this.type !== "openai") {
      return super.generateStructured(request, schema);
    }
    const schemaDescription = JSON.stringify(z4.toJSONSchema(schema), null, 2);
    const structuredPrompt = `${request.prompt}

Return JSON only. Follow this JSON Schema exactly:
${schemaDescription}`;
    const text = this.apiStyle === "chat" ? await this.generateStructuredViaChatCompletions({
      ...request,
      prompt: structuredPrompt
    }) : await this.generateStructuredViaResponses({
      ...request,
      prompt: structuredPrompt
    });
    return schema.parse(JSON.parse(extractJson(text)));
  }
  async generateImage(request) {
    const encodedAttachments = await this.encodeAttachments(request.attachments);
    const response = await fetch(`${this.baseUrl}/images/generations`, {
      method: "POST",
      headers: {
        "content-type": "application/json",
        ...buildAuthHeaders(this.apiKey),
        ...this.headers
      },
      body: JSON.stringify({
        model: this.model,
        prompt: request.prompt,
        size: request.width && request.height ? `${Math.max(256, Math.round(request.width))}x${Math.max(256, Math.round(request.height))}` : void 0,
        response_format: "b64_json",
        ...encodedAttachments.length ? {
          input_image: encodedAttachments.map((item) => `data:${item.mimeType};base64,${item.base64}`)
        } : {}
      })
    });
    if (!response.ok) {
      throw new Error(`Provider ${this.id} failed: ${response.status} ${response.statusText}`);
    }
    const payload = await response.json();
    const image = payload.data?.[0];
    if (!image?.b64_json) {
      throw new Error(`Provider ${this.id} returned no image data.`);
    }
    return {
      mimeType: "image/png",
      bytes: Buffer.from(image.b64_json, "base64"),
      width: request.width,
      height: request.height,
      revisedPrompt: image.revised_prompt
    };
  }
  async generateViaResponses(request) {
    const encodedAttachments = await this.encodeAttachments(request.attachments);
    const input = encodedAttachments.length ? [
      {
        role: "user",
        content: [
          { type: "input_text", text: request.prompt },
          ...encodedAttachments.map((item) => ({
            type: "input_image",
            image_url: `data:${item.mimeType};base64,${item.base64}`
          }))
        ]
      }
    ] : request.prompt;
    const response = await fetch(`${this.baseUrl}/responses`, {
      method: "POST",
      headers: {
        "content-type": "application/json",
        ...buildAuthHeaders(this.apiKey),
        ...this.headers
      },
      body: JSON.stringify({
        model: this.model,
        input,
        instructions: request.system,
        max_output_tokens: request.maxOutputTokens
      })
    });
    if (!response.ok) {
      throw new Error(`Provider ${this.id} failed: ${response.status} ${response.statusText}`);
    }
    const payload = await response.json();
    return {
      text: payload.output_text ?? "",
      usage: payload.usage ? { inputTokens: payload.usage.input_tokens, outputTokens: payload.usage.output_tokens } : void 0
    };
  }
  async generateStructuredViaResponses(request) {
    const encodedAttachments = await this.encodeAttachments(request.attachments);
    const input = encodedAttachments.length ? [
      {
        role: "user",
        content: [
          { type: "input_text", text: request.prompt },
          ...encodedAttachments.map((item) => ({
            type: "input_image",
            image_url: `data:${item.mimeType};base64,${item.base64}`
          }))
        ]
      }
    ] : request.prompt;
    const response = await fetch(`${this.baseUrl}/responses`, {
      method: "POST",
      headers: {
        "content-type": "application/json",
        ...buildAuthHeaders(this.apiKey),
        ...this.headers
      },
      body: JSON.stringify({
        model: this.model,
        input,
        instructions: request.system,
        max_output_tokens: request.maxOutputTokens,
        text: {
          format: {
            type: "json_object"
          }
        }
      })
    });
    if (!response.ok) {
      throw new Error(`Provider ${this.id} failed: ${response.status} ${response.statusText}`);
    }
    const payload = await response.json();
    return payload.output_text ?? "";
  }
  async generateViaChatCompletions(request) {
    const encodedAttachments = await this.encodeAttachments(request.attachments);
    const content = encodedAttachments.length ? [
      { type: "text", text: request.prompt },
      ...encodedAttachments.map((item) => ({
        type: "image_url",
        image_url: {
          url: `data:${item.mimeType};base64,${item.base64}`
        }
      }))
    ] : request.prompt;
    const messages = [...request.system ? [{ role: "system", content: request.system }] : [], { role: "user", content }];
    const response = await fetch(`${this.baseUrl}/chat/completions`, {
      method: "POST",
      headers: {
        "content-type": "application/json",
        ...buildAuthHeaders(this.apiKey),
        ...this.headers
      },
      body: JSON.stringify({
        model: this.model,
        messages,
        max_tokens: request.maxOutputTokens
      })
    });
    if (!response.ok) {
      throw new Error(`Provider ${this.id} failed: ${response.status} ${response.statusText}`);
    }
    const payload = await response.json();
    const contentValue = payload.choices?.[0]?.message?.content;
    const text = Array.isArray(contentValue) ? contentValue.map((item) => item.text ?? "").join("\n") : contentValue ?? "";
    return {
      text,
      usage: payload.usage ? { inputTokens: payload.usage.prompt_tokens, outputTokens: payload.usage.completion_tokens } : void 0
    };
  }
  async generateStructuredViaChatCompletions(request) {
    const encodedAttachments = await this.encodeAttachments(request.attachments);
    const content = encodedAttachments.length ? [
      { type: "text", text: request.prompt },
      ...encodedAttachments.map((item) => ({
        type: "image_url",
        image_url: {
          url: `data:${item.mimeType};base64,${item.base64}`
        }
      }))
    ] : request.prompt;
    const messages = [...request.system ? [{ role: "system", content: request.system }] : [], { role: "user", content }];
    const response = await fetch(`${this.baseUrl}/chat/completions`, {
      method: "POST",
      headers: {
        "content-type": "application/json",
        ...buildAuthHeaders(this.apiKey),
        ...this.headers
      },
      body: JSON.stringify({
        model: this.model,
        messages,
        max_tokens: request.maxOutputTokens,
        response_format: {
          type: "json_object"
        }
      })
    });
    if (!response.ok) {
      throw new Error(`Provider ${this.id} failed: ${response.status} ${response.statusText}`);
    }
    const payload = await response.json();
    const contentValue = payload.choices?.[0]?.message?.content;
    return Array.isArray(contentValue) ? contentValue.map((item) => item.text ?? "").join("\n") : contentValue ?? "";
  }
};

// src/providers/registry.ts
var customModuleSchema = z5.object({
  createAdapter: z5.function({
    input: [z5.string(), z5.custom(), z5.string()],
    output: z5.promise(z5.custom())
  })
});
function resolveCapabilities(config, fallback) {
  return config.capabilities?.length ? config.capabilities : fallback;
}
function envOrUndefined(name) {
  return name ? process.env[name] : void 0;
}
async function createProvider(id, config, rootDir) {
  switch (config.type) {
    case "heuristic":
      return new HeuristicProviderAdapter(id, config.model);
    case "openai":
      return new OpenAiCompatibleProviderAdapter(id, "openai", config.model, {
        baseUrl: config.baseUrl ?? "https://api.openai.com/v1",
        apiKey: envOrUndefined(config.apiKeyEnv),
        headers: config.headers,
        apiStyle: config.apiStyle ?? "responses",
        capabilities: resolveCapabilities(config, ["responses", "chat", "structured", "tools", "vision", "streaming", "image_generation"])
      });
    case "ollama":
      return new OpenAiCompatibleProviderAdapter(id, "ollama", config.model, {
        baseUrl: config.baseUrl ?? "http://localhost:11434/v1",
        apiKey: envOrUndefined(config.apiKeyEnv) ?? "ollama",
        headers: config.headers,
        apiStyle: config.apiStyle ?? "responses",
        capabilities: resolveCapabilities(config, ["responses", "chat", "structured", "tools", "vision", "streaming", "local"])
      });
    case "openai-compatible":
      return new OpenAiCompatibleProviderAdapter(id, "openai-compatible", config.model, {
        baseUrl: config.baseUrl ?? "http://localhost:8000/v1",
        apiKey: envOrUndefined(config.apiKeyEnv),
        headers: config.headers,
        apiStyle: config.apiStyle ?? "responses",
        capabilities: resolveCapabilities(config, ["chat", "structured"])
      });
    case "anthropic":
      return new AnthropicProviderAdapter(id, config.model, {
        apiKey: envOrUndefined(config.apiKeyEnv),
        headers: config.headers,
        baseUrl: config.baseUrl
      });
    case "gemini":
      return new GeminiProviderAdapter(id, config.model, {
        apiKey: envOrUndefined(config.apiKeyEnv),
        baseUrl: config.baseUrl
      });
    case "custom": {
      if (!config.module) {
        throw new Error(`Provider ${id} is type "custom" but no module path was configured.`);
      }
      const resolvedModule = path3.isAbsolute(config.module) ? config.module : path3.resolve(rootDir, config.module);
      const loaded = await import(pathToFileURL(resolvedModule).href);
      const parsed = customModuleSchema.parse(loaded);
      return parsed.createAdapter(id, config, rootDir);
    }
    default:
      throw new Error(`Unsupported provider type ${String(config.type)}`);
  }
}
async function getProviderForTask(rootDir, task) {
  const { config } = await loadVaultConfig(rootDir);
  const providerId = config.tasks[task];
  if (!providerId) {
    throw new Error(`No provider configured for task "${String(task)}".`);
  }
  const providerConfig = config.providers[providerId];
  if (!providerConfig) {
    throw new Error(`No provider configured with id "${providerId}" for task "${task}".`);
  }
  return createProvider(providerId, providerConfig, rootDir);
}
function assertProviderCapability(provider, capability) {
  if (!provider.capabilities.has(capability)) {
    throw new Error(`Provider ${provider.id} does not support required capability "${capability}".`);
  }
}
async function getResolvedPaths(rootDir) {
  return (await loadVaultConfig(rootDir)).paths;
}

export {
  slugify,
  sha256,
  ensureDir,
  fileExists,
  readJsonFile,
  writeJsonFile,
  appendJsonLine,
  writeFileIfChanged,
  toPosix,
  firstSentences,
  uniqueBy,
  normalizeWhitespace,
  truncate,
  listFilesRecursive,
  defaultVaultConfig,
  defaultVaultSchema,
  resolvePaths,
  loadVaultConfig,
  initWorkspace,
  createProvider,
  getProviderForTask,
  assertProviderCapability,
  getResolvedPaths
};
