Quellcodebibliothek Statistik Leitseite products/Sources/formale Sprachen/JAVA/Openclaw/extensions/discord/src/monitor/   (KI Agentensystem Version 22©)  Datei vom 26.3.2026 mit Größe 36 kB image not shown  

Quelle  native-command-ui.ts

  Sprache: JAVA
 

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

import {
  Button,
  Container,
  Row,
  StringSelectMenu,
  TextDisplay,
  type AutocompleteInteraction,
  type ButtonInteraction,
  type CommandInteraction,
  type ComponentData,
  type StringSelectMenuInteraction,
} from "@buape/carbon";
import { ButtonStyle } from "discord-api-types/v10";
import { resolveDefaultModelForAgent } from "openclaw/plugin-sdk/agent-runtime";
import {
  buildCommandTextFromArgs,
  findCommandByNativeName,
  listChatCommands,
  resolveStoredModelOverride,
  serializeCommandArgs,
  type ChatCommandDefinition,
  type CommandArgDefinition,
  type CommandArgValues,
  type CommandArgs,
} from "openclaw/plugin-sdk/command-auth";
import type { OpenClawConfig, loadConfig } from "openclaw/plugin-sdk/config-runtime";
import { loadSessionStore, resolveStorePath } from "openclaw/plugin-sdk/config-runtime";
import type { ResolvedAgentRoute } from "openclaw/plugin-sdk/routing";
import { logVerbose } from "openclaw/plugin-sdk/runtime-env";
import {
  chunkItems,
  normalizeLowercaseStringOrEmpty,
  normalizeOptionalString,
  withTimeout,
} from "openclaw/plugin-sdk/text-runtime";
import { resolveDiscordSlashCommandConfig } from "./commands.js";
import {
  readDiscordModelPickerRecentModels,
  recordDiscordModelPickerRecentModel,
  type DiscordModelPickerPreferenceScope,
} from "./model-picker-preferences.js";
import {
  DISCORD_MODEL_PICKER_CUSTOM_ID_KEY,
  loadDiscordModelPickerData,
  parseDiscordModelPickerData,
  renderDiscordModelPickerModelsView,
  renderDiscordModelPickerProvidersView,
  renderDiscordModelPickerRecentsView,
  toDiscordModelPickerMessagePayload,
  type DiscordModelPickerCommandContext,
} from "./model-picker.js";
import { resolveDiscordNativeInteractionRouteState } from "./native-command-route.js";
import { resolveDiscordNativeInteractionChannelContext } from "./native-interaction-channel-context.js";
import type { ThreadBindingManager } from "./thread-bindings.js";

type DiscordConfig = NonNullable<OpenClawConfig["channels"]>["discord"];

const DISCORD_COMMAND_ARG_CUSTOM_ID_KEY = "cmdarg";

export type DiscordCommandArgContext = {
  cfg: ReturnType<typeof loadConfig>;
  discordConfig: DiscordConfig;
  accountId: string;
  sessionPrefix: string;
  threadBindings: ThreadBindingManager;
  postApplySettleMs?: number;
};

export type DiscordModelPickerContext = DiscordCommandArgContext;

export type DispatchDiscordCommandInteractionParams = {
  interaction: CommandInteraction | ButtonInteraction | StringSelectMenuInteraction;
  prompt: string;
  command: ChatCommandDefinition;
  commandArgs?: CommandArgs;
  cfg: ReturnType<typeof loadConfig>;
  discordConfig: DiscordConfig;
  accountId: string;
  sessionPrefix: string;
  preferFollowUp: boolean;
  threadBindings: ThreadBindingManager;
  responseEphemeral?: boolean;
  suppressReplies?: boolean;
};

export type DispatchDiscordCommandInteraction = (
  params: DispatchDiscordCommandInteractionParams,
) => Promise<void>;

export type SafeDiscordInteractionCall = <T>(
  label: string,
  fn: () => Promise<T>,
) => Promise<T | null>;

function createCommandArgsWithValue(params: { argName: string; value: string }): CommandArgs {
  const values: CommandArgValues = { [params.argName]: params.value };
  return { values };
}

function encodeDiscordCommandArgValue(value: string): string {
  return encodeURIComponent(value);
}

function decodeDiscordCommandArgValue(value: string): string {
  try {
    return decodeURIComponent(value);
  } catch {
    return value;
  }
}

export function buildDiscordCommandArgCustomId(params: {
  command: string;
  arg: string;
  value: string;
  userId: string;
}): string {
  return [
    `${DISCORD_COMMAND_ARG_CUSTOM_ID_KEY}:command=${encodeDiscordCommandArgValue(params.command)}`,
    `arg=${encodeDiscordCommandArgValue(params.arg)}`,
    `value=${encodeDiscordCommandArgValue(params.value)}`,
    `user=${encodeDiscordCommandArgValue(params.userId)}`,
  ].join(";");
}

