From c53c0f2f2eb797aaffaa627f66b1e1e0601f1724 Mon Sep 17 00:00:00 2001 From: Mackenzie Zastrow Date: Mon, 8 Dec 2025 13:50:22 -0500 Subject: [PATCH 1/5] Unify bedrock & openai client creation In order to better share code between browser and node integ tests, use a single implementation for creating clients and inside of the factory methods, do the conditional check for what environment we're running in. As part of this I refactored the setup to take advantage of vitest features of having the global setup inject data into the running tests - including whether we're running in the browser and if we're running in CI/CD. --- test/integ/__fixtures__/_setup-global.ts | 103 ++++++++++++++++++ test/integ/__fixtures__/_setup-test.ts | 19 ++++ test/integ/__fixtures__/model-providers.ts | 53 +++++++++ test/integ/__fixtures__/model-test-helpers.ts | 66 ----------- test/integ/__fixtures__/test-helpers.ts | 8 +- test/integ/agent.test.ts | 24 +--- test/integ/bash.test.ts | 81 +++++++------- test/integ/bedrock.test.ts | 33 +++--- test/integ/browser/agent.browser.test.ts | 42 ++----- test/integ/browser/bedrock.browser.test.ts | 17 +-- test/integ/browser/vitest.d.ts | 8 -- test/integ/file-editor.test.ts | 10 +- test/integ/http-request.test.ts | 10 +- test/integ/integ-setup.ts | 63 ----------- test/integ/mcp.test.ts | 6 +- test/integ/notebook.test.ts | 12 +- test/integ/openai.test.ts | 23 ++-- test/integ/tsconfig.json | 3 +- test/integ/vitest.d.ts | 17 +++ tsconfig.base.json | 4 +- vitest.config.ts | 30 ++--- 21 files changed, 311 insertions(+), 321 deletions(-) create mode 100644 test/integ/__fixtures__/_setup-global.ts create mode 100644 test/integ/__fixtures__/_setup-test.ts create mode 100644 test/integ/__fixtures__/model-providers.ts delete mode 100644 test/integ/browser/vitest.d.ts delete mode 100644 test/integ/integ-setup.ts create mode 100644 test/integ/vitest.d.ts diff --git a/test/integ/__fixtures__/_setup-global.ts b/test/integ/__fixtures__/_setup-global.ts new file mode 100644 index 00000000..2f0d83f6 --- /dev/null +++ b/test/integ/__fixtures__/_setup-global.ts @@ -0,0 +1,103 @@ +/** + * Global setup that runs once before all integration tests and possibly runs in the *parent* process + */ + +import { SecretsManagerClient, GetSecretValueCommand } from '@aws-sdk/client-secrets-manager' +import type { TestProject } from 'vitest/node' +import type { ProvidedContext } from 'vitest' +import { fromNodeProviderChain } from '@aws-sdk/credential-providers' + +/** + * Load API keys as environment variables from AWS Secrets Manager + */ +async function loadApiKeysFromSecretsManager(): Promise { + const client = new SecretsManagerClient({ + region: process.env.AWS_REGION || 'us-east-1', + }) + + try { + const secretName = 'model-provider-api-key' + const command = new GetSecretValueCommand({ + SecretId: secretName, + }) + const response = await client.send(command) + + if (response.SecretString) { + const secret = JSON.parse(response.SecretString) + // Only add API keys for currently supported providers + const supportedProviders = ['openai'] + Object.entries(secret).forEach(([key, value]) => { + if (supportedProviders.includes(key.toLowerCase())) { + process.env[`${key.toUpperCase()}_API_KEY`] = String(value) + } + }) + } + } catch (e) { + console.warn('Error retrieving secret', e) + } + + /* + * Validate that required environment variables are set when running in GitHub Actions. + * This prevents tests from being unintentionally skipped due to missing credentials. + */ + if (process.env.GITHUB_ACTIONS !== 'true') { + console.warn('Tests running outside GitHub Actions, skipping required provider validation') + return + } + + const requiredProviders: Set = new Set(['OPENAI_API_KEY']) + + for (const provider of requiredProviders) { + if (!process.env[provider]) { + throw new Error(`Missing required environment variables for ${provider}`) + } + } +} + +/** + * Perform shared setup for the integration tests unless it's already been. + */ +export async function setup(project: TestProject): Promise { + console.log('Global setup: Loading API keys from Secrets Manager...') + await loadApiKeysFromSecretsManager() + console.log('Global setup: API keys loaded into environment') + + project.provide('isBrowser', project.isBrowserEnabled()) + project.provide('isCI', !!globalThis.process.env.CI) + project.provide('provider-openai', await getOpenAITestContext()) + project.provide('provider-bedrock', await getBedrockTestContext()) +} + +async function getOpenAITestContext(): Promise { + const apiKey = process.env.OPENAI_API_KEY + const shouldSkip = !!apiKey + + if (shouldSkip) { + console.log('⏭️ OpenAI API key not available - integration tests will be skipped') + } else { + console.log('⏭️ OpenAI API key available - integration tests will run') + } + + return { + apiKey: apiKey, + shouldSkip: shouldSkip, + } +} + +async function getBedrockTestContext(): Promise { + try { + const credentialProvider = fromNodeProviderChain() + const credentials = await credentialProvider() + console.log('⏭️ Bedrock credentials available - integration tests will run') + return { + shouldSkip: false, + credentials: credentials, + } + } catch { + console.log('⏭️ Bedrock credentials not available - integration tests will be skipped') + return { + shouldSkip: true, + credentials: undefined, + } + } +} diff --git a/test/integ/__fixtures__/_setup-test.ts b/test/integ/__fixtures__/_setup-test.ts new file mode 100644 index 00000000..70482fc0 --- /dev/null +++ b/test/integ/__fixtures__/_setup-test.ts @@ -0,0 +1,19 @@ +/** + * Global setup that runs once before all integration tests and possibly runs in the *parent* process + */ + +import { beforeAll } from 'vitest' +import { configureLogging } from '$/sdk/logging/index.js' +import { isCI } from './test-helpers.js' + +beforeAll(() => { + // When running under CI/CD, preserve all logs including debug + if (isCI()) { + configureLogging({ + debug: (...args: unknown[]) => console.debug(...args), + info: (...args: unknown[]) => console.info(...args), + warn: (...args: unknown[]) => console.warn(...args), + error: (...args: unknown[]) => console.error(...args), + }) + } +}) diff --git a/test/integ/__fixtures__/model-providers.ts b/test/integ/__fixtures__/model-providers.ts new file mode 100644 index 00000000..d1ed6d8c --- /dev/null +++ b/test/integ/__fixtures__/model-providers.ts @@ -0,0 +1,53 @@ +/** + * Contains helpers for creating various model providers that work both in node & the browser + */ + +import { isCI } from './test-helpers.js' +import { inject } from 'vitest' +import { BedrockModel, type BedrockModelOptions } from '$/sdk/models/bedrock.js' +import { OpenAIModel, type OpenAIModelOptions } from '$/sdk/models/openai.js' + +export const bedrock = { + name: 'BedrockModel', + get skip() { + return !isCI() && inject('provider-bedrock').shouldSkip + }, + createModel: (options: BedrockModelOptions = {}): BedrockModel => { + const credentials = inject('provider-bedrock').credentials + if (!credentials) { + throw new Error('No Bedrock credentials provided') + } + + return new BedrockModel({ + ...options, + clientConfig: { + ...(options.clientConfig ?? {}), + credentials: credentials, + }, + }) + }, +} + +export const openai = { + name: 'OpenAIModel', + get skip() { + return !isCI() && inject('provider-openai').shouldSkip + }, + createModel: (config: OpenAIModelOptions = {}): OpenAIModel => { + const apiKey = inject('provider-openai').apiKey + if (!apiKey) { + throw new Error('No OpenAI apiKey provided') + } + + return new OpenAIModel({ + ...config, + apiKey: apiKey, + clientConfig: { + ...(config.clientConfig ?? {}), + dangerouslyAllowBrowser: true, + }, + }) + }, +} + +export const allProviders = [bedrock, openai] diff --git a/test/integ/__fixtures__/model-test-helpers.ts b/test/integ/__fixtures__/model-test-helpers.ts index fad6b359..69375fd3 100644 --- a/test/integ/__fixtures__/model-test-helpers.ts +++ b/test/integ/__fixtures__/model-test-helpers.ts @@ -1,4 +1,3 @@ -import { fromNodeProviderChain } from '@aws-sdk/credential-providers' import type { ContentBlock, Message } from '$/sdk/types/messages.js' /** @@ -19,68 +18,3 @@ export const getMessageText = (message: Message): string => { .map((block) => block.text) .join('\n') } - -/** - * Determines whether AWS integration tests should run based on environment and credentials. - * - * In CI environments, tests always run (credentials are expected to be configured). - * In local environments, tests run only if AWS credentials are available. - * - * @returns Promise - true if tests should run, false if they should be skipped - */ -export async function shouldSkipBedrockTests(): Promise { - // In a CI environment, we ALWAYS expect credentials to be configured. - // A failure is better than a skip. - if (process.env.CI) { - console.log('✅ Running in CI environment, integration tests will run.') - return false - } - - // In a local environment, we check for credentials as a convenience. - try { - const credentialProvider = fromNodeProviderChain() - await credentialProvider() - console.log('✅ AWS credentials found locally, integration tests will run.') - return false - } catch { - console.log('⏭️ AWS credentials not available locally, integration tests will be skipped.') - return true - } -} - -/** - * Determines if OpenAI integration tests should be skipped. - * In CI environments, throws an error if API key is missing (tests should not be skipped). - * In local development, skips tests if API key is not available. - * - * @returns true if tests should be skipped, false if they should run - * @throws Error if running in CI and API key is missing - */ -export function shouldSkipOpenAITests(): boolean { - try { - const isCI = !!process.env.CI - const hasKey = !!process.env.OPENAI_API_KEY - - if (isCI && !hasKey) { - throw new Error('OpenAI API key must be available in CI environments') - } - - if (hasKey) { - if (isCI) { - console.log('✅ Running in CI environment with OpenAI API key - tests will run') - } else { - console.log('✅ OpenAI API key found for integration tests') - } - return false - } else { - console.log('⏭️ OpenAI API key not available - integration tests will be skipped') - return true - } - } catch (error) { - if (error instanceof Error && error.message.includes('CI environments')) { - throw error - } - console.log('⏭️ OpenAI API key not available - integration tests will be skipped') - return true - } -} diff --git a/test/integ/__fixtures__/test-helpers.ts b/test/integ/__fixtures__/test-helpers.ts index 478032b7..592b1a28 100644 --- a/test/integ/__fixtures__/test-helpers.ts +++ b/test/integ/__fixtures__/test-helpers.ts @@ -1,8 +1,14 @@ +import { inject } from 'vitest' + /** * Checks whether we're running tests in the browser. */ export const isInBrowser = () => { - return globalThis?.process?.env == null + return inject('isBrowser') +} + +export function isCI() { + return inject('isCI') } /** diff --git a/test/integ/agent.test.ts b/test/integ/agent.test.ts index 7b144a12..f906c7c9 100644 --- a/test/integ/agent.test.ts +++ b/test/integ/agent.test.ts @@ -1,17 +1,15 @@ -import { describe, it, expect } from 'vitest' +import { describe, expect, it } from 'vitest' import { Agent, DocumentBlock, ImageBlock, Message, TextBlock, tool } from '@strands-agents/sdk' -import { BedrockModel } from '@strands-agents/sdk/bedrock' import { notebook } from '@strands-agents/sdk/vended_tools/notebook' import { httpRequest } from '@strands-agents/sdk/vended_tools/http_request' -import { OpenAIModel } from '@strands-agents/sdk/openai' import { z } from 'zod' import { collectGenerator } from '$/sdk/__fixtures__/model-test-helpers.js' -import { shouldSkipBedrockTests, shouldSkipOpenAITests } from './__fixtures__/model-test-helpers.js' import { loadFixture } from './__fixtures__/test-helpers.js' // Import fixtures using Vite's ?url suffix import yellowPngUrl from './__resources__/yellow.png?url' +import { allProviders } from './__fixtures__/model-providers.js' // Calculator tool for testing const calculatorTool = tool({ @@ -33,21 +31,7 @@ const calculatorTool = tool({ }, }) -// Provider configurations -const providers = [ - { - name: 'BedrockModel', - skip: await shouldSkipBedrockTests(), - createModel: () => new BedrockModel(), - }, - { - name: 'OpenAIModel', - skip: shouldSkipOpenAITests(), - createModel: () => new OpenAIModel(), - }, -] - -describe.each(providers)('Agent with $name', ({ name, skip, createModel }) => { +describe.each(allProviders)('Agent with $name', ({ name, skip, createModel }) => { describe.skipIf(skip)(`${name} Integration Tests`, () => { describe('Basic Functionality', () => { it('handles invocation, streaming, system prompts, and tool use', async () => { @@ -242,7 +226,7 @@ describe.each(providers)('Agent with $name', ({ name, skip, createModel }) => { it('handles tool invocation', async () => { const agent = new Agent({ - model: await createModel(), + model: createModel(), tools: [notebook, httpRequest], printer: false, }) diff --git a/test/integ/bash.test.ts b/test/integ/bash.test.ts index e880a42f..c3a42e6b 100644 --- a/test/integ/bash.test.ts +++ b/test/integ/bash.test.ts @@ -1,52 +1,49 @@ -import { describe, it, expect } from 'vitest' -import { Agent, BedrockModel } from '$/sdk/index.js' +import { describe, expect, it } from 'vitest' +import { Agent } from '$/sdk/index.js' import { bash } from '$/sdk/vended-tools/bash/index.js' -import { getMessageText, shouldSkipBedrockTests } from './__fixtures__/model-test-helpers.js' +import { getMessageText } from './__fixtures__/model-test-helpers.js' +import { bedrock } from './__fixtures__/model-providers.js' -describe.skipIf((await shouldSkipBedrockTests()) || process.platform === 'win32')( - 'Bash Tool Integration', - { timeout: 60000 }, - () => { - // Shared agent configuration for all tests - const createAgent = () => - new Agent({ - model: new BedrockModel({ - region: 'us-east-1', - }), - tools: [bash], - }) +describe.skipIf(bedrock.skip || process.platform === 'win32')('Bash Tool Integration', { timeout: 60000 }, () => { + // Shared agent configuration for all tests + const createAgent = () => + new Agent({ + model: bedrock.createModel({ + region: 'us-east-1', + }), + tools: [bash], + }) - describe('basic execution', () => { - it('captures stdout streams correctly', async () => { - const agent = createAgent() - const stdoutResult = await agent.invoke('Use bash to echo "Hello from bash"') - expect(getMessageText(stdoutResult.lastMessage)).toContain('Hello from bash') - }) + describe('basic execution', () => { + it('captures stdout streams correctly', async () => { + const agent = createAgent() + const stdoutResult = await agent.invoke('Use bash to echo "Hello from bash"') + expect(getMessageText(stdoutResult.lastMessage)).toContain('Hello from bash') + }) - it('captures stderr streams correctly', async () => { - const agent = createAgent() - const stderrResult = await agent.invoke('Use bash to run: echo "error" >&2') - expect(getMessageText(stderrResult.lastMessage)).toContain('error') - }) + it('captures stderr streams correctly', async () => { + const agent = createAgent() + const stderrResult = await agent.invoke('Use bash to run: echo "error" >&2') + expect(getMessageText(stderrResult.lastMessage)).toContain('error') + }) - it('handles complex command patterns', async () => { - const agent = createAgent() + it('handles complex command patterns', async () => { + const agent = createAgent() - // Test command sequencing - const seqResult = await agent.invoke('Use bash to: create a variable TEST=hello, then echo it') - expect(getMessageText(seqResult.lastMessage).toLowerCase()).toContain('hello') - }) + // Test command sequencing + const seqResult = await agent.invoke('Use bash to: create a variable TEST=hello, then echo it') + expect(getMessageText(seqResult.lastMessage).toLowerCase()).toContain('hello') }) + }) - describe('error handling', () => { - it('handles command errors gracefully', async () => { - const agent = createAgent() - const result = await agent.invoke('Use bash to run: nonexistent_command_xyz') + describe('error handling', () => { + it('handles command errors gracefully', async () => { + const agent = createAgent() + const result = await agent.invoke('Use bash to run: nonexistent_command_xyz') - // Should indicate command not found or error - const lastMessage = getMessageText(result.lastMessage).toLowerCase() - expect(lastMessage).toMatch(/not found|error|command/) - }) + // Should indicate command not found or error + const lastMessage = getMessageText(result.lastMessage).toLowerCase() + expect(lastMessage).toMatch(/not found|error|command/) }) - } -) + }) +}) diff --git a/test/integ/bedrock.test.ts b/test/integ/bedrock.test.ts index 10c51a46..dfffbe6e 100644 --- a/test/integ/bedrock.test.ts +++ b/test/integ/bedrock.test.ts @@ -1,21 +1,20 @@ -import { describe, it, expect, vi } from 'vitest' +import { describe, expect, it, vi } from 'vitest' import { - BedrockModel, - Message, Agent, - TextBlock, + Message, NullConversationManager, SlidingWindowConversationManager, + TextBlock, } from '@strands-agents/sdk' import { collectIterator } from '$/sdk/__fixtures__/model-test-helpers.js' -import { shouldSkipBedrockTests } from './__fixtures__/model-test-helpers.js' +import { bedrock } from './__fixtures__/model-providers.js' -describe.skipIf(await shouldSkipBedrockTests())('BedrockModel Integration Tests', () => { +describe.skipIf(bedrock.skip)('BedrockModel Integration Tests', () => { describe('Streaming', () => { describe('Configuration', () => { it.concurrent('respects maxTokens configuration', async () => { - const provider = new BedrockModel({ maxTokens: 20 }) + const provider = bedrock.createModel({ maxTokens: 20 }) const messages: Message[] = [ { type: 'message', @@ -34,7 +33,7 @@ describe.skipIf(await shouldSkipBedrockTests())('BedrockModel Integration Tests' }) it.concurrent('uses system prompt cache on subsequent requests', async () => { - const provider = new BedrockModel({ maxTokens: 100 }) + const provider = bedrock.createModel({ maxTokens: 100 }) const largeContext = `Context information: ${'hello '.repeat(2000)} [test-${Date.now()}-${Math.random()}]` const cachedSystemPrompt = [ { type: 'textBlock' as const, text: 'You are a helpful assistant.' }, @@ -62,7 +61,7 @@ describe.skipIf(await shouldSkipBedrockTests())('BedrockModel Integration Tests' }) it.concurrent('uses message cache points on subsequent requests', async () => { - const provider = new BedrockModel({ maxTokens: 100 }) + const provider = bedrock.createModel({ maxTokens: 100 }) const largeContext = `Context information: ${'hello '.repeat(2000)} [test-${Date.now()}-${Math.random()}]` const messagesWithCachePoint = (text: string): Message[] => [ { @@ -90,7 +89,7 @@ describe.skipIf(await shouldSkipBedrockTests())('BedrockModel Integration Tests' describe('Error Handling', () => { it.concurrent('handles invalid model ID gracefully', async () => { - const provider = new BedrockModel({ modelId: 'invalid-model-id-that-does-not-exist' }) + const provider = bedrock.createModel({ modelId: 'invalid-model-id-that-does-not-exist' }) const messages: Message[] = [{ type: 'message', role: 'user', content: [{ type: 'textBlock', text: 'Hello' }] }] await expect(collectIterator(provider.stream(messages))).rejects.toThrow() }) @@ -100,7 +99,7 @@ describe.skipIf(await shouldSkipBedrockTests())('BedrockModel Integration Tests' describe('Agent with Conversation Manager', () => { it('manages conversation history with SlidingWindowConversationManager', async () => { const agent = new Agent({ - model: new BedrockModel({ maxTokens: 100 }), + model: bedrock.createModel({ maxTokens: 100 }), conversationManager: new SlidingWindowConversationManager({ windowSize: 4 }), }) @@ -121,7 +120,7 @@ describe.skipIf(await shouldSkipBedrockTests())('BedrockModel Integration Tests' it('throws ContextWindowOverflowError with NullConversationManager', async () => { const agent = new Agent({ - model: new BedrockModel({ maxTokens: 50 }), + model: bedrock.createModel({ maxTokens: 50 }), conversationManager: new NullConversationManager(), }) @@ -135,7 +134,7 @@ describe.skipIf(await shouldSkipBedrockTests())('BedrockModel Integration Tests' describe('Region Configuration', () => { it('uses explicit region when provided', async () => { - const provider = new BedrockModel({ + const provider = bedrock.createModel({ region: 'us-east-1', maxTokens: 50, }) @@ -151,7 +150,7 @@ describe.skipIf(await shouldSkipBedrockTests())('BedrockModel Integration Tests' vi.stubEnv('AWS_REGION', undefined) vi.stubEnv('AWS_DEFAULT_REGION', undefined) - const provider = new BedrockModel({ + const provider = bedrock.createModel({ maxTokens: 50, }) @@ -175,7 +174,7 @@ describe.skipIf(await shouldSkipBedrockTests())('BedrockModel Integration Tests' // Use vitest to stub the environment variable vi.stubEnv('AWS_REGION', 'eu-central-1') - const provider = new BedrockModel({ + const provider = bedrock.createModel({ maxTokens: 50, }) @@ -189,7 +188,7 @@ describe.skipIf(await shouldSkipBedrockTests())('BedrockModel Integration Tests' // Use vitest to stub the environment variable vi.stubEnv('AWS_REGION', 'eu-west-1') - const provider = new BedrockModel({ + const provider = bedrock.createModel({ region: 'ap-southeast-2', maxTokens: 50, }) @@ -201,7 +200,7 @@ describe.skipIf(await shouldSkipBedrockTests())('BedrockModel Integration Tests' }) it('uses region from clientConfig when provided', async () => { - const provider = new BedrockModel({ + const provider = bedrock.createModel({ clientConfig: { region: 'ap-northeast-1' }, maxTokens: 50, }) diff --git a/test/integ/browser/agent.browser.test.ts b/test/integ/browser/agent.browser.test.ts index 0789a88b..0c4aa81f 100644 --- a/test/integ/browser/agent.browser.test.ts +++ b/test/integ/browser/agent.browser.test.ts @@ -1,8 +1,5 @@ import { describe, it, expect } from 'vitest' -import { commands } from 'vitest/browser' import { Agent, DocumentBlock, ImageBlock, Message, TextBlock, tool } from '@strands-agents/sdk' -import { BedrockModel } from '@strands-agents/sdk/bedrock' -import { OpenAIModel } from '@strands-agents/sdk/openai' import { notebook } from '@strands-agents/sdk/vended_tools/notebook' import { httpRequest } from '@strands-agents/sdk/vended_tools/http_request' import { z } from 'zod' @@ -12,6 +9,7 @@ import { collectGenerator } from '$/sdk/__fixtures__/model-test-helpers.js' // Import fixtures import yellowPngUrl from '../__resources__/yellow.png?url' import { loadFixture } from '../__fixtures__/test-helpers.js' +import { allProviders } from '../__fixtures__/model-providers.js' // Calculator tool for testing const calculatorTool = tool({ @@ -33,36 +31,10 @@ const calculatorTool = tool({ }, }) -// Provider configurations with browser credential handling -const providers = [ - { - name: 'BedrockModel', - createModel: async () => { - const credentials = await commands.getAwsCredentials() - return new BedrockModel({ - region: 'us-east-1', - clientConfig: { - credentials, - }, - }) - }, - }, - { - name: 'OpenAIModel', - createModel: async () => - new OpenAIModel({ - apiKey: await commands.getOpenAIAPIKey(), - clientConfig: { - dangerouslyAllowBrowser: true, - }, - }), - }, -] - -describe.each(providers)('Agent Browser Tests with $name', async ({ name, createModel }) => { - describe(`${name} Browser Integration`, () => { +describe.each(allProviders)('Agent Browser Tests with $name', async ({ name, skip, createModel }) => { + describe.skipIf(skip)(`${name} Browser Integration`, () => { it('handles basic invocation', async () => { - const agent = new Agent({ model: await createModel(), printer: false }) + const agent = new Agent({ model: createModel(), printer: false }) const result = await agent.invoke('Say hello in one word') expect(result.stopReason).toBe('endTurn') @@ -72,7 +44,7 @@ describe.each(providers)('Agent Browser Tests with $name', async ({ name, create it('handles tool use', async () => { const agent = new Agent({ - model: await createModel(), + model: createModel(), printer: false, systemPrompt: 'Use the calculator tool to solve math problems. Respond with only the numeric result.', tools: [calculatorTool], @@ -103,7 +75,7 @@ describe.each(providers)('Agent Browser Tests with $name', async ({ name, create }) const agent = new Agent({ - model: await createModel(), + model: createModel(), messages: [ new Message({ role: 'user', @@ -126,7 +98,7 @@ describe.each(providers)('Agent Browser Tests with $name', async ({ name, create it('handles tool invocation', async () => { const agent = new Agent({ - model: await createModel(), + model: createModel(), tools: [notebook, httpRequest], printer: false, }) diff --git a/test/integ/browser/bedrock.browser.test.ts b/test/integ/browser/bedrock.browser.test.ts index 3307c50e..37a355e1 100644 --- a/test/integ/browser/bedrock.browser.test.ts +++ b/test/integ/browser/bedrock.browser.test.ts @@ -1,8 +1,7 @@ import { describe, it, expect } from 'vitest' -import { BedrockModel } from '@strands-agents/sdk/bedrock' import { Message, TextBlock } from '@strands-agents/sdk' -import { commands } from 'vitest/browser' import { collectIterator } from '$/sdk/__fixtures__/model-test-helpers.js' +import { bedrock } from '../__fixtures__/model-providers.js' describe('Region Configuration', () => { const sayHighMessage = Message.fromMessageData({ @@ -11,12 +10,9 @@ describe('Region Configuration', () => { }) it('uses explicit region when provided', async () => { - const provider = new BedrockModel({ + const provider = bedrock.createModel({ region: 'us-east-1', maxTokens: 50, - clientConfig: { - credentials: await commands.getAwsCredentials(), - }, }) // Validate region configuration by checking config.region() directly @@ -29,11 +25,8 @@ describe('Region Configuration', () => { }) it('defaults to us-west-2 when no region provided and AWS SDK does not resolve one', async () => { - const provider = new BedrockModel({ + const provider = bedrock.createModel({ maxTokens: 50, - clientConfig: { - credentials: await commands.getAwsCredentials(), - }, }) // Validate region defaults to us-west-2 @@ -46,8 +39,8 @@ describe('Region Configuration', () => { }) it('uses region from clientConfig when provided', async () => { - const provider = new BedrockModel({ - clientConfig: { region: 'ap-northeast-1', credentials: await commands.getAwsCredentials() }, + const provider = bedrock.createModel({ + clientConfig: { region: 'ap-northeast-1' }, maxTokens: 50, }) diff --git a/test/integ/browser/vitest.d.ts b/test/integ/browser/vitest.d.ts deleted file mode 100644 index 63541a1f..00000000 --- a/test/integ/browser/vitest.d.ts +++ /dev/null @@ -1,8 +0,0 @@ -import type { AwsCredentialIdentity } from '@aws-sdk/types' - -declare module 'vitest/browser' { - interface BrowserCommands { - getAwsCredentials: () => Promise - getOpenAIAPIKey: () => Promise - } -} diff --git a/test/integ/file-editor.test.ts b/test/integ/file-editor.test.ts index 7d712b72..84f9af0b 100644 --- a/test/integ/file-editor.test.ts +++ b/test/integ/file-editor.test.ts @@ -1,19 +1,19 @@ -import { describe, it, expect, beforeEach, afterEach } from 'vitest' -import { Agent, BedrockModel } from '$/sdk/index.js' +import { afterEach, beforeEach, describe, expect, it } from 'vitest' +import { Agent } from '$/sdk/index.js' import { fileEditor } from '$/sdk/vended-tools/file_editor/index.js' import { collectGenerator } from '$/sdk/__fixtures__/model-test-helpers.js' -import { shouldSkipBedrockTests } from './__fixtures__/model-test-helpers.js' import { promises as fs } from 'fs' import * as path from 'path' import { tmpdir } from 'os' +import { bedrock } from './__fixtures__/model-providers.js' -describe.skipIf(await shouldSkipBedrockTests())('FileEditor Tool Integration', () => { +describe.skipIf(bedrock.skip)('FileEditor Tool Integration', () => { let testDir: string // Shared agent configuration for all tests const createAgent = () => new Agent({ - model: new BedrockModel({ + model: bedrock.createModel({ region: 'us-east-1', }), tools: [fileEditor], diff --git a/test/integ/http-request.test.ts b/test/integ/http-request.test.ts index 65ab4b1f..299fbb6c 100644 --- a/test/integ/http-request.test.ts +++ b/test/integ/http-request.test.ts @@ -1,12 +1,12 @@ -import { describe, it, expect } from 'vitest' +import { describe, expect, it } from 'vitest' import { httpRequest } from '@strands-agents/sdk/vended_tools/http_request' -import { Agent, BedrockModel } from '@strands-agents/sdk' -import { shouldSkipBedrockTests } from './__fixtures__/model-test-helpers.js' +import { Agent } from '@strands-agents/sdk' +import { bedrock } from './__fixtures__/model-providers.js' -describe.skipIf(await shouldSkipBedrockTests())('httpRequest tool (integration)', () => { +describe.skipIf(bedrock.skip)('httpRequest tool (integration)', () => { it('agent uses http_request tool to fetch weather from Open-Meteo', async () => { const agent = new Agent({ - model: new BedrockModel({ maxTokens: 500 }), + model: bedrock.createModel({ maxTokens: 500 }), tools: [httpRequest], printer: false, }) diff --git a/test/integ/integ-setup.ts b/test/integ/integ-setup.ts deleted file mode 100644 index b9095260..00000000 --- a/test/integ/integ-setup.ts +++ /dev/null @@ -1,63 +0,0 @@ -/** - * Global setup that runs once before all integration tests - * Loads API keys from AWS Secrets Manager into environment variables - */ - -import { SecretsManagerClient, GetSecretValueCommand } from '@aws-sdk/client-secrets-manager' - -async function loadApiKeysFromSecretsManager(): Promise { - // Load API keys as environment variables from AWS Secrets Manager - const client = new SecretsManagerClient({ - region: process.env.AWS_REGION || 'us-east-1', - }) - console.log('Loading API keys from Secrets Manager') - - try { - const secretName = 'model-provider-api-key' - const command = new GetSecretValueCommand({ - SecretId: secretName, - }) - const response = await client.send(command) - - if (response.SecretString) { - const secret = JSON.parse(response.SecretString) - // Only add API keys for currently supported providers - const supportedProviders = ['openai'] - Object.entries(secret).forEach(([key, value]) => { - if (supportedProviders.includes(key.toLowerCase())) { - process.env[`${key.toUpperCase()}_API_KEY`] = String(value) - } - }) - } - } catch (e) { - console.warn('Error retrieving secret', e) - } - - /* - * Validate that required environment variables are set when running in GitHub Actions. - * This prevents tests from being unintentionally skipped due to missing credentials. - */ - if (process.env.GITHUB_ACTIONS !== 'true') { - console.warn('Tests running outside GitHub Actions, skipping required provider validation') - return - } - - const requiredProviders: Set = new Set(['OPENAI_API_KEY']) - - for (const provider of requiredProviders) { - if (!process.env[provider]) { - throw new Error(`Missing required environment variables for ${provider}`) - } - } -} - -export async function setup(): Promise { - console.log('Global setup: Loading API keys from Secrets Manager...') - - try { - await loadApiKeysFromSecretsManager() - console.log('Global setup complete: API keys loaded into environment') - } catch (error) { - console.error('Global setup failed:', error) - } -} diff --git a/test/integ/mcp.test.ts b/test/integ/mcp.test.ts index ccbef567..d01d0bc3 100644 --- a/test/integ/mcp.test.ts +++ b/test/integ/mcp.test.ts @@ -7,13 +7,13 @@ import { describe, it, expect, beforeAll, afterAll } from 'vitest' import { McpClient, Agent } from '@strands-agents/sdk' -import { BedrockModel } from '@strands-agents/sdk/bedrock' import { StdioClientTransport } from '@modelcontextprotocol/sdk/client/stdio.js' import { StreamableHTTPClientTransport } from '@modelcontextprotocol/sdk/client/streamableHttp.js' import { resolve } from 'node:path' import { URL } from 'node:url' import { startHTTPServer, type HttpServerInfo } from './__fixtures__/test-mcp-server.js' import type { Transport } from '@modelcontextprotocol/sdk/shared/transport.js' +import { bedrock } from './__fixtures__/model-providers.js' type TransportConfig = { name: string @@ -64,7 +64,7 @@ describe('MCP Integration Tests', () => { describe.each(transports)('$name transport', ({ createClient }) => { it('agent can use multiple MCP tools in a conversation', async () => { const client = await createClient() - const model = new BedrockModel({ maxTokens: 300 }) + const model = bedrock.createModel({ maxTokens: 300 }) const agent = new Agent({ systemPrompt: @@ -97,7 +97,7 @@ describe('MCP Integration Tests', () => { it('agent handles MCP tool errors gracefully', async () => { const client = await createClient() - const model = new BedrockModel({ maxTokens: 200 }) + const model = bedrock.createModel({ maxTokens: 200 }) const agent = new Agent({ systemPrompt: 'You are a helpful assistant. If asked to test errors, use the error_tool.', diff --git a/test/integ/notebook.test.ts b/test/integ/notebook.test.ts index 9c053918..a4f194bf 100644 --- a/test/integ/notebook.test.ts +++ b/test/integ/notebook.test.ts @@ -1,14 +1,14 @@ -import { describe, it, expect } from 'vitest' -import { Agent, BedrockModel } from '$/sdk/index.js' -import type { AgentStreamEvent, AgentResult } from '$/sdk/index.js' +import { describe, expect, it } from 'vitest' +import type { AgentResult, AgentStreamEvent } from '$/sdk/index.js' +import { Agent } from '$/sdk/index.js' import { notebook } from '$/sdk/vended-tools/notebook/index.js' import { collectGenerator } from '$/sdk/__fixtures__/model-test-helpers.js' -import { shouldSkipBedrockTests } from './__fixtures__/model-test-helpers.js' +import { bedrock } from './__fixtures__/model-providers.js' -describe.skipIf(await shouldSkipBedrockTests())('Notebook Tool Integration', () => { +describe.skipIf(bedrock.skip)('Notebook Tool Integration', () => { // Shared agent configuration for all tests const agentParams = { - model: new BedrockModel({ + model: bedrock.createModel({ region: 'us-east-1', }), tools: [notebook], diff --git a/test/integ/openai.test.ts b/test/integ/openai.test.ts index 7efcebf8..69430166 100644 --- a/test/integ/openai.test.ts +++ b/test/integ/openai.test.ts @@ -1,16 +1,15 @@ -import { describe, it, expect } from 'vitest' -import { OpenAIModel } from '@strands-agents/sdk/openai' -import { Message } from '@strands-agents/sdk' +import { describe, expect, it } from 'vitest' import type { ToolSpec } from '@strands-agents/sdk' +import { Message } from '@strands-agents/sdk' import { collectIterator } from '$/sdk/__fixtures__/model-test-helpers.js' -import { shouldSkipOpenAITests } from './__fixtures__/model-test-helpers.js' +import { openai } from './__fixtures__/model-providers.js' -describe.skipIf(shouldSkipOpenAITests())('OpenAIModel Integration Tests', () => { +describe.skipIf(openai.skip)('OpenAIModel Integration Tests', () => { describe('Configuration', () => { it.concurrent('respects maxTokens configuration', async () => { - const provider = new OpenAIModel({ + const provider = openai.createModel({ modelId: 'gpt-4o-mini', maxTokens: 20, // Very small limit }) @@ -34,7 +33,7 @@ describe.skipIf(shouldSkipOpenAITests())('OpenAIModel Integration Tests', () => }) it.concurrent('respects temperature configuration', async () => { - const provider = new OpenAIModel({ + const provider = openai.createModel({ modelId: 'gpt-4o-mini', temperature: 0, // Deterministic maxTokens: 50, @@ -77,7 +76,7 @@ describe.skipIf(shouldSkipOpenAITests())('OpenAIModel Integration Tests', () => describe('Error Handling', () => { it.concurrent('handles invalid model ID gracefully', async () => { - const provider = new OpenAIModel({ + const provider = openai.createModel({ modelId: 'invalid-model-id-that-does-not-exist-xyz', }) @@ -99,7 +98,7 @@ describe.skipIf(shouldSkipOpenAITests())('OpenAIModel Integration Tests', () => describe('Content Block Lifecycle', () => { it.concurrent('emits complete content block lifecycle events', async () => { - const provider = new OpenAIModel({ + const provider = openai.createModel({ modelId: 'gpt-4o-mini', maxTokens: 50, }) @@ -139,7 +138,7 @@ describe.skipIf(shouldSkipOpenAITests())('OpenAIModel Integration Tests', () => describe('Stop Reasons', () => { it.concurrent('returns endTurn stop reason for natural completion', async () => { - const provider = new OpenAIModel({ + const provider = openai.createModel({ modelId: 'gpt-4o-mini', maxTokens: 100, }) @@ -159,7 +158,7 @@ describe.skipIf(shouldSkipOpenAITests())('OpenAIModel Integration Tests', () => }) it.concurrent('returns maxTokens stop reason when token limit reached', async () => { - const provider = new OpenAIModel({ + const provider = openai.createModel({ modelId: 'gpt-4o-mini', maxTokens: 10, // Very small limit to force cutoff }) @@ -179,7 +178,7 @@ describe.skipIf(shouldSkipOpenAITests())('OpenAIModel Integration Tests', () => }) it.concurrent('returns toolUse stop reason when requesting tool use', async () => { - const provider = new OpenAIModel({ + const provider = openai.createModel({ modelId: 'gpt-4o-mini', maxTokens: 200, }) diff --git a/test/integ/tsconfig.json b/test/integ/tsconfig.json index c38af910..f4fdc6a8 100644 --- a/test/integ/tsconfig.json +++ b/test/integ/tsconfig.json @@ -3,8 +3,7 @@ "compilerOptions": { "paths": { "$/sdk/*": ["../../src/*"] - }, - "types": ["vite/client", "vitest/importMeta", "@types/node"] + } }, "references": [{ "path": "../../src/tsconfig.json" }] } diff --git a/test/integ/vitest.d.ts b/test/integ/vitest.d.ts new file mode 100644 index 00000000..f18d2ea7 --- /dev/null +++ b/test/integ/vitest.d.ts @@ -0,0 +1,17 @@ +import 'vitest' +import type { AwsCredentialIdentity } from '@aws-sdk/types' + +declare module 'vitest' { + export interface ProvidedContext { + isCI: boolean + isBrowser: boolean + ['provider-openai']: { + shouldSkip: boolean + apiKey: string | undefined + } + ['provider-bedrock']: { + shouldSkip: boolean + credentials: AwsCredentialIdentity | undefined + } + } +} diff --git a/tsconfig.base.json b/tsconfig.base.json index 98cd8d29..b681c006 100644 --- a/tsconfig.base.json +++ b/tsconfig.base.json @@ -27,6 +27,6 @@ "verbatimModuleSyntax": true, "sourceMap": true, "removeComments": false, - "types": ["vitest/importMeta"] + "types": ["vite/client", "vitest/importMeta", "@types/node"] } -} \ No newline at end of file +} diff --git a/vitest.config.ts b/vitest.config.ts index 84f8195e..f3fda9b9 100644 --- a/vitest.config.ts +++ b/vitest.config.ts @@ -1,9 +1,6 @@ import { defineConfig } from 'vitest/config' import { playwright } from '@vitest/browser-playwright' -import type { AwsCredentialIdentity } from '@aws-sdk/types' -import { fromNodeProviderChain } from '@aws-sdk/credential-providers' -import type { BrowserCommand } from 'vitest/node' -import path from 'path' +import * as path from 'node:path' import { fileURLToPath } from 'url' const __dirname = path.dirname(fileURLToPath(import.meta.url)) @@ -15,21 +12,12 @@ if (process.platform === 'win32') { coverageExclude.push('src/vended-tools/bash/**') } -const getAwsCredentials: BrowserCommand<[], AwsCredentialIdentity> = async ({ testPath, provider }) => { - const credentialProvider = fromNodeProviderChain() - return await credentialProvider() -} - -const getOpenAIAPIKey: BrowserCommand<[], string | undefined> = async ({ testPath, provider }) => { - return process.env.OPENAI_API_KEY -} - export default defineConfig({ test: { unstubEnvs: true, reporters: [ 'default', - ['junit', { outputFile: 'test/.artifacts/test-report/junit/report.xml' }], + ['junit', { outputFile: 'test/.artifacts/test-report/junit/report.xml', includeConsoleOutput: true }], ['json', { outputFile: 'test/.artifacts/test-report/json/report.json' }], ], projects: [ @@ -53,6 +41,7 @@ export default defineConfig({ browser: { enabled: true, provider: playwright(), + headless: true, screenshotDirectory: 'test/.artifacts/browser-screenshots/', instances: [ { @@ -73,7 +62,8 @@ export default defineConfig({ name: { label: 'integ-node', color: 'magenta' }, testTimeout: 30000, retry: 1, - globalSetup: './test/integ/integ-setup.ts', + globalSetup: './test/integ/__fixtures__/_setup-global.ts', + setupFiles: './test/integ/__fixtures__/_setup-test.ts', sequence: { concurrent: true, }, @@ -91,20 +81,16 @@ export default defineConfig({ browser: { enabled: true, provider: playwright(), + headless: true, screenshotDirectory: 'test/.artifacts/browser-screenshots/', instances: [ { browser: 'chromium', }, ], - // These act as passthrough commands that browser tests can use to communicate with the test server running in node. - // This allows browsers to get access to credential secrets - commands: { - getAwsCredentials, - getOpenAIAPIKey, - }, }, - globalSetup: './test/integ/integ-setup.ts', + globalSetup: './test/integ/__fixtures__/_setup-global.ts', + setupFiles: './test/integ/__fixtures__/_setup-test.ts', sequence: { concurrent: true, }, From 552802bf36fbc5c0e5e0a7d2ff62d2da008423b2 Mon Sep 17 00:00:00 2001 From: Mackenzie Zastrow Date: Tue, 9 Dec 2025 11:09:59 -0500 Subject: [PATCH 2/5] Throw when keys are not available on CI/CD --- test/integ/__fixtures__/_setup-global.ts | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/test/integ/__fixtures__/_setup-global.ts b/test/integ/__fixtures__/_setup-global.ts index 2f0d83f6..239bc89f 100644 --- a/test/integ/__fixtures__/_setup-global.ts +++ b/test/integ/__fixtures__/_setup-global.ts @@ -62,18 +62,23 @@ export async function setup(project: TestProject): Promise { await loadApiKeysFromSecretsManager() console.log('Global setup: API keys loaded into environment') + const isCI = !!globalThis.process.env.CI + project.provide('isBrowser', project.isBrowserEnabled()) - project.provide('isCI', !!globalThis.process.env.CI) - project.provide('provider-openai', await getOpenAITestContext()) - project.provide('provider-bedrock', await getBedrockTestContext()) + project.provide('isCI', isCI) + project.provide('provider-openai', await getOpenAITestContext(isCI)) + project.provide('provider-bedrock', await getBedrockTestContext(isCI)) } -async function getOpenAITestContext(): Promise { +async function getOpenAITestContext(isCI: boolean): Promise { const apiKey = process.env.OPENAI_API_KEY const shouldSkip = !!apiKey if (shouldSkip) { console.log('⏭️ OpenAI API key not available - integration tests will be skipped') + if (isCI) { + throw new Error('CI/CD should be running all tests') + } } else { console.log('⏭️ OpenAI API key available - integration tests will run') } @@ -84,7 +89,7 @@ async function getOpenAITestContext(): Promise { +async function getBedrockTestContext(isCI: boolean): Promise { try { const credentialProvider = fromNodeProviderChain() const credentials = await credentialProvider() @@ -95,6 +100,9 @@ async function getBedrockTestContext(): Promise Date: Tue, 9 Dec 2025 11:21:59 -0500 Subject: [PATCH 3/5] Fix OpenAI skip logic --- test/integ/__fixtures__/_setup-global.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/integ/__fixtures__/_setup-global.ts b/test/integ/__fixtures__/_setup-global.ts index 239bc89f..6ea24e99 100644 --- a/test/integ/__fixtures__/_setup-global.ts +++ b/test/integ/__fixtures__/_setup-global.ts @@ -72,7 +72,7 @@ export async function setup(project: TestProject): Promise { async function getOpenAITestContext(isCI: boolean): Promise { const apiKey = process.env.OPENAI_API_KEY - const shouldSkip = !!apiKey + const shouldSkip = !apiKey if (shouldSkip) { console.log('⏭️ OpenAI API key not available - integration tests will be skipped') From b2b3ef914fe1ed0310296969cc576702b95e1976 Mon Sep 17 00:00:00 2001 From: Mackenzie Zastrow Date: Tue, 9 Dec 2025 11:34:56 -0500 Subject: [PATCH 4/5] Increase test timeout --- vitest.config.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/vitest.config.ts b/vitest.config.ts index f3fda9b9..86f4f4d9 100644 --- a/vitest.config.ts +++ b/vitest.config.ts @@ -60,7 +60,7 @@ export default defineConfig({ include: ['test/integ/**/*.test.ts'], exclude: ['test/integ/**/*.browser.test.ts'], name: { label: 'integ-node', color: 'magenta' }, - testTimeout: 30000, + testTimeout: 60 * 1000, retry: 1, globalSetup: './test/integ/__fixtures__/_setup-global.ts', setupFiles: './test/integ/__fixtures__/_setup-test.ts', @@ -77,7 +77,7 @@ export default defineConfig({ }, include: ['test/integ/**/*.browser.test.ts'], name: { label: 'integ-browser', color: 'yellow' }, - testTimeout: 30000, + testTimeout: 60 * 1000, browser: { enabled: true, provider: playwright(), From 74b796d779309b2187d5608f07bf5765735a5edd Mon Sep 17 00:00:00 2001 From: Mackenzie Zastrow Date: Wed, 24 Dec 2025 11:52:11 -0500 Subject: [PATCH 5/5] Address PR comments --- test/integ/__fixtures__/_setup-global.ts | 6 ++++-- test/integ/__fixtures__/_setup-test.ts | 4 +++- test/integ/__fixtures__/model-providers.ts | 5 ++--- test/integ/bash.test.ts | 2 +- 4 files changed, 10 insertions(+), 7 deletions(-) diff --git a/test/integ/__fixtures__/_setup-global.ts b/test/integ/__fixtures__/_setup-global.ts index 6ea24e99..19b088d9 100644 --- a/test/integ/__fixtures__/_setup-global.ts +++ b/test/integ/__fixtures__/_setup-global.ts @@ -1,5 +1,7 @@ /** - * Global setup that runs once before all integration tests and possibly runs in the *parent* process + * Global setup that runs once before all integration tests and possibly runs in the *parent* process. + * + * _setup-test on the other hand runs in the *child* process. */ import { SecretsManagerClient, GetSecretValueCommand } from '@aws-sdk/client-secrets-manager' @@ -55,7 +57,7 @@ async function loadApiKeysFromSecretsManager(): Promise { } /** - * Perform shared setup for the integration tests unless it's already been. + * Perform shared setup for the integration tests. */ export async function setup(project: TestProject): Promise { console.log('Global setup: Loading API keys from Secrets Manager...') diff --git a/test/integ/__fixtures__/_setup-test.ts b/test/integ/__fixtures__/_setup-test.ts index 70482fc0..bbb57deb 100644 --- a/test/integ/__fixtures__/_setup-test.ts +++ b/test/integ/__fixtures__/_setup-test.ts @@ -1,5 +1,7 @@ /** - * Global setup that runs once before all integration tests and possibly runs in the *parent* process + * Test setup that runs once before all integration tests, but in the *child* process. + * + * _setup-global on the other hand runs in the *parent* process. */ import { beforeAll } from 'vitest' diff --git a/test/integ/__fixtures__/model-providers.ts b/test/integ/__fixtures__/model-providers.ts index d1ed6d8c..cc8c9c92 100644 --- a/test/integ/__fixtures__/model-providers.ts +++ b/test/integ/__fixtures__/model-providers.ts @@ -2,7 +2,6 @@ * Contains helpers for creating various model providers that work both in node & the browser */ -import { isCI } from './test-helpers.js' import { inject } from 'vitest' import { BedrockModel, type BedrockModelOptions } from '$/sdk/models/bedrock.js' import { OpenAIModel, type OpenAIModelOptions } from '$/sdk/models/openai.js' @@ -10,7 +9,7 @@ import { OpenAIModel, type OpenAIModelOptions } from '$/sdk/models/openai.js' export const bedrock = { name: 'BedrockModel', get skip() { - return !isCI() && inject('provider-bedrock').shouldSkip + return inject('provider-bedrock').shouldSkip }, createModel: (options: BedrockModelOptions = {}): BedrockModel => { const credentials = inject('provider-bedrock').credentials @@ -31,7 +30,7 @@ export const bedrock = { export const openai = { name: 'OpenAIModel', get skip() { - return !isCI() && inject('provider-openai').shouldSkip + return inject('provider-openai').shouldSkip }, createModel: (config: OpenAIModelOptions = {}): OpenAIModel => { const apiKey = inject('provider-openai').apiKey diff --git a/test/integ/bash.test.ts b/test/integ/bash.test.ts index c3a42e6b..ea6c7f24 100644 --- a/test/integ/bash.test.ts +++ b/test/integ/bash.test.ts @@ -4,7 +4,7 @@ import { bash } from '$/sdk/vended-tools/bash/index.js' import { getMessageText } from './__fixtures__/model-test-helpers.js' import { bedrock } from './__fixtures__/model-providers.js' -describe.skipIf(bedrock.skip || process.platform === 'win32')('Bash Tool Integration', { timeout: 60000 }, () => { +describe.skipIf(bedrock.skip || process.platform === 'win32')('Bash Tool Integration', () => { // Shared agent configuration for all tests const createAgent = () => new Agent({