chore: merge released dev into PR #1690

This commit is contained in:
Tam Nhu Tran committed 2026-08-08 21:14:50 -04:00
commit 59eec74f40
4 files changed
+151 -5

No files matched your search

@@ -321,4 +321,100 @@ describe('ProxyRequestTransformer regressions', () => {
expect(result.tool_choice).toBe('auto');
});
it('merges the top-level system field with a mid-array system message into one leading system message', () => {
// Claude Code sends the main system prompt via the top-level `system`
// field AND a skill/plugin listing as a `role: "system"` message inside
// `messages` (see #1459). Prepending the top-level field unconditionally
// used to leave two non-adjacent `system` messages in the payload, which
// strict OpenAI-compatible backends (LiteLLM among them) reject with:
// `400 A 'system' message can only appear at index 0 of the messages array.`
const result = new ProxyRequestTransformer().transform({
system: [{ type: 'text', text: 'You are Claude Code, a CLI tool.' }],
messages: [
{ role: 'user', content: 'ping' },
{
role: 'system',
content: 'The following skills are available for use with the Skill tool:\n- foo',
},
{ role: 'user', content: 'pong' },
],
});
const systemMessages = result.messages.filter((message) => message.role === 'system');
expect(systemMessages).toHaveLength(1);
expect(result.messages[0]).toEqual({
role: 'system',
content:
'You are Claude Code, a CLI tool.\n\nThe following skills are available for use with the Skill tool:\n- foo',
});
expect(result.messages[1]).toEqual({ role: 'user', content: 'ping\npong' });
});
it('hoists a late system message after complete parallel tool results without disturbing tool order', () => {
const result = new ProxyRequestTransformer().transform({
system: 'base instructions',
messages: [
{ role: 'user', content: 'inspect both files' },
{
role: 'assistant',
content: [
{ type: 'tool_use', id: 'toolu_1', name: 'read', input: { path: 'a.ts' } },
{ type: 'tool_use', id: 'toolu_2', name: 'read', input: { path: 'b.ts' } },
],
},
{
role: 'user',
content: [
{ type: 'tool_result', tool_use_id: 'toolu_1', content: 'a contents' },
{ type: 'tool_result', tool_use_id: 'toolu_2', content: 'b contents' },
],
},
{ role: 'system', content: 'late instructions' },
{ role: 'user', content: 'compare them' },
],
});
expect(result.messages).toEqual([
{ role: 'system', content: 'base instructions\n\nlate instructions' },
{ role: 'user', content: 'inspect both files' },
{
role: 'assistant',
content: '',
tool_calls: [
{
id: 'toolu_1',
type: 'function',
function: { name: 'read', arguments: '{"path":"a.ts"}' },
},
{
id: 'toolu_2',
type: 'function',
function: { name: 'read', arguments: '{"path":"b.ts"}' },
},
],
},
{ role: 'tool', tool_call_id: 'toolu_1', content: 'a contents' },
{ role: 'tool', tool_call_id: 'toolu_2', content: 'b contents' },
{ role: 'user', content: 'compare them' },
]);
});
it('rejects a system message inserted before pending tool results', () => {
expect(() =>
new ProxyRequestTransformer().transform({
messages: [
{
role: 'assistant',
content: [{ type: 'tool_use', id: 'toolu_1', name: 'read', input: { path: 'a.ts' } }],
},
{ role: 'system', content: 'interrupting instructions' },
{
role: 'user',
content: [{ type: 'tool_result', tool_use_id: 'toolu_1', content: 'a contents' }],
},
],
})
).toThrow('role must be "user" with tool_result blocks after assistant tool_use');
});
});
@@ -43,7 +43,7 @@ describe('ProxyRequestTransformer', () => {
});
});
it('accepts Claude Code system messages in the messages array', () => {
it('accepts Claude Code system messages in the messages array and hoists them to a single leading system message', () => {
const transformer = new ProxyRequestTransformer();
const result = transformer.transform({
messages: [
@@ -53,10 +53,14 @@ describe('ProxyRequestTransformer', () => {
],
});
// Strict OpenAI-compatible backends (e.g. LiteLLM) reject any payload
// where `system` is not alone at index 0, so a mid-array `system`
// message must be hoisted rather than left in place. See #1459 for why
// the message must be accepted at all, and the coalesce-duplicate-system
// fix for why it can't simply stay where it landed.
expect(result.messages).toEqual([
{ role: 'user', content: 'hello' },
{ role: 'system', content: 'answer tersely' },
{ role: 'user', content: 'which model is this?' },
{ role: 'user', content: 'hello\nwhich model is this?' },
]);
});