diff --git a/eslint.config.js b/eslint.config.js index 90c31ebd..8e376ae9 100644 --- a/eslint.config.js +++ b/eslint.config.js @@ -10,21 +10,16 @@ export default [ files: ['src/**/*.ts'], tsconfig: './src/tsconfig.json', }), + // Prevent non-vended-tools from importing vended-tools + noVendedToolsImports({ + files: ['src/**/*.ts'], + ignores: ['src/vended-tools/**/*.ts'], + }), // Then unit-test rules to UTs unitTestRules({ files: ['src/**/__tests__/**/*.ts'], tsconfig: './src/tsconfig.json', }), - // Apply SDK rules to vended_tool files - sdkRules({ - files: ['vended_tools/**/*.ts'], - tsconfig: './vended_tools/tsconfig.json', - }), - // Then unit-test rules to UTs - unitTestRules({ - files: ['vended_tools/**/__tests__/**/*.ts'], - tsconfig: './vended_tools/tsconfig.json', - }), // Apply UT rules to the integ tests unitTestRules({ files: ['tests_integ/**/*.ts'], @@ -122,3 +117,24 @@ function integTestRules(options) { }, } } + +function noVendedToolsImports(options) { + return { + files: options.files, + ignores: options.ignores, + rules: { + 'no-restricted-imports': [ + 'error', + { + patterns: [ + { + group: ['**/vended-tools', '**/vended-tools/**'], + message: + 'Core SDK files should not import from vended-tools. Vended tools are optional and independently importable.', + }, + ], + }, + ], + }, + } +} diff --git a/package.json b/package.json index c7c73c57..4829ac1c 100644 --- a/package.json +++ b/package.json @@ -23,24 +23,24 @@ "default": "./dist/src/models/bedrock.js" }, "./vended_tools/notebook": { - "types": "./dist/vended_tools/notebook/index.d.ts", - "default": "./dist/vended_tools/notebook/index.js" + "types": "./dist/src/vended-tools/notebook/index.d.ts", + "default": "./dist/src/vended-tools/notebook/index.js" }, "./vended_tools/file_editor": { - "types": "./dist/vended_tools/file_editor/index.d.ts", - "default": "./dist/vended_tools/file_editor/index.js" + "types": "./dist/src/vended-tools/file_editor/index.d.ts", + "default": "./dist/src/vended-tools/file_editor/index.js" }, "./vended_tools/http_request": { - "types": "./dist/vended_tools/http_request/index.d.ts", - "default": "./dist/vended_tools/http_request/index.js" + "types": "./dist/src/vended-tools/http_request/index.d.ts", + "default": "./dist/src/vended-tools/http_request/index.js" }, "./vended_tools/bash": { - "types": "./dist/vended_tools/bash/index.d.ts", - "default": "./dist/vended_tools/bash/index.js" + "types": "./dist/src/vended-tools/bash/index.d.ts", + "default": "./dist/src/vended-tools/bash/index.js" } }, "scripts": { - "build": "tsc --project src/tsconfig.json && tsc --project vended_tools/tsconfig.json", + "build": "tsc --project src/tsconfig.json", "check": "npm run lint && npm run format && npm run type-check && npm run test:coverage && npm run test:package", "clean": "rm -rf node_modules dist package-lock.json", "test": "vitest run --project unit-node", @@ -55,11 +55,11 @@ "test:all": "vitest run --project unit-node --project unit-browser", "test:all:coverage": "vitest run --coverage --project unit-node --project unit-browser", "test:package": "cd test/packages/esm-module && npm install && node esm.js && cd ../cjs-module && npm install && node cjs.js", - "lint": "eslint src tests_integ vended_tools", - "lint:fix": "eslint src tests_integ vended_tools --fix", - "format": "prettier --write src tests_integ vended_tools", - "format:check": "prettier --check src tests_integ vended_tools", - "type-check": "tsc --noEmit --project src/tsconfig.json && tsc --noEmit --project vended_tools/tsconfig.json && tsc --noEmit --project tests_integ/tsconfig.json", + "lint": "eslint src tests_integ", + "lint:fix": "eslint src tests_integ --fix", + "format": "prettier --write src tests_integ", + "format:check": "prettier --check src tests_integ", + "type-check": "tsc --noEmit --project src/tsconfig.json && tsc --noEmit --project tests_integ/tsconfig.json", "type-check:watch": "tsc --noEmit --watch", "prepare": "npm run build && husky" }, diff --git a/src/index.ts b/src/index.ts index 5158d0aa..740b1fea 100644 --- a/src/index.ts +++ b/src/index.ts @@ -86,7 +86,6 @@ export type { ToolSpec, ToolUse, ToolResultStatus, ToolChoice } from './tools/ty // Tool interface and related types export type { - Tool, InvokableTool, ToolContext, ToolStreamEventData, @@ -94,6 +93,9 @@ export type { ToolStreamGenerator, } from './tools/tool.js' +// Tool base class +export { Tool } from './tools/tool.js' + // FunctionTool implementation export { FunctionTool } from './tools/function-tool.js' diff --git a/vended_tools/bash/README.md b/src/vended-tools/bash/README.md similarity index 100% rename from vended_tools/bash/README.md rename to src/vended-tools/bash/README.md diff --git a/vended_tools/bash/__tests__/bash.test.ts b/src/vended-tools/bash/__tests__/bash.test.ts similarity index 98% rename from vended_tools/bash/__tests__/bash.test.ts rename to src/vended-tools/bash/__tests__/bash.test.ts index 744debf0..fd0ce261 100644 --- a/vended_tools/bash/__tests__/bash.test.ts +++ b/src/vended-tools/bash/__tests__/bash.test.ts @@ -1,9 +1,9 @@ import { describe, it, expect, vi, afterEach } from 'vitest' import { bash } from '../index.js' import { BashTimeoutError, BashSessionError, type BashOutput } from '../index.js' -import type { ToolContext } from '../../../src/index.js' -import { AgentState } from '../../../src/agent/state.js' -import { isNode } from '../../../src/__fixtures__/environment.js' +import type { ToolContext } from '../../../index.js' +import { AgentState } from '../../../agent/state.js' +import { isNode } from '../../../__fixtures__/environment.js' // Skip all tests if not in Node.js environment describe.skipIf(!isNode || process.platform === 'win32')('bash tool', () => { diff --git a/vended_tools/bash/bash.ts b/src/vended-tools/bash/bash.ts similarity index 99% rename from vended_tools/bash/bash.ts rename to src/vended-tools/bash/bash.ts index 8524aacf..994539f4 100644 --- a/vended_tools/bash/bash.ts +++ b/src/vended-tools/bash/bash.ts @@ -1,5 +1,5 @@ /* eslint-env node */ -import { tool } from '../../src/tools/zod-tool.js' +import { tool } from '../../tools/zod-tool.js' import { z } from 'zod' import { spawn, type ChildProcess } from 'child_process' import { Buffer } from 'buffer' diff --git a/vended_tools/bash/index.ts b/src/vended-tools/bash/index.ts similarity index 100% rename from vended_tools/bash/index.ts rename to src/vended-tools/bash/index.ts diff --git a/vended_tools/bash/types.ts b/src/vended-tools/bash/types.ts similarity index 100% rename from vended_tools/bash/types.ts rename to src/vended-tools/bash/types.ts diff --git a/vended_tools/file_editor/README.md b/src/vended-tools/file_editor/README.md similarity index 100% rename from vended_tools/file_editor/README.md rename to src/vended-tools/file_editor/README.md diff --git a/vended_tools/file_editor/__tests__/file-editor.test.ts b/src/vended-tools/file_editor/__tests__/file-editor.test.ts similarity index 99% rename from vended_tools/file_editor/__tests__/file-editor.test.ts rename to src/vended-tools/file_editor/__tests__/file-editor.test.ts index 5f81233d..ca0d3ce5 100644 --- a/vended_tools/file_editor/__tests__/file-editor.test.ts +++ b/src/vended-tools/file_editor/__tests__/file-editor.test.ts @@ -1,7 +1,7 @@ import { describe, it, expect, beforeEach, afterEach } from 'vitest' import { fileEditor } from '../file-editor.js' -import type { ToolContext } from '../../../src/index.js' -import { AgentState } from '../../../src/agent/state.js' +import type { ToolContext } from '../../../index.js' +import { AgentState } from '../../../agent/state.js' import { promises as fs } from 'fs' import * as path from 'path' import { tmpdir } from 'os' diff --git a/vended_tools/file_editor/file-editor.ts b/src/vended-tools/file_editor/file-editor.ts similarity index 99% rename from vended_tools/file_editor/file-editor.ts rename to src/vended-tools/file_editor/file-editor.ts index 2a185e41..27aa51c0 100644 --- a/vended_tools/file_editor/file-editor.ts +++ b/src/vended-tools/file_editor/file-editor.ts @@ -1,4 +1,4 @@ -import { tool } from '../../src/tools/zod-tool.js' +import { tool } from '../../tools/zod-tool.js' import { z } from 'zod' import type { IFileReader } from './types.js' import { promises as fs } from 'fs' diff --git a/vended_tools/file_editor/index.ts b/src/vended-tools/file_editor/index.ts similarity index 100% rename from vended_tools/file_editor/index.ts rename to src/vended-tools/file_editor/index.ts diff --git a/vended_tools/file_editor/types.ts b/src/vended-tools/file_editor/types.ts similarity index 100% rename from vended_tools/file_editor/types.ts rename to src/vended-tools/file_editor/types.ts diff --git a/vended_tools/http_request/README.md b/src/vended-tools/http_request/README.md similarity index 100% rename from vended_tools/http_request/README.md rename to src/vended-tools/http_request/README.md diff --git a/vended_tools/http_request/__tests__/http-request.test.ts b/src/vended-tools/http_request/__tests__/http-request.test.ts similarity index 100% rename from vended_tools/http_request/__tests__/http-request.test.ts rename to src/vended-tools/http_request/__tests__/http-request.test.ts diff --git a/vended_tools/http_request/http-request.ts b/src/vended-tools/http_request/http-request.ts similarity index 98% rename from vended_tools/http_request/http-request.ts rename to src/vended-tools/http_request/http-request.ts index ec5bbe35..1caf4729 100644 --- a/vended_tools/http_request/http-request.ts +++ b/src/vended-tools/http_request/http-request.ts @@ -1,5 +1,5 @@ /* eslint-env browser, node */ -import { tool } from '../../src/tools/zod-tool.js' +import { tool } from '../../tools/zod-tool.js' import { z } from 'zod' /** diff --git a/vended_tools/http_request/index.ts b/src/vended-tools/http_request/index.ts similarity index 100% rename from vended_tools/http_request/index.ts rename to src/vended-tools/http_request/index.ts diff --git a/vended_tools/http_request/types.ts b/src/vended-tools/http_request/types.ts similarity index 100% rename from vended_tools/http_request/types.ts rename to src/vended-tools/http_request/types.ts diff --git a/vended_tools/notebook/README.md b/src/vended-tools/notebook/README.md similarity index 100% rename from vended_tools/notebook/README.md rename to src/vended-tools/notebook/README.md diff --git a/vended_tools/notebook/__tests__/notebook.test.ts b/src/vended-tools/notebook/__tests__/notebook.test.ts similarity index 99% rename from vended_tools/notebook/__tests__/notebook.test.ts rename to src/vended-tools/notebook/__tests__/notebook.test.ts index 3f57e5eb..c6bf1ecd 100644 --- a/vended_tools/notebook/__tests__/notebook.test.ts +++ b/src/vended-tools/notebook/__tests__/notebook.test.ts @@ -1,8 +1,8 @@ import { describe, it, expect } from 'vitest' import { notebook } from '../notebook.js' import type { NotebookState } from '../types.js' -import type { ToolContext } from '../../../src/index.js' -import { AgentState } from '../../../src/agent/state.js' +import type { ToolContext } from '../../../index.js' +import { AgentState } from '../../../agent/state.js' describe('notebook tool', () => { // Helper to create fresh state and context for each test diff --git a/vended_tools/notebook/index.ts b/src/vended-tools/notebook/index.ts similarity index 100% rename from vended_tools/notebook/index.ts rename to src/vended-tools/notebook/index.ts diff --git a/vended_tools/notebook/notebook.ts b/src/vended-tools/notebook/notebook.ts similarity index 99% rename from vended_tools/notebook/notebook.ts rename to src/vended-tools/notebook/notebook.ts index 469d050b..2ba8d3ca 100644 --- a/vended_tools/notebook/notebook.ts +++ b/src/vended-tools/notebook/notebook.ts @@ -1,4 +1,4 @@ -import { tool } from '../../src/index.js' +import { tool } from '../../index.js' import { z } from 'zod' import type { NotebookState } from './types.js' diff --git a/vended_tools/notebook/types.ts b/src/vended-tools/notebook/types.ts similarity index 100% rename from vended_tools/notebook/types.ts rename to src/vended-tools/notebook/types.ts diff --git a/test/packages/cjs-module/cjs.js b/test/packages/cjs-module/cjs.js index 4e1443cb..4324cd25 100644 --- a/test/packages/cjs-module/cjs.js +++ b/test/packages/cjs-module/cjs.js @@ -3,7 +3,13 @@ * This script runs in a pure Node.js ES module environment. */ -const { Agent, BedrockModel, tool } = require('@strands-agents/sdk') +const { Agent, BedrockModel, tool, Tool } = require('@strands-agents/sdk') + +const { notebook } = require('@strands-agents/sdk/vended_tools/notebook') +const { fileEditor } = require('@strands-agents/sdk/vended_tools/file_editor') +const { httpRequest } = require('@strands-agents/sdk/vended_tools/http_request') +const { bash } = require('@strands-agents/sdk/vended_tools/bash') + const { z } = require('zod') console.log('✓ Import from main entry point successful') @@ -54,6 +60,19 @@ async function main() { if (agent.tools.length == 0) { throw new Error('Tool was not correctly added to the agent') } + + const tools = { + notebook, + fileEditor, + httpRequest, + bash, + } + + for (const tool of Object.values(tools)) { + if (!(tool instanceof Tool)) { + throw new Error(`Tool ${tool.name} isn't an instance of a tool`) + } + } } main().catch((error) => { diff --git a/test/packages/esm-module/esm.js b/test/packages/esm-module/esm.js index 5c9b4c8d..cb30ac90 100644 --- a/test/packages/esm-module/esm.js +++ b/test/packages/esm-module/esm.js @@ -3,7 +3,13 @@ * This script runs in a pure Node.js ES module environment. */ -import { Agent, BedrockModel, tool } from '@strands-agents/sdk' +import { Agent, BedrockModel, tool, Tool } from '@strands-agents/sdk' + +import { notebook } from '@strands-agents/sdk/vended_tools/notebook' +import { fileEditor } from '@strands-agents/sdk/vended_tools/file_editor' +import { httpRequest } from '@strands-agents/sdk/vended_tools/http_request' +import { bash } from '@strands-agents/sdk/vended_tools/bash' + import { z } from 'zod' console.log('✓ Import from main entry point successful') @@ -53,3 +59,42 @@ const agent = new Agent({ if (agent.tools.length == 0) { throw new Error('Tool was not correctly added to the agent') } + +async function validateScratchpad() { + let context = { agent: agent } + notebook.invoke( + { + mode: 'create', + name: 'scratchpad', + newStr: 'Content', + }, + context + ) + + const result = await notebook.invoke( + { + mode: 'read', + name: 'scratchpad', + }, + context + ) + + if (result !== 'Content') { + throw new Error(`Tool returned invalid response: ${result}`) + } + + console.log('Notebook created successful') +} + +const tools = { + notebook, + fileEditor, + httpRequest, + bash, +} + +for (const tool of Object.values(tools)) { + if (!(tool instanceof Tool)) { + throw new Error(`Tool ${tool.name} isn't an instance of a tool`) + } +} diff --git a/tests_integ/bash.test.ts b/tests_integ/bash.test.ts index 64221be8..73ba9ed8 100644 --- a/tests_integ/bash.test.ts +++ b/tests_integ/bash.test.ts @@ -1,6 +1,6 @@ import { describe, it, expect } from 'vitest' import { Agent, BedrockModel } from '$/sdk/index.js' -import { bash } from '../vended_tools/bash/index.js' +import { bash } from '$/sdk/vended-tools/bash/index.js' import { getMessageText, shouldRunTests } from './__fixtures__/model-test-helpers.js' describe.skipIf(!(await shouldRunTests()) || process.platform === 'win32')( diff --git a/tests_integ/file-editor.test.ts b/tests_integ/file-editor.test.ts index 8b6d3110..ffa10c55 100644 --- a/tests_integ/file-editor.test.ts +++ b/tests_integ/file-editor.test.ts @@ -1,6 +1,6 @@ import { describe, it, expect, beforeEach, afterEach } from 'vitest' import { Agent, BedrockModel } from '$/sdk/index.js' -import { fileEditor } from '../vended_tools/file_editor/index.js' +import { fileEditor } from '$/sdk/vended-tools/file_editor/index.js' import { collectGenerator } from '$/sdk/__fixtures__/model-test-helpers.js' import { shouldRunTests } from './__fixtures__/model-test-helpers.js' import { promises as fs } from 'fs' diff --git a/tests_integ/notebook.test.ts b/tests_integ/notebook.test.ts index 8cb7d22f..c091487f 100644 --- a/tests_integ/notebook.test.ts +++ b/tests_integ/notebook.test.ts @@ -1,7 +1,7 @@ import { describe, it, expect } from 'vitest' import { Agent, BedrockModel } from '$/sdk/index.js' import type { AgentStreamEvent, AgentResult } from '$/sdk/index.js' -import { notebook } from '../vended_tools/notebook/index.js' +import { notebook } from '$/sdk/vended-tools/notebook/index.js' import { collectGenerator } from '$/sdk/__fixtures__/model-test-helpers.js' import { shouldRunTests } from './__fixtures__/model-test-helpers.js' diff --git a/tests_integ/tsconfig.json b/tests_integ/tsconfig.json index af7eda60..f086eed7 100644 --- a/tests_integ/tsconfig.json +++ b/tests_integ/tsconfig.json @@ -2,10 +2,9 @@ "extends": "../tsconfig.base.json", "compilerOptions": { "paths": { - "$/sdk/*": ["../src/*"], - "$/vended/*": ["../vended_tools/*"] + "$/sdk/*": ["../src/*"] }, - "types": ["vite/client", "vitest/importMeta"] + "types": ["vite/client", "vitest/importMeta", "@types/node"] }, - "references": [{ "path": "../src/tsconfig.json" }, { "path": "../vended_tools/tsconfig.json" }] + "references": [{ "path": "../src/tsconfig.json" }] } diff --git a/vended_tools/tsconfig.json b/vended_tools/tsconfig.json deleted file mode 100644 index 2cd034ac..00000000 --- a/vended_tools/tsconfig.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "extends": "../tsconfig.base.json", - "compilerOptions": { - "types": ["@types/node"] - }, - "references": [{ "path": "../src/tsconfig.json" }] -} diff --git a/vitest.config.ts b/vitest.config.ts index f08c9f81..c703d575 100644 --- a/vitest.config.ts +++ b/vitest.config.ts @@ -10,9 +10,9 @@ const __dirname = path.dirname(fileURLToPath(import.meta.url)) // Conditionally exclude bash tool from coverage on Windows // since tests are skipped on Windows (bash not available) -const coverageExclude = ['src/**/__tests__/**', 'src/**/__fixtures__/**', 'vended_tools/**/__tests__/**'] +const coverageExclude = ['src/**/__tests__/**', 'src/**/__fixtures__/**', 'src/vended-tools/**/__tests__/**'] if (process.platform === 'win32') { - coverageExclude.push('vended_tools/bash/**') + coverageExclude.push('src/vended-tools/bash/**') } const getAwsCredentials: BrowserCommand<[], AwsCredentialIdentity> = async ({ testPath, provider }) => { @@ -35,7 +35,7 @@ export default defineConfig({ projects: [ { test: { - include: ['src/**/__tests__/**/*.test.ts', 'vended_tools/**/__tests__/**/*.test.ts'], + include: ['src/**/__tests__/**/*.test.ts', 'src/vended-tools/**/__tests__/**/*.test.ts'], includeSource: ['src/**/*.{js,ts}'], name: { label: 'unit-node', color: 'green' }, typecheck: { @@ -47,8 +47,8 @@ export default defineConfig({ }, { test: { - include: ['src/**/__tests__/**/*.test.ts', 'vended_tools/**/__tests__/**/*.test.ts'], - exclude: ['vended_tools/file_editor/**/*.test.ts', 'vended_tools/bash/**/*.test.ts'], + include: ['src/**/__tests__/**/*.test.ts'], + exclude: ['src/vended-tools/file_editor/**/*.test.ts', 'src/vended-tools/bash/**/*.test.ts'], name: { label: 'unit-browser', color: 'cyan' }, browser: { enabled: true, @@ -66,7 +66,7 @@ export default defineConfig({ test: { alias: { '$/sdk': path.resolve(__dirname, './src'), - '$/vended': path.resolve(__dirname, './vended_tools'), + '$/vended': path.resolve(__dirname, './src/vended-tools'), }, include: ['tests_integ/**/*.test.ts'], exclude: ['tests_integ/**/*.browser.test.ts'], @@ -83,7 +83,7 @@ export default defineConfig({ test: { alias: { '$/sdk': path.resolve(__dirname, './src'), - '$/vended': path.resolve(__dirname, './vended_tools'), + '$/vended': path.resolve(__dirname, './src/vended-tools'), }, include: ['tests_integ/**/*.browser.test.ts'], name: { label: 'integ-browser', color: 'yellow' }, @@ -117,7 +117,7 @@ export default defineConfig({ coverage: { provider: 'v8', reporter: ['text', 'json', 'html'], - include: ['src/**/*.{ts,js}', 'vended_tools/**/*.{ts,js}'], + include: ['src/**/*.{ts,js}', 'src/vended-tools/**/*.{ts,js}'], exclude: coverageExclude, thresholds: { lines: 80,