make shared materialize function

This commit is contained in:
Nate Kelley 2025-09-03 10:08:10 -06:00
parent fca5a645ae
commit a3c9ce8900
No known key found for this signature in database
GPG Key ID: FD90372AB8D98B4F
7 changed files with 34 additions and 66 deletions

View File

@ -1,20 +1,8 @@
import { type Sandbox, createSandbox } from '@buster/sandbox';
import { materialize } from '@buster/test-utils';
import { afterAll, beforeAll, describe, expect, it } from 'vitest';
import { createBashTool } from './bash-tool';
async function materialize<T>(value: T | AsyncIterable<T>): Promise<T> {
const asyncIterator = (value as any)?.[Symbol.asyncIterator];
if (typeof asyncIterator === 'function') {
let lastChunk: T | undefined;
for await (const chunk of value as AsyncIterable<T>) {
lastChunk = chunk;
}
if (lastChunk === undefined) throw new Error('Stream yielded no values');
return lastChunk;
}
return value as T;
}
describe.sequential('bash-tool integration test', () => {
const hasApiKey = !!process.env.DAYTONA_API_KEY;
let sharedSandbox: Sandbox;

View File

@ -1,3 +1,4 @@
import { materialize } from '@buster/test-utils';
import { beforeEach, describe, expect, it, vi } from 'vitest';
import { createBashTool } from './bash-tool';
@ -10,19 +11,6 @@ vi.mock('braintrust', () => ({
wrapTraced: vi.fn((fn) => fn),
}));
async function materialize<T>(value: T | AsyncIterable<T>): Promise<T> {
const asyncIterator = (value as any)?.[Symbol.asyncIterator];
if (typeof asyncIterator === 'function') {
let lastChunk: T | undefined;
for await (const chunk of value as AsyncIterable<T>) {
lastChunk = chunk;
}
if (lastChunk === undefined) throw new Error('Stream yielded no values');
return lastChunk;
}
return value as T;
}
describe('createBashTool', () => {
const mockSandbox = {
id: 'test-sandbox',

View File

@ -1,3 +1,4 @@
import { materialize } from '@buster/test-utils';
import type { ToolCallOptions } from 'ai';
import { beforeEach, describe, expect, it, vi } from 'vitest';
import { createCreateDashboardsTool } from './create-dashboards-tool';
@ -16,20 +17,6 @@ vi.mock('./create-dashboards-execute', () => ({
createCreateDashboardsExecute: vi.fn(() => vi.fn()),
}));
// Helper to handle tools that may return a value or an AsyncIterable of values
async function materialize<T>(value: T | AsyncIterable<T>): Promise<T> {
const asyncIterator = (value as any)?.[Symbol.asyncIterator];
if (typeof asyncIterator === 'function') {
let lastChunk: T | undefined;
for await (const chunk of value as AsyncIterable<T>) {
lastChunk = chunk;
}
if (lastChunk === undefined) throw new Error('Stream yielded no values');
return lastChunk;
}
return value as T;
}
describe('create-dashboards-tool streaming integration', () => {
let context: CreateDashboardsContext;
let updateMessageEntries: ReturnType<typeof vi.fn>;

View File

@ -1,4 +1,5 @@
import { updateMessageEntries } from '@buster/database';
import { materialize } from '@buster/test-utils';
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
import { createModifyDashboardsTool } from './modify-dashboards-tool';
import type { ModifyDashboardsContext, ModifyDashboardsInput } from './modify-dashboards-tool';
@ -11,19 +12,6 @@ vi.mock('@buster/database', () => ({
metricFilesToDashboardFiles: {},
}));
async function materialize<T>(value: T | AsyncIterable<T>): Promise<T> {
const asyncIterator = (value as any)?.[Symbol.asyncIterator];
if (typeof asyncIterator === 'function') {
let lastChunk: T | undefined;
for await (const chunk of value as AsyncIterable<T>) {
lastChunk = chunk;
}
if (lastChunk === undefined) throw new Error('Stream yielded no values');
return lastChunk;
}
return value as T;
}
// Mock the execute function directly since it's called internally
vi.mock('./modify-dashboards-execute', () => ({
createModifyDashboardsExecute: vi.fn(() =>

View File

@ -1,4 +1,5 @@
import { updateMessageFields } from '@buster/database';
import { materialize } from '@buster/test-utils';
import { beforeEach, describe, expect, it, vi } from 'vitest';
import type { ModifyMetricsContext, ModifyMetricsInput } from './modify-metrics-tool';
import { createModifyMetricsTool } from './modify-metrics-tool';
@ -12,19 +13,6 @@ vi.mock('./modify-metrics-execute', () => ({
createModifyMetricsExecute: vi.fn(() => vi.fn()),
}));
async function materialize<T>(value: T | AsyncIterable<T>): Promise<T> {
const asyncIterator = (value as any)?.[Symbol.asyncIterator];
if (typeof asyncIterator === 'function') {
let lastChunk: T | undefined;
for await (const chunk of value as AsyncIterable<T>) {
lastChunk = chunk;
}
if (lastChunk === undefined) throw new Error('Stream yielded no values');
return lastChunk;
}
return value as T;
}
describe('modify-metrics-tool streaming integration', () => {
let context: ModifyMetricsContext;
const mockToolCallOptions = {

View File

@ -9,3 +9,6 @@ export * from './envHelpers';
// Mock helpers
export * from './mock-helpers';
// Streaming utilities
export * from './streaming-utils';

View File

@ -0,0 +1,26 @@
/**
* Streaming test utilities for handling AsyncIterable values in tests
*/
/**
* Helper to handle tools that may return a value or an AsyncIterable of values.
* For AsyncIterable values, it materializes the stream by consuming all chunks
* and returning the last one.
*
* @param value - The value to materialize, either a direct value or an AsyncIterable
* @returns Promise resolving to the materialized value
* @throws Error if the stream yields no values
*/
export async function materialize<T>(value: T | AsyncIterable<T>): Promise<T> {
// biome-ignore lint/suspicious/noExplicitAny: we are ignoring this for now
const asyncIterator = (value as any)?.[Symbol.asyncIterator];
if (typeof asyncIterator === 'function') {
let lastChunk: T | undefined;
for await (const chunk of value as AsyncIterable<T>) {
lastChunk = chunk;
}
if (lastChunk === undefined) throw new Error('Stream yielded no values');
return lastChunk;
}
return value as T;
}