function parseDiscordCommandArgData(
  data: ComponentData,
): { command: string; arg: string; value: string; userId: string } | null {
  if (!data || typeof data !== "object") {
    return null;
  }
  const coerce = (value: unknown) =>
    typeof value === "string" || typeof value === "number" ? String(value) : "";
  const rawCommand = coerce(data.command);
  const rawArg = coerce(data.arg);
  const rawValue = coerce(data.value);
  const rawUser = coerce(data.user);
  if (!rawCommand || !rawArg || !rawValue || !rawUser) {
    return null;
  }
  return {
    command: decodeDiscordCommandArgValue(rawCommand),
    arg: decodeDiscordCommandArgValue(rawArg),
    value: decodeDiscordCommandArgValue(rawValue),
    userId: decodeDiscordCommandArgValue(rawUser),
  };
}

function resolveDiscordModelPickerCommandContext(
  command: ChatCommandDefinition,
): DiscordModelPickerCommandContext | null {
  const normalized = normalizeLowercaseStringOrEmpty(command.nativeName ?? command.key);
  if (normalized === "model" || normalized === "models") {
    return normalized;
  }
  return null;
}

function resolveCommandArgStringValue(args: CommandArgs | undefined, key: string): string {
  const value = args?.values?.[key];
  if (typeof value !== "string") {
    return "";
  }
  return value.trim();
}

export function shouldOpenDiscordModelPickerFromCommand(params: {
  command: ChatCommandDefinition;
  commandArgs?: CommandArgs;
}): DiscordModelPickerCommandContext | null {
  const context = resolveDiscordModelPickerCommandContext(params.command);
  if (!context) {
    return null;
  }

  const serializedArgs =
    normalizeOptionalString(serializeCommandArgs(params.command, params.commandArgs)) ?? "";
  if (context === "model") {
    const modelValue = resolveCommandArgStringValue(params.commandArgs, "model");
    return !modelValue && !serializedArgs ? context : null;
  }

  return serializedArgs ? null : context;
}

function buildDiscordModelPickerCurrentModel(
  defaultProvider: string,
  defaultModel: string,
): string {
  return `${defaultProvider}/${defaultModel}`;
}

function buildDiscordModelPickerAllowedModelRefs(
  data: Awaited<ReturnType<typeof loadDiscordModelPickerData>>,
): Set<string> {
  const out = new Set<string>();
  for (const provider of data.providers) {
    const models = data.byProvider.get(provider);
    if (!models) {
      continue;
    }
    for (const model of models) {
      out.add(`${provider}/${model}`);
    }
  }
  return out;
}

function resolveDiscordModelPickerPreferenceScope(params: {
  interaction: CommandInteraction | ButtonInteraction | StringSelectMenuInteraction;
  accountId: string;
  userId: string;
}): DiscordModelPickerPreferenceScope {
  return {
    accountId: params.accountId,
    guildId: params.interaction.guild?.id ?? undefined,
    userId: params.userId,
  };
}

function buildDiscordModelPickerNoticePayload(message: string): { components: Container[] } {
  return {
    components: [new Container([new TextDisplay(message)])],
  };
}

async function resolveDiscordModelPickerRouteState(params: {
  interaction:
    | CommandInteraction
    | ButtonInteraction
    | StringSelectMenuInteraction
    | AutocompleteInteraction;
  cfg: ReturnType<typeof loadConfig>;
  accountId: string;
  threadBindings: ThreadBindingManager;
  enforceConfiguredBindingReadiness?: boolean;
}) {
  const { interaction, cfg, accountId } = params;
  const { isDirectMessage, isGroupDm, isThreadChannel, rawChannelId, threadParentId } =
    await resolveDiscordNativeInteractionChannelContext({
      channel: interaction.channel,
      client: interaction.client,
      hasGuild: Boolean(interaction.guild),
      channelIdFallback: "unknown",
    });
  const memberRoleIds = Array.isArray(interaction.rawData.member?.roles)
    ? interaction.rawData.member.roles.map((roleId: string) => roleId)
    : [];

  const threadBinding = isThreadChannel
    ? params.threadBindings.getByThreadId(rawChannelId)
    : undefined;
  return await resolveDiscordNativeInteractionRouteState({
    cfg,
    accountId,
    guildId: interaction.guild?.id ?? undefined,
    memberRoleIds,
    isDirectMessage,
    isGroupDm,
    directUserId: interaction.user?.id ?? rawChannelId,
    conversationId: rawChannelId,
    parentConversationId: threadParentId,
    threadBinding,
    enforceConfiguredBindingReadiness: params.enforceConfiguredBindingReadiness,
  });
}

async function resolveDiscordModelPickerRoute(params: {
  interaction:
    | CommandInteraction
    | ButtonInteraction
    | StringSelectMenuInteraction
    | AutocompleteInteraction;
  cfg: ReturnType<typeof loadConfig>;
  accountId: string;
  threadBindings: ThreadBindingManager;
}) {
  const resolved = await resolveDiscordModelPickerRouteState(params);
  return resolved.effectiveRoute;
}

