diff --git a/eslint.config.js b/eslint.config.js index 13a3d1c3..5b3b7f3d 100644 --- a/eslint.config.js +++ b/eslint.config.js @@ -238,7 +238,7 @@ export default tseslint.config( prettierConfig, // extra settings for scripts that we run directly with node { - files: ['./integration-tests/**/*.js'], + files: ['./integration-tests/**/*.{js,ts,tsx}'], languageOptions: { globals: { ...globals.node, diff --git a/integration-tests/globalSetup.ts b/integration-tests/globalSetup.ts index 77105af2..a8a9877f 100644 --- a/integration-tests/globalSetup.ts +++ b/integration-tests/globalSetup.ts @@ -1,6 +1,6 @@ /** * @license - * Copyright 2025 Google LLC + * Copyright 2025 Qwen Team * SPDX-License-Identifier: Apache-2.0 */ @@ -30,6 +30,7 @@ const __dirname = dirname(fileURLToPath(import.meta.url)); const rootDir = join(__dirname, '..'); const integrationTestsDir = join(rootDir, '.integration-tests'); let runDir = ''; // Make runDir accessible in teardown +let sdkE2eRunDir = ''; // SDK E2E test run directory const memoryFilePath = join( os.homedir(), @@ -48,14 +49,36 @@ export async function setup() { // File doesn't exist, which is fine. } + // Setup for CLI integration tests runDir = join(integrationTestsDir, `${Date.now()}`); await mkdir(runDir, { recursive: true }); + // Setup for SDK E2E tests (separate directory with prefix) + sdkE2eRunDir = join(integrationTestsDir, `sdk-e2e-${Date.now()}`); + await mkdir(sdkE2eRunDir, { recursive: true }); + // Clean up old test runs, but keep the latest few for debugging try { const testRuns = await readdir(integrationTestsDir); - if (testRuns.length > 5) { - const oldRuns = testRuns.sort().slice(0, testRuns.length - 5); + + // Clean up old CLI integration test runs (without sdk-e2e- prefix) + const cliTestRuns = testRuns.filter((run) => !run.startsWith('sdk-e2e-')); + if (cliTestRuns.length > 5) { + const oldRuns = cliTestRuns.sort().slice(0, cliTestRuns.length - 5); + await Promise.all( + oldRuns.map((oldRun) => + rm(join(integrationTestsDir, oldRun), { + recursive: true, + force: true, + }), + ), + ); + } + + // Clean up old SDK E2E test runs (with sdk-e2e- prefix) + const sdkTestRuns = testRuns.filter((run) => run.startsWith('sdk-e2e-')); + if (sdkTestRuns.length > 5) { + const oldRuns = sdkTestRuns.sort().slice(0, sdkTestRuns.length - 5); await Promise.all( oldRuns.map((oldRun) => rm(join(integrationTestsDir, oldRun), { @@ -69,24 +92,37 @@ export async function setup() { console.error('Error cleaning up old test runs:', e); } + // Environment variables for CLI integration tests process.env['INTEGRATION_TEST_FILE_DIR'] = runDir; process.env['GEMINI_CLI_INTEGRATION_TEST'] = 'true'; process.env['TELEMETRY_LOG_FILE'] = join(runDir, 'telemetry.log'); + // Environment variables for SDK E2E tests + process.env['E2E_TEST_FILE_DIR'] = sdkE2eRunDir; + process.env['TEST_CLI_PATH'] = join(rootDir, 'dist/cli.js'); + if (process.env['KEEP_OUTPUT']) { console.log(`Keeping output for test run in: ${runDir}`); + console.log(`Keeping output for SDK E2E test run in: ${sdkE2eRunDir}`); } process.env['VERBOSE'] = process.env['VERBOSE'] ?? 'false'; console.log(`\nIntegration test output directory: ${runDir}`); + console.log(`SDK E2E test output directory: ${sdkE2eRunDir}`); + console.log(`CLI path: ${process.env['TEST_CLI_PATH']}`); } export async function teardown() { - // Cleanup the test run directory unless KEEP_OUTPUT is set + // Cleanup the CLI test run directory unless KEEP_OUTPUT is set if (process.env['KEEP_OUTPUT'] !== 'true' && runDir) { await rm(runDir, { recursive: true, force: true }); } + // Cleanup the SDK E2E test run directory unless KEEP_OUTPUT is set + if (process.env['KEEP_OUTPUT'] !== 'true' && sdkE2eRunDir) { + await rm(sdkE2eRunDir, { recursive: true, force: true }); + } + if (originalMemoryContent !== null) { await mkdir(dirname(memoryFilePath), { recursive: true }); await writeFile(memoryFilePath, originalMemoryContent, 'utf-8'); diff --git a/packages/sdk-typescript/test/e2e/abort-and-lifecycle.test.ts b/integration-tests/sdk-typescript/abort-and-lifecycle.test.ts similarity index 99% rename from packages/sdk-typescript/test/e2e/abort-and-lifecycle.test.ts rename to integration-tests/sdk-typescript/abort-and-lifecycle.test.ts index 806a4a20..b0b4c3fd 100644 --- a/packages/sdk-typescript/test/e2e/abort-and-lifecycle.test.ts +++ b/integration-tests/sdk-typescript/abort-and-lifecycle.test.ts @@ -13,7 +13,7 @@ import { isSDKAssistantMessage, type TextBlock, type ContentBlock, -} from '../../src/index.js'; +} from '@qwen-code/sdk-typescript'; import { SDKTestHelper, createSharedTestOptions } from './test-helper.js'; const SHARED_TEST_OPTIONS = createSharedTestOptions(); diff --git a/packages/sdk-typescript/test/e2e/configuration-options.test.ts b/integration-tests/sdk-typescript/configuration-options.test.ts similarity index 99% rename from packages/sdk-typescript/test/e2e/configuration-options.test.ts rename to integration-tests/sdk-typescript/configuration-options.test.ts index e81af7fd..bac0a368 100644 --- a/packages/sdk-typescript/test/e2e/configuration-options.test.ts +++ b/integration-tests/sdk-typescript/configuration-options.test.ts @@ -12,12 +12,12 @@ */ import { describe, it, expect, beforeEach, afterEach } from 'vitest'; -import { query } from '../../src/index.js'; import { + query, isSDKAssistantMessage, isSDKSystemMessage, type SDKMessage, -} from '../../src/types/protocol.js'; +} from '@qwen-code/sdk-typescript'; import { SDKTestHelper, extractText, diff --git a/packages/sdk-typescript/test/e2e/mcp-server.test.ts b/integration-tests/sdk-typescript/mcp-server.test.ts similarity index 99% rename from packages/sdk-typescript/test/e2e/mcp-server.test.ts rename to integration-tests/sdk-typescript/mcp-server.test.ts index dd13d205..110c1924 100644 --- a/packages/sdk-typescript/test/e2e/mcp-server.test.ts +++ b/integration-tests/sdk-typescript/mcp-server.test.ts @@ -10,8 +10,8 @@ */ import { describe, it, expect, beforeEach, afterEach } from 'vitest'; -import { query } from '../../src/index.js'; import { + query, isSDKAssistantMessage, isSDKResultMessage, isSDKSystemMessage, @@ -19,7 +19,7 @@ import { type SDKMessage, type ToolUseBlock, type SDKSystemMessage, -} from '../../src/types/protocol.js'; +} from '@qwen-code/sdk-typescript'; import { SDKTestHelper, createMCPServer, diff --git a/packages/sdk-typescript/test/e2e/multi-turn.test.ts b/integration-tests/sdk-typescript/multi-turn.test.ts similarity index 99% rename from packages/sdk-typescript/test/e2e/multi-turn.test.ts rename to integration-tests/sdk-typescript/multi-turn.test.ts index 689a6468..17b6f675 100644 --- a/packages/sdk-typescript/test/e2e/multi-turn.test.ts +++ b/integration-tests/sdk-typescript/multi-turn.test.ts @@ -4,8 +4,8 @@ */ import { describe, it, expect, beforeEach, afterEach } from 'vitest'; -import { query } from '../../src/index.js'; import { + query, isSDKUserMessage, isSDKAssistantMessage, isSDKSystemMessage, @@ -21,7 +21,7 @@ import { type SDKMessage, type ControlMessage, type ToolUseBlock, -} from '../../src/types/protocol.js'; +} from '@qwen-code/sdk-typescript'; import { SDKTestHelper, createSharedTestOptions } from './test-helper.js'; const SHARED_TEST_OPTIONS = createSharedTestOptions(); diff --git a/packages/sdk-typescript/test/e2e/permission-control.test.ts b/integration-tests/sdk-typescript/permission-control.test.ts similarity index 99% rename from packages/sdk-typescript/test/e2e/permission-control.test.ts rename to integration-tests/sdk-typescript/permission-control.test.ts index 587fa500..31c7768a 100644 --- a/packages/sdk-typescript/test/e2e/permission-control.test.ts +++ b/integration-tests/sdk-typescript/permission-control.test.ts @@ -13,8 +13,8 @@ import { beforeEach, afterEach, } from 'vitest'; -import { query } from '../../src/index.js'; import { + query, isSDKAssistantMessage, isSDKResultMessage, isSDKUserMessage, @@ -22,7 +22,7 @@ import { type SDKUserMessage, type ToolUseBlock, type ContentBlock, -} from '../../src/types/protocol.js'; +} from '@qwen-code/sdk-typescript'; import { SDKTestHelper, createSharedTestOptions, diff --git a/packages/sdk-typescript/test/e2e/single-turn.test.ts b/integration-tests/sdk-typescript/single-turn.test.ts similarity index 99% rename from packages/sdk-typescript/test/e2e/single-turn.test.ts rename to integration-tests/sdk-typescript/single-turn.test.ts index 4adb7c0b..aa2716f3 100644 --- a/packages/sdk-typescript/test/e2e/single-turn.test.ts +++ b/integration-tests/sdk-typescript/single-turn.test.ts @@ -4,8 +4,8 @@ */ import { describe, it, expect, beforeEach, afterEach } from 'vitest'; -import { query } from '../../src/index.js'; import { + query, isSDKAssistantMessage, isSDKSystemMessage, isSDKResultMessage, @@ -13,7 +13,7 @@ import { type SDKMessage, type SDKSystemMessage, type SDKAssistantMessage, -} from '../../src/types/protocol.js'; +} from '@qwen-code/sdk-typescript'; import { SDKTestHelper, extractText, diff --git a/packages/sdk-typescript/test/e2e/subagents.test.ts b/integration-tests/sdk-typescript/subagents.test.ts similarity index 99% rename from packages/sdk-typescript/test/e2e/subagents.test.ts rename to integration-tests/sdk-typescript/subagents.test.ts index 06e3fd36..86516053 100644 --- a/packages/sdk-typescript/test/e2e/subagents.test.ts +++ b/integration-tests/sdk-typescript/subagents.test.ts @@ -10,14 +10,14 @@ */ import { describe, it, expect, beforeEach, afterEach } from 'vitest'; -import { query } from '../../src/index.js'; import { + query, isSDKAssistantMessage, type SDKMessage, type SubagentConfig, type ContentBlock, type ToolUseBlock, -} from '../../src/types/protocol.js'; +} from '@qwen-code/sdk-typescript'; import { SDKTestHelper, extractText, diff --git a/packages/sdk-typescript/test/e2e/system-control.test.ts b/integration-tests/sdk-typescript/system-control.test.ts similarity index 98% rename from packages/sdk-typescript/test/e2e/system-control.test.ts rename to integration-tests/sdk-typescript/system-control.test.ts index 3515532e..069eccd9 100644 --- a/packages/sdk-typescript/test/e2e/system-control.test.ts +++ b/integration-tests/sdk-typescript/system-control.test.ts @@ -4,12 +4,12 @@ */ import { describe, it, expect, beforeEach, afterEach } from 'vitest'; -import { query } from '../../src/index.js'; import { + query, isSDKAssistantMessage, isSDKSystemMessage, type SDKUserMessage, -} from '../../src/types/protocol.js'; +} from '@qwen-code/sdk-typescript'; import { SDKTestHelper, createSharedTestOptions } from './test-helper.js'; const SHARED_TEST_OPTIONS = createSharedTestOptions(); @@ -265,7 +265,7 @@ describe('System Control (E2E)', () => { // First model change await q.setModel('qwen3-turbo'); - resumeResolve1?.(); + resumeResolve1!(); // Wait for second response await Promise.race([ @@ -277,7 +277,7 @@ describe('System Control (E2E)', () => { // Second model change await q.setModel('qwen3-vl-plus'); - resumeResolve2?.(); + resumeResolve2!(); // Wait for third response await Promise.race([ diff --git a/packages/sdk-typescript/test/e2e/test-helper.ts b/integration-tests/sdk-typescript/test-helper.ts similarity index 99% rename from packages/sdk-typescript/test/e2e/test-helper.ts rename to integration-tests/sdk-typescript/test-helper.ts index 4b1465ad..cd95051f 100644 --- a/packages/sdk-typescript/test/e2e/test-helper.ts +++ b/integration-tests/sdk-typescript/test-helper.ts @@ -21,12 +21,12 @@ import type { ContentBlock, TextBlock, ToolUseBlock, -} from '../../src/types/protocol.js'; +} from '@qwen-code/sdk-typescript'; import { isSDKAssistantMessage, isSDKSystemMessage, isSDKResultMessage, -} from '../../src/types/protocol.js'; +} from '@qwen-code/sdk-typescript'; // ============================================================================ // Core Test Helper Class diff --git a/packages/sdk-typescript/test/e2e/tool-control.test.ts b/integration-tests/sdk-typescript/tool-control.test.ts similarity index 99% rename from packages/sdk-typescript/test/e2e/tool-control.test.ts rename to integration-tests/sdk-typescript/tool-control.test.ts index 30a811df..036d779e 100644 --- a/packages/sdk-typescript/test/e2e/tool-control.test.ts +++ b/integration-tests/sdk-typescript/tool-control.test.ts @@ -12,11 +12,11 @@ */ import { describe, it, expect, beforeEach, afterEach } from 'vitest'; -import { query } from '../../src/index.js'; import { + query, isSDKAssistantMessage, type SDKMessage, -} from '../../src/types/protocol.js'; +} from '@qwen-code/sdk-typescript'; import { SDKTestHelper, extractText, diff --git a/integration-tests/tsconfig.json b/integration-tests/tsconfig.json index 295741e1..7f2a010d 100644 --- a/integration-tests/tsconfig.json +++ b/integration-tests/tsconfig.json @@ -2,7 +2,13 @@ "extends": "../tsconfig.json", "compilerOptions": { "noEmit": true, - "allowJs": true + "allowJs": true, + "baseUrl": ".", + "paths": { + "@qwen-code/sdk-typescript": [ + "../packages/sdk-typescript/dist/index.d.ts" + ] + } }, "include": ["**/*.ts"], "references": [{ "path": "../packages/core" }] diff --git a/integration-tests/vitest.config.ts b/integration-tests/vitest.config.ts index c8b79ad6..a452583c 100644 --- a/integration-tests/vitest.config.ts +++ b/integration-tests/vitest.config.ts @@ -1,12 +1,15 @@ /** * @license - * Copyright 2025 Google LLC + * Copyright 2025 Qwen Team * SPDX-License-Identifier: Apache-2.0 */ import { defineConfig } from 'vitest/config'; +import { dirname, resolve } from 'node:path'; +import { fileURLToPath } from 'node:url'; -const timeoutMinutes = Number(process.env.TB_TIMEOUT_MINUTES || '5'); +const __dirname = dirname(fileURLToPath(import.meta.url)); +const timeoutMinutes = Number(process.env['TB_TIMEOUT_MINUTES'] || '5'); const testTimeoutMs = timeoutMinutes * 60 * 1000; export default defineConfig({ @@ -25,4 +28,13 @@ export default defineConfig({ }, }, }, + resolve: { + alias: { + // Use built SDK bundle for e2e tests + '@qwen-code/sdk-typescript': resolve( + __dirname, + '../packages/sdk-typescript/dist/index.mjs', + ), + }, + }, }); diff --git a/packages/sdk-typescript/package.json b/packages/sdk-typescript/package.json index 63fed227..d2787bf8 100644 --- a/packages/sdk-typescript/package.json +++ b/packages/sdk-typescript/package.json @@ -22,6 +22,7 @@ "scripts": { "build": "node scripts/build.js", "test": "vitest run", + "test:ci": "vitest run", "test:watch": "vitest", "test:coverage": "vitest run --coverage", "lint": "eslint src test", diff --git a/packages/sdk-typescript/src/index.ts b/packages/sdk-typescript/src/index.ts index f8bf81c5..da40baf2 100644 --- a/packages/sdk-typescript/src/index.ts +++ b/packages/sdk-typescript/src/index.ts @@ -18,6 +18,14 @@ export type { SDKResultMessage, SDKPartialAssistantMessage, SDKMessage, + ControlMessage, + CLIControlRequest, + CLIControlResponse, + ControlCancelRequest, + SubagentConfig, + SubagentLevel, + ModelConfig, + RunConfig, } from './types/protocol.js'; export { @@ -26,6 +34,9 @@ export { isSDKSystemMessage, isSDKResultMessage, isSDKPartialAssistantMessage, + isControlRequest, + isControlResponse, + isControlCancel, } from './types/protocol.js'; export type { diff --git a/packages/sdk-typescript/test/e2e/globalSetup.ts b/packages/sdk-typescript/test/e2e/globalSetup.ts deleted file mode 100644 index 4f98b877..00000000 --- a/packages/sdk-typescript/test/e2e/globalSetup.ts +++ /dev/null @@ -1,57 +0,0 @@ -/** - * @license - * Copyright 2025 Qwen Team - * SPDX-License-Identifier: Apache-2.0 - */ - -import { mkdir, readdir, rm } from 'node:fs/promises'; -import { join, dirname } from 'node:path'; -import { fileURLToPath } from 'node:url'; - -const __dirname = dirname(fileURLToPath(import.meta.url)); -const rootDir = join(__dirname, '../..'); -const e2eTestsDir = join(rootDir, '.integration-tests'); -let runDir = ''; - -export async function setup() { - runDir = join(e2eTestsDir, `sdk-e2e-${Date.now()}`); - await mkdir(runDir, { recursive: true }); - - // Clean up old test runs, but keep the latest few for debugging - try { - const testRuns = await readdir(e2eTestsDir); - const sdkTestRuns = testRuns.filter((run) => run.startsWith('sdk-e2e-')); - if (sdkTestRuns.length > 5) { - const oldRuns = sdkTestRuns.sort().slice(0, sdkTestRuns.length - 5); - await Promise.all( - oldRuns.map((oldRun) => - rm(join(e2eTestsDir, oldRun), { - recursive: true, - force: true, - }), - ), - ); - } - } catch (e) { - console.error('Error cleaning up old test runs:', e); - } - - process.env['E2E_TEST_FILE_DIR'] = runDir; - process.env['QWEN_CLI_E2E_TEST'] = 'true'; - process.env['TEST_CLI_PATH'] = join(rootDir, '../../dist/cli.js'); - - if (process.env['KEEP_OUTPUT']) { - console.log(`Keeping output for test run in: ${runDir}`); - } - process.env['VERBOSE'] = process.env['VERBOSE'] ?? 'false'; - - console.log(`\nSDK E2E test output directory: ${runDir}`); - console.log(`CLI path: ${process.env['TEST_CLI_PATH']}`); -} - -export async function teardown() { - // Cleanup the test run directory unless KEEP_OUTPUT is set - if (process.env['KEEP_OUTPUT'] !== 'true' && runDir) { - await rm(runDir, { recursive: true, force: true }); - } -}