Anforderungen  |   Konzepte  |   Entwurf  |   Entwicklung  |   Qualitätssicherung  |   Lebenszyklus  |   Steuerung
 
 
 
 


Quelle  scan.ts

  Sprache: JAVA
 

Spracherkennung für: .ts vermutete Sprache: Unknown {[0] [0] [0]} [Methode: Schwerpunktbildung, einfache Gewichte, sechs Dimensionen]

import { cancel, multiselect as clackMultiselect, isCancel } from "@clack/prompts";
import { resolveApiKeyForProvider } from "../../agents/model-auth.js";
import { type ModelScanResult, scanOpenRouterModels } from "../../agents/model-scan.js";
import { withProgressTotals } from "../../cli/progress.js";
import { logConfigUpdated } from "../../config/logging.js";
import { toAgentModelListLike } from "../../config/model-input.js";
import { type RuntimeEnv, writeRuntimeJson } from "../../runtime.js";
import {
  stylePromptHint,
  stylePromptMessage,
  stylePromptTitle,
} from "../../terminal/prompt-style.js";
import { pad, truncate } from "./list.format.js";
import { loadModelsConfig } from "./load-config.js";
import { formatMs, formatTokenK, updateConfig } from "./shared.js";

const MODEL_PAD = 42;
const CTX_PAD = 8;

const multiselect = <T>(params: Parameters<typeof clackMultiselect<T>>[0]) =>
  clackMultiselect({
    ...params,
    message: stylePromptMessage(params.message),
    options: params.options.map((opt) =>
      opt.hint === undefined ? opt : { ...opt, hint: stylePromptHint(opt.hint) },
    ),
  });

function guardPromptCancel<T>(value: T | symbol, runtime: RuntimeEnv): T {
  if (isCancel(value)) {
    cancel(stylePromptTitle("Model scan cancelled.") ?? "Model scan cancelled.");
    runtime.exit(0);
    throw new Error("unreachable");
  }
  return value;
}

function sortScanResults(results: ModelScanResult[]): ModelScanResult[] {
  return results.slice().toSorted((a, b) => {
    const aImage = a.image.ok ? 1 : 0;
    const bImage = b.image.ok ? 1 : 0;
    if (aImage !== bImage) {
      return bImage - aImage;
    }

    const aToolLatency = a.tool.latencyMs ?? Number.POSITIVE_INFINITY;
    const bToolLatency = b.tool.latencyMs ?? Number.POSITIVE_INFINITY;
    if (aToolLatency !== bToolLatency) {
      return aToolLatency - bToolLatency;
    }

    return compareScanMetadata(a, b);
  });
}

function sortImageResults(results: ModelScanResult[]): ModelScanResult[] {
  return results.slice().toSorted((a, b) => {
    const aLatency = a.image.latencyMs ?? Number.POSITIVE_INFINITY;
    const bLatency = b.image.latencyMs ?? Number.POSITIVE_INFINITY;
    if (aLatency !== bLatency) {
      return aLatency - bLatency;
    }

    return compareScanMetadata(a, b);
  });
}

function compareScanMetadata(a: ModelScanResult, b: ModelScanResult): number {
  const aCtx = a.contextLength ?? 0;
  const bCtx = b.contextLength ?? 0;
  if (aCtx !== bCtx) {
    return bCtx - aCtx;
  }

  const aParams = a.inferredParamB ?? 0;
  const bParams = b.inferredParamB ?? 0;
  if (aParams !== bParams) {
    return bParams - aParams;
  }

  return a.modelRef.localeCompare(b.modelRef);
}

function buildScanHint(result: ModelScanResult): string {
  const toolLabel = result.tool.ok ? `tool ${formatMs(result.tool.latencyMs)}` : "tool fail";
  const imageLabel = result.image.skipped
    ? "img skip"
    : result.image.ok
      ? `img ${formatMs(result.image.latencyMs)}`
      : "img fail";
  const ctxLabel = result.contextLength ? `ctx ${formatTokenK(result.contextLength)}` : "ctx ?";
  const paramLabel = result.inferredParamB ? `${result.inferredParamB}b` : null;
  return [toolLabel, imageLabel, ctxLabel, paramLabel].filter(Boolean).join(" | ");
}

function printScanSummary(results: ModelScanResult[], runtime: RuntimeEnv) {
  const toolOk = results.filter((r) => r.tool.ok);
  const imageOk = results.filter((r) => r.image.ok);
  const toolImageOk = results.filter((r) => r.tool.ok && r.image.ok);
  const imageOnly = imageOk.filter((r) => !r.tool.ok);
  runtime.log(
    `Scan results: tested ${results.length}, tool ok ${toolOk.length}, image ok ${imageOk.length}, tool+image ok ${toolImageOk.length}, image only ${imageOnly.length}`,
  );
}