export async function resolveDiscordNativeChoiceContext(params: {
  interaction: AutocompleteInteraction;
  cfg: ReturnType<typeof loadConfig>;
  accountId: string;
  threadBindings: ThreadBindingManager;
}): Promise<{ provider?: string; model?: string } | null> {
  try {
    const resolved = await resolveDiscordModelPickerRouteState({
      interaction: params.interaction,
      cfg: params.cfg,
      accountId: params.accountId,
      threadBindings: params.threadBindings,
      enforceConfiguredBindingReadiness: true,
    });
    if (resolved.bindingReadiness && !resolved.bindingReadiness.ok) {
      return null;
    }
    const route = resolved.effectiveRoute;
    const fallback = resolveDefaultModelForAgent({
      cfg: params.cfg,
      agentId: route.agentId,
    });
    const storePath = resolveStorePath(params.cfg.session?.store, {
      agentId: route.agentId,
    });
    const sessionStore = loadSessionStore(storePath);
    const sessionEntry = sessionStore[route.sessionKey];
    const override = resolveStoredModelOverride({
      sessionEntry,
      sessionStore,
      sessionKey: route.sessionKey,
      defaultProvider: fallback.provider,
    });
    if (!override?.model) {
      return {
        provider: fallback.provider,
        model: fallback.model,
      };
    }
    return {
      provider: override.provider || fallback.provider,
      model: override.model,
    };
  } catch {
    return null;
  }
}

function resolveDiscordModelPickerCurrentModel(params: {
  cfg: ReturnType<typeof loadConfig>;
  route: ResolvedAgentRoute;
  data: Awaited<ReturnType<typeof loadDiscordModelPickerData>>;
}): string {
  const fallback = buildDiscordModelPickerCurrentModel(
    params.data.resolvedDefault.provider,
    params.data.resolvedDefault.model,
  );
  try {
    const storePath = resolveStorePath(params.cfg.session?.store, {
      agentId: params.route.agentId,
    });
    const sessionStore = loadSessionStore(storePath, { skipCache: true });
    const sessionEntry = sessionStore[params.route.sessionKey];
    const override = resolveStoredModelOverride({
      sessionEntry,
      sessionStore,
      sessionKey: params.route.sessionKey,
      defaultProvider: params.data.resolvedDefault.provider,
    });
    if (!override?.model) {
      return fallback;
    }
    const provider = (override.provider || params.data.resolvedDefault.provider).trim();
    if (!provider) {
      return fallback;
    }
    return `${provider}/${override.model}`;
  } catch {
    return fallback;
  }
}

function resolveDiscordModelPickerCurrentRuntime(params: {
  cfg: ReturnType<typeof loadConfig>;
  route: ResolvedAgentRoute;
}): string {
  try {
    const storePath = resolveStorePath(params.cfg.session?.store, {
      agentId: params.route.agentId,
    });
    const sessionStore = loadSessionStore(storePath, { skipCache: true });
    const sessionRuntime = normalizeOptionalString(
      sessionStore[params.route.sessionKey]?.agentRuntimeOverride,
    );
    if (sessionRuntime) {
      return sessionRuntime;
    }
  } catch {
    // Fall through to configured defaults when the session store is unavailable.
  }

  const agentRuntime = normalizeOptionalString(
    params.cfg.agents?.list?.find(
      (entry) => normalizeOptionalString(entry.id) === params.route.agentId,
    )?.embeddedHarness?.runtime,
  );
  if (agentRuntime) {
    return agentRuntime;
  }
  return normalizeOptionalString(params.cfg.agents?.defaults?.embeddedHarness?.runtime) ?? "auto";
}

export async function replyWithDiscordModelPickerProviders(params: {
  interaction: CommandInteraction | ButtonInteraction | StringSelectMenuInteraction;
  cfg: ReturnType<typeof loadConfig>;
  command: DiscordModelPickerCommandContext;
  userId: string;
  accountId: string;
  threadBindings: ThreadBindingManager;
  preferFollowUp: boolean;
  safeInteractionCall: SafeDiscordInteractionCall;
}) {
  const route = await resolveDiscordModelPickerRoute({
    interaction: params.interaction,
    cfg: params.cfg,
    accountId: params.accountId,
    threadBindings: params.threadBindings,
  });
  const data = await loadDiscordModelPickerData(params.cfg, route.agentId);
  const currentModel = resolveDiscordModelPickerCurrentModel({
    cfg: params.cfg,
    route,
    data,
  });
  const currentRuntime = resolveDiscordModelPickerCurrentRuntime({
    cfg: params.cfg,
    route,
  });
  const quickModels = await readDiscordModelPickerRecentModels({
    scope: resolveDiscordModelPickerPreferenceScope({
      interaction: params.interaction,
      accountId: params.accountId,
      userId: params.userId,
    }),
    allowedModelRefs: buildDiscordModelPickerAllowedModelRefs(data),
    limit: 5,
  });

  const rendered = renderDiscordModelPickerModelsView({
    command: params.command,
    userId: params.userId,
    data,
    provider: splitDiscordModelRef(currentModel ?? "")?.provider ?? data.resolvedDefault.provider,
    page: 1,
    providerPage: 1,
    currentModel,
    currentRuntime,
    quickModels,
  });
  const payload = {
    ...toDiscordModelPickerMessagePayload(rendered),
    ephemeral: true,
  };

  await params.safeInteractionCall("model picker reply", async () => {
    if (params.preferFollowUp) {
      await params.interaction.followUp(payload);
      return;
    }
    await params.interaction.reply(payload);
  });
}

