2026-04-28 14:20:35 +09:00
|
|
|
import type { KeywordType } from "../../config/schema/keyword-detector"
|
2026-05-15 23:16:05 +09:00
|
|
|
import { isRealUserTextPart } from "../../shared/internal-initiator-marker"
|
2025-12-13 13:37:37 +09:00
|
|
|
import {
|
|
|
|
|
CODE_BLOCK_PATTERN,
|
|
|
|
|
INLINE_CODE_PATTERN,
|
2026-05-15 23:16:05 +09:00
|
|
|
KEYWORD_DETECTORS,
|
2025-12-13 13:37:37 +09:00
|
|
|
} from "./constants"
|
|
|
|
|
|
2026-01-01 20:58:02 +09:00
|
|
|
export interface DetectedKeyword {
|
2026-04-28 14:20:35 +09:00
|
|
|
type: KeywordType
|
2026-01-01 20:58:02 +09:00
|
|
|
message: string
|
|
|
|
|
}
|
|
|
|
|
|
2025-12-13 13:37:37 +09:00
|
|
|
export function removeCodeBlocks(text: string): string {
|
|
|
|
|
return text.replace(CODE_BLOCK_PATTERN, "").replace(INLINE_CODE_PATTERN, "")
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-30 16:08:15 +09:00
|
|
|
const SLASH_COMMAND_LEAD_PATTERN = /^\s*\/[a-zA-Z][\w-]*(?:\s|$)/
|
|
|
|
|
|
|
|
|
|
export function looksLikeSlashCommand(text: string): boolean {
|
|
|
|
|
return SLASH_COMMAND_LEAD_PATTERN.test(text)
|
|
|
|
|
}
|
|
|
|
|
|
2026-01-05 16:26:29 +09:00
|
|
|
function resolveMessage(
|
2026-02-01 19:26:57 +09:00
|
|
|
message: string | ((agentName?: string, modelID?: string) => string),
|
|
|
|
|
agentName?: string,
|
|
|
|
|
modelID?: string
|
2026-01-05 16:26:29 +09:00
|
|
|
): string {
|
2026-02-01 19:26:57 +09:00
|
|
|
return typeof message === "function" ? message(agentName, modelID) : message
|
2026-01-05 16:26:29 +09:00
|
|
|
}
|
|
|
|
|
|
2026-04-28 14:20:35 +09:00
|
|
|
export function detectKeywords(
|
|
|
|
|
text: string,
|
|
|
|
|
agentName?: string,
|
|
|
|
|
modelID?: string,
|
|
|
|
|
disabledKeywords?: ReadonlyArray<KeywordType>,
|
2026-05-16 02:01:29 +02:00
|
|
|
enabledExpansions?: ReadonlyArray<KeywordType>,
|
2026-04-28 14:20:35 +09:00
|
|
|
): string[] {
|
2026-05-16 02:01:29 +02:00
|
|
|
return detectKeywordsWithType(text, agentName, modelID, disabledKeywords, enabledExpansions).map(
|
2026-04-28 14:20:35 +09:00
|
|
|
({ message }) => message,
|
|
|
|
|
)
|
2025-12-13 13:37:37 +09:00
|
|
|
}
|
|
|
|
|
|
2026-04-28 14:20:35 +09:00
|
|
|
export function detectKeywordsWithType(
|
|
|
|
|
text: string,
|
|
|
|
|
agentName?: string,
|
|
|
|
|
modelID?: string,
|
|
|
|
|
disabledKeywords?: ReadonlyArray<KeywordType>,
|
2026-05-16 02:01:29 +02:00
|
|
|
enabledExpansions?: ReadonlyArray<KeywordType>,
|
2026-04-28 14:20:35 +09:00
|
|
|
): DetectedKeyword[] {
|
2026-01-01 20:58:02 +09:00
|
|
|
const textWithoutCode = removeCodeBlocks(text)
|
2026-04-28 14:20:35 +09:00
|
|
|
const disabled = new Set<KeywordType>(disabledKeywords ?? [])
|
2026-04-29 17:36:18 +09:00
|
|
|
// Intersection rule: combo requires BOTH base keywords enabled
|
|
|
|
|
if (disabled.has("ultrawork") || disabled.has("hyperplan")) {
|
|
|
|
|
disabled.add("hyperplan-ultrawork")
|
|
|
|
|
}
|
2026-05-16 02:01:29 +02:00
|
|
|
// Allowlist: if enabledExpansions is set, only those types fire
|
|
|
|
|
const allowlist = enabledExpansions ? new Set<KeywordType>(enabledExpansions) : null
|
2026-04-29 17:36:18 +09:00
|
|
|
return KEYWORD_DETECTORS.map(({ type, pattern, message }) => ({
|
2026-01-01 20:58:02 +09:00
|
|
|
matches: pattern.test(textWithoutCode),
|
2026-04-29 17:36:18 +09:00
|
|
|
type,
|
2026-02-01 19:26:57 +09:00
|
|
|
message: resolveMessage(message, agentName, modelID),
|
2026-01-01 20:58:02 +09:00
|
|
|
}))
|
2026-05-16 02:01:29 +02:00
|
|
|
.filter((result) => {
|
|
|
|
|
if (!result.matches) return false
|
|
|
|
|
if (allowlist && !allowlist.has(result.type)) return false
|
|
|
|
|
if (disabled.has(result.type)) return false
|
|
|
|
|
return true
|
|
|
|
|
})
|
2026-01-01 20:58:02 +09:00
|
|
|
.map(({ type, message }) => ({ type, message }))
|
|
|
|
|
}
|
|
|
|
|
|
2025-12-13 13:37:37 +09:00
|
|
|
export function extractPromptText(
|
2026-05-15 23:16:05 +09:00
|
|
|
parts: Array<{ type: string; text?: string; synthetic?: boolean }>
|
2025-12-13 13:37:37 +09:00
|
|
|
): string {
|
|
|
|
|
return parts
|
2026-05-15 23:16:05 +09:00
|
|
|
.filter(isRealUserTextPart)
|
2025-12-13 13:37:37 +09:00
|
|
|
.map((p) => p.text || "")
|
2025-12-14 11:38:33 +09:00
|
|
|
.join(" ")
|
2025-12-13 13:37:37 +09:00
|
|
|
}
|