From baa7b5a51d7c35d9a10a5b05f1f76c6a5872ef87 Mon Sep 17 00:00:00 2001 From: Joe Maples Date: Sun, 19 Apr 2026 04:02:34 +0000 Subject: [PATCH 1/2] fix: consolidate multiple system messages into single string for model compatibility Some models don't support multiple system messages. Instead of pushing rules as a new array element, join existing array elements into a single string and append the formatted rules. --- src/index.runtime.test.ts | 28 ++++++++++++++++++++++++++++ src/runtime.ts | 5 ++++- 2 files changed, 32 insertions(+), 1 deletion(-) diff --git a/src/index.runtime.test.ts b/src/index.runtime.test.ts index d4e245c..dc0e8fe 100644 --- a/src/index.runtime.test.ts +++ b/src/index.runtime.test.ts @@ -331,6 +331,34 @@ describe('OpenCodeRulesPlugin', () => { expect(result.system).toContain('Rule Content'); }); + it('should consolidate array system messages into single string', async () => { + const { testDir, globalRulesDir } = getTestDirs(); + writeFileSync(path.join(globalRulesDir, 'rule.md'), '# My Rule'); + process.env.XDG_CONFIG_HOME = path.join(testDir, '.config'); + + const { + default: { server: plugin }, + } = await import('./index.js'); + const mockInput = createMockPluginInput({ testDir }); + + const hooks = await plugin( + mockInput as unknown as Parameters[0] + ); + const systemTransform = hooks['experimental.chat.system.transform'] as ( + input: unknown, + output: { system: string[] } + ) => Promise<{ system: string }>; + const result = await systemTransform( + {}, + { system: ['First message.', 'Second message.'] } + ); + + expect(typeof result.system).toBe('string'); + expect(result.system).toContain('First message.'); + expect(result.system).toContain('Second message.'); + expect(result.system).toContain('My Rule'); + }); + it('should not modify messages in messages.transform hook', async () => { const { testDir, globalRulesDir } = getTestDirs(); writeFileSync(path.join(globalRulesDir, 'rule.md'), '# Rule'); diff --git a/src/runtime.ts b/src/runtime.ts index 27dab6c..8c6de1d 100644 --- a/src/runtime.ts +++ b/src/runtime.ts @@ -324,7 +324,10 @@ export class OpenCodeRulesRuntime { } if (Array.isArray(output.system)) { - output.system.push(combinedSystem); + output.system = + output.system.join('\n\n') + + (output.system.length > 0 ? '\n\n' : '') + + combinedSystem; } else { output.system = output.system ? `${output.system}\n\n${combinedSystem}` From 4ddb94cf91c4d1b9174c5e8c14640ea4a3201203 Mon Sep 17 00:00:00 2001 From: Joe Maples Date: Sun, 3 May 2026 14:11:00 -0400 Subject: [PATCH 2/2] test: add edge case tests for array system message consolidation - Add test for empty array system messages (system: []) - Add test for single-element array system messages - Update SystemTransformOutput type to reflect string-only return --- src/index.runtime.test.ts | 47 +++++++++++++++++++++++++++++++++++++++ src/runtime.ts | 2 +- 2 files changed, 48 insertions(+), 1 deletion(-) diff --git a/src/index.runtime.test.ts b/src/index.runtime.test.ts index dc0e8fe..40de2b0 100644 --- a/src/index.runtime.test.ts +++ b/src/index.runtime.test.ts @@ -359,6 +359,53 @@ describe('OpenCodeRulesPlugin', () => { expect(result.system).toContain('My Rule'); }); + it('should handle empty array system messages', async () => { + const { testDir, globalRulesDir } = getTestDirs(); + writeFileSync(path.join(globalRulesDir, 'rule.md'), '# My Rule'); + process.env.XDG_CONFIG_HOME = path.join(testDir, '.config'); + + const { + default: { server: plugin }, + } = await import('./index.js'); + const mockInput = createMockPluginInput({ testDir }); + + const hooks = await plugin( + mockInput as unknown as Parameters[0] + ); + const systemTransform = hooks['experimental.chat.system.transform'] as ( + input: unknown, + output: { system: string[] } + ) => Promise<{ system: string }>; + const result = await systemTransform({}, { system: [] }); + + expect(typeof result.system).toBe('string'); + expect(result.system).toContain('My Rule'); + }); + + it('should handle single-element array system message', async () => { + const { testDir, globalRulesDir } = getTestDirs(); + writeFileSync(path.join(globalRulesDir, 'rule.md'), '# My Rule'); + process.env.XDG_CONFIG_HOME = path.join(testDir, '.config'); + + const { + default: { server: plugin }, + } = await import('./index.js'); + const mockInput = createMockPluginInput({ testDir }); + + const hooks = await plugin( + mockInput as unknown as Parameters[0] + ); + const systemTransform = hooks['experimental.chat.system.transform'] as ( + input: unknown, + output: { system: string[] } + ) => Promise<{ system: string }>; + const result = await systemTransform({}, { system: ['Only message.'] }); + + expect(typeof result.system).toBe('string'); + expect(result.system).toContain('Only message.'); + expect(result.system).toContain('My Rule'); + }); + it('should not modify messages in messages.transform hook', async () => { const { testDir, globalRulesDir } = getTestDirs(); writeFileSync(path.join(globalRulesDir, 'rule.md'), '# Rule'); diff --git a/src/runtime.ts b/src/runtime.ts index 8c6de1d..0582d9e 100644 --- a/src/runtime.ts +++ b/src/runtime.ts @@ -37,7 +37,7 @@ interface SystemTransformInput { } interface SystemTransformOutput { - system?: string | string[]; + system?: string; } interface OpenCodeRulesRuntimeOptions {