function resolveModelPickerSelectionValue(
  interaction: ButtonInteraction | StringSelectMenuInteraction,
): string | null {
  const rawValues = (interaction as { values?: string[] }).values;
  if (!Array.isArray(rawValues) || rawValues.length === 0) {
    return null;
  }
  const first = rawValues[0];
  if (typeof first !== "string") {
    return null;
  }
  const trimmed = first.trim();
  return trimmed || null;
}

function buildDiscordModelPickerSelectionCommand(params: {
  modelRef: string;
  runtime?: string;
}): { command: ChatCommandDefinition; args: CommandArgs; prompt: string } | null {
  const commandDefinition =
    findCommandByNativeName("model", "discord") ??
    listChatCommands().find((entry) => entry.key === "model");
  if (!commandDefinition) {
    return null;
  }
  const runtime = normalizeOptionalString(params.runtime);
  const raw = runtime ? `${params.modelRef} --runtime ${runtime}` : params.modelRef;
  const commandArgs: CommandArgs = {
    values: {
      model: params.modelRef,
    },
    raw,
  };
  return {
    command: commandDefinition,
    args: commandArgs,
    prompt: buildCommandTextFromArgs(commandDefinition, commandArgs),
  };
}

function listDiscordModelPickerProviderModels(
  data: Awaited<ReturnType<typeof loadDiscordModelPickerData>>,
  provider: string,
): string[] {
  const modelSet = data.byProvider.get(provider);
  if (!modelSet) {
    return [];
  }
  return [...modelSet].toSorted();
}

function resolveDiscordModelPickerModelIndex(params: {
  data: Awaited<ReturnType<typeof loadDiscordModelPickerData>>;
  provider: string;
  model: string;
}): number | null {
  const models = listDiscordModelPickerProviderModels(params.data, params.provider);
  if (!models.length) {
    return null;
  }
  const index = models.indexOf(params.model);
  if (index < 0) {
    return null;
  }
  return index + 1;
}

function resolveDiscordModelPickerModelByIndex(params: {
  data: Awaited<ReturnType<typeof loadDiscordModelPickerData>>;
  provider: string;
  modelIndex?: number;
}): string | null {
  if (!params.modelIndex || params.modelIndex < 1) {
    return null;
  }
  const models = listDiscordModelPickerProviderModels(params.data, params.provider);
  if (!models.length) {
    return null;
  }
  return models[params.modelIndex - 1] ?? null;
}

function splitDiscordModelRef(modelRef: string): { provider: string; model: string } | null {
  const trimmed = modelRef.trim();
  const slashIndex = trimmed.indexOf("/");
  if (slashIndex <= 0 || slashIndex >= trimmed.length - 1) {
    return null;
  }
  const provider = trimmed.slice(0, slashIndex).trim();
  const model = trimmed.slice(slashIndex + 1).trim();
  if (!provider || !model) {
    return null;
  }
  return { provider, model };
}

