chore: import upstream snapshot with attribution
Publish CLI Package / publish-npm (push) Waiting to run
Publish Python SDK / publish-pypi (push) Waiting to run
Publish TypeScript SDK / publish-npm (push) Waiting to run
CI / Migrate Dev DB (push) Has been skipped
CI / Detect Version (push) Has been cancelled
CI / Migrate DB (push) Has been cancelled
CI / Build Dev ECR (./docker/app.Dockerfile, ECR_APP) (push) Has been cancelled
CI / Build Dev ECR (./docker/db.Dockerfile, ECR_MIGRATIONS) (push) Has been cancelled
CI / Build Dev ECR (./docker/pii.Dockerfile, ECR_PII) (push) Has been cancelled
CI / Build Dev ECR (./docker/realtime.Dockerfile, ECR_REALTIME) (push) Has been cancelled
CI / Deploy Trigger.dev (Dev) (push) Has been cancelled
CI / Build AMD64 (./docker/app.Dockerfile, ECR_APP, ghcr.io/simstudioai/simstudio) (push) Has been cancelled
CI / Build AMD64 (./docker/db.Dockerfile, ECR_MIGRATIONS, ghcr.io/simstudioai/migrations) (push) Has been cancelled
CI / Build AMD64 (./docker/pii.Dockerfile, ECR_PII, ghcr.io/simstudioai/pii) (push) Has been cancelled
CI / Build AMD64 (./docker/realtime.Dockerfile, ECR_REALTIME, ghcr.io/simstudioai/realtime) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/app.Dockerfile, ghcr.io/simstudioai/simstudio) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/db.Dockerfile, ghcr.io/simstudioai/migrations) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/pii.Dockerfile, ghcr.io/simstudioai/pii) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/realtime.Dockerfile, ghcr.io/simstudioai/realtime) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/migrations) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/pii) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/realtime) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/simstudio) (push) Has been cancelled
CI / Check Docs Changes (push) Has been cancelled
CI / Process Docs (push) Has been cancelled
CI / Create GitHub Release (push) Has been cancelled
CI / Test and Build (push) Has been cancelled
Publish CLI Package / publish-npm (push) Waiting to run
Publish Python SDK / publish-pypi (push) Waiting to run
Publish TypeScript SDK / publish-npm (push) Waiting to run
CI / Migrate Dev DB (push) Has been skipped
CI / Detect Version (push) Has been cancelled
CI / Migrate DB (push) Has been cancelled
CI / Build Dev ECR (./docker/app.Dockerfile, ECR_APP) (push) Has been cancelled
CI / Build Dev ECR (./docker/db.Dockerfile, ECR_MIGRATIONS) (push) Has been cancelled
CI / Build Dev ECR (./docker/pii.Dockerfile, ECR_PII) (push) Has been cancelled
CI / Build Dev ECR (./docker/realtime.Dockerfile, ECR_REALTIME) (push) Has been cancelled
CI / Deploy Trigger.dev (Dev) (push) Has been cancelled
CI / Build AMD64 (./docker/app.Dockerfile, ECR_APP, ghcr.io/simstudioai/simstudio) (push) Has been cancelled
CI / Build AMD64 (./docker/db.Dockerfile, ECR_MIGRATIONS, ghcr.io/simstudioai/migrations) (push) Has been cancelled
CI / Build AMD64 (./docker/pii.Dockerfile, ECR_PII, ghcr.io/simstudioai/pii) (push) Has been cancelled
CI / Build AMD64 (./docker/realtime.Dockerfile, ECR_REALTIME, ghcr.io/simstudioai/realtime) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/app.Dockerfile, ghcr.io/simstudioai/simstudio) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/db.Dockerfile, ghcr.io/simstudioai/migrations) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/pii.Dockerfile, ghcr.io/simstudioai/pii) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/realtime.Dockerfile, ghcr.io/simstudioai/realtime) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/migrations) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/pii) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/realtime) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/simstudio) (push) Has been cancelled
CI / Check Docs Changes (push) Has been cancelled
CI / Process Docs (push) Has been cancelled
CI / Create GitHub Release (push) Has been cancelled
CI / Test and Build (push) Has been cancelled
This commit is contained in:
@@ -0,0 +1,210 @@
|
||||
/**
|
||||
* @vitest-environment node
|
||||
*/
|
||||
import { describe, expect, it, vi } from 'vitest'
|
||||
import type { NormalizedBlockOutput } from '@/executor/types'
|
||||
import { createStreamingExecution } from '@/providers/streaming-execution'
|
||||
|
||||
/**
|
||||
* Builds a fake stream factory mirroring the providers' `createReadableStreamFrom*`
|
||||
* helpers: it returns a sentinel stream and synchronously invokes the drain
|
||||
* callback so the test can assert the populated output without a real stream.
|
||||
*/
|
||||
function fakeStreamFactory(
|
||||
drain: (handles: { output: NormalizedBlockOutput; finalizeTiming: () => void }) => void
|
||||
) {
|
||||
const stream = new ReadableStream()
|
||||
return {
|
||||
stream,
|
||||
createStream: (handles: { output: NormalizedBlockOutput; finalizeTiming: () => void }) => {
|
||||
drain(handles)
|
||||
return stream
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
describe('createStreamingExecution', () => {
|
||||
const providerStartTime = 1_000
|
||||
const providerStartTimeISO = new Date(providerStartTime).toISOString()
|
||||
|
||||
it('assembles the simple (no-tools) shape and finalizes timing on drain', () => {
|
||||
const drainTime = 5_000
|
||||
vi.spyOn(Date, 'now').mockReturnValue(drainTime)
|
||||
|
||||
const { stream, createStream } = fakeStreamFactory(({ output, finalizeTiming }) => {
|
||||
output.content = 'hello'
|
||||
output.tokens = { input: 10, output: 20, total: 30 }
|
||||
output.cost = { input: 0.1, output: 0.2, total: 0.3 }
|
||||
finalizeTiming()
|
||||
})
|
||||
|
||||
const result = createStreamingExecution({
|
||||
model: 'test-model',
|
||||
providerStartTime,
|
||||
providerStartTimeISO,
|
||||
timing: { kind: 'simple', segmentName: 'test-model' },
|
||||
initialTokens: { input: 0, output: 0, total: 0 },
|
||||
initialCost: { input: 0, output: 0, total: 0 },
|
||||
isStreaming: true,
|
||||
createStream,
|
||||
})
|
||||
|
||||
expect(result.stream).toBe(stream)
|
||||
|
||||
const output = result.execution.output
|
||||
expect(output.content).toBe('hello')
|
||||
expect(output.model).toBe('test-model')
|
||||
expect(output.tokens).toEqual({ input: 10, output: 20, total: 30 })
|
||||
expect(output.cost).toEqual({ input: 0.1, output: 0.2, total: 0.3 })
|
||||
expect(output.toolCalls).toBeUndefined()
|
||||
|
||||
const timing = output.providerTiming
|
||||
expect(timing?.startTime).toBe(providerStartTimeISO)
|
||||
expect(timing?.endTime).toBe(new Date(drainTime).toISOString())
|
||||
expect(timing?.duration).toBe(drainTime - providerStartTime)
|
||||
expect(timing?.modelTime).toBeUndefined()
|
||||
|
||||
const segment = timing?.timeSegments?.[0]
|
||||
expect(segment).toMatchObject({
|
||||
type: 'model',
|
||||
name: 'test-model',
|
||||
startTime: providerStartTime,
|
||||
})
|
||||
expect(segment?.endTime).toBe(drainTime)
|
||||
expect(segment?.duration).toBe(drainTime - providerStartTime)
|
||||
|
||||
expect(result.execution.success).toBe(true)
|
||||
expect(result.execution.logs).toEqual([])
|
||||
expect(result.execution.isStreaming).toBe(true)
|
||||
expect(result.execution.metadata?.startTime).toBe(providerStartTimeISO)
|
||||
|
||||
vi.restoreAllMocks()
|
||||
})
|
||||
|
||||
it('assembles the accumulated (post-tools) shape with pre-built segments', () => {
|
||||
const drainTime = 7_000
|
||||
vi.spyOn(Date, 'now').mockReturnValue(drainTime)
|
||||
|
||||
const timeSegments = [
|
||||
{ type: 'model' as const, name: 'iter 1', startTime: 1_000, endTime: 2_000, duration: 1_000 },
|
||||
{ type: 'tool' as const, name: 'lookup', startTime: 2_000, endTime: 2_500, duration: 500 },
|
||||
]
|
||||
|
||||
const { createStream } = fakeStreamFactory(({ output }) => {
|
||||
output.content = 'final'
|
||||
output.tokens = { input: 110, output: 220, total: 330 }
|
||||
output.cost = { input: 1.1, output: 2.2, toolCost: 0.5, total: 3.8 }
|
||||
})
|
||||
|
||||
const result = createStreamingExecution({
|
||||
model: 'tool-model',
|
||||
providerStartTime,
|
||||
providerStartTimeISO,
|
||||
timing: {
|
||||
kind: 'accumulated',
|
||||
modelTime: 1_500,
|
||||
toolsTime: 500,
|
||||
firstResponseTime: 800,
|
||||
iterations: 2,
|
||||
timeSegments,
|
||||
},
|
||||
initialTokens: { input: 100, output: 200, total: 300 },
|
||||
initialCost: { input: 1, output: 2, toolCost: undefined, total: 3 },
|
||||
toolCalls: { list: [{ name: 'lookup' }], count: 1 },
|
||||
isStreaming: true,
|
||||
createStream,
|
||||
})
|
||||
|
||||
const output = result.execution.output
|
||||
expect(output.content).toBe('final')
|
||||
expect(output.tokens).toEqual({ input: 110, output: 220, total: 330 })
|
||||
expect(output.cost).toEqual({ input: 1.1, output: 2.2, toolCost: 0.5, total: 3.8 })
|
||||
expect(output.toolCalls).toEqual({ list: [{ name: 'lookup' }], count: 1 })
|
||||
|
||||
const timing = output.providerTiming
|
||||
expect(timing?.modelTime).toBe(1_500)
|
||||
expect(timing?.toolsTime).toBe(500)
|
||||
expect(timing?.firstResponseTime).toBe(800)
|
||||
expect(timing?.iterations).toBe(2)
|
||||
expect(timing?.timeSegments).toBe(timeSegments)
|
||||
expect(timing?.startTime).toBe(providerStartTimeISO)
|
||||
expect(timing?.endTime).toBe(new Date(drainTime).toISOString())
|
||||
expect(timing?.duration).toBe(drainTime - providerStartTime)
|
||||
|
||||
vi.restoreAllMocks()
|
||||
})
|
||||
|
||||
it('only finalizes timing when the provider calls finalizeTiming', () => {
|
||||
const constructTime = 1_200
|
||||
vi.spyOn(Date, 'now').mockReturnValue(constructTime)
|
||||
|
||||
const result = createStreamingExecution({
|
||||
model: 'no-finalize',
|
||||
providerStartTime,
|
||||
providerStartTimeISO,
|
||||
timing: {
|
||||
kind: 'accumulated',
|
||||
modelTime: 0,
|
||||
toolsTime: 0,
|
||||
firstResponseTime: 0,
|
||||
iterations: 1,
|
||||
timeSegments: [],
|
||||
},
|
||||
initialTokens: { input: 0, output: 0, total: 0 },
|
||||
initialCost: { input: 0, output: 0, total: 0 },
|
||||
createStream: ({ output }) => {
|
||||
output.content = 'no-timing-mutation'
|
||||
return new ReadableStream()
|
||||
},
|
||||
})
|
||||
|
||||
const timing = result.execution.output.providerTiming
|
||||
expect(timing?.endTime).toBe(new Date(constructTime).toISOString())
|
||||
expect(timing?.duration).toBe(constructTime - providerStartTime)
|
||||
expect(result.execution.isStreaming).toBeUndefined()
|
||||
|
||||
vi.restoreAllMocks()
|
||||
})
|
||||
|
||||
it('finalizeTiming touches only top-level aggregate for accumulated timing', () => {
|
||||
const constructTime = 1_000
|
||||
const drainTime = 9_000
|
||||
const nowMock = vi.spyOn(Date, 'now').mockReturnValue(constructTime)
|
||||
|
||||
const segment = { type: 'model' as const, name: 's', startTime: 1, endTime: 2, duration: 1 }
|
||||
|
||||
const result = createStreamingExecution({
|
||||
model: 'm',
|
||||
providerStartTime,
|
||||
providerStartTimeISO,
|
||||
timing: {
|
||||
kind: 'accumulated',
|
||||
modelTime: 0,
|
||||
toolsTime: 0,
|
||||
firstResponseTime: 0,
|
||||
iterations: 1,
|
||||
timeSegments: [segment],
|
||||
},
|
||||
initialTokens: { input: 0, output: 0, total: 0 },
|
||||
initialCost: { input: 0, output: 0, total: 0 },
|
||||
createStream: ({ finalizeTiming }) => {
|
||||
nowMock.mockReturnValue(drainTime)
|
||||
finalizeTiming()
|
||||
return new ReadableStream()
|
||||
},
|
||||
})
|
||||
|
||||
const timing = result.execution.output.providerTiming
|
||||
expect(timing?.endTime).toBe(new Date(drainTime).toISOString())
|
||||
expect(timing?.duration).toBe(drainTime - providerStartTime)
|
||||
expect(timing?.timeSegments?.[0]).toEqual({
|
||||
type: 'model',
|
||||
name: 's',
|
||||
startTime: 1,
|
||||
endTime: 2,
|
||||
duration: 1,
|
||||
})
|
||||
|
||||
vi.restoreAllMocks()
|
||||
})
|
||||
})
|
||||
Reference in New Issue
Block a user