UNPKG

openclaw

Version:

Multi-channel AI gateway with extensible messaging integrations

950 lines (949 loc) 48.1 kB
import { o as asDateTimestampMs } from "./number-coercion-CLj0HTDM.js"; import { l as normalizeOptionalString, o as normalizeLowercaseStringOrEmpty } from "./string-coerce-CIXf7egm.js"; import { m as shortenHomePath } from "./utils-P__uGsPB.js"; import { u as resolveAgentDir } from "./agent-scope-config-DcbEhP0R.js"; import { s as coerceSecretRef } from "./types.secrets-kC0nOetj.js"; import { c as normalizeProviderId } from "./model-ref-shared-Dz7QU0Lx.js"; import { t as modelKey } from "./model-key-CMdQNkZf.js"; import { f as parseConfiguredModelVisibilityEntries, l as isModelKeyAllowedBySet, r as buildConfiguredModelCatalog, y as resolveModelRefFromString } from "./model-selection-shared-BlLyx1r2.js"; import { A as resolveCollapsedSessionAuthPinSource } from "./agent-scope-DbtJyKUL.js"; import { t as findNormalizedProviderValue } from "./model-selection-normalize-D1HuPOqZ.js"; import { n as resolveAuthStorePathForDisplay } from "./paths-DLuhw03H.js"; import { t as resolveAgentHarnessPolicy } from "./policy-D9i1QMuw.js"; import { a as enqueueSystemEvent } from "./system-events-C03jsM0j.js"; import { c as readSessionInputProfileId } from "./ingress-drain-DcmwOHP5.js"; import { r as ensureAuthProfileStore } from "./store-F1B2duCT.js"; import { o as isProfileInCooldown } from "./usage-state-CAKmPrwS.js"; import { a as resolveAuthProfileOrder, t as isConfiguredAwsSdkAuthProfileForProvider } from "./order-CC2RBzI5.js"; import { t as resolveEnvApiKey } from "./model-auth-env-Dq9W4xg9.js"; import { t as getChannelPlugin } from "./registry-Cz6cv4VC.js"; import "./plugins-Cozj1Enf.js"; import { f as emitSessionLifecycleEvent } from "./session-history-eviction-C4srftLJ.js"; import { s as isModelSelectionLocked, t as MODEL_SELECTION_LOCKED_MESSAGE } from "./model-overrides-Bm7jo7Di.js"; import { n as isThinkingLevelSupported, o as resolveSupportedThinkingLevel, t as formatThinkingLevels } from "./thinking-DterdXhr.js"; import { i as resolveSessionRuntimeOverrideForProvider } from "./session-runtime-compat-DNAICzi6.js"; import { o as resolveEffectiveAgentRuntime } from "./thinking-runtime-BiFbTVdI.js"; import { S as resolveUsableCustomProviderApiKey } from "./model-auth-provider-config-C_kr_q2g.js"; import { t as resolveAuthProfileDisplayLabel } from "./auth-profiles-BdUEhE7u.js"; import "./model-auth-C48_DZ-I.js"; import "./model-selection-di2kjKCB.js"; import { i as formatFastModeCurrentStatus, r as formatFastModeCommandOptions, s as formatFastModeValue } from "./fast-mode-CCX0YiYh.js"; import { n as resolveSandboxRuntimeStatus } from "./runtime-status-6c8Jb1Kg.js"; import "./sandbox-A2KOznlu.js"; import { t as buildAgentRuntimeAuthPlan } from "./auth-D2bx7kf3.js"; import { p as renderExecTargetLabel } from "./bash-tools.exec-runtime-CbUMPDGW.js"; import { t as resolveExecDefaults } from "./exec-defaults-CET-_UfH.js"; import { t as resolveFastModeState } from "./fast-mode-CTXu8q8K.js"; import { n as resolveSelectedAndActiveModel } from "./model-runtime-BzIef07I.js"; import { c as refreshQueuedFollowupSession, t as resolveQueueSettingsCore } from "./settings-jmDsm5RI.js"; import "./queue-BpaiO3X2.js"; import { t as triggerSessionPatchHook } from "./session-patch-hooks-Bav35gWY.js"; import { t as applyModelOverrideWithAuthProfileCompatibility } from "./auth-profile-preservation-DCNwoc5M.js"; import { t as RUNTIME_MODEL_VISIBILITY_NORMALIZATION } from "./model-visibility-policy-mYoB7nkk.js"; import { t as resolveConfiguredModelEntries } from "./configured-model-entries-CV1BLgOW.js"; import { t as persistStickyModelSelectionBestEffort } from "./sticky-model-selection-BlEz_Ddb.js"; import { _ as resolveModelSelectionFromDirective, a as enqueueModeSwitchEvents, c as formatElevatedUnavailableText, d as formatInternalVerbosePersistenceDeniedText, f as formatModelSelectionScopeAck, g as withOptions, h as resolveDirectiveTouchedSessionFields, i as canPersistSessionDirectiveDefaults, l as formatInternalExecPersistenceDeniedText, m as rejectSessionDirectiveTransaction, n as acknowledgeIgnoredSessionDirective, o as formatDirectiveAck, p as persistSessionDirectiveSnapshot, r as applySessionDirectiveFields, s as formatElevatedRuntimeHint, t as DIRECTIVE_ACK_MESSAGES, u as formatInternalVerboseCurrentReplyOnlyText, v as maybeHandleUnexpectedDirectiveArguments } from "./directive-handling.shared-DfXsGtSW.js"; import { a as prepareModelSelectionRuntime, s as applyModelRuntimeDirective, t as findSelectedCatalogEntry } from "./model-runtime-normalization-CVjCcJkR.js"; import { t as resolveRuntimePolicySessionKey } from "./runtime-policy-session-key-DCz5SylW.js"; import { i as resolveModelsCommandReply } from "./commands-models-BlJRzrxq.js"; import { r as formatRemainingShort } from "./auth-health-BDRAK0UV.js"; import { t as maskApiKey } from "./secret-mask-BEdLuCrN.js"; //#region src/auto-reply/reply/directive-handling.auth.ts function resolveStoredCredentialLabel(params) { const masked = maskApiKey(typeof params.value === "string" ? params.value : ""); if (masked !== "missing") return masked; if (coerceSecretRef(params.refValue)) return params.mode === "compact" ? "(ref)" : "ref"; return "missing"; } function formatExpirationLabel(expires, now, formatUntil, compactExpiredPrefix = " expired") { const timestampMs = asDateTimestampMs(expires); if (timestampMs === void 0 || timestampMs <= 0) return ""; return timestampMs <= now ? compactExpiredPrefix : ` exp ${formatUntil(timestampMs)}`; } function formatFlagsSuffix(flags) { return flags.length > 0 ? ` (${flags.join(", ")})` : ""; } function isStoredAuthProfileType(value) { return value === "api_key" || value === "oauth" || value === "token"; } /** Resolves the displayed auth source for a provider without exposing secrets. */ const resolveAuthLabel = async (provider, cfg, modelsPath, agentDir, mode = "compact", workspaceDir, options) => { const formatPath = (value) => shortenHomePath(value); const store = ensureAuthProfileStore(agentDir, { allowKeychainPrompt: false }); const rawOrder = resolveAuthProfileOrder({ cfg, store, provider }); const acceptedProfileTypes = options?.acceptedProfileTypes ? new Set(options.acceptedProfileTypes) : void 0; const order = acceptedProfileTypes ? rawOrder.filter((profileId) => { const profile = store.profiles[profileId]; if (profile) return acceptedProfileTypes.has(profile.type); const configuredMode = cfg.auth?.profiles?.[profileId]?.mode; return isStoredAuthProfileType(configuredMode) ? acceptedProfileTypes.has(configuredMode) : true; }) : rawOrder; const providerKey = normalizeProviderId(provider); const lastGood = findNormalizedProviderValue(store.lastGood, providerKey); const nextProfileId = order[0]; const now = Date.now(); const formatUntil = (timestampMs) => formatRemainingShort(timestampMs - now, { underMinuteLabel: "soon" }); if (order.length > 0) { if (mode === "compact") { const profileId = nextProfileId; if (!profileId) return { label: "missing", source: "missing" }; const profile = store.profiles[profileId]; const configProfile = cfg.auth?.profiles?.[profileId]; const configOnlyAwsSdk = !profile ? isConfiguredAwsSdkAuthProfileForProvider({ cfg, provider, profileId }) : false; const more = order.length > 1 ? ` (+${order.length - 1})` : ""; if (configOnlyAwsSdk) return { label: `${profileId} aws-sdk${more}`, source: "" }; if (!profile || configProfile?.provider && configProfile.provider !== profile.provider || configProfile?.mode && configProfile.mode !== profile.type && !(configProfile.mode === "oauth" && profile.type === "token")) return { label: `${profileId} missing${more}`, source: "" }; if (profile.type === "api_key") return { label: `${profileId} api-key ${resolveStoredCredentialLabel({ value: profile.key, refValue: profile.keyRef, mode })}${more}`, source: "" }; if (profile.type === "token") return { label: `${profileId} token ${resolveStoredCredentialLabel({ value: profile.token, refValue: profile.tokenRef, mode })}${formatExpirationLabel(profile.expires, now, formatUntil)}${more}`, source: "" }; const display = resolveAuthProfileDisplayLabel({ cfg, store, profileId }); return { label: `${display === profileId ? profileId : display} oauth${formatExpirationLabel(profile.expires, now, formatUntil)}${more}`, source: "" }; } return { label: order.map((profileId) => { const profile = store.profiles[profileId]; const configProfile = cfg.auth?.profiles?.[profileId]; const flags = []; if (profileId === nextProfileId) flags.push("next"); if (lastGood && profileId === lastGood) flags.push("lastGood"); if (isProfileInCooldown(store, profileId)) { const until = store.usageStats?.[profileId]?.cooldownUntil; if (typeof until === "number" && Number.isFinite(until) && until > now) flags.push(`cooldown ${formatUntil(until)}`); else flags.push("cooldown"); } if (!profile && isConfiguredAwsSdkAuthProfileForProvider({ cfg, provider, profileId })) return `${profileId}=aws-sdk${formatFlagsSuffix(flags)}`; if (!profile || configProfile?.provider && configProfile.provider !== profile.provider || configProfile?.mode && configProfile.mode !== profile.type && !(configProfile.mode === "oauth" && profile.type === "token")) return `${profileId}=missing${formatFlagsSuffix(flags)}`; if (profile.type === "api_key") return `${profileId}=${resolveStoredCredentialLabel({ value: profile.key, refValue: profile.keyRef, mode })}${formatFlagsSuffix(flags)}`; if (profile.type === "token") { const tokenLabel = resolveStoredCredentialLabel({ value: profile.token, refValue: profile.tokenRef, mode }); const expirationFlag = formatExpirationLabel(profile.expires, now, formatUntil, "expired"); if (expirationFlag) flags.push(expirationFlag); return `${profileId}=token:${tokenLabel}${formatFlagsSuffix(flags)}`; } const display = resolveAuthProfileDisplayLabel({ cfg, store, profileId }); const suffix = display === profileId ? "" : display.startsWith(profileId) ? display.slice(profileId.length).trim() : `(${display})`; const expirationFlag = formatExpirationLabel(profile.expires, now, formatUntil, "expired"); if (expirationFlag) flags.push(expirationFlag); return `${profileId}=OAuth${suffix ? ` ${suffix}` : ""}${formatFlagsSuffix(flags)}`; }).join(", "), source: `auth profile store: ${formatPath(resolveAuthStorePathForDisplay(agentDir))}` }; } const envKey = resolveEnvApiKey(provider, process.env, { config: cfg, workspaceDir }); if (envKey) return { label: envKey.source.includes("ANTHROPIC_OAUTH_TOKEN") || normalizeLowercaseStringOrEmpty(envKey.source).includes("oauth") ? "OAuth (env)" : maskApiKey(envKey.apiKey), source: mode === "verbose" ? envKey.source : "" }; const customKey = resolveUsableCustomProviderApiKey({ cfg, provider })?.apiKey; if (customKey) return { label: maskApiKey(customKey), source: mode === "verbose" ? `models.json: ${formatPath(modelsPath)}` : "" }; return { label: "missing", source: "missing" }; }; /** Formats an auth label plus source for one-line status output. */ const formatAuthLabel = (auth) => { if (!auth.source || auth.source === auth.label || auth.source === "missing") return auth.label; return `${auth.label} (${auth.source})`; }; //#endregion //#region src/auto-reply/reply/directive-handling.model-picker.ts /** Resolves optional endpoint/API labels for a provider in picker details. */ function resolveProviderEndpointLabel(provider, cfg) { const normalized = normalizeProviderId(provider); const providers = cfg.models?.providers ?? {}; const entry = findNormalizedProviderValue(providers, normalized); const endpoint = normalizeOptionalString(entry?.baseUrl); const api = normalizeOptionalString(entry?.api); return { endpoint: endpoint || void 0, api: api || void 0 }; } //#endregion //#region src/auto-reply/reply/directive-handling.model.ts function isMissingAuthLabel(auth) { return auth.label === "missing" && auth.source === "missing"; } function resolveStatusHarnessRuntime(params) { const sessionRuntime = resolveSessionRuntimeOverrideForProvider({ provider: params.provider, entry: params.sessionEntry, cfg: params.cfg }); if (sessionRuntime) return sessionRuntime; return params.defaultRuntime; } function resolveStatusAcceptedProfileTypes(params) { if (normalizeProviderId(params.provider) !== "openai" || params.harnessRuntime === "codex") return; return ["api_key"]; } async function resolveStatusAuthLabel(params) { const provider = normalizeProviderId(params.provider); const harnessPolicy = resolveAgentHarnessPolicy({ provider, modelId: params.modelId, config: params.cfg, agentId: params.activeAgentId }); const harnessRuntime = resolveStatusHarnessRuntime({ sessionEntry: params.sessionEntry, defaultRuntime: harnessPolicy.runtime, provider, cfg: params.cfg }); const auth = await resolveAuthLabel(params.provider, params.cfg, params.modelsPath, params.agentDir, params.authMode, params.workspaceDir, { acceptedProfileTypes: resolveStatusAcceptedProfileTypes({ provider, harnessRuntime }) }); if (!isMissingAuthLabel(auth)) return formatAuthLabel(auth); const effectiveAuthProvider = buildAgentRuntimeAuthPlan({ provider, config: params.cfg, workspaceDir: params.workspaceDir, harnessRuntime }).harnessAuthProvider; if (!effectiveAuthProvider || effectiveAuthProvider === provider) return formatAuthLabel(auth); const runtimeAuth = await resolveAuthLabel(effectiveAuthProvider, params.cfg, params.modelsPath, params.agentDir, params.authMode, params.workspaceDir); if (isMissingAuthLabel(runtimeAuth)) return formatAuthLabel(auth); return `via ${harnessRuntime} runtime / ${effectiveAuthProvider} ${formatAuthLabel(runtimeAuth)}`; } function pushUniqueCatalogEntry(params) { const provider = normalizeProviderId(params.provider); const id = normalizeOptionalString(params.id) ?? ""; if (!provider || !id) return; const key = modelKey(provider, id); if (params.keys.has(key)) return; params.keys.add(key); params.out.push({ provider, id, name: params.fallbackNameToId ? params.name ?? id : params.name }); } function buildModelPickerCatalog(params) { const configuredProjection = resolveConfiguredModelEntries({ cfg: params.cfg, agentId: params.agentId, defaultProvider: params.defaultProvider, defaultModel: params.defaultModel, aliasIndex: params.aliasIndex, ...RUNTIME_MODEL_VISIBILITY_NORMALIZATION }); const resolvedDefault = configuredProjection.defaultRef; const configuredCatalog = configuredProjection.entries.map((entry) => ({ provider: entry.ref.provider, id: entry.ref.model, name: entry.ref.model })); const keys = /* @__PURE__ */ new Set(); const out = []; const push = (entry) => { pushUniqueCatalogEntry({ keys, out, provider: entry.provider, id: entry.id ?? "", name: entry.name, fallbackNameToId: false }); }; const visibility = parseConfiguredModelVisibilityEntries({ cfg: params.cfg, agentId: params.agentId }); if (!visibility.hasEntries) { for (const entry of params.allowedModelCatalog) push({ provider: entry.provider, id: entry.id ?? "", name: entry.name }); for (const entry of configuredCatalog) push(entry); return out; } for (const entry of params.allowedModelCatalog.filter((candidate) => isModelKeyAllowedBySet(params.allowedModelKeys, modelKey(candidate.provider, candidate.id ?? "")))) push({ provider: entry.provider, id: entry.id ?? "", name: entry.name }); for (const raw of visibility.exactModelRefs) { const resolved = resolveModelRefFromString({ cfg: params.cfg, agentId: params.agentId, raw, defaultProvider: params.defaultProvider, aliasIndex: params.policyAliasIndex, ...RUNTIME_MODEL_VISIBILITY_NORMALIZATION }); if (!resolved) continue; const catalogEntry = params.allowedModelCatalog.find((entry) => modelKey(entry.provider, entry.id ?? "") === modelKey(resolved.ref.provider, resolved.ref.model)); push(catalogEntry ? { provider: catalogEntry.provider, id: catalogEntry.id ?? "", name: catalogEntry.name } : { provider: resolved.ref.provider, id: resolved.ref.model, name: resolved.ref.model }); } if (resolvedDefault.model && isModelKeyAllowedBySet(params.allowedModelKeys, modelKey(resolvedDefault.provider, resolvedDefault.model))) push({ provider: resolvedDefault.provider, id: resolvedDefault.model, name: resolvedDefault.model }); return out; } function filterMissingAuthNestedProviderDuplicates(params) { const configuredKeys = new Set(buildConfiguredModelCatalog({ cfg: params.cfg }).map((entry) => modelKey(entry.provider, entry.id))); const wrapperKeys = /* @__PURE__ */ new Set(); for (const entry of params.entries) { const id = normalizeOptionalString(entry.id) ?? ""; const slash = id.indexOf("/"); if (slash <= 0) continue; const nestedProvider = normalizeProviderId(id.slice(0, slash)); const nestedModel = normalizeOptionalString(id.slice(slash + 1)) ?? ""; const wrapperProvider = normalizeProviderId(entry.provider); if (!nestedProvider || !nestedModel || nestedProvider === wrapperProvider) continue; wrapperKeys.add(modelKey(nestedProvider, nestedModel)); } if (wrapperKeys.size === 0) return params.entries; return params.entries.filter((entry) => { const provider = normalizeProviderId(entry.provider); const id = normalizeOptionalString(entry.id) ?? ""; const key = modelKey(provider, id); if (configuredKeys.has(key)) return true; return params.authByProvider.get(provider) !== "missing" || !wrapperKeys.has(key); }); } async function maybeHandleModelDirectiveInfo(params) { if (!params.directives.hasModelDirective) return; const rawDirective = normalizeOptionalString(params.directives.rawModelDirective); const directive = rawDirective ? normalizeLowercaseStringOrEmpty(rawDirective) : void 0; const isLiteralModelDirective = params.directives.modelDirectiveSource !== "alias"; const wantsStatus = isLiteralModelDirective && directive === "status"; const wantsSummary = isLiteralModelDirective && !rawDirective; const wantsLegacyList = isLiteralModelDirective && directive === "list"; if (!wantsSummary && !wantsStatus && !wantsLegacyList) return; if (params.directives.rawModelProfile) return { text: "Auth profile override requires a model selection.", isError: true }; if (params.directives.rawModelRuntime) return { text: "Runtime override requires a model selection.", isError: true }; if (params.directives.modelScope) return { text: `${params.directives.modelScope === "session" ? "Session-only" : params.directives.modelScope === "agent" ? "Agent" : "Global"} scope requires a model selection.`, isError: true }; if (wantsLegacyList) return await resolveModelsCommandReply({ cfg: params.cfg, commandBodyNormalized: "/models", surface: params.surface, currentModel: `${params.provider}/${params.model}`, agentId: params.activeAgentId, agentDir: params.agentDir, workspaceDir: params.workspaceDir, sessionEntry: isCompleteSessionEntry(params.sessionEntry) ? params.sessionEntry : void 0 }) ?? { text: "No models available." }; if (wantsSummary) { const modelRefs = resolveSelectedAndActiveModel({ selectedProvider: params.provider, selectedModel: params.model, sessionEntry: params.sessionEntry }); const current = modelRefs.selected.label; const thinkingRuntime = resolveEffectiveAgentRuntime({ cfg: params.cfg, provider: params.provider, modelId: params.model, agentId: params.activeAgentId, sessionKey: params.runtimePolicySessionKey, sessionEntry: params.sessionEntry }); const thinkingLine = `Think: ${resolveSupportedThinkingLevel({ provider: params.provider, model: params.model, level: params.currentThinkLevel, catalog: params.thinkingCatalog, agentRuntime: thinkingRuntime })} (change with /think <level>)`; const activeRuntimeLine = modelRefs.activeDiffers ? `Active: ${modelRefs.active.label} (runtime)` : null; const channelData = (params.surface ? getChannelPlugin(params.surface) : null)?.commands?.buildModelBrowseChannelData?.(); if (channelData) return { text: [ `Current: ${current}${modelRefs.activeDiffers ? " (selected)" : ""}`, activeRuntimeLine, thinkingLine, "", "Tap below to select a model, or use:", "/model <provider/model> -s for this session only", "/model <provider/model> -a to update this agent's default", "/model <provider/model> -g to update the global default", "/model <provider/model> --runtime <runtime> -s to switch harnesses", "/model status for details" ].filter(Boolean).join("\n"), channelData }; return { text: [ `Current: ${current}${modelRefs.activeDiffers ? " (selected)" : ""}`, activeRuntimeLine, thinkingLine, "", "Session: /model <provider/model> -s", "Agent default: /model <provider/model> -a", "Global default: /model <provider/model> -g", "Runtime: /model <provider/model> --runtime <runtime> -s", "Browse: /models (providers) or /models <provider> (models)", "More: /model status" ].filter(Boolean).join("\n") }; } const pickerCatalog = buildModelPickerCatalog({ cfg: params.cfg, defaultProvider: params.defaultProvider, defaultModel: params.defaultModel, agentId: params.activeAgentId, aliasIndex: params.aliasIndex, policyAliasIndex: params.policyAliasIndex ?? params.aliasIndex, allowedModelKeys: params.allowedModelKeys, allowedModelCatalog: params.allowedModelCatalog }); const modelsPath = `${params.agentDir}/models.json`; const formatPath = (value) => shortenHomePath(value); const authMode = "verbose"; if (pickerCatalog.length === 0) return { text: "No models available." }; const authByProvider = /* @__PURE__ */ new Map(); for (const entry of pickerCatalog) { const provider = normalizeProviderId(entry.provider); if (authByProvider.has(provider)) continue; const authLabel = await resolveStatusAuthLabel({ provider, modelId: entry.id, cfg: params.cfg, modelsPath, agentDir: params.agentDir, activeAgentId: params.activeAgentId, authMode, workspaceDir: params.workspaceDir, sessionEntry: params.sessionEntry }); authByProvider.set(provider, authLabel); } const modelRefs = resolveSelectedAndActiveModel({ selectedProvider: params.provider, selectedModel: params.model, sessionEntry: params.sessionEntry }); const current = modelRefs.selected.label; const defaultLabel = `${params.defaultProvider}/${params.defaultModel}`; const lines = [ `Current: ${current}${modelRefs.activeDiffers ? " (selected)" : ""}`, modelRefs.activeDiffers ? `Active: ${modelRefs.active.label} (runtime)` : null, `Default: ${defaultLabel}`, `Agent: ${params.activeAgentId}`, `Auth store: ${formatPath(resolveAuthStorePathForDisplay(params.agentDir))}` ].filter((line) => Boolean(line)); if (params.resetModelOverride) lines.push(`(previous selection reset to default)`); const byProvider = /* @__PURE__ */ new Map(); const statusCatalog = filterMissingAuthNestedProviderDuplicates({ cfg: params.cfg, entries: pickerCatalog, authByProvider }); for (const entry of statusCatalog) { const provider = normalizeProviderId(entry.provider); const models = byProvider.get(provider); if (models) { models.push(entry); continue; } byProvider.set(provider, [entry]); } for (const provider of byProvider.keys()) { const models = byProvider.get(provider); if (!models) continue; const authLabel = authByProvider.get(provider) ?? "missing"; const endpoint = resolveProviderEndpointLabel(provider, params.cfg); const endpointSuffix = endpoint.endpoint ? ` endpoint: ${endpoint.endpoint}` : " endpoint: default"; const apiSuffix = endpoint.api ? ` api: ${endpoint.api}` : ""; lines.push(""); lines.push(`[${provider}]${endpointSuffix}${apiSuffix} auth: ${authLabel}`); for (const entry of models) { const label = `${provider}/${entry.id}`; const aliases = params.aliasIndex.byKey.get(label); const aliasSuffix = aliases && aliases.length > 0 ? ` (${aliases.join(", ")})` : ""; lines.push(` • ${label}${aliasSuffix}`); } } return { text: lines.join("\n") }; } function isCompleteSessionEntry(entry) { return Boolean(entry && typeof entry.sessionId === "string" && typeof entry.updatedAt === "number"); } //#endregion //#region src/auto-reply/reply/directive-handling.queue-validation.ts /** Validates `/queue` directives and returns immediate status/error replies. */ function maybeHandleQueueDirective(params) { const { directives } = params; if (!directives.hasQueueDirective) return; if (!directives.queueMode && !directives.queueReset && !directives.hasQueueOptions && directives.rawQueueMode === void 0 && directives.rawDebounce === void 0 && directives.rawCap === void 0 && directives.rawDrop === void 0) { const settings = resolveQueueSettingsCore({ cfg: params.cfg, channel: params.channel, sessionEntry: params.sessionEntry }); const debounceLabel = typeof settings.debounceMs === "number" ? `${settings.debounceMs}ms` : "default"; const capLabel = typeof settings.cap === "number" ? String(settings.cap) : "default"; const dropLabel = settings.dropPolicy ?? "default"; return { text: withOptions(`Current queue settings: mode=${settings.mode}, debounce=${debounceLabel}, cap=${capLabel}, drop=${dropLabel}.`, "modes steer, followup, collect, interrupt; debounce:<ms|s|m>, cap:<n>, drop:old|new|summarize") }; } const queueModeInvalid = !directives.queueMode && !directives.queueReset && Boolean(directives.rawQueueMode); const queueDebounceInvalid = directives.rawDebounce !== void 0 && typeof directives.debounceMs !== "number"; const queueCapInvalid = directives.rawCap !== void 0 && typeof directives.cap !== "number"; const queueDropInvalid = directives.rawDrop !== void 0 && !directives.dropPolicy; if (queueModeInvalid || queueDebounceInvalid || queueCapInvalid || queueDropInvalid) { const errors = []; if (queueModeInvalid) errors.push(`Unrecognized queue mode "${directives.rawQueueMode ?? ""}". Valid modes: steer, followup, collect, interrupt.`); if (queueDebounceInvalid) errors.push(`Invalid debounce "${directives.rawDebounce ?? ""}". Use ms/s/m (e.g. debounce:1500ms, debounce:2s).`); if (queueCapInvalid) errors.push(`Invalid cap "${directives.rawCap ?? ""}". Use a positive integer (e.g. cap:10).`); if (queueDropInvalid) errors.push(`Invalid drop policy "${directives.rawDrop ?? ""}". Use drop:old, drop:new, or drop:summarize.`); return { text: errors.join(" ") }; } } //#endregion //#region src/auto-reply/reply/directive-runtime-context.ts function resolveDirectiveRuntimeContext(params) { const activeAgentId = params.agentId; const agentDir = resolveAgentDir(params.cfg, activeAgentId); const runtimePolicySessionKey = resolveRuntimePolicySessionKey({ agentId: activeAgentId, cfg: params.cfg, ctx: params.ctx, sessionKey: params.sessionKey }); return { activeAgentId, agentDir, runtimePolicySessionKey, runtimeIsSandboxed: resolveSandboxRuntimeStatus({ cfg: params.cfg, agentId: activeAgentId, sessionKey: params.sessionKey, classificationSessionKey: runtimePolicySessionKey }).sandboxed }; } //#endregion //#region src/auto-reply/reply/directive-handling.impl.ts /** Applies directive-only command state changes without running the agent. */ /** Handles inline directives that can be acknowledged without a model turn. */ async function handleDirectiveOnly(params) { const { directives, sessionEntry, sessionStore, sessionKey, storePath, elevatedEnabled, elevatedAllowed, defaultProvider, defaultModel, aliasIndex, policyAliasIndex, allowedModelKeys, allowedModelCatalog, resetModelOverride, provider, model, formatModelSwitchEvent, currentThinkLevel, currentFastMode, currentVerboseLevel, currentReasoningLevel, currentElevatedLevel } = params; const allowPrivilegedPersistence = canPersistSessionDirectiveDefaults(params); const rejectModelTransaction = (errorText) => rejectSessionDirectiveTransaction(params.persistenceState, errorText); const acknowledgeIgnoredDirective = (reply, ignoredDirective) => acknowledgeIgnoredSessionDirective({ reply, directives, ignoredDirective, persistenceState: params.persistenceState, applyRemainingDirectives: (remainingDirectives) => handleDirectiveOnly({ ...params, directives: remainingDirectives }) }); const delegatedTraceAllowed = (params.gatewayClientScopes ?? []).includes("operator.admin"); if (directives.hasTraceDirective && !params.senderIsOwner && !delegatedTraceAllowed) return acknowledgeIgnoredDirective({ text: "❌ /trace is restricted to owners and gateway clients with operator.admin scope." }, "hasTraceDirective"); const { activeAgentId, agentDir, runtimePolicySessionKey, runtimeIsSandboxed } = resolveDirectiveRuntimeContext(params); const shouldHintDirectRuntime = directives.hasElevatedDirective && !runtimeIsSandboxed; let thinkingCatalog = params.thinkingCatalog && params.thinkingCatalog.length > 0 ? params.thinkingCatalog : allowedModelCatalog.length > 0 ? allowedModelCatalog : void 0; const modelInfo = await maybeHandleModelDirectiveInfo({ directives, cfg: params.cfg, agentDir, activeAgentId, provider, model, defaultProvider, defaultModel, aliasIndex, policyAliasIndex, allowedModelKeys, allowedModelCatalog, currentThinkLevel: currentThinkLevel ?? "off", thinkingCatalog, runtimePolicySessionKey, resetModelOverride, workspaceDir: params.workspaceDir, surface: params.surface, sessionEntry }); if (modelInfo) return acknowledgeIgnoredDirective(modelInfo, "hasModelDirective"); const modelResolution = resolveModelSelectionFromDirective({ directives, cfg: params.cfg, agentDir, defaultProvider, defaultModel, aliasIndex, allowedModelKeys, allowedModelCatalog, provider, agentId: activeAgentId, modelPolicy: params.modelPolicy, requesterProfileId: params.ctx ? readSessionInputProfileId(params.ctx) : void 0 }); if (modelResolution.errorText) return rejectModelTransaction(modelResolution.errorText); const modelSelection = modelResolution.modelSelection; const profileOverride = modelResolution.profileOverride; if (modelSelection && isModelSelectionLocked(sessionEntry)) return rejectModelTransaction(MODEL_SELECTION_LOCKED_MESSAGE); const resolvedProvider = modelSelection?.provider ?? provider; const resolvedModel = modelSelection?.model ?? model; let modelRuntimeResolution = { kind: "unchanged" }; if (modelSelection) { const prepared = await prepareModelSelectionRuntime({ cfg: params.cfg, agentId: activeAgentId, provider: resolvedProvider, model: resolvedModel, catalog: thinkingCatalog ?? [], rawRuntime: directives.rawModelRuntime, sessionEntry }); if (prepared.status === "rejected") return rejectModelTransaction(prepared.message); thinkingCatalog = prepared.catalog; modelRuntimeResolution = prepared.runtime; } const prospectiveSessionEntry = { ...sessionEntry }; applyModelRuntimeDirective(prospectiveSessionEntry, modelRuntimeResolution); const selectedCatalogEntry = findSelectedCatalogEntry({ catalog: thinkingCatalog, provider: resolvedProvider, model: resolvedModel }); const resolveThinkingRuntime = (entry) => resolveEffectiveAgentRuntime({ cfg: params.cfg, provider: resolvedProvider, modelId: resolvedModel, modelApi: selectedCatalogEntry?.api, modelBaseUrl: selectedCatalogEntry?.baseUrl, agentId: activeAgentId, sessionKey: runtimePolicySessionKey, sessionEntry: entry }); const thinkingRuntime = resolveThinkingRuntime(prospectiveSessionEntry); const thinkingPolicy = { provider: resolvedProvider, model: resolvedModel, catalog: thinkingCatalog, agentRuntime: thinkingRuntime }; const fastModeState = resolveFastModeState({ cfg: params.cfg, provider: resolvedProvider, model: resolvedModel, agentId: activeAgentId, sessionEntry: directives.clearFastMode ? void 0 : sessionEntry }); const effectiveFastMode = directives.fastMode ?? (directives.clearFastMode ? fastModeState.mode : currentFastMode) ?? fastModeState.mode; const effectiveFastModeSource = directives.fastMode !== void 0 ? "session" : fastModeState.source; if (directives.hasThinkDirective && !directives.thinkLevel && !directives.clearThinkLevel) { if (!directives.rawThinkLevel) { const level = resolveSupportedThinkingLevel({ ...thinkingPolicy, level: currentThinkLevel ?? "off" }); return acknowledgeIgnoredDirective({ text: withOptions(`Current thinking level: ${level}.`, `default, ${formatThinkingLevels(resolvedProvider, resolvedModel, ", ", thinkingCatalog, thinkingRuntime)}`) }, "hasThinkDirective"); } return acknowledgeIgnoredDirective({ text: `Unrecognized thinking level "${directives.rawThinkLevel}". Valid levels: default, ${formatThinkingLevels(resolvedProvider, resolvedModel, ", ", thinkingCatalog, thinkingRuntime)}.` }, "hasThinkDirective"); } if (directives.hasVerboseDirective && !directives.verboseLevel) return acknowledgeIgnoredDirective({ text: directives.rawVerboseLevel ? `Unrecognized verbose level "${directives.rawVerboseLevel}". Valid levels: off, on, full.` : withOptions(`Current verbose level: ${currentVerboseLevel ?? "off"}.`, "on, full, off") }, "hasVerboseDirective"); if (directives.hasTraceDirective && !directives.traceLevel) return acknowledgeIgnoredDirective({ text: directives.rawTraceLevel ? `Unrecognized trace level "${directives.rawTraceLevel}". Valid levels: off, on, raw.` : withOptions(`Current trace level: ${sessionEntry.traceLevel ?? "off"}.`, "on, off, raw") }, "hasTraceDirective"); if (directives.hasFastDirective && directives.fastMode === void 0 && !directives.clearFastMode) { const isFastStatus = normalizeLowercaseStringOrEmpty(directives.rawFastMode) === "status"; if (!directives.rawFastMode || isFastStatus) { const statusText = formatFastModeCurrentStatus({ mode: effectiveFastMode, source: effectiveFastModeSource, fastAutoOnSeconds: fastModeState.fastAutoOnSeconds }); return acknowledgeIgnoredDirective({ text: isFastStatus ? statusText : withOptions(statusText, formatFastModeCommandOptions({ fastAutoOnSeconds: fastModeState.fastAutoOnSeconds })) }, "hasFastDirective"); } return acknowledgeIgnoredDirective({ text: `Unrecognized fast mode "${directives.rawFastMode}". Valid levels: on, off, auto, default, status.` }, "hasFastDirective"); } if (directives.hasReasoningDirective && !directives.reasoningLevel) return acknowledgeIgnoredDirective({ text: directives.rawReasoningLevel ? `Unrecognized reasoning level "${directives.rawReasoningLevel}". Valid levels: on, off, stream.` : withOptions(`Current reasoning level: ${currentReasoningLevel ?? "off"}.`, "on, off, stream") }, "hasReasoningDirective"); if (directives.hasElevatedDirective) { if (!directives.elevatedLevel && directives.rawElevatedLevel) return acknowledgeIgnoredDirective({ text: `Unrecognized elevated level "${directives.rawElevatedLevel}". Valid levels: off, on, ask, full.` }, "hasElevatedDirective"); if (!elevatedEnabled || !elevatedAllowed) return acknowledgeIgnoredDirective({ text: formatElevatedUnavailableText({ runtimeSandboxed: runtimeIsSandboxed, failures: params.elevatedFailures, sessionKey: params.sessionKey }) }, "hasElevatedDirective"); if (!directives.elevatedLevel) return acknowledgeIgnoredDirective({ text: [withOptions(`Current elevated level: ${currentElevatedLevel ?? "off"}.`, "on, off, ask, full"), shouldHintDirectRuntime ? formatElevatedRuntimeHint() : null].filter(Boolean).join("\n") }, "hasElevatedDirective"); } if (directives.hasExecDirective) { const invalidExecMessage = directives.invalidExecHost ? `Unrecognized exec host "${directives.rawExecHost ?? ""}". Valid hosts: auto, sandbox, gateway, node.` : directives.invalidExecSecurity ? `Unrecognized exec security "${directives.rawExecSecurity ?? ""}". Valid: deny, allowlist, full.` : directives.invalidExecAsk ? `Unrecognized exec ask "${directives.rawExecAsk ?? ""}". Valid: off, on-miss, always.` : directives.invalidExecNode ? "Exec node requires a value." : void 0; if (invalidExecMessage) return acknowledgeIgnoredDirective({ text: invalidExecMessage }, "hasExecDirective"); const unexpectedExecArguments = maybeHandleUnexpectedDirectiveArguments(directives); if (unexpectedExecArguments) return unexpectedExecArguments; if (!directives.hasExecOptions) { const execDefaults = resolveExecDefaults({ cfg: params.cfg, sessionEntry, agentId: activeAgentId, sandboxAvailable: runtimeIsSandboxed }); const nodeLabel = execDefaults.node ? `node=${execDefaults.node}` : "node=(unset)"; return acknowledgeIgnoredDirective({ text: withOptions(`Current exec defaults: host=${renderExecTargetLabel(execDefaults.host)}, effective=${execDefaults.effectiveHost}, security=${execDefaults.security}, ask=${execDefaults.ask}, ${nodeLabel}.`, "host=auto|sandbox|gateway|node, security=deny|allowlist|full, ask=off|on-miss|always, node=<id>") }, "hasExecDirective"); } } const queueAck = maybeHandleQueueDirective({ directives, cfg: params.cfg, channel: provider, sessionEntry }); if (queueAck) return acknowledgeIgnoredDirective(queueAck, "hasQueueDirective"); const unexpectedArguments = maybeHandleUnexpectedDirectiveArguments(directives); if (unexpectedArguments) return unexpectedArguments; if (directives.hasThinkDirective && directives.thinkLevel && !isThinkingLevelSupported({ ...thinkingPolicy, level: directives.thinkLevel })) return rejectModelTransaction(`Thinking level "${directives.thinkLevel}" is not supported for ${resolvedProvider}/${resolvedModel}. Use one of: ${formatThinkingLevels(resolvedProvider, resolvedModel, ", ", thinkingCatalog, thinkingRuntime)}.`); const nextThinkLevel = sessionEntry.thinkingLevel; const remappedUnsupportedThinkLevel = nextThinkLevel && (params.persistenceState ? modelSelection : !directives.hasThinkDirective) ? resolveSupportedThinkingLevel({ ...thinkingPolicy, level: nextThinkLevel }) : void 0; const shouldRemapUnsupportedThinkLevel = Boolean(remappedUnsupportedThinkLevel) && remappedUnsupportedThinkLevel !== nextThinkLevel; const prevReasoningLevel = currentReasoningLevel ?? sessionEntry.reasoningLevel ?? "off"; const elevatedChanged = directives.hasElevatedDirective && directives.elevatedLevel !== void 0 && directives.elevatedLevel !== (currentElevatedLevel ?? sessionEntry.elevatedLevel ?? "off") && elevatedEnabled && elevatedAllowed; let modelSelectionUpdated = false; let configuredDefaultUpdate; const touchedSessionFields = resolveDirectiveTouchedSessionFields({ directives, allowPrivilegedPersistence, directiveOnly: !params.persistenceState }); if (shouldRemapUnsupportedThinkLevel && !touchedSessionFields.includes("thinkingLevel")) touchedSessionFields.push("thinkingLevel"); const fastModeChanged = directives.hasFastDirective && directives.fastMode !== void 0 && directives.fastMode !== currentFastMode || directives.clearFastMode && currentFastMode !== fastModeState.mode; const reasoningChanged = directives.hasReasoningDirective && directives.reasoningLevel !== void 0 && directives.reasoningLevel !== prevReasoningLevel; if (touchedSessionFields.length > 0) { const authProfileError = modelResolution.validateAuthProfileSelection?.(); if (authProfileError) return rejectModelTransaction(authProfileError); const initialSessionEntry = { ...sessionEntry }; const directiveFieldsUpdated = !params.persistenceState && applySessionDirectiveFields({ directives, sessionEntry, allowPrivilegedPersistence, allowElevatedPersistence: elevatedEnabled && elevatedAllowed }); if (shouldRemapUnsupportedThinkLevel && remappedUnsupportedThinkLevel) sessionEntry.thinkingLevel = remappedUnsupportedThinkLevel; if (modelSelection) { const applied = applyModelOverrideWithAuthProfileCompatibility({ cfg: params.cfg, agentDir, entry: sessionEntry, currentProvider: provider, selection: modelSelection, profileOverride, markLiveSwitchPending: true }); const appliedRuntime = applyModelRuntimeDirective(sessionEntry, modelRuntimeResolution); modelSelectionUpdated = applied.updated || appliedRuntime.updated; } sessionEntry.updatedAt = Date.now(); sessionStore[sessionKey] = sessionEntry; if (storePath) { const persistence = await persistSessionDirectiveSnapshot({ storePath, sessionKey, initialEntry: initialSessionEntry, sessionEntry, sessionStore, hasModelSelection: Boolean(modelSelection), reassertLiveModelSwitchPending: modelSelectionUpdated && sessionEntry.liveModelSwitchPending === true, touchedFields: touchedSessionFields, validateCommit: modelResolution.validateAuthProfileSelection }); if (persistence.status !== "applied") return rejectModelTransaction(persistence.status === "commit-rejected" ? persistence.error : persistence.status === "model-selection-locked" ? MODEL_SELECTION_LOCKED_MESSAGE : modelSelection ? "Model change was not applied because the session changed. Retry." : "Session settings were not applied because the session changed. Retry."); } if (modelSelection && (!modelSelection.isDefault || params.stickyModelSelectionTarget) && params.canPersistStickyModelSelection === true) configuredDefaultUpdate = persistStickyModelSelectionBestEffort({ agentId: activeAgentId, model: `${modelSelection.provider}/${modelSelection.model}`, ...params.stickyModelSelectionTarget ? { target: params.stickyModelSelectionTarget } : {} }); if (sessionKey && (directiveFieldsUpdated || shouldRemapUnsupportedThinkLevel || modelSelectionUpdated)) emitSessionLifecycleEvent({ sessionKey, agentId: activeAgentId, reason: "patch" }); if (modelSelection && modelSelectionUpdated && sessionKey) { triggerSessionPatchHook({ cfg: params.cfg, sessionEntry, sessionKey, patch: { key: sessionKey, model: directives.rawModelDirective ?? `${modelSelection.provider}/${modelSelection.model}` } }); refreshQueuedFollowupSession({ key: sessionKey, nextProvider: modelSelection.provider, nextModel: modelSelection.model, nextRouteResolution: "resolved", nextModelOverrideSource: modelSelection.isDefault ? void 0 : "user", nextAuthProfileId: sessionEntry.authProfileOverride, nextAuthProfileIdSource: resolveCollapsedSessionAuthPinSource(sessionEntry), nextThinking: { level: sessionEntry.thinkingLevel, catalog: thinkingCatalog, agentRuntime: resolveThinkingRuntime(sessionEntry) } }); } } if (modelSelection) { const nextLabel = `${modelSelection.provider}/${modelSelection.model}`; if (nextLabel !== params.initialModelLabel) enqueueSystemEvent(formatModelSwitchEvent(nextLabel, modelSelection.alias), { sessionKey, contextKey: `model:${nextLabel}` }); } if (!params.persistenceState) enqueueModeSwitchEvents({ enqueueSystemEvent, sessionEntry, sessionKey, elevatedChanged, reasoningChanged }); if (params.persistenceState) params.persistenceState.outcome = { kind: "applied", provider: resolvedProvider, model: resolvedModel, modelCatalog: thinkingCatalog }; const parts = []; if (directives.clearThinkLevel) parts.push("Thinking level reset to default."); else if (directives.hasThinkDirective && directives.thinkLevel) parts.push(directives.thinkLevel === "off" ? "Thinking disabled." : `Thinking level set to ${directives.thinkLevel}.`); if (directives.clearFastMode) parts.push(formatDirectiveAck("Fast mode reset to default.")); else if (directives.hasFastDirective && directives.fastMode !== void 0) parts.push(directives.fastMode === "auto" ? formatDirectiveAck("Fast mode set to auto.") : directives.fastMode ? formatDirectiveAck("Fast mode enabled.") : formatDirectiveAck("Fast mode disabled.")); if (directives.hasVerboseDirective && directives.verboseLevel) { const message = allowPrivilegedPersistence ? DIRECTIVE_ACK_MESSAGES.verbose[directives.verboseLevel] : formatInternalVerboseCurrentReplyOnlyText(); parts.push(formatDirectiveAck(message)); } if (directives.hasTraceDirective && directives.traceLevel) parts.push(formatDirectiveAck(DIRECTIVE_ACK_MESSAGES.trace[directives.traceLevel])); if (directives.hasVerboseDirective && directives.verboseLevel && !allowPrivilegedPersistence) parts.push(formatDirectiveAck(formatInternalVerbosePersistenceDeniedText())); if (directives.hasReasoningDirective && directives.reasoningLevel) parts.push(formatDirectiveAck(DIRECTIVE_ACK_MESSAGES.reasoning[directives.reasoningLevel])); if (directives.hasElevatedDirective && directives.elevatedLevel) { parts.push(formatDirectiveAck(DIRECTIVE_ACK_MESSAGES.elevated[directives.elevatedLevel])); if (shouldHintDirectRuntime) parts.push(formatElevatedRuntimeHint()); } if (directives.hasExecDirective && directives.hasExecOptions) for (const [label, options] of [[allowPrivilegedPersistence && "Exec defaults set", { host: directives.execHost, node: directives.execNode }], ["Exec policy for this run only", { security: directives.execSecurity, ask: directives.execAsk }]]) { const execParts = Object.entries(options).filter(([, value]) => Boolean(value)).map(([key, value]) => `${key}=${value}`); if (execParts.length > 0) { const message = label ? `${label} (${execParts.join(", ")}).` : formatInternalExecPersistenceDeniedText(); parts.push(formatDirectiveAck(message)); } } if (modelSelection) { const label = `${modelSelection.provider}/${modelSelection.model}`; const labelWithAlias = modelSelection.alias ? `${modelSelection.alias} (${label})` : label; parts.push(formatModelSelectionScopeAck({ isDefault: modelSelection.isDefault, label: labelWithAlias, configuredDefaultUpdate, ...params.stickyModelSelectionTarget ? { stickyModelSelectionTarget: params.stickyModelSelectionTarget } : {} })); if (profileOverride) parts.push(`Auth profile set to ${profileOverride}.`); if (modelRuntimeResolution.kind === "clear") parts.push("Runtime reset to configured policy."); else if (modelRuntimeResolution.kind === "set") parts.push(`Runtime set to ${modelRuntimeResolution.runtime} for this session.`); } if (shouldRemapUnsupportedThinkLevel && remappedUnsupportedThinkLevel) parts.push(`Thinking level set to ${remappedUnsupportedThinkLevel} (${nextThinkLevel} not supported for ${resolvedProvider}/${resolvedModel}).`); if (directives.hasQueueDirective && directives.queueMode) parts.push(formatDirectiveAck(`Queue mode set to ${directives.queueMode}.`)); else if (directives.hasQueueDirective && directives.queueReset) parts.push(formatDirectiveAck("Queue mode reset to default.")); if (directives.hasQueueDirective && typeof directives.debounceMs === "number") parts.push(formatDirectiveAck(`Queue debounce set to ${directives.debounceMs}ms.`)); if (directives.hasQueueDirective && typeof directives.cap === "number") parts.push(formatDirectiveAck(`Queue cap set to ${directives.cap}.`)); if (directives.hasQueueDirective && directives.dropPolicy) parts.push(formatDirectiveAck(`Queue drop set to ${directives.dropPolicy}.`)); if (fastModeChanged && !params.persistenceState) { const nextFastMode = directives.clearFastMode ? fastModeState.mode : sessionEntry.fastMode; enqueueSystemEvent(nextFastMode === "auto" ? "Fast mode set to auto." : `Fast mode ${nextFastMode ? "enabled" : "disabled"}.`, { sessionKey, contextKey: `fast:${formatFastModeValue(nextFastMode)}` }); } const ack = parts.join(" ").trim(); if (!ack && directives.hasStatusDirective) return; return { text: ack || "OK." }; } //#endregion export { handleDirectiveOnly };