export async function handleDiscordModelPickerInteraction(params: {
  interaction: ButtonInteraction | StringSelectMenuInteraction;
  data: ComponentData;
  ctx: DiscordModelPickerContext;
  safeInteractionCall: SafeDiscordInteractionCall;
  dispatchCommandInteraction: DispatchDiscordCommandInteraction;
}) {
  const { interaction, data, ctx } = params;
  const parsed = parseDiscordModelPickerData(data);
  if (!parsed) {
    await params.safeInteractionCall("model picker update", () =>
      interaction.update(
        buildDiscordModelPickerNoticePayload(
          "Sorry, that model picker interaction is no longer available.",
        ),
      ),
    );
    return;
  }

  if (interaction.user?.id && interaction.user.id !== parsed.userId) {
    await params.safeInteractionCall("model picker ack", () => interaction.acknowledge());
    return;
  }

  const route = await resolveDiscordModelPickerRoute({
    interaction,
    cfg: ctx.cfg,
    accountId: ctx.accountId,
    threadBindings: ctx.threadBindings,
  });
  const pickerData = await loadDiscordModelPickerData(ctx.cfg, route.agentId);
  const currentModelRef = resolveDiscordModelPickerCurrentModel({
    cfg: ctx.cfg,
    route,
    data: pickerData,
  });
  const currentRuntime = resolveDiscordModelPickerCurrentRuntime({
    cfg: ctx.cfg,
    route,
  });
  const allowedModelRefs = buildDiscordModelPickerAllowedModelRefs(pickerData);
  const preferenceScope = resolveDiscordModelPickerPreferenceScope({
    interaction,
    accountId: ctx.accountId,
    userId: parsed.userId,
  });
  const quickModels = await readDiscordModelPickerRecentModels({
    scope: preferenceScope,
    allowedModelRefs,
    limit: 5,
  });

  if (parsed.action === "recents") {
    const rendered = renderDiscordModelPickerRecentsView({
      command: parsed.command,
      userId: parsed.userId,
      data: pickerData,
      quickModels,
      currentModel: currentModelRef,
      provider: parsed.provider,
      page: parsed.page,
      providerPage: parsed.providerPage,
    });

    await params.safeInteractionCall("model picker update", () =>
      interaction.update(toDiscordModelPickerMessagePayload(rendered)),
    );
    return;
  }

  if (parsed.action === "back" && parsed.view === "providers") {
    const rendered = renderDiscordModelPickerProvidersView({
      command: parsed.command,
      userId: parsed.userId,
      data: pickerData,
      page: parsed.page,
      currentModel: currentModelRef,
    });

    await params.safeInteractionCall("model picker update", () =>
      interaction.update(toDiscordModelPickerMessagePayload(rendered)),
    );
    return;
  }

  if (parsed.action === "back" && parsed.view === "models") {
    const provider =
      parsed.provider ??
      splitDiscordModelRef(currentModelRef ?? "")?.provider ??
      pickerData.resolvedDefault.provider;

    const rendered = renderDiscordModelPickerModelsView({
      command: parsed.command,
      userId: parsed.userId,
      data: pickerData,
      provider,
      page: parsed.page ?? 1,
      providerPage: parsed.providerPage ?? 1,
      currentModel: currentModelRef,
      currentRuntime,
      quickModels,
    });

    await params.safeInteractionCall("model picker update", () =>
      interaction.update(toDiscordModelPickerMessagePayload(rendered)),
    );
    return;
  }

  if (parsed.action === "provider") {
    const selectedProvider = resolveModelPickerSelectionValue(interaction) ?? parsed.provider;
    if (!selectedProvider || !pickerData.byProvider.has(selectedProvider)) {
      await params.safeInteractionCall("model picker update", () =>
        interaction.update(
          buildDiscordModelPickerNoticePayload("Sorry, that provider isn't available anymore."),
        ),
      );
      return;
    }

    const rendered = renderDiscordModelPickerModelsView({
      command: parsed.command,
      userId: parsed.userId,
      data: pickerData,
      provider: selectedProvider,
      page: 1,
      providerPage: parsed.providerPage ?? parsed.page,
      currentModel: currentModelRef,
      currentRuntime,
      quickModels,
    });

    await params.safeInteractionCall("model picker update", () =>
      interaction.update(toDiscordModelPickerMessagePayload(rendered)),
    );
    return;
  }

  if (parsed.action === "model") {
    const selectedModel = resolveModelPickerSelectionValue(interaction);
    const provider = parsed.provider;
    if (!provider || !selectedModel) {
      await params.safeInteractionCall("model picker update", () =>
        interaction.update(
          buildDiscordModelPickerNoticePayload("Sorry, I couldn't read that model selection."),
        ),
      );
      return;
    }

    const modelIndex = resolveDiscordModelPickerModelIndex({
      data: pickerData,
      provider,
      model: selectedModel,
    });
    if (!modelIndex) {
      await params.safeInteractionCall("model picker update", () =>
        interaction.update(
          buildDiscordModelPickerNoticePayload("Sorry, that model isn't available anymore."),
        ),
      );
      return;
    }

    const modelRef = `${provider}/${selectedModel}`;
    const rendered = renderDiscordModelPickerModelsView({
      command: parsed.command,
      userId: parsed.userId,
      data: pickerData,
      provider,
      page: parsed.page,
      providerPage: parsed.providerPage ?? 1,
      currentModel: currentModelRef,
      currentRuntime,
      pendingModel: modelRef,
      pendingModelIndex: modelIndex,
      pendingRuntime: parsed.runtime,
      quickModels,
    });

    await params.safeInteractionCall("model picker update", () =>
      interaction.update(toDiscordModelPickerMessagePayload(rendered)),
    );
    return;
  }

  if (parsed.action === "runtime") {
    const selectedRuntime =
      resolveModelPickerSelectionValue(interaction) ?? parsed.runtime ?? "auto";
    const provider = parsed.provider;
    if (!provider || !pickerData.byProvider.has(provider)) {
      await params.safeInteractionCall("model picker update", () =>
        interaction.update(
          buildDiscordModelPickerNoticePayload("Sorry, that provider isn't available anymore."),
        ),
      );
      return;
    }

    const selectedModel = resolveDiscordModelPickerModelByIndex({
      data: pickerData,
      provider,
      modelIndex: parsed.modelIndex,
    });
    const pendingModel = selectedModel ? `${provider}/${selectedModel}` : undefined;
    const rendered = renderDiscordModelPickerModelsView({
      command: parsed.command,
      userId: parsed.userId,
      data: pickerData,
      provider,
      page: parsed.page,
      providerPage: parsed.providerPage ?? 1,
      currentModel: currentModelRef,
      currentRuntime,
      ...(pendingModel ? { pendingModel } : {}),
      pendingModelIndex: parsed.modelIndex,
      pendingRuntime: selectedRuntime,
      quickModels,
    });

    await params.safeInteractionCall("model picker update", () =>
      interaction.update(toDiscordModelPickerMessagePayload(rendered)),
    );
    return;
  }

  if (parsed.action === "submit" || parsed.action === "reset" || parsed.action === "quick") {
    let modelRef: string | null = null;

    if (parsed.action === "reset") {
      modelRef = `${pickerData.resolvedDefault.provider}/${pickerData.resolvedDefault.model}`;
    } else if (parsed.action === "quick") {
      const slot = parsed.recentSlot ?? 0;
      modelRef = slot >= 1 ? (quickModels[slot - 1] ?? null) : null;
    } else if (parsed.view === "recents") {
      const defaultModelRef = `${pickerData.resolvedDefault.provider}/${pickerData.resolvedDefault.model}`;
      const dedupedRecents = quickModels.filter((ref) => ref !== defaultModelRef);
      const slot = parsed.recentSlot ?? 0;
      if (slot === 1) {
        modelRef = defaultModelRef;
      } else if (slot >= 2) {
        modelRef = dedupedRecents[slot - 2] ?? null;
      }
    } else {
      const provider = parsed.provider;
      const selectedModel = resolveDiscordModelPickerModelByIndex({
        data: pickerData,
        provider: provider ?? "",
        modelIndex: parsed.modelIndex,
      });
      modelRef = provider && selectedModel ? `${provider}/${selectedModel}` : null;
    }
    const parsedModelRef = modelRef ? splitDiscordModelRef(modelRef) : null;
    if (
      !parsedModelRef ||
      !pickerData.byProvider.get(parsedModelRef.provider)?.has(parsedModelRef.model)
    ) {
      await params.safeInteractionCall("model picker update", () =>
        interaction.update(
          buildDiscordModelPickerNoticePayload(
            "That selection expired. Please choose a model again.",
          ),
        ),
      );
      return;
    }

    const resolvedModelRef = `${parsedModelRef.provider}/${parsedModelRef.model}`;

    const parsedRuntime = normalizeOptionalString(parsed.runtime);
    const runtimeOverride =
      parsedRuntime ??
      (currentRuntime !== "auto" && currentRuntime !== "default" ? currentRuntime : undefined);
    const selectionCommand = buildDiscordModelPickerSelectionCommand({
      modelRef: resolvedModelRef,
      runtime: runtimeOverride,
    });
    if (!selectionCommand) {
      await params.safeInteractionCall("model picker update", () =>
        interaction.update(
          buildDiscordModelPickerNoticePayload("Sorry, /model is unavailable right now."),
        ),
      );
      return;
    }

    const updateResult = await params.safeInteractionCall("model picker update", () =>
      interaction.update(
        buildDiscordModelPickerNoticePayload(`Applying model change to ${resolvedModelRef}...`),
      ),
    );
    if (updateResult === null) {
      return;
    }

    try {
      await withTimeout(
        params.dispatchCommandInteraction({
          interaction,
          prompt: selectionCommand.prompt,
          command: selectionCommand.command,
          commandArgs: selectionCommand.args,
          cfg: ctx.cfg,
          discordConfig: ctx.discordConfig,
          accountId: ctx.accountId,
          sessionPrefix: ctx.sessionPrefix,
          preferFollowUp: true,
          threadBindings: ctx.threadBindings,
          suppressReplies: true,
        }),
        12000,
      );
    } catch (error) {
      if (error instanceof Error && error.message === "timeout") {
        await params.safeInteractionCall("model picker follow-up", () =>
          interaction.followUp({
            ...buildDiscordModelPickerNoticePayload(
              `⏳ Model change to ${resolvedModelRef} is still processing. Check /status in a few seconds.`,
            ),
            ephemeral: true,
          }),
        );
        return;
      }

      await params.safeInteractionCall("model picker follow-up", () =>
        interaction.followUp({
          ...buildDiscordModelPickerNoticePayload(
            `❌ Failed to apply ${resolvedModelRef}. Try /model ${resolvedModelRef} directly.`,
          ),
          ephemeral: true,
        }),
      );
      return;
    }

    const settleMs = ctx.postApplySettleMs ?? 250;
    if (settleMs > 0) {
      await new Promise((resolve) => setTimeout(resolve, settleMs));
    }

    const effectiveModelRef = resolveDiscordModelPickerCurrentModel({
      cfg: ctx.cfg,
      route,
      data: pickerData,
    });
    const persisted = effectiveModelRef === resolvedModelRef;

    if (!persisted) {
      logVerbose(
        `discord: model picker override mismatch — expected ${resolvedModelRef} but read ${effectiveModelRef} from session key ${route.sessionKey}`,
      );
    }

    if (persisted) {
      await recordDiscordModelPickerRecentModel({
        scope: preferenceScope,
        modelRef: resolvedModelRef,
        limit: 5,
      }).catch(() => undefined);
    }

    await params.safeInteractionCall("model picker follow-up", () =>
      interaction.followUp({
        ...buildDiscordModelPickerNoticePayload(
          persisted
            ? `✅ Model set to ${resolvedModelRef}.`
            : `⚠️ Tried to set ${resolvedModelRef}, but current model is ${effectiveModelRef}.`,
        ),
        ephemeral: true,
      }),
    );
    return;
  }

  if (parsed.action === "cancel") {
    const displayModel = currentModelRef ?? "default";
    await params.safeInteractionCall("model picker update", () =>
      interaction.update(buildDiscordModelPickerNoticePayload(`ℹ️ Model kept as ${displayModel}.`)),
    );
  }
}

