feat(hooks): add ultrawork-model-override hook for per-agent model swap
This commit is contained in:
parent
aad938a21f
commit
e863fe2013
@ -80,6 +80,7 @@
|
|||||||
"non-interactive-env",
|
"non-interactive-env",
|
||||||
"interactive-bash-session",
|
"interactive-bash-session",
|
||||||
"thinking-block-validator",
|
"thinking-block-validator",
|
||||||
|
"ultrawork-model-override",
|
||||||
"ralph-loop",
|
"ralph-loop",
|
||||||
"category-skill-reminder",
|
"category-skill-reminder",
|
||||||
"compaction-context-injector",
|
"compaction-context-injector",
|
||||||
@ -284,6 +285,21 @@
|
|||||||
],
|
],
|
||||||
"additionalProperties": false
|
"additionalProperties": false
|
||||||
},
|
},
|
||||||
|
"ultrawork": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"model": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"variant": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": [
|
||||||
|
"model"
|
||||||
|
],
|
||||||
|
"additionalProperties": false
|
||||||
|
},
|
||||||
"reasoningEffort": {
|
"reasoningEffort": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"enum": [
|
"enum": [
|
||||||
@ -466,6 +482,21 @@
|
|||||||
],
|
],
|
||||||
"additionalProperties": false
|
"additionalProperties": false
|
||||||
},
|
},
|
||||||
|
"ultrawork": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"model": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"variant": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": [
|
||||||
|
"model"
|
||||||
|
],
|
||||||
|
"additionalProperties": false
|
||||||
|
},
|
||||||
"reasoningEffort": {
|
"reasoningEffort": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"enum": [
|
"enum": [
|
||||||
@ -648,6 +679,21 @@
|
|||||||
],
|
],
|
||||||
"additionalProperties": false
|
"additionalProperties": false
|
||||||
},
|
},
|
||||||
|
"ultrawork": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"model": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"variant": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": [
|
||||||
|
"model"
|
||||||
|
],
|
||||||
|
"additionalProperties": false
|
||||||
|
},
|
||||||
"reasoningEffort": {
|
"reasoningEffort": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"enum": [
|
"enum": [
|
||||||
@ -830,6 +876,21 @@
|
|||||||
],
|
],
|
||||||
"additionalProperties": false
|
"additionalProperties": false
|
||||||
},
|
},
|
||||||
|
"ultrawork": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"model": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"variant": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": [
|
||||||
|
"model"
|
||||||
|
],
|
||||||
|
"additionalProperties": false
|
||||||
|
},
|
||||||
"reasoningEffort": {
|
"reasoningEffort": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"enum": [
|
"enum": [
|
||||||
@ -1012,6 +1073,21 @@
|
|||||||
],
|
],
|
||||||
"additionalProperties": false
|
"additionalProperties": false
|
||||||
},
|
},
|
||||||
|
"ultrawork": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"model": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"variant": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": [
|
||||||
|
"model"
|
||||||
|
],
|
||||||
|
"additionalProperties": false
|
||||||
|
},
|
||||||
"reasoningEffort": {
|
"reasoningEffort": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"enum": [
|
"enum": [
|
||||||
@ -1194,6 +1270,21 @@
|
|||||||
],
|
],
|
||||||
"additionalProperties": false
|
"additionalProperties": false
|
||||||
},
|
},
|
||||||
|
"ultrawork": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"model": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"variant": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": [
|
||||||
|
"model"
|
||||||
|
],
|
||||||
|
"additionalProperties": false
|
||||||
|
},
|
||||||
"reasoningEffort": {
|
"reasoningEffort": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"enum": [
|
"enum": [
|
||||||
@ -1376,6 +1467,21 @@
|
|||||||
],
|
],
|
||||||
"additionalProperties": false
|
"additionalProperties": false
|
||||||
},
|
},
|
||||||
|
"ultrawork": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"model": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"variant": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": [
|
||||||
|
"model"
|
||||||
|
],
|
||||||
|
"additionalProperties": false
|
||||||
|
},
|
||||||
"reasoningEffort": {
|
"reasoningEffort": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"enum": [
|
"enum": [
|
||||||
@ -1558,6 +1664,21 @@
|
|||||||
],
|
],
|
||||||
"additionalProperties": false
|
"additionalProperties": false
|
||||||
},
|
},
|
||||||
|
"ultrawork": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"model": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"variant": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": [
|
||||||
|
"model"
|
||||||
|
],
|
||||||
|
"additionalProperties": false
|
||||||
|
},
|
||||||
"reasoningEffort": {
|
"reasoningEffort": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"enum": [
|
"enum": [
|
||||||
@ -1740,6 +1861,21 @@
|
|||||||
],
|
],
|
||||||
"additionalProperties": false
|
"additionalProperties": false
|
||||||
},
|
},
|
||||||
|
"ultrawork": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"model": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"variant": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": [
|
||||||
|
"model"
|
||||||
|
],
|
||||||
|
"additionalProperties": false
|
||||||
|
},
|
||||||
"reasoningEffort": {
|
"reasoningEffort": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"enum": [
|
"enum": [
|
||||||
@ -1922,6 +2058,21 @@
|
|||||||
],
|
],
|
||||||
"additionalProperties": false
|
"additionalProperties": false
|
||||||
},
|
},
|
||||||
|
"ultrawork": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"model": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"variant": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": [
|
||||||
|
"model"
|
||||||
|
],
|
||||||
|
"additionalProperties": false
|
||||||
|
},
|
||||||
"reasoningEffort": {
|
"reasoningEffort": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"enum": [
|
"enum": [
|
||||||
@ -2104,6 +2255,21 @@
|
|||||||
],
|
],
|
||||||
"additionalProperties": false
|
"additionalProperties": false
|
||||||
},
|
},
|
||||||
|
"ultrawork": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"model": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"variant": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": [
|
||||||
|
"model"
|
||||||
|
],
|
||||||
|
"additionalProperties": false
|
||||||
|
},
|
||||||
"reasoningEffort": {
|
"reasoningEffort": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"enum": [
|
"enum": [
|
||||||
@ -2286,6 +2452,21 @@
|
|||||||
],
|
],
|
||||||
"additionalProperties": false
|
"additionalProperties": false
|
||||||
},
|
},
|
||||||
|
"ultrawork": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"model": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"variant": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": [
|
||||||
|
"model"
|
||||||
|
],
|
||||||
|
"additionalProperties": false
|
||||||
|
},
|
||||||
"reasoningEffort": {
|
"reasoningEffort": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"enum": [
|
"enum": [
|
||||||
@ -2468,6 +2649,21 @@
|
|||||||
],
|
],
|
||||||
"additionalProperties": false
|
"additionalProperties": false
|
||||||
},
|
},
|
||||||
|
"ultrawork": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"model": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"variant": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": [
|
||||||
|
"model"
|
||||||
|
],
|
||||||
|
"additionalProperties": false
|
||||||
|
},
|
||||||
"reasoningEffort": {
|
"reasoningEffort": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"enum": [
|
"enum": [
|
||||||
@ -2650,6 +2846,21 @@
|
|||||||
],
|
],
|
||||||
"additionalProperties": false
|
"additionalProperties": false
|
||||||
},
|
},
|
||||||
|
"ultrawork": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"model": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"variant": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": [
|
||||||
|
"model"
|
||||||
|
],
|
||||||
|
"additionalProperties": false
|
||||||
|
},
|
||||||
"reasoningEffort": {
|
"reasoningEffort": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"enum": [
|
"enum": [
|
||||||
|
|||||||
@ -977,6 +977,8 @@ Available hooks: `todo-continuation-enforcer`, `context-window-monitor`, `sessio
|
|||||||
|
|
||||||
**Note on `directory-agents-injector`**: This hook is **automatically disabled** when running on OpenCode 1.1.37+ because OpenCode now has native support for dynamically resolving AGENTS.md files from subdirectories (PR #10678). This prevents duplicate AGENTS.md injection. For older OpenCode versions, the hook remains active to provide the same functionality.
|
**Note on `directory-agents-injector`**: This hook is **automatically disabled** when running on OpenCode 1.1.37+ because OpenCode now has native support for dynamically resolving AGENTS.md files from subdirectories (PR #10678). This prevents duplicate AGENTS.md injection. For older OpenCode versions, the hook remains active to provide the same functionality.
|
||||||
|
|
||||||
|
**Note on `no-sisyphus-gpt`**: Disabling this hook is **STRONGLY discouraged**. Sisyphus is NOT optimized for GPT models — running Sisyphus with GPT performs worse than vanilla Codex and wastes your money. This hook automatically switches to Hephaestus when a GPT model is detected, which is the correct agent for GPT. Only disable this if you fully understand the consequences.
|
||||||
|
|
||||||
**Note on `auto-update-checker` and `startup-toast`**: The `startup-toast` hook is a sub-feature of `auto-update-checker`. To disable only the startup toast notification while keeping update checking enabled, add `"startup-toast"` to `disabled_hooks`. To disable all update checking features (including the toast), add `"auto-update-checker"` to `disabled_hooks`.
|
**Note on `auto-update-checker` and `startup-toast`**: The `startup-toast` hook is a sub-feature of `auto-update-checker`. To disable only the startup toast notification while keeping update checking enabled, add `"startup-toast"` to `disabled_hooks`. To disable all update checking features (including the toast), add `"auto-update-checker"` to `disabled_hooks`.
|
||||||
|
|
||||||
## Disabled Commands
|
## Disabled Commands
|
||||||
|
|||||||
@ -32,6 +32,11 @@ export const AgentOverrideConfigSchema = z.object({
|
|||||||
budgetTokens: z.number().optional(),
|
budgetTokens: z.number().optional(),
|
||||||
})
|
})
|
||||||
.optional(),
|
.optional(),
|
||||||
|
/** Ultrawork model override configuration. */
|
||||||
|
ultrawork: z.object({
|
||||||
|
model: z.string(),
|
||||||
|
variant: z.string().optional(),
|
||||||
|
}).optional(),
|
||||||
/** Reasoning effort level (OpenAI). Overrides category and default settings. */
|
/** Reasoning effort level (OpenAI). Overrides category and default settings. */
|
||||||
reasoningEffort: z.enum(["low", "medium", "high", "xhigh"]).optional(),
|
reasoningEffort: z.enum(["low", "medium", "high", "xhigh"]).optional(),
|
||||||
/** Text verbosity level. */
|
/** Text verbosity level. */
|
||||||
|
|||||||
@ -25,6 +25,7 @@ export const HookNameSchema = z.enum([
|
|||||||
"interactive-bash-session",
|
"interactive-bash-session",
|
||||||
|
|
||||||
"thinking-block-validator",
|
"thinking-block-validator",
|
||||||
|
"ultrawork-model-override",
|
||||||
"ralph-loop",
|
"ralph-loop",
|
||||||
"category-skill-reminder",
|
"category-skill-reminder",
|
||||||
|
|
||||||
|
|||||||
@ -46,3 +46,5 @@ export { createPreemptiveCompactionHook } from "./preemptive-compaction";
|
|||||||
export { createTasksTodowriteDisablerHook } from "./tasks-todowrite-disabler";
|
export { createTasksTodowriteDisablerHook } from "./tasks-todowrite-disabler";
|
||||||
export { createWriteExistingFileGuardHook } from "./write-existing-file-guard";
|
export { createWriteExistingFileGuardHook } from "./write-existing-file-guard";
|
||||||
export { createHashlineReadEnhancerHook } from "./hashline-read-enhancer";
|
export { createHashlineReadEnhancerHook } from "./hashline-read-enhancer";
|
||||||
|
|
||||||
|
export { createUltraworkModelOverrideHook } from "./ultrawork-model-override";
|
||||||
|
|||||||
83
src/hooks/ultrawork-model-override/hook.ts
Normal file
83
src/hooks/ultrawork-model-override/hook.ts
Normal file
@ -0,0 +1,83 @@
|
|||||||
|
import type { AgentOverrides } from "../../config"
|
||||||
|
import { log } from "../../shared"
|
||||||
|
import { getAgentConfigKey } from "../../shared/agent-display-names"
|
||||||
|
|
||||||
|
function isRecord(value: unknown): value is Record<string, unknown> {
|
||||||
|
return typeof value === "object" && value !== null
|
||||||
|
}
|
||||||
|
|
||||||
|
function getUltraworkConfig(agents: AgentOverrides | undefined, configKey: string) {
|
||||||
|
if (!agents) return undefined
|
||||||
|
|
||||||
|
for (const [agentKey, override] of Object.entries(agents)) {
|
||||||
|
if (getAgentConfigKey(agentKey) === configKey) {
|
||||||
|
return override?.ultrawork
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return undefined
|
||||||
|
}
|
||||||
|
|
||||||
|
export function createUltraworkModelOverrideHook(args: { agents?: AgentOverrides }) {
|
||||||
|
let didLogSpikeInput = false
|
||||||
|
|
||||||
|
return {
|
||||||
|
"chat.params": async (input: unknown, output: unknown): Promise<void> => {
|
||||||
|
if (!didLogSpikeInput) {
|
||||||
|
didLogSpikeInput = true
|
||||||
|
|
||||||
|
const inputRecord = isRecord(input) ? input : null
|
||||||
|
const messageRecord = isRecord(inputRecord?.message) ? inputRecord.message : null
|
||||||
|
|
||||||
|
log("ultrawork-model-override spike: raw chat.params input", {
|
||||||
|
inputType: typeof input,
|
||||||
|
outputType: typeof output,
|
||||||
|
hasMessage: messageRecord !== null,
|
||||||
|
messageKeys: messageRecord ? Object.keys(messageRecord) : [],
|
||||||
|
hasMessageModel: messageRecord ? "model" in messageRecord : false,
|
||||||
|
messageModelType: messageRecord ? typeof messageRecord.model : "undefined",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!isRecord(input)) return
|
||||||
|
|
||||||
|
const message = input.message
|
||||||
|
if (!isRecord(message)) return
|
||||||
|
if (message.variant !== "max") return
|
||||||
|
|
||||||
|
const agentName = input.agent
|
||||||
|
if (typeof agentName !== "string") return
|
||||||
|
|
||||||
|
const configKey = getAgentConfigKey(agentName)
|
||||||
|
const ultrawork = getUltraworkConfig(args.agents, configKey)
|
||||||
|
if (!ultrawork?.model) return
|
||||||
|
|
||||||
|
const separatorIndex = ultrawork.model.indexOf("/")
|
||||||
|
const providerID = separatorIndex === -1 ? ultrawork.model : ultrawork.model.slice(0, separatorIndex)
|
||||||
|
const modelID = separatorIndex === -1 ? "" : ultrawork.model.slice(separatorIndex + 1)
|
||||||
|
|
||||||
|
const previousModel = isRecord(message.model)
|
||||||
|
? {
|
||||||
|
providerID:
|
||||||
|
typeof message.model.providerID === "string" ? message.model.providerID : undefined,
|
||||||
|
modelID: typeof message.model.modelID === "string" ? message.model.modelID : undefined,
|
||||||
|
}
|
||||||
|
: undefined
|
||||||
|
|
||||||
|
message.model = { providerID, modelID }
|
||||||
|
|
||||||
|
if (ultrawork.variant !== undefined) {
|
||||||
|
message.variant = ultrawork.variant
|
||||||
|
}
|
||||||
|
|
||||||
|
log("ultrawork-model-override: swapped model", {
|
||||||
|
sessionID: typeof input.sessionID === "string" ? input.sessionID : undefined,
|
||||||
|
agent: agentName,
|
||||||
|
configKey,
|
||||||
|
from: previousModel,
|
||||||
|
to: message.model,
|
||||||
|
variant: message.variant,
|
||||||
|
})
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
1
src/hooks/ultrawork-model-override/index.ts
Normal file
1
src/hooks/ultrawork-model-override/index.ts
Normal file
@ -0,0 +1 @@
|
|||||||
|
export { createUltraworkModelOverrideHook } from "./hook"
|
||||||
@ -30,7 +30,11 @@ export function createPluginInterface(args: {
|
|||||||
return {
|
return {
|
||||||
tool: tools,
|
tool: tools,
|
||||||
|
|
||||||
"chat.params": createChatParamsHandler({ anthropicEffort: hooks.anthropicEffort }),
|
"chat.params": async (input, output) => {
|
||||||
|
await hooks.ultraworkModelOverride?.["chat.params"]?.(input, output)
|
||||||
|
const handler = createChatParamsHandler({ anthropicEffort: hooks.anthropicEffort })
|
||||||
|
await handler(input, output)
|
||||||
|
},
|
||||||
|
|
||||||
"chat.message": createChatMessageHandler({
|
"chat.message": createChatMessageHandler({
|
||||||
ctx,
|
ctx,
|
||||||
|
|||||||
@ -25,6 +25,7 @@ import {
|
|||||||
createPreemptiveCompactionHook,
|
createPreemptiveCompactionHook,
|
||||||
} from "../../hooks"
|
} from "../../hooks"
|
||||||
import { createAnthropicEffortHook } from "../../hooks/anthropic-effort"
|
import { createAnthropicEffortHook } from "../../hooks/anthropic-effort"
|
||||||
|
import { createUltraworkModelOverrideHook } from "../../hooks/ultrawork-model-override"
|
||||||
import {
|
import {
|
||||||
detectExternalNotificationPlugin,
|
detectExternalNotificationPlugin,
|
||||||
getNotificationConflictWarning,
|
getNotificationConflictWarning,
|
||||||
@ -55,6 +56,7 @@ export type SessionHooks = {
|
|||||||
questionLabelTruncator: ReturnType<typeof createQuestionLabelTruncatorHook>
|
questionLabelTruncator: ReturnType<typeof createQuestionLabelTruncatorHook>
|
||||||
taskResumeInfo: ReturnType<typeof createTaskResumeInfoHook>
|
taskResumeInfo: ReturnType<typeof createTaskResumeInfoHook>
|
||||||
anthropicEffort: ReturnType<typeof createAnthropicEffortHook> | null
|
anthropicEffort: ReturnType<typeof createAnthropicEffortHook> | null
|
||||||
|
ultraworkModelOverride: ReturnType<typeof createUltraworkModelOverrideHook> | null
|
||||||
}
|
}
|
||||||
|
|
||||||
export function createSessionHooks(args: {
|
export function createSessionHooks(args: {
|
||||||
@ -169,6 +171,10 @@ export function createSessionHooks(args: {
|
|||||||
? safeHook("anthropic-effort", () => createAnthropicEffortHook())
|
? safeHook("anthropic-effort", () => createAnthropicEffortHook())
|
||||||
: null
|
: null
|
||||||
|
|
||||||
|
const ultraworkModelOverride = isHookEnabled("ultrawork-model-override")
|
||||||
|
? safeHook("ultrawork-model-override", () => createUltraworkModelOverrideHook({ agents: pluginConfig.agents }))
|
||||||
|
: null
|
||||||
|
|
||||||
return {
|
return {
|
||||||
contextWindowMonitor,
|
contextWindowMonitor,
|
||||||
preemptiveCompaction,
|
preemptiveCompaction,
|
||||||
@ -191,5 +197,6 @@ export function createSessionHooks(args: {
|
|||||||
questionLabelTruncator,
|
questionLabelTruncator,
|
||||||
taskResumeInfo,
|
taskResumeInfo,
|
||||||
anthropicEffort,
|
anthropicEffort,
|
||||||
|
ultraworkModelOverride,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user