mirror of
https://github.com/hanzoai/insights-js.git
synced 2026-10-30 18:34:19 +00:00
204 lines
6.7 KiB
TypeScript
204 lines
6.7 KiB
TypeScript
import { toContentString, getTokensSource } from '../src/utils'
|
|
|
|
describe('getTokensSource', () => {
|
|
it.each([
|
|
['undefined properties', undefined, 'sdk'],
|
|
['empty properties', {}, 'sdk'],
|
|
['unrelated properties', { foo: 'bar' }, 'sdk'],
|
|
['$ai_input_tokens override', { $ai_input_tokens: 999 }, 'passthrough'],
|
|
['$ai_output_tokens override', { $ai_output_tokens: 999 }, 'passthrough'],
|
|
['$ai_total_tokens override', { $ai_total_tokens: 999 }, 'passthrough'],
|
|
['$ai_cache_read_input_tokens override', { $ai_cache_read_input_tokens: 500 }, 'passthrough'],
|
|
['$ai_cache_creation_input_tokens override', { $ai_cache_creation_input_tokens: 200 }, 'passthrough'],
|
|
['$ai_reasoning_tokens override', { $ai_reasoning_tokens: 300 }, 'passthrough'],
|
|
['mixed override and custom', { $ai_input_tokens: 999, custom_key: 'value' }, 'passthrough'],
|
|
])('%s → %s', (_name, props, expected) => {
|
|
expect(getTokensSource(props)).toBe(expected)
|
|
})
|
|
})
|
|
|
|
describe('toContentString', () => {
|
|
describe('string inputs', () => {
|
|
it('should return strings as-is', () => {
|
|
expect(toContentString('hello world')).toBe('hello world')
|
|
})
|
|
|
|
it('should return empty strings as-is', () => {
|
|
expect(toContentString('')).toBe('')
|
|
})
|
|
|
|
it('should handle strings with special characters', () => {
|
|
const input = 'Hello "world" with\nnewlines and\ttabs'
|
|
expect(toContentString(input)).toBe(input)
|
|
})
|
|
})
|
|
|
|
describe('primitive inputs', () => {
|
|
it('should convert numbers to strings', () => {
|
|
expect(toContentString(123)).toBe('123')
|
|
expect(toContentString(0)).toBe('0')
|
|
expect(toContentString(-42.5)).toBe('-42.5')
|
|
})
|
|
|
|
it('should convert booleans to strings', () => {
|
|
expect(toContentString(true)).toBe('true')
|
|
expect(toContentString(false)).toBe('false')
|
|
})
|
|
|
|
it('should convert null to string', () => {
|
|
expect(toContentString(null)).toBe('null')
|
|
})
|
|
|
|
it('should convert undefined to string', () => {
|
|
expect(toContentString(undefined)).toBe('undefined')
|
|
})
|
|
})
|
|
|
|
describe('object inputs', () => {
|
|
it('should JSON.stringify simple objects', () => {
|
|
const input = { foo: 'bar', baz: 42 }
|
|
expect(toContentString(input)).toBe('{"foo":"bar","baz":42}')
|
|
})
|
|
|
|
it('should JSON.stringify nested objects', () => {
|
|
const input = {
|
|
level1: {
|
|
level2: {
|
|
value: 'deep',
|
|
},
|
|
},
|
|
}
|
|
expect(toContentString(input)).toBe('{"level1":{"level2":{"value":"deep"}}}')
|
|
})
|
|
|
|
it('should JSON.stringify objects with null values', () => {
|
|
const input = { key: null }
|
|
expect(toContentString(input)).toBe('{"key":null}')
|
|
})
|
|
|
|
it('should handle the [object Object] case', () => {
|
|
const input = { type: 'text', text: 'Hello world' }
|
|
const result = toContentString(input)
|
|
expect(result).toBe('{"type":"text","text":"Hello world"}')
|
|
expect(result).not.toBe('[object Object]')
|
|
})
|
|
})
|
|
|
|
describe('array inputs', () => {
|
|
it('should JSON.stringify simple arrays', () => {
|
|
expect(toContentString([1, 2, 3])).toBe('[1,2,3]')
|
|
})
|
|
|
|
it('should JSON.stringify string arrays', () => {
|
|
expect(toContentString(['a', 'b', 'c'])).toBe('["a","b","c"]')
|
|
})
|
|
|
|
it('should JSON.stringify arrays of objects', () => {
|
|
const input = [{ type: 'text', text: 'Hello' }]
|
|
expect(toContentString(input)).toBe('[{"type":"text","text":"Hello"}]')
|
|
})
|
|
|
|
it('should handle structured content format (the main bug case)', () => {
|
|
const input = [
|
|
{
|
|
type: 'output_text',
|
|
text: 'Hi there! How are you today?',
|
|
},
|
|
]
|
|
const result = toContentString(input)
|
|
expect(result).toBe('[{"type":"output_text","text":"Hi there! How are you today?"}]')
|
|
expect(result).not.toBe('[object Object]')
|
|
})
|
|
|
|
it('should handle multiple content items', () => {
|
|
const input = [
|
|
{ type: 'text', text: 'Hello' },
|
|
{ type: 'text', text: 'World' },
|
|
]
|
|
expect(toContentString(input)).toBe('[{"type":"text","text":"Hello"},{"type":"text","text":"World"}]')
|
|
})
|
|
|
|
it('should handle empty arrays', () => {
|
|
expect(toContentString([])).toBe('[]')
|
|
})
|
|
})
|
|
|
|
describe('OpenAI Response API format', () => {
|
|
it('should handle output_text format', () => {
|
|
const input = [
|
|
{
|
|
type: 'output_text',
|
|
text: "I'm just a friendly AI, so I don't have feelings, but I'm here and ready to help.",
|
|
},
|
|
]
|
|
const result = toContentString(input)
|
|
expect(result).toContain('"type":"output_text"')
|
|
expect(result).toContain('"text":"I\'m just a friendly AI')
|
|
expect(result).not.toBe('[object Object]')
|
|
})
|
|
|
|
it('should handle input_text format', () => {
|
|
const input = [
|
|
{
|
|
type: 'input_text',
|
|
text: 'Hello!',
|
|
},
|
|
]
|
|
expect(toContentString(input)).toBe('[{"type":"input_text","text":"Hello!"}]')
|
|
})
|
|
})
|
|
|
|
describe('Anthropic format', () => {
|
|
it('should handle text content blocks', () => {
|
|
const input = [
|
|
{
|
|
type: 'text',
|
|
text: 'Hello from Anthropic',
|
|
},
|
|
]
|
|
expect(toContentString(input)).toBe('[{"type":"text","text":"Hello from Anthropic"}]')
|
|
})
|
|
})
|
|
|
|
describe('edge cases', () => {
|
|
it('should handle objects with special characters in values', () => {
|
|
const input = {
|
|
message: 'Line 1\nLine 2\tTabbed',
|
|
quote: 'He said "hello"',
|
|
}
|
|
const result = toContentString(input)
|
|
expect(result).toContain('Line 1\\nLine 2\\tTabbed')
|
|
expect(result).toContain('He said \\"hello\\"')
|
|
})
|
|
|
|
it('should handle arrays with mixed types', () => {
|
|
const input = [1, 'two', { three: 3 }, null, true]
|
|
expect(toContentString(input)).toBe('[1,"two",{"three":3},null,true]')
|
|
})
|
|
|
|
it('should handle functions by converting to undefined in JSON', () => {
|
|
const input = { fn: () => {} }
|
|
expect(toContentString(input)).toBe('{}')
|
|
})
|
|
|
|
it('should handle dates by converting to ISO string', () => {
|
|
const date = new Date('2024-01-01T00:00:00.000Z')
|
|
const input = { timestamp: date }
|
|
expect(toContentString(input)).toBe('{"timestamp":"2024-01-01T00:00:00.000Z"}')
|
|
})
|
|
|
|
it('should handle circular references gracefully', () => {
|
|
const obj: any = { name: 'test' }
|
|
obj.self = obj // Create circular reference
|
|
const result = toContentString(obj)
|
|
expect(result).toBe('[object Object]') // Falls back to String()
|
|
})
|
|
|
|
it('should handle BigInt gracefully', () => {
|
|
const input = { big: BigInt(9007199254740991) }
|
|
const result = toContentString(input)
|
|
expect(result).toBe('[object Object]') // Falls back to String()
|
|
})
|
|
})
|
|
})
|