69a4b2f49c
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).
66 lines
1.8 KiB
TypeScript
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,
|
|
)
|
|
}
|
|
}
|