fix inbox lifecycle downstream contracts
This commit is contained in:
46
packages/llm/llm/tests/adapter-failure.spec.ts
Normal file
46
packages/llm/llm/tests/adapter-failure.spec.ts
Normal file
@@ -0,0 +1,46 @@
|
||||
import { describe, expect, it } from 'vitest'
|
||||
import { normalizeLlmFailure } from '../src/adapter-failure.ts'
|
||||
|
||||
describe('adapter failure normalization', () => {
|
||||
it('contains hostile non-Error coercion', () => {
|
||||
const thrown = { [Symbol.toPrimitive]: () => { throw new Error('coercion failed') } }
|
||||
expect(normalizeLlmFailure(thrown)).toEqual({ message: 'LLM adapter failed', code: 'UNKNOWN' })
|
||||
})
|
||||
|
||||
it('contains hostile Error property reflection', () => {
|
||||
const withFailure = new Error('provider failed') as Error & { failure: unknown; code: string }
|
||||
withFailure.failure = { message: 'provider failed', code: 'FOREIGN' }
|
||||
withFailure.code = 'FOREIGN'
|
||||
const hostileCode = new Proxy(withFailure, {
|
||||
getOwnPropertyDescriptor(target, property) {
|
||||
if (property === 'code') throw new Error('code descriptor failed')
|
||||
return Reflect.getOwnPropertyDescriptor(target, property)
|
||||
},
|
||||
})
|
||||
expect(normalizeLlmFailure(hostileCode)).toEqual({ message: 'provider failed', code: 'UNKNOWN' })
|
||||
|
||||
const hostileFailure = new Proxy(new Error('provider failed'), {
|
||||
getOwnPropertyDescriptor() { throw new Error('failure descriptor failed') },
|
||||
})
|
||||
expect(normalizeLlmFailure(hostileFailure)).toEqual({ message: 'provider failed', code: 'UNKNOWN' })
|
||||
})
|
||||
|
||||
it('rejects malformed or accessor-backed failure snapshots', () => {
|
||||
const malformed = new Error('provider failed') as Error & { failure: unknown; code: string }
|
||||
malformed.failure = { message: 'provider failed', code: 'FOREIGN', requestId: '' }
|
||||
malformed.code = 'FOREIGN'
|
||||
expect(normalizeLlmFailure(malformed)).toEqual({ message: 'provider failed', code: 'UNKNOWN' })
|
||||
|
||||
const accessorBacked = new Error('provider failed') as Error & { failure: unknown }
|
||||
accessorBacked.failure = Object.defineProperty({}, 'message', {
|
||||
get() { throw new Error('failure getter failed') },
|
||||
})
|
||||
expect(normalizeLlmFailure(accessorBacked)).toEqual({ message: 'provider failed', code: 'UNKNOWN' })
|
||||
})
|
||||
|
||||
it('falls back when an Error message accessor throws', () => {
|
||||
const error = new Error('provider failed')
|
||||
Object.defineProperty(error, 'message', { get() { throw new Error('message getter failed') } })
|
||||
expect(normalizeLlmFailure(error)).toEqual({ message: 'LLM adapter failed', code: 'UNKNOWN' })
|
||||
})
|
||||
})
|
||||
@@ -489,6 +489,23 @@ describe('LlmService', () => {
|
||||
expect(cleanupCalls).toBe(1)
|
||||
})
|
||||
|
||||
it('allows downstream close when an adapter iterator has no return method', async () => {
|
||||
const adapter = new class extends LlmAdapter {
|
||||
stream(_options: GenerateOptions): AsyncIterable<StreamChunk> {
|
||||
return {
|
||||
[Symbol.asyncIterator](): AsyncIterator<StreamChunk> {
|
||||
return { next: () => Promise.resolve({ done: false, value: SCRIPT[0]! }) }
|
||||
},
|
||||
}
|
||||
}
|
||||
}()
|
||||
const ctx = new Context()
|
||||
await ctx.plugin(LlmService)
|
||||
ctx.llm.registerAdapter(['test'], adapter)
|
||||
|
||||
for await (const _chunk of ctx.llm.stream({ provider: 'test', model: 'test', messages: [] })) break
|
||||
})
|
||||
|
||||
it('unregisters adapters when the owning fiber is disposed (HMR safety)', async () => {
|
||||
const ctx = new Context()
|
||||
await ctx.plugin(LlmService)
|
||||
|
||||
Reference in New Issue
Block a user