function resolveMaxToolResultChars(opts?: { maxToolResultChars?: number }): number { return Math.max(1, opts?.maxToolResultChars ?? DEFAULT_MAX_LIVE_TOOL_RESULT_CHARS);
}
function normalizePersistedToolResultName(
message: AgentMessage,
fallbackName?: string,
): AgentMessage { if ((message as { role?: unknown }).role !== "toolResult") { return message;
} const toolResult = message as Extract<AgentMessage, { role: "toolResult" }>; const rawToolName = (toolResult as { toolName?: unknown }).toolName; const normalizedToolName = normalizeOptionalString(rawToolName); if (normalizedToolName) { if (rawToolName === normalizedToolName) { return toolResult;
} return { ...toolResult, toolName: normalizedToolName };
}
const guardedAppend = (message: AgentMessage) => {
let nextMessage = message; const role = (message as { role?: unknown }).role; if (role === "assistant") { const sanitized = sanitizeToolCallInputs([message], {
allowedToolNames: opts?.allowedToolNames,
}); if (sanitized.length === 0) { if (pendingState.shouldFlushForSanitizedDrop()) {
flushPendingToolResults();
} return undefined;
}
nextMessage = sanitized[0];
} const nextRole = (nextMessage as { role?: unknown }).role;
if (nextRole === "toolResult") { const id = extractToolResultId(nextMessage as Extract<AgentMessage, { role: "toolResult" }>); const toolName = id ? pendingState.getToolName(id) : undefined; if (id) {
pendingState.delete(id);
} const normalizedToolResult = normalizePersistedToolResultName(nextMessage, toolName); // Apply hard size cap before persistence to prevent oversized tool results // from consuming the entire context window on subsequent LLM calls. const capped = capToolResultSize(persistMessage(normalizedToolResult), maxToolResultChars); const persisted = applyBeforeWriteHook(
persistToolResult(capped, {
toolCallId: id ?? undefined,
toolName,
isSynthetic: false,
}),
); if (!persisted) { return undefined;
} return originalAppend(capToolResultSize(persisted, maxToolResultChars) as never);
}
// Skip tool call extraction for aborted/errored assistant messages. // When stopReason is "error" or "aborted", the tool_use blocks may be incomplete // and should not have synthetic tool_results created. Creating synthetic results // for incomplete tool calls causes API 400 errors: // "unexpected tool_use_id found in tool_result blocks" // This matches the behavior in repairToolUseResultPairing (session-transcript-repair.ts) const stopReason = (nextMessage as { stopReason?: string }).stopReason; const toolCalls =
nextRole === "assistant" && stopReason !== "aborted" && stopReason !== "error"
? extractToolCallsFromAssistant(nextMessage as Extract<AgentMessage, { role: "assistant" }>)
: [];
// Always clear pending tool call state before appending non-tool-result messages. // flushPendingToolResults() only inserts synthetic results when allowSyntheticToolResults // is true; it always clears the pending map. Without this, providers that disable // synthetic results (e.g. OpenAI) accumulate stale pending state when a user message // interrupts in-flight tool calls, leaving orphaned tool_use blocks in the transcript // that cause API 400 errors on subsequent requests. if (pendingState.shouldFlushBeforeNonToolResult(nextRole, toolCalls.length)) {
flushPendingToolResults();
} // If new tool calls arrive while older ones are pending, flush the old ones first. if (pendingState.shouldFlushBeforeNewToolCalls(toolCalls.length)) {
flushPendingToolResults();
}
const finalMessage = applyBeforeWriteHook(persistMessage(nextMessage)); if (!finalMessage) { return undefined;
} const result = originalAppend(finalMessage as never);
const sessionFile = (
sessionManager as { getSessionFile?: () => string | null }
).getSessionFile?.(); if (sessionFile) {
emitSessionTranscriptUpdate({
sessionFile,
sessionKey: opts?.sessionKey,
message: finalMessage,
messageId: typeof result === "string" ? result : undefined,
});
}
if (toolCalls.length > 0) {
pendingState.trackToolCalls(toolCalls);
}
return result;
};
// Monkey-patch appendMessage with our guarded version.
sessionManager.appendMessage = guardedAppend as SessionManager["appendMessage"];
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.