export async function handleDiscordCommandArgInteraction(params: {
  interaction: ButtonInteraction;
  data: ComponentData;
  ctx: DiscordCommandArgContext;
  safeInteractionCall: SafeDiscordInteractionCall;
  dispatchCommandInteraction: DispatchDiscordCommandInteraction;
}) {
  const { interaction, data, ctx } = params;
  const parsed = parseDiscordCommandArgData(data);
  if (!parsed) {
    await params.safeInteractionCall("command arg update", () =>
      interaction.update({
        content: "Sorry, that selection is no longer available.",
        components: [],
      }),
    );
    return;
  }
  if (interaction.user?.id && interaction.user.id !== parsed.userId) {
    await params.safeInteractionCall("command arg ack", () => interaction.acknowledge());
    return;
  }
  const commandDefinition =
    findCommandByNativeName(parsed.command, "discord") ??
    listChatCommands().find((entry) => entry.key === parsed.command);
  if (!commandDefinition) {
    await params.safeInteractionCall("command arg update", () =>
      interaction.update({
        content: "Sorry, that command is no longer available.",
        components: [],
      }),
    );
    return;
  }
  const argUpdateResult = await params.safeInteractionCall("command arg update", () =>
    interaction.update({
      content: `✅ Selected ${parsed.value}.`,
      components: [],
    }),
  );
  if (argUpdateResult === null) {
    return;
  }
  const commandArgs = createCommandArgsWithValue({
    argName: parsed.arg,
    value: parsed.value,
  });
  const commandArgsWithRaw: CommandArgs = {
    ...commandArgs,
    raw: serializeCommandArgs(commandDefinition, commandArgs),
  };
  const prompt = buildCommandTextFromArgs(commandDefinition, commandArgsWithRaw);
  await params.dispatchCommandInteraction({
    interaction,
    prompt,
    command: commandDefinition,
    commandArgs: commandArgsWithRaw,
    cfg: ctx.cfg,
    discordConfig: ctx.discordConfig,
    accountId: ctx.accountId,
    sessionPrefix: ctx.sessionPrefix,
    preferFollowUp: true,
    threadBindings: ctx.threadBindings,
    responseEphemeral: resolveDiscordSlashCommandConfig(ctx.discordConfig?.slashCommand).ephemeral,
  });
}

