mirror of
https://github.com/windmill-labs/windmill.git
synced 2026-10-04 16:02:17 +00:00
* fix(frontend): prevent truncated ai chat tool-call arguments from bricking the session Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix: heal empty tool-call arguments when replaying chat history Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * docs: state sanitize invariant without drafting history Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
59 lines
1.8 KiB
TypeScript
59 lines
1.8 KiB
TypeScript
import type { ChatCompletionMessageParam } from 'openai/resources/chat/completions.mjs'
|
|
|
|
/**
|
|
* A tool call whose `arguments` string is not valid JSON (typically a stream
|
|
* cut mid-arguments) must never reach a provider: the whole request is
|
|
* rejected, and once persisted in the session history every follow-up request
|
|
* fails the same way.
|
|
*
|
|
* Empty arguments count as valid: some providers stream '' for no-arg tool
|
|
* calls, and flagging those would wrongly report the tool as not executed.
|
|
* Callers persisting arguments must normalize '' to '{}' themselves.
|
|
*/
|
|
export function hasValidToolCallArguments(args: string | undefined): boolean {
|
|
if (!args) {
|
|
return true
|
|
}
|
|
try {
|
|
JSON.parse(args)
|
|
return true
|
|
} catch {
|
|
return false
|
|
}
|
|
}
|
|
|
|
// '' is valid per hasValidToolCallArguments but JSON.parse('') still throws
|
|
// provider-side, so replaying history additionally requires non-empty args.
|
|
function isReplayableArguments(args: string | undefined): boolean {
|
|
return !!args && hasValidToolCallArguments(args)
|
|
}
|
|
|
|
/**
|
|
* Replaces unparseable or empty assistant tool_call arguments with '{}' in the
|
|
* outgoing copy of the history, so a session whose persisted history contains
|
|
* a truncated tool call recovers instead of failing every request. The paired
|
|
* tool result already tells the model the call failed.
|
|
*/
|
|
export function sanitizeToolCallArguments(
|
|
messages: ChatCompletionMessageParam[]
|
|
): ChatCompletionMessageParam[] {
|
|
return messages.map((m) => {
|
|
if (
|
|
m.role !== 'assistant' ||
|
|
!m.tool_calls?.some(
|
|
(t) => t.type === 'function' && !isReplayableArguments(t.function.arguments)
|
|
)
|
|
) {
|
|
return m
|
|
}
|
|
return {
|
|
...m,
|
|
tool_calls: m.tool_calls.map((t) =>
|
|
t.type === 'function' && !isReplayableArguments(t.function.arguments)
|
|
? { ...t, function: { ...t.function, arguments: '{}' } }
|
|
: t
|
|
)
|
|
}
|
|
})
|
|
}
|