function printScanTable(results: ModelScanResult[], runtime: RuntimeEnv) {
  const header = [
    pad("Model", MODEL_PAD),
    pad("Tool", 10),
    pad("Image", 10),
    pad("Ctx", CTX_PAD),
    pad("Params", 8),
    "Notes",
  ].join(" ");
  runtime.log(header);

  for (const entry of results) {
    const modelLabel = pad(truncate(entry.modelRef, MODEL_PAD), MODEL_PAD);
    const toolLabel = pad(entry.tool.ok ? formatMs(entry.tool.latencyMs) : "fail", 10);
    const imageLabel = pad(
      entry.image.ok ? formatMs(entry.image.latencyMs) : entry.image.skipped ? "skip" : "fail",
      10,
    );
    const ctxLabel = pad(formatTokenK(entry.contextLength), CTX_PAD);
    const paramsLabel = pad(entry.inferredParamB ? `${entry.inferredParamB}b` : "-", 8);
    const notes = entry.modality ? `modality:${entry.modality}` : "";

    runtime.log([modelLabel, toolLabel, imageLabel, ctxLabel, paramsLabel, notes].join(" "));
  }
}

export async function modelsScanCommand(
  opts: {
    minParams?: string;
    maxAgeDays?: string;
    provider?: string;
    maxCandidates?: string;
    timeout?: string;
    concurrency?: string;
    yes?: boolean;
    input?: boolean;
    setDefault?: boolean;
    setImage?: boolean;
    json?: boolean;
    probe?: boolean;
  },
  runtime: RuntimeEnv,
) {
  const minParams = opts.minParams ? Number(opts.minParams) : undefined;
  if (minParams !== undefined && (!Number.isFinite(minParams) || minParams < 0)) {
    throw new Error("--min-params must be >= 0");
  }
  const maxAgeDays = opts.maxAgeDays ? Number(opts.maxAgeDays) : undefined;
  if (maxAgeDays !== undefined && (!Number.isFinite(maxAgeDays) || maxAgeDays < 0)) {
    throw new Error("--max-age-days must be >= 0");
  }
  const maxCandidates = opts.maxCandidates ? Number(opts.maxCandidates) : 6;
  if (!Number.isFinite(maxCandidates) || maxCandidates <= 0) {
    throw new Error("--max-candidates must be > 0");
  }
  const timeout = opts.timeout ? Number(opts.timeout) : undefined;
  if (timeout !== undefined && (!Number.isFinite(timeout) || timeout <= 0)) {
    throw new Error("--timeout must be > 0");
  }
  const concurrency = opts.concurrency ? Number(opts.concurrency) : undefined;
  if (concurrency !== undefined && (!Number.isFinite(concurrency) || concurrency <= 0)) {
    throw new Error("--concurrency must be > 0");
  }

  const cfg = await loadModelsConfig({ commandName: "models scan", runtime });
  const probe = opts.probe ?? true;
  let storedKey: string | undefined;
  if (probe) {
    try {
      const resolved = await resolveApiKeyForProvider({
        provider: "openrouter",
        cfg,
      });
      storedKey = resolved.apiKey;
    } catch {
      storedKey = undefined;
    }
  }
  const results = await withProgressTotals(
    {
      label: "Scanning OpenRouter models...",
      indeterminate: false,
      enabled: opts.json !== true,
    },
    async (update) =>
      await scanOpenRouterModels({
        apiKey: storedKey ?? undefined,
        minParamB: minParams,
        maxAgeDays,
        providerFilter: opts.provider,
        timeoutMs: timeout,
        concurrency,
        probe,
        onProgress: ({ phase, completed, total }) => {
          if (phase !== "probe") {
            return;
          }
          const labelBase = probe ? "Probing models" : "Scanning models";
          update({
            completed,
            total,
            label: `${labelBase} (${completed}/${total})`,
          });
        },
      }),
  );

  if (!probe) {
    if (!opts.json) {
      runtime.log(
        `Found ${results.length} OpenRouter free models (metadata only; pass --probe to test tools/images).`,
      );
      printScanTable(sortScanResults(results), runtime);
    } else {
      writeRuntimeJson(runtime, results);
    }
    return;
  }

  const toolOk = results.filter((entry) => entry.tool.ok);
  if (toolOk.length === 0) {
    throw new Error("No tool-capable OpenRouter free models found.");
  }

  const sorted = sortScanResults(results);
  const toolSorted = sortScanResults(toolOk);
  const imageOk = results.filter((entry) => entry.image.ok);
  const imageSorted = sortImageResults(imageOk);
  const imagePreferred = toolSorted.filter((entry) => entry.image.ok);
  const preselectPool = imagePreferred.length > 0 ? imagePreferred : toolSorted;
  const preselected = preselectPool
    .slice(0, Math.floor(maxCandidates))
    .map((entry) => entry.modelRef);
  const imagePreselected = imageSorted
    .slice(0, Math.floor(maxCandidates))
    .map((entry) => entry.modelRef);

  if (!opts.json) {
    printScanSummary(results, runtime);
    printScanTable(sorted, runtime);
  }

  const noInput = opts.input === false;
  const canPrompt = process.stdin.isTTY && !opts.yes && !noInput && !opts.json;
  let selected: string[] = preselected;
  let selectedImages: string[] = imagePreselected;

  if (canPrompt) {
    const selection = await multiselect({
      message: "Select fallback models (ordered)",
      options: toolSorted.map((entry) => ({
        value: entry.modelRef,
        label: entry.modelRef,
        hint: buildScanHint(entry),
      })),
      initialValues: preselected,
    });

    selected = guardPromptCancel(selection, runtime);
    if (imageSorted.length > 0) {
      const imageSelection = await multiselect({
        message: "Select image fallback models (ordered)",
        options: imageSorted.map((entry) => ({
          value: entry.modelRef,
          label: entry.modelRef,
          hint: buildScanHint(entry),
        })),
        initialValues: imagePreselected,
      });

      selectedImages = guardPromptCancel(imageSelection, runtime);
    }
  } else if (!process.stdin.isTTY && !opts.yes && !noInput && !opts.json) {
    throw new Error("Non-interactive scan: pass --yes to apply defaults.");
  }

  if (selected.length === 0) {
    throw new Error("No models selected for fallbacks.");
  }
  if (opts.setImage && selectedImages.length === 0) {
    throw new Error("No image-capable models selected for image model.");
  }

  const _updated = await updateConfig((cfg) => {
    const nextModels = { ...cfg.agents?.defaults?.models };
    for (const entry of selected) {
      if (!nextModels[entry]) {
        nextModels[entry] = {};
      }
    }
    for (const entry of selectedImages) {
      if (!nextModels[entry]) {
        nextModels[entry] = {};
      }
    }
    const existingImageModel = toAgentModelListLike(cfg.agents?.defaults?.imageModel);
    const nextImageModel =
      selectedImages.length > 0
        ? {
            ...(existingImageModel?.primary ? { primary: existingImageModel.primary } : undefined),
            fallbacks: selectedImages,
            ...(opts.setImage ? { primary: selectedImages[0] } : {}),
          }
        : cfg.agents?.defaults?.imageModel;
    const existingModel = toAgentModelListLike(cfg.agents?.defaults?.model);
    const defaults = {
      ...cfg.agents?.defaults,
      model: {
        ...(existingModel?.primary ? { primary: existingModel.primary } : undefined),
        fallbacks: selected,
        ...(opts.setDefault ? { primary: selected[0] } : {}),
      },
      ...(nextImageModel ? { imageModel: nextImageModel } : {}),
      models: nextModels,
    } satisfies NonNullable<NonNullable<typeof cfg.agents>["defaults"]>;
    return {
      ...cfg,
      agents: {
        ...cfg.agents,
        defaults,
      },
    };
  });

  if (opts.json) {
    writeRuntimeJson(runtime, {
      selected,
      selectedImages,
      setDefault: Boolean(opts.setDefault),
      setImage: Boolean(opts.setImage),
      results,
      warnings: [],
    });
    return;
  }

  logConfigUpdated(runtime);
  runtime.log(`Fallbacks: ${selected.join(", ")}`);
  if (selectedImages.length > 0) {
    runtime.log(`Image fallbacks: ${selectedImages.join(", ")}`);
  }
  if (opts.setDefault) {
    runtime.log(`Default model: ${selected[0]}`);
  }
  if (opts.setImage && selectedImages.length > 0) {
    runtime.log(`Image model: ${selectedImages[0]}`);
  }
}

¤ Dauer der Verarbeitung: 0.22 Sekunden  (vorverarbeitet am  2026-04-27) ¤

*© Formatika GbR, Deutschland






Wurzel

Suchen

Beweissystem der NASA

Beweissystem Isabelle

NIST Cobol Testsuite

Cephes Mathematical Library

Wiener Entwicklungsmethode

Haftungshinweis

Die Informationen auf dieser Webseite wurden nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit, noch Qualität der bereit gestellten Informationen zugesichert.

Bemerkung:

Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.






                                                                                                                                                                                                                                                                                                                                                                                                     


Neuigkeiten

     Aktuelles
     Motto des Tages

Software

     Produkte
     Quellcodebibliothek

Aktivitäten

     Artikel über Sicherheit
     Anleitung zur Aktivierung von SSL

Muße

     Gedichte
     Musik
     Bilder

Jenseits des Üblichen ....

Besucherstatistik

Besucherstatistik

Monitoring

Montastic status badge