feat(core): add agent execution context
This commit is contained in:
@@ -0,0 +1,136 @@
|
||||
import { describe, expect, it } from 'vitest'
|
||||
import { Context } from 'cordis'
|
||||
import { AgentId, type Agent } from '@deepseek-ai/dsh-agent'
|
||||
import AgentExecutionProvider from '@deepseek-ai/dsh-agent-execution'
|
||||
import type { AgentExecution, AgentExecutionService } from '@deepseek-ai/dsh-agent-execution'
|
||||
|
||||
function execution(id: string): AgentExecution {
|
||||
return { agent: { id: AgentId(id) } as Agent }
|
||||
}
|
||||
|
||||
async function harness(): Promise<{
|
||||
ctx: Context
|
||||
service: AgentExecutionService
|
||||
dispose: () => Promise<void>
|
||||
}> {
|
||||
const ctx = new Context()
|
||||
const fiber = await ctx.plugin(AgentExecutionProvider)
|
||||
return {
|
||||
ctx,
|
||||
service: ctx.agentExecution,
|
||||
dispose: fiber.dispose,
|
||||
}
|
||||
}
|
||||
|
||||
describe('AgentExecutionProvider', () => {
|
||||
it('reports an absent boundary and requires an active execution', async () => {
|
||||
const { service, dispose } = await harness()
|
||||
expect(service.current()).toBeUndefined()
|
||||
expect(() => service.require()).toThrow('no agent execution context is active')
|
||||
await dispose()
|
||||
})
|
||||
|
||||
it('preserves exact synchronous and Promise return identities across await', async () => {
|
||||
const { service, dispose } = await harness()
|
||||
const active = execution('identity')
|
||||
const value = { result: true }
|
||||
expect(service.run(active, () => {
|
||||
expect(service.require()).toBe(active)
|
||||
return value
|
||||
})).toBe(value)
|
||||
|
||||
const promise = service.run(active, async () => {
|
||||
expect(service.require()).toBe(active)
|
||||
await Promise.resolve()
|
||||
expect(service.require()).toBe(active)
|
||||
return value
|
||||
})
|
||||
expect(service.run(active, () => promise)).toBe(promise)
|
||||
await expect(promise).resolves.toBe(value)
|
||||
expect(service.current()).toBeUndefined()
|
||||
await dispose()
|
||||
})
|
||||
|
||||
it('isolates overlapping executions', async () => {
|
||||
const { service, dispose } = await harness()
|
||||
const a = execution('a')
|
||||
const b = execution('b')
|
||||
const bothStarted = Promise.withResolvers<boolean>()
|
||||
const release = Promise.withResolvers<boolean>()
|
||||
let starts = 0
|
||||
const run = (active: AgentExecution): Promise<void> => service.run(active, async () => {
|
||||
expect(service.require()).toBe(active)
|
||||
starts += 1
|
||||
if (starts === 2) bothStarted.resolve(true)
|
||||
await release.promise
|
||||
expect(service.require()).toBe(active)
|
||||
})
|
||||
|
||||
const pending = [run(a), run(b)]
|
||||
await bothStarted.promise
|
||||
expect(service.current()).toBeUndefined()
|
||||
release.resolve(true)
|
||||
await Promise.all(pending)
|
||||
await dispose()
|
||||
})
|
||||
|
||||
it('restores nested and explicitly cleared boundaries', async () => {
|
||||
const { service, dispose } = await harness()
|
||||
const parent = execution('parent')
|
||||
const child = execution('child')
|
||||
|
||||
service.run(parent, () => {
|
||||
expect(service.require()).toBe(parent)
|
||||
service.run(child, () => { expect(service.require()).toBe(child) })
|
||||
expect(service.require()).toBe(parent)
|
||||
service.run(undefined, () => {
|
||||
expect(service.current()).toBeUndefined()
|
||||
expect(() => service.require()).toThrow('no agent execution context is active')
|
||||
})
|
||||
expect(service.require()).toBe(parent)
|
||||
})
|
||||
expect(service.current()).toBeUndefined()
|
||||
await dispose()
|
||||
})
|
||||
|
||||
it('restores context after synchronous throws and rejected operations', async () => {
|
||||
const { service, dispose } = await harness()
|
||||
const parent = execution('parent')
|
||||
const child = execution('child')
|
||||
const syncError = new Error('sync failure')
|
||||
const asyncError = new Error('async failure')
|
||||
|
||||
service.run(parent, () => {
|
||||
expect(() => service.run(child, () => { throw syncError })).toThrow(syncError)
|
||||
expect(service.require()).toBe(parent)
|
||||
})
|
||||
await expect(service.run(child, async () => {
|
||||
await Promise.resolve()
|
||||
throw asyncError
|
||||
})).rejects.toBe(asyncError)
|
||||
expect(service.current()).toBeUndefined()
|
||||
await dispose()
|
||||
})
|
||||
|
||||
it('stops new boundaries, drains active Promises, and invalidates retained references', async () => {
|
||||
const { ctx, service, dispose } = await harness()
|
||||
const active = execution('draining')
|
||||
const release = Promise.withResolvers<boolean>()
|
||||
const pending = service.run(active, async () => {
|
||||
await release.promise
|
||||
expect(service.require()).toBe(active)
|
||||
})
|
||||
let disposed = false
|
||||
const disposal = dispose().then(() => { disposed = true })
|
||||
await Promise.resolve()
|
||||
|
||||
expect(() => service.run(active, () => 1)).toThrow('agent execution service is disposed')
|
||||
expect(disposed).toBe(false)
|
||||
expect(ctx.get('agentExecution')).toBeUndefined()
|
||||
release.resolve(true)
|
||||
await pending
|
||||
await disposal
|
||||
expect(() => service.current()).toThrow('agent execution service is disposed')
|
||||
expect(() => service.require()).toThrow('agent execution service is disposed')
|
||||
})
|
||||
})
|
||||
Reference in New Issue
Block a user