final tweak

This commit is contained in:
dal 2025-08-24 22:09:45 -06:00
parent 7b9afdbb2f
commit 9bdfe81cd5
No known key found for this signature in database
GPG Key ID: 16F4B0E1E9F61122
2 changed files with 132 additions and 17 deletions

View File

@ -67,10 +67,24 @@ export function convertCoreMessageToModelMessage(message: unknown): ModelMessage
case 'tool':
// Tool messages are always arrays of ToolResultPart
if (Array.isArray(content)) {
// Convert and flatten nested results
const convertedContent = [];
for (const part of content) {
const converted = convertToolResultPart(part);
if (converted !== null) {
if (Array.isArray(converted)) {
// Flatten nested arrays (from wrapper tool results)
convertedContent.push(...converted);
} else {
convertedContent.push(converted);
}
}
}
return {
role: 'tool',
// biome-ignore lint/suspicious/noExplicitAny: necessary for v4 to v5 conversion
content: content.map(convertToolResultPart) as any,
content: convertedContent as any,
};
}
@ -134,7 +148,7 @@ function convertContentPart(part: unknown): unknown {
/**
* Convert tool result parts from v4 to v5 format
*/
function convertToolResultPart(part: unknown): unknown {
function convertToolResultPart(part: unknown): unknown | unknown[] | null {
if (!part || typeof part !== 'object') {
return part;
}
@ -145,10 +159,54 @@ function convertToolResultPart(part: unknown): unknown {
return part;
}
// Check if this is a wrapper with empty toolCallId/toolName and nested results
if (
'toolCallId' in p &&
(p.toolCallId === '' || !p.toolCallId) &&
'toolName' in p &&
(p.toolName === '' || !p.toolName) &&
'result' in p &&
Array.isArray(p.result)
) {
// This is a wrapper - extract and convert the nested tool results
const nestedResults = [];
for (const nestedItem of p.result) {
if (
nestedItem &&
typeof nestedItem === 'object' &&
'type' in nestedItem &&
nestedItem.type === 'tool-result'
) {
const converted = convertToolResultPart(nestedItem);
if (converted !== null) {
if (Array.isArray(converted)) {
nestedResults.push(...converted);
} else {
nestedResults.push(converted);
}
}
}
}
// Return the array of nested results to be flattened
return nestedResults.length > 0 ? nestedResults : null;
}
// Convert result → output with proper structure
if ('result' in p && !('output' in p)) {
const { result, experimental_content, isError, ...rest } = p;
// Ensure toolCallId exists and is valid
// If it's missing or invalid, skip this result
if (!rest.toolCallId || typeof rest.toolCallId !== 'string' || rest.toolCallId.trim() === '') {
return null;
}
// Check if toolCallId matches the required pattern
const validPattern = /^[a-zA-Z0-9_-]+$/;
if (!validPattern.test(rest.toolCallId as string)) {
return null;
}
// Convert result to structured output format
let output: { type: string; value: unknown };
if (isError) {
@ -167,12 +225,6 @@ function convertToolResultPart(part: unknown): unknown {
}
}
// Ensure toolCallId exists and is valid
// If it's missing or invalid, preserve the original part
if (!rest.toolCallId || typeof rest.toolCallId !== 'string') {
return part;
}
return {
...rest,
output,

View File

@ -67,9 +67,23 @@ export function convertCoreToModel(messages: unknown): ModelMessage[] {
case 'tool':
// Tool messages: convert result to structured output
if (Array.isArray(content)) {
// Convert and flatten nested results
const convertedContent = [];
for (const part of content) {
const converted = convertToolResultPart(part);
if (converted !== null) {
if (Array.isArray(converted)) {
// Flatten nested arrays (from wrapper tool results)
convertedContent.push(...converted);
} else {
convertedContent.push(converted);
}
}
}
return {
...msg,
content: content.map(convertToolResultPart),
content: convertedContent,
} as ModelMessage;
}
return { role: 'tool', content: [] } as ModelMessage;
@ -133,7 +147,7 @@ function convertContentPart(part: unknown): unknown {
/**
* Convert tool result parts from v4 to v5 format
*/
function convertToolResultPart(part: unknown): unknown {
function convertToolResultPart(part: unknown): unknown | unknown[] | null {
if (!part || typeof part !== 'object') {
return part;
}
@ -144,10 +158,64 @@ function convertToolResultPart(part: unknown): unknown {
return part;
}
// Convert result → structured output
// Check if this is a wrapper with empty toolCallId/toolName and nested results
if (
'toolCallId' in p &&
(p.toolCallId === '' || !p.toolCallId) &&
'toolName' in p &&
(p.toolName === '' || !p.toolName) &&
'result' in p &&
Array.isArray(p.result)
) {
// This is a wrapper - extract and convert the nested tool results
const nestedResults = [];
for (const nestedItem of p.result) {
if (
nestedItem &&
typeof nestedItem === 'object' &&
'type' in nestedItem &&
nestedItem.type === 'tool-result'
) {
const converted = convertToolResultPart(nestedItem);
if (converted !== null) {
if (Array.isArray(converted)) {
nestedResults.push(...converted);
} else {
nestedResults.push(converted);
}
}
}
}
// Return the array of nested results to be flattened
return nestedResults.length > 0 ? nestedResults : null;
}
// Only convert if we have 'result' field but not 'output' (v4 format)
if ('result' in p && !('output' in p)) {
const { result, experimental_content, isError, ...rest } = p;
// Validate toolCallId exists and matches Anthropic's pattern
const toolCallId = rest.toolCallId;
if (!toolCallId || typeof toolCallId !== 'string' || toolCallId.trim() === '') {
console.warn('[chatConversationHistory] Skipping tool-result with invalid toolCallId:', {
toolCallId,
toolName: rest.toolName,
});
// Skip this tool result entirely if toolCallId is invalid
return null;
}
// Check if toolCallId matches the required pattern
const validPattern = /^[a-zA-Z0-9_-]+$/;
if (!validPattern.test(toolCallId)) {
console.warn('[chatConversationHistory] Tool-result has invalid toolCallId format:', {
toolCallId,
toolName: rest.toolName,
});
// Skip this tool result if the ID doesn't match the pattern
return null;
}
// Convert to v5's structured output format
let output: { type: string; value: unknown };
if (isError) {
@ -166,18 +234,13 @@ function convertToolResultPart(part: unknown): unknown {
}
}
// Ensure toolCallId exists and is valid
// If it's missing or invalid, preserve the original part
if (!rest.toolCallId || typeof rest.toolCallId !== 'string') {
return part;
}
return {
...rest,
output,
};
}
// Already in v5 format or doesn't need conversion
return part;
}