diff --git a/agents/base-chat.ts b/agents/base-chat.ts index 0ce94245ac..e177738732 100644 --- a/agents/base-chat.ts +++ b/agents/base-chat.ts @@ -101,7 +101,8 @@ End every response by calling the suggest_followups tool with exactly 3 followup // `model` is absent only when the generator is driven directly (tests) or // by a runtime predating AgentStepContext.model. - const contextWindow = CONTEXT_WINDOWS[model ?? ''] ?? DEFAULT_CONTEXT_WINDOW + const contextWindow = + (model && CONTEXT_WINDOWS[model]) ?? DEFAULT_CONTEXT_WINDOW const maxContextLength = Math.floor(contextWindow * CONTEXT_BUDGET_FRACTION) while (true) { diff --git a/common/src/util/__tests__/string.test.ts b/common/src/util/__tests__/string.test.ts index 3a141ca6b6..965ddbeacc 100644 --- a/common/src/util/__tests__/string.test.ts +++ b/common/src/util/__tests__/string.test.ts @@ -1,6 +1,6 @@ import { describe, expect, it } from 'bun:test' -import { pluralize } from '../string' +import { pluralize, truncateStringWithMessage } from '../string' describe('pluralize', () => { it('should handle singular and plural cases correctly', () => { @@ -235,5 +235,127 @@ describe('pluralize', () => { expect(pluralize(2, 'query')).toBe('2 queries') expect(pluralize(2, 'dependency')).toBe('2 dependencies') }) + + describe('truncateStringWithMessage', () => { + it('should truncate from end by default', () => { + const result = truncateStringWithMessage({ + str: 'Hello world, this is a test string', + maxLength: 20 + }) + expect(result).toContain('TRUNCATED') + expect(result.startsWith('Hello')).toBe(true) + }) + + it('should handle negative available length for END truncation', () => { + const result = truncateStringWithMessage({ + str: 'Hello', + maxLength: 5, + remove: 'END' + }) + expect(result).toBe('\n[TRUNCATED DUE TO LENGTH...]') + }) + + it('should handle zero available length for END truncation', () => { + const result = truncateStringWithMessage({ + str: 'Hello world', + maxLength: 10, + remove: 'END' + }) + expect(result).toBe('\n[TRUNCATED DUE TO LENGTH...]') + }) + + it('should truncate from start correctly', () => { + const result = truncateStringWithMessage({ + str: 'Hello world, this is a test string', + maxLength: 50, + remove: 'START' + }) + expect(result).toContain('TRUNCATED DUE TO LENGTH') + expect(result.endsWith('string')).toBe(true) + }) + + it('should handle negative available length for START truncation', () => { + const result = truncateStringWithMessage({ + str: 'Hello world', + maxLength: 5, + remove: 'START' + }) + expect(result).toBe('[...TRUNCATED DUE TO LENGTH]\n') + }) + + it('should handle zero available length for START truncation', () => { + const result = truncateStringWithMessage({ + str: 'Hello world', + maxLength: 10, + remove: 'START' + }) + expect(result).toBe('[...TRUNCATED DUE TO LENGTH]\n') + }) + + it('should truncate from middle correctly', () => { + const result = truncateStringWithMessage({ + str: 'Hello world, this is a test string', + maxLength: 20, + remove: 'MIDDLE' + }) + expect(result).toContain('TRUNCATED') + expect(result.startsWith('Hello')).toBe(true) + expect(result.endsWith('string')).toBe(true) + }) + + it('should truncate from start correctly', () => { + const result = truncateStringWithMessage({ + str: 'Hello world, this is a test string', + maxLength: 50, + remove: 'START' + }) + expect(result).toContain('TRUNCATED DUE TO LENGTH') + expect(result.endsWith('string')).toBe(true) + }) + + it('should handle negative available length for MIDDLE truncation', () => { + const result = truncateStringWithMessage({ + str: 'Hello world', + maxLength: 5, + remove: 'MIDDLE' + }) + expect(result).toBe('\n[...TRUNCATED DUE TO LENGTH...]\n') + }) + + it('should handle zero available length for MIDDLE truncation', () => { + const result = truncateStringWithMessage({ + str: 'Hello world', + maxLength: 10, + remove: 'MIDDLE' + }) + expect(result).toBe('\n[...TRUNCATED DUE TO LENGTH...]\n') + }) + + it('should return original string when within maxLength', () => { + const result = truncateStringWithMessage({ + str: 'Short', + maxLength: 100 + }) + expect(result).toBe('Short') + }) + + it('should use custom message when provided', () => { + const result = truncateStringWithMessage({ + str: 'Hello world, this is a test string', + maxLength: 20, + message: 'CUSTOM MSG' + }) + expect(result).toContain('CUSTOM MSG') + expect(result.startsWith('Hello')).toBe(true) + }) + + it('should handle empty string', () => { + const result = truncateStringWithMessage({ + str: '', + maxLength: 10 + }) + expect(result).toBe('') + }) + }) }) diff --git a/common/src/util/string.ts b/common/src/util/string.ts index 506de962fd..96f31f73f4 100644 --- a/common/src/util/string.ts +++ b/common/src/util/string.ts @@ -24,15 +24,17 @@ export const truncateStringWithMessage = ({ if (remove === 'END') { const suffix = `\n[${message}...]` - return str.slice(0, maxLength - suffix.length) + suffix + const availableLength = Math.max(0, maxLength - suffix.length) + return str.slice(0, availableLength) + suffix } if (remove === 'START') { const prefix = `[...${message}]\n` - return prefix + str.slice(str.length - maxLength + prefix.length) + const availableLength = Math.max(0, maxLength - prefix.length) + return prefix + str.slice(str.length - availableLength) } const middle = `\n[...${message}...]\n` - const length = Math.floor((maxLength - middle.length) / 2) + const length = Math.max(0, Math.floor((maxLength - middle.length) / 2)) return str.slice(0, length) + middle + str.slice(-length) }