class DiscordCommandArgButton extends Button {
  label: string;
  customId: string;
  style = ButtonStyle.Secondary;
  private ctx: DiscordCommandArgContext;
  private safeInteractionCall: SafeDiscordInteractionCall;
  private dispatchCommandInteraction: DispatchDiscordCommandInteraction;

  constructor(params: {
    label: string;
    customId: string;
    ctx: DiscordCommandArgContext;
    safeInteractionCall: SafeDiscordInteractionCall;
    dispatchCommandInteraction: DispatchDiscordCommandInteraction;
  }) {
    super();
    this.label = params.label;
    this.customId = params.customId;
    this.ctx = params.ctx;
    this.safeInteractionCall = params.safeInteractionCall;
    this.dispatchCommandInteraction = params.dispatchCommandInteraction;
  }

  async run(interaction: ButtonInteraction, data: ComponentData) {
    await handleDiscordCommandArgInteraction({
      interaction,
      data,
      ctx: this.ctx,
      safeInteractionCall: this.safeInteractionCall,
      dispatchCommandInteraction: this.dispatchCommandInteraction,
    });
  }
}

export function buildDiscordCommandArgMenu(params: {
  command: ChatCommandDefinition;
  menu: {
    arg: CommandArgDefinition;
    choices: Array<{ value: string; label: string }>;
    title?: string;
  };
  interaction: CommandInteraction;
  ctx: DiscordCommandArgContext;
  safeInteractionCall: SafeDiscordInteractionCall;
  dispatchCommandInteraction: DispatchDiscordCommandInteraction;
}): { content: string; components: Row<Button>[] } {
  const { command, menu, interaction } = params;
  const commandLabel = command.nativeName ?? command.key;
  const userId = interaction.user?.id ?? "";
  const rows = chunkItems(menu.choices, 4).map((choices) => {
    const buttons = choices.map(
      (choice) =>
        new DiscordCommandArgButton({
          label: choice.label,
          customId: buildDiscordCommandArgCustomId({
            command: commandLabel,
            arg: menu.arg.name,
            value: choice.value,
            userId,
          }),
          ctx: params.ctx,
          safeInteractionCall: params.safeInteractionCall,
          dispatchCommandInteraction: params.dispatchCommandInteraction,
        }),
    );
    return new Row(buttons);
  });
  const content =
    menu.title ?? `Choose ${menu.arg.description || menu.arg.name} for /${commandLabel}.`;
  return { content, components: rows };
}

