feat: extend proxy runtime parity

This commit is contained in:
ciat-777
2026-03-18 12:01:05 +08:00
parent 20ef75e76d
commit cd37b11722
21 changed files with 2756 additions and 112 deletions
@@ -524,6 +524,44 @@ describe('convertOpenAiBodyToAnthropicMessagesBody', () => {
]);
});
it('preserves tool_reference blocks inside tool_result content arrays', () => {
const result = sanitizeAnthropicMessagesBody({
model: 'claude-opus-4-6',
messages: [
{
role: 'user',
content: [
{
type: 'tool_result',
tool_use_id: 'tool_1',
content: [
{ type: 'tool_reference', tool_name: 'lookup' },
{ type: 'text', text: 'follow-up' },
],
},
],
},
],
});
expect(result.messages).toEqual([
{
role: 'user',
content: [
{
type: 'tool_result',
tool_use_id: 'tool_1',
content: [
{ type: 'tool_reference', tool_name: 'lookup' },
{ type: 'text', text: 'follow-up' },
],
cache_control: { type: 'ephemeral' },
},
],
},
]);
});
it('adds a second message cache anchor around the 20-block window for long prompts', () => {
const longContent = Array.from({ length: 25 }, (_, index) => ({
type: 'text',
@@ -187,12 +187,39 @@ function normalizeTextCandidate(value: unknown): string {
return asTrimmedString(value.text ?? value.content ?? value.output_text);
}
function normalizeToolMessageContent(raw: unknown): string {
function sanitizeToolReferenceBlock(item: Record<string, unknown>): Record<string, unknown> | null {
const toolName = asTrimmedString(item.tool_name ?? item.toolName ?? item.name);
if (!toolName) return null;
const next: Record<string, unknown> = {
...item,
type: 'tool_reference',
tool_name: toolName,
};
delete next.toolName;
if ('name' in next) delete next.name;
return next;
}
function normalizeToolMessageContent(raw: unknown): string | Array<Record<string, unknown>> {
if (raw === undefined || raw === null) return '';
if (typeof raw === 'string') return raw;
if (typeof raw === 'number' || typeof raw === 'boolean') return String(raw);
if (Array.isArray(raw)) {
const structuredBlocks = raw
.map((item) => {
if (typeof item === 'string') return toAnthropicTextBlock(item);
if (!isRecord(item)) return null;
const type = asTrimmedString(item.type).toLowerCase();
if (type === 'tool_reference') return sanitizeToolReferenceBlock(item);
if (type === 'text' || type === 'input_text' || type === 'output_text') {
return toAnthropicTextBlock(normalizeTextCandidate(item));
}
return null;
})
.filter((item): item is Record<string, unknown> => item !== null);
if (structuredBlocks.length > 0) return structuredBlocks;
const textParts = raw
.map((item) => {
if (typeof item === 'string') return item;
@@ -329,7 +356,7 @@ function sanitizeAnthropicContentBlock(item: Record<string, unknown>): Record<st
if (type === 'tool_result') {
const toolUseId = asTrimmedString(item.tool_use_id ?? item.toolUseId);
const content = normalizeToolMessageContent(item.content ?? item.result);
if (!toolUseId || !content) return null;
if (!toolUseId || (typeof content === 'string' ? !content : content.length <= 0)) return null;
const next: Record<string, unknown> = {
type: 'tool_result',
tool_use_id: toolUseId,
@@ -355,6 +382,10 @@ function sanitizeAnthropicContentBlock(item: Record<string, unknown>): Record<st
return next;
}
if (type === 'tool_reference') {
return sanitizeToolReferenceBlock(item);
}
return { ...item };
}
@@ -846,8 +877,11 @@ export function convertOpenAiBodyToAnthropicMessagesBody(
asTrimmedString(toolCandidate.tool_call_id)
|| asTrimmedString(toolCandidate.id)
);
const toolResultContent = normalizeToolMessageContent(toolCandidate.content).trim();
if (toolUseId && toolResultContent) {
const toolResultContent = normalizeToolMessageContent(toolCandidate.content);
const hasToolResultContent = typeof toolResultContent === 'string'
? toolResultContent.trim().length > 0
: toolResultContent.length > 0;
if (toolUseId && hasToolResultContent) {
toolResultBlocks.push({
type: 'tool_result',
tool_use_id: toolUseId,