// src/model-resolution/failover.ts
var UPSTREAM_UNAVAILABLE_CODES = [
  "provider_inference_unavailable",
  "upstream_unavailable",
  "insufficient_quota",
  "model_not_available",
  "server_error",
  "bad_gateway",
  "service_unavailable"
];
var UPSTREAM_UNAVAILABLE_STATUSES = [429, 500, 502, 503, 504];
var UPSTREAM_UNAVAILABLE_MESSAGES = [
  "bad gateway",
  "service unavailable",
  "inference temporarily unavailable",
  "provider inference is unavailable",
  "insufficient balance",
  "usage limits",
  "quota exceeded",
  "rate limit",
  "overloaded",
  "temporarily unavailable"
];
function readString(source, key) {
  const value = source[key];
  return typeof value === "string" && value.trim().length > 0 ? value : void 0;
}
function isUpstreamUnavailable(signal) {
  if (signal === null || typeof signal !== "object") return false;
  const record = signal;
  if (record.success === true) return false;
  const nested = record.error;
  const nestedRecord = nested !== null && typeof nested === "object" ? nested : void 0;
  const code = readString(record, "errorCode") ?? readString(record, "code") ?? (nestedRecord ? readString(nestedRecord, "code") ?? readString(nestedRecord, "type") : void 0);
  if (code && UPSTREAM_UNAVAILABLE_CODES.includes(code)) return true;
  for (const key of ["status", "statusCode", "httpStatus"]) {
    const value = record[key];
    if (typeof value === "number" && UPSTREAM_UNAVAILABLE_STATUSES.includes(value)) return true;
  }
  const message = readString(record, "message") ?? readString(record, "error") ?? (nestedRecord ? readString(nestedRecord, "message") : void 0);
  if (!message) return false;
  const lowered = message.toLowerCase();
  return UPSTREAM_UNAVAILABLE_MESSAGES.some((fragment) => lowered.includes(fragment));
}
var ModelFailoverExhaustedError = class extends Error {
  attempts;
  constructor(attempts) {
    const trail = attempts.map((a) => `${a.model}: ${a.reason ?? "failed"}`).join(" | ");
    super(`All ${attempts.length} model(s) failed. ${trail}`);
    this.name = "ModelFailoverExhaustedError";
    this.attempts = attempts;
  }
};
function describe(signal) {
  if (signal instanceof Error) return signal.message;
  if (signal !== null && typeof signal === "object") {
    const record = signal;
    const message = readString(record, "error") ?? readString(record, "message") ?? readString(record, "errorCode");
    if (message) return message;
  }
  return String(signal);
}
async function runWithModelFailover(input) {
  const models = input.models.map((m) => m.trim()).filter((m) => m.length > 0);
  if (models.length === 0) throw new Error("runWithModelFailover requires at least one model");
  const isUnavailableResult = input.isUnavailableResult ?? ((r) => isUpstreamUnavailable(r));
  const isUnavailableError = input.isUnavailableError ?? isUpstreamUnavailable;
  const attempts = [];
  for (let index = 0; index < models.length; index += 1) {
    const model = models[index];
    let result;
    try {
      result = await input.run(model);
    } catch (error) {
      if (!isUnavailableError(error)) throw error;
      const attempt = { model, ok: false, reason: describe(error) };
      attempts.push(attempt);
      const next = models[index + 1];
      if (next) input.onFallback?.(attempt, next);
      continue;
    }
    if (isUnavailableResult(result)) {
      const attempt = { model, ok: false, reason: describe(result) };
      attempts.push(attempt);
      const next = models[index + 1];
      if (next) input.onFallback?.(attempt, next);
      continue;
    }
    attempts.push({ model, ok: true });
    return { value: result, model, attempts, usedFallback: index > 0 };
  }
  throw new ModelFailoverExhaustedError(attempts);
}
function buildModelChain(preferred, fallbacks) {
  const chain = [];
  for (const model of [preferred, ...fallbacks]) {
    const cleaned = typeof model === "string" ? model.trim() : "";
    if (cleaned.length > 0 && !chain.includes(cleaned)) chain.push(cleaned);
  }
  return chain;
}

// src/model-resolution/index.ts
function canonicalModelId(model) {
  if (model.id.includes("/")) return model.id;
  const provider = model._provider ?? model.provider;
  return provider ? `${provider}/${model.id}` : model.id;
}
function resolveChatModel(input) {
  const request = cleanModelId(input.requestModel);
  if (request) return { model: request, source: "request" };
  const workspace = cleanModelId(input.workspaceModel);
  if (workspace) return { model: workspace, source: "workspace" };
  const env = cleanModelId(input.envModel);
  if (env) return { model: env, source: "env" };
  return { model: input.defaultModel, source: "default" };
}
async function validateChatModelId(modelId, input) {
  const cleaned = cleanModelId(modelId);
  if (!cleaned) return { succeeded: false, error: "Model id must be a non-empty string." };
  if (!isWellFormedModelId(cleaned)) return { succeeded: false, error: `Model id is malformed: ${cleaned}` };
  const allowed = new Set(input.allowlist ?? []);
  if (allowed.has(cleaned)) return { succeeded: true, value: cleaned };
  if (cleanModelId(input.envModel) === cleaned) return { succeeded: true, value: cleaned };
  if (!input.loadModels || typeof input.routerBaseUrl !== "string" || input.routerBaseUrl.length === 0) {
    return { succeeded: false, error: `Model is not available: ${cleaned}` };
  }
  let catalog;
  try {
    catalog = await input.loadModels(input.routerBaseUrl);
  } catch (err) {
    const message = err instanceof Error ? err.message : String(err);
    return { succeeded: false, error: `Could not validate model catalog: ${message}` };
  }
  const ids = new Set(catalog.flatMap(catalogIdsForModel));
  if (ids.has(cleaned)) return { succeeded: true, value: cleaned };
  if (!cleaned.includes("/")) {
    const canonicalBySuffix = /* @__PURE__ */ new Map();
    for (const model of catalog) {
      if (typeof model.id !== "string" || !model.id.trim()) continue;
      const canonical = canonicalModelId(model);
      if (!canonical.includes("/")) continue;
      const suffix = canonical.split("/").slice(1).join("/");
      const entries = canonicalBySuffix.get(suffix);
      if (entries) entries.push(canonical);
      else canonicalBySuffix.set(suffix, [canonical]);
    }
    const matches = canonicalBySuffix.get(cleaned);
    if (matches && matches.length === 1) return { succeeded: true, value: matches[0] };
  }
  return { succeeded: false, error: `Model is not available: ${cleaned}` };
}
function cleanModelId(value) {
  if (typeof value !== "string") return void 0;
  const trimmed = value.trim();
  return trimmed.length > 0 ? trimmed : void 0;
}
function isWellFormedModelId(modelId) {
  if (modelId.length > 200) return false;
  return /^[A-Za-z0-9._/@:-]+$/.test(modelId);
}
function catalogIdsForModel(model) {
  const ids = /* @__PURE__ */ new Set();
  if (typeof model.id === "string" && model.id.trim()) ids.add(model.id.trim());
  if (typeof model.id === "string" && model.id.trim() && !model.id.includes("/")) {
    const canonical = canonicalModelId(model);
    if (canonical.includes("/")) ids.add(canonical);
  }
  return [...ids];
}
export {
  ModelFailoverExhaustedError,
  UPSTREAM_UNAVAILABLE_CODES,
  UPSTREAM_UNAVAILABLE_STATUSES,
  buildModelChain,
  catalogIdsForModel,
  cleanModelId,
  isUpstreamUnavailable,
  isWellFormedModelId,
  resolveChatModel,
  runWithModelFailover,
  validateChatModelId
};
//# sourceMappingURL=index.js.map