Files
oh-my-opencode/src/plugin/messages-transform.ts
T
mrosnerr 69a4b2f49c fix(messages-transform): isolate hook failures so tool-pair-validator always runs
Previously each transform hook was awaited sequentially without per-hook
error handling. If contextInjectorMessagesTransform or thinkingBlockValidator
threw, toolPairValidator was silently skipped, leaving orphaned tool_use
blocks in the post-compaction API payload and producing
"messages.N: tool_use ids were found without tool_result blocks immediately
after" 400s from Anthropic.

Wraps each hook in runHookSafely so an upstream throw is logged but the
chain continues. Adds regression tests covering the isolation contract and
the consecutive-assistants compaction tail case (ses_22bd806).
2026-04-28 17:38:28 -04:00

66 lines
1.8 KiB
TypeScript

import type { Message, Part } from "@opencode-ai/sdk"
import { log } from "../shared/logger"
import type { CreatedHooks } from "../create-hooks"
type MessageWithParts = {
info: Message
parts: Part[]
}
type MessagesTransformOutput = { messages: MessageWithParts[] }
async function runMessagesTransformHookSafely<I, O>(
hookName: string,
handler: ((input: I, output: O) => unknown | Promise<unknown>) | null | undefined,
input: I,
output: O,
): Promise<void> {
if (!handler) return
try {
await Promise.resolve(handler(input, output))
} catch (error) {
// Isolate per-handler failures so later handlers (notably toolPairValidator)
// always run. A throw here used to leave orphaned tool_use blocks in the
// post-compaction payload, producing API 400s like
// "tool_use ids were found without tool_result blocks immediately after".
log("[messages-transform] hook execution failed", {
hook: hookName,
error,
})
}
}
export function createMessagesTransformHandler(args: {
hooks: CreatedHooks
}): (input: Record<string, never>, output: MessagesTransformOutput) => Promise<void> {
return async (input, output): Promise<void> => {
await runMessagesTransformHookSafely(
"contextInjectorMessagesTransform",
args.hooks.contextInjectorMessagesTransform?.[
"experimental.chat.messages.transform"
],
input,
output,
)
await runMessagesTransformHookSafely(
"thinkingBlockValidator",
args.hooks.thinkingBlockValidator?.[
"experimental.chat.messages.transform"
],
input,
output,
)
await runMessagesTransformHookSafely(
"toolPairValidator",
args.hooks.toolPairValidator?.[
"experimental.chat.messages.transform"
],
input,
output,
)
}
}