class DiscordCommandArgFallbackButton extends Button {
  label = "cmdarg";
  customId = "cmdarg:seed=1";
  private ctx: DiscordCommandArgContext;
  private safeInteractionCall: SafeDiscordInteractionCall;
  private dispatchCommandInteraction: DispatchDiscordCommandInteraction;

  constructor(params: {
    ctx: DiscordCommandArgContext;
    safeInteractionCall: SafeDiscordInteractionCall;
    dispatchCommandInteraction: DispatchDiscordCommandInteraction;
  }) {
    super();
    this.ctx = params.ctx;
    this.safeInteractionCall = params.safeInteractionCall;
    this.dispatchCommandInteraction = params.dispatchCommandInteraction;
  }

  async run(interaction: ButtonInteraction, data: ComponentData) {
    await handleDiscordCommandArgInteraction({
      interaction,
      data,
      ctx: this.ctx,
      safeInteractionCall: this.safeInteractionCall,
      dispatchCommandInteraction: this.dispatchCommandInteraction,
    });
  }
}

class DiscordModelPickerFallbackButton extends Button {
  label = "modelpick";
  customId = `${DISCORD_MODEL_PICKER_CUSTOM_ID_KEY}:seed=btn`;
  private ctx: DiscordModelPickerContext;
  private safeInteractionCall: SafeDiscordInteractionCall;
  private dispatchCommandInteraction: DispatchDiscordCommandInteraction;

  constructor(params: {
    ctx: DiscordModelPickerContext;
    safeInteractionCall: SafeDiscordInteractionCall;
    dispatchCommandInteraction: DispatchDiscordCommandInteraction;
  }) {
    super();
    this.ctx = params.ctx;
    this.safeInteractionCall = params.safeInteractionCall;
    this.dispatchCommandInteraction = params.dispatchCommandInteraction;
  }

  async run(interaction: ButtonInteraction, data: ComponentData) {
    await handleDiscordModelPickerInteraction({
      interaction,
      data,
      ctx: this.ctx,
      safeInteractionCall: this.safeInteractionCall,
      dispatchCommandInteraction: this.dispatchCommandInteraction,
    });
  }
}

class DiscordModelPickerFallbackSelect extends StringSelectMenu {
  customId = `${DISCORD_MODEL_PICKER_CUSTOM_ID_KEY}:seed=sel`;
  options = [];
  private ctx: DiscordModelPickerContext;
  private safeInteractionCall: SafeDiscordInteractionCall;
  private dispatchCommandInteraction: DispatchDiscordCommandInteraction;

  constructor(params: {
    ctx: DiscordModelPickerContext;
    safeInteractionCall: SafeDiscordInteractionCall;
    dispatchCommandInteraction: DispatchDiscordCommandInteraction;
  }) {
    super();
    this.ctx = params.ctx;
    this.safeInteractionCall = params.safeInteractionCall;
    this.dispatchCommandInteraction = params.dispatchCommandInteraction;
  }

  async run(interaction: StringSelectMenuInteraction, data: ComponentData) {
    await handleDiscordModelPickerInteraction({
      interaction,
      data,
      ctx: this.ctx,
      safeInteractionCall: this.safeInteractionCall,
      dispatchCommandInteraction: this.dispatchCommandInteraction,
    });
  }
}

export function createDiscordCommandArgFallbackButton(params: {
  ctx: DiscordCommandArgContext;
  safeInteractionCall: SafeDiscordInteractionCall;
  dispatchCommandInteraction: DispatchDiscordCommandInteraction;
}): Button {
  return new DiscordCommandArgFallbackButton(params);
}

export function createDiscordModelPickerFallbackButton(params: {
  ctx: DiscordModelPickerContext;
  safeInteractionCall: SafeDiscordInteractionCall;
  dispatchCommandInteraction: DispatchDiscordCommandInteraction;
}): Button {
  return new DiscordModelPickerFallbackButton(params);
}

export function createDiscordModelPickerFallbackSelect(params: {
  ctx: DiscordModelPickerContext;
  safeInteractionCall: SafeDiscordInteractionCall;
  dispatchCommandInteraction: DispatchDiscordCommandInteraction;
}): StringSelectMenu {
  return new DiscordModelPickerFallbackSelect(params);
}

¤ Dauer der Verarbeitung: 0.24 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.