Skip to content

feat: add dual esm + cjs build with shim guard, updated exports, and lambda support #333

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 4 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions .changeset/odd-islands-melt.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
---
'@openai/agents-extensions': patch
'@openai/agents-realtime': patch
'@openai/agents-openai': patch
'@openai/agents-core': patch
'@openai/agents': patch
---

Dual ESM + CJS builds with import.meta shim guard and Lambda smoke-tests, plus patch bumps for all packages.
221 changes: 191 additions & 30 deletions eslint.config.mjs
Original file line number Diff line number Diff line change
@@ -1,52 +1,213 @@
// export default {
// parser: '@typescript-eslint/parser',
// plugins: ['@typescript-eslint', 'unused-imports', 'prettier'],
// rules: {
// 'no-unused-vars': 'off',
// 'prettier/prettier': 'error',
// 'unused-imports/no-unused-imports': 'error',
// },
// root: true,
// };

import eslint from '@eslint/js';
// import someOtherConfig from 'eslint-config-other-configuration-that-enables-formatting-rules';
import prettierConfig from 'eslint-config-prettier';
import tseslint from 'typescript-eslint';
import { globalIgnores } from 'eslint/config';

export default tseslint.config(
// Ignore build outputs and large example folders
globalIgnores([
'**/dist/**',
'**/dist-cjs/**',
'**/node_modules/**',
'**/docs/.astro/**',
'examples/realtime-next/**',
'examples/realtime-demo/**',
'examples/nextjs/**',
'integration-tests//**',
'examples/tools/**',
'integration-tests/**',
// Docs code snippets: not intended to pass lint as runtime code
'examples/docs/**',
'packages/**/test/**/*.cjs',
'packages/**/test/**/*.cjs.map',
]),

// Base + TS + Prettier
eslint.configs.recommended,
tseslint.configs.recommended,
prettierConfig,
[
{
rules: {
'@typescript-eslint/no-explicit-any': 'off',
'@typescript-eslint/no-unused-vars': [
'error',
{
argsIgnorePattern: '^_',
varsIgnorePattern: '^_',
caughtErrorsIgnorePattern: '^_',
},
],

// Repo-wide TS rules
{
rules: {
'@typescript-eslint/no-explicit-any': 'off',
'@typescript-eslint/no-unused-vars': [
'error',
{
argsIgnorePattern: '^_',
varsIgnorePattern: '^_',
caughtErrorsIgnorePattern: '^_',
},
],
},
},

// Allow CommonJS in .cjs files (configs, tests, scripts, CJS entrypoints)
{
files: ['**/*.cjs'],
languageOptions: {
sourceType: 'script', // treat as CommonJS
ecmaVersion: 'latest',
globals: {
// Node/CJS
require: 'readonly',
module: 'readonly',
exports: 'readonly',
__dirname: 'readonly',
__filename: 'readonly',
process: 'readonly',
console: 'readonly',
Buffer: 'readonly',
setTimeout: 'readonly',
clearTimeout: 'readonly',
setImmediate: 'readonly',
structuredClone: 'readonly',
URL: 'readonly',
AbortController: 'readonly',
// Some runtimes/polyfills supply these even in CJS contexts
Response: 'readonly',
},
},
{
files: ['examples/docs/**'],
rules: {
'@typescript-eslint/no-unused-vars': 'off',
rules: {
'@typescript-eslint/no-require-imports': 'off',
// Helpful if these plugins are present:
'import/no-commonjs': 'off',
'n/no-missing-require': 'off',
'n/no-unsupported-features/es-syntax': 'off',
'unicorn/prefer-module': 'off',
// Some CJS files intentionally use expression statements
'@typescript-eslint/no-unused-expressions': 'off',
},
},

// Tests override: Node + web-like globals used/mocked in CJS tests
{
files: ['**/test/**/*.cjs'],
languageOptions: {
sourceType: 'script',
ecmaVersion: 'latest',
globals: {
// Node-ish
global: 'readonly',
process: 'readonly',
require: 'readonly',
module: 'readonly',
exports: 'readonly',
console: 'readonly',
setTimeout: 'readonly',
clearTimeout: 'readonly',
setImmediate: 'readonly',
Buffer: 'readonly',
URL: 'readonly',
AbortController: 'readonly',

// Web APIs commonly mocked/polyfilled in tests
Event: 'readonly',
EventTarget: 'readonly',
MessageEvent: 'readonly',
TextEncoder: 'readonly',
FormData: 'readonly',
fetch: 'readonly',
navigator: 'readonly',
document: 'readonly',
window: 'readonly',
atob: 'readonly',
btoa: 'readonly',
Response: 'readonly',
RTCPeerConnection: 'readonly',

// Vitest-style test globals
describe: 'readonly',
it: 'readonly',
test: 'readonly',
expect: 'readonly',
beforeAll: 'readonly',
afterAll: 'readonly',
beforeEach: 'readonly',
afterEach: 'readonly',
vi: 'readonly',
},
},
],
rules: {
'@typescript-eslint/no-require-imports': 'off',
},
},

// Source CJS that target browser-like environments (e.g., realtime WebRTC)
{
files: ['**/src/**/*.cjs'],
languageOptions: {
sourceType: 'script',
ecmaVersion: 'latest',
globals: {
// Node
require: 'readonly',
module: 'readonly',
exports: 'readonly',
process: 'readonly',
console: 'readonly',
setTimeout: 'readonly',
clearTimeout: 'readonly',
setImmediate: 'readonly',
Buffer: 'readonly',
structuredClone: 'readonly',
URL: 'readonly',
AbortController: 'readonly',
// Web
Event: 'readonly',
EventTarget: 'readonly',
MessageEvent: 'readonly',
TextEncoder: 'readonly',
FormData: 'readonly',
fetch: 'readonly',
navigator: 'readonly',
document: 'readonly',
window: 'readonly',
atob: 'readonly',
btoa: 'readonly',
RTCPeerConnection: 'readonly',
CustomEvent: 'readonly',
crypto: 'readonly',
Response: 'readonly',
},
},
},

// Shims often intentionally shadow/process globals; relax specific rules there
{
files: [
'**/src/shims/**/*.cjs',
'**/src/shims/**/*.js',
'**/src/shims/**/*.ts',
'docs/src/scripts/**/*.cjs', // docs script: allow redefining __filename/__dirname
],
rules: {
'no-redeclare': 'off',
},
},

// Lambda test folder uses CommonJS
{
files: ['lambda-test/**'],
languageOptions: {
sourceType: 'script',
globals: {
require: 'readonly',
module: 'readonly',
exports: 'readonly',
process: 'readonly',
},
},
rules: {
'@typescript-eslint/no-require-imports': 'off',
},
},

// Declaration files: don't flag private class members as unused in .d.ts
{
files: ['**/*.d.ts'],
rules: {
'no-unused-private-class-members': 'off',
'@typescript-eslint/no-unused-vars': 'off',
},
},
);
20 changes: 20 additions & 0 deletions integration-tests/esm-import.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { describe, test, beforeAll, expect } from 'vitest';
import { execa as execaBase } from 'execa';

const execa = execaBase({ cwd: './integration-tests/esm-import' });

describe('ESM import', () => {
beforeAll(async () => {
// Remove existing modules to ensure a clean install.
console.log('[esm-import] Removing node_modules');
await execa`rm -rf node_modules`;
console.log('[esm-import] Installing dependencies');
await execa`npm install`;
}, 60000);

test('should import @openai/agents under Node 22', async () => {
const { exitCode } =
await execa`npx --yes node@22 -e "import('@openai/agents');"`;
expect(exitCode).toBe(0);
});
});
2 changes: 2 additions & 0 deletions integration-tests/esm-import/.npmrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
@openai:registry=http://localhost:4873
package-lock=false
7 changes: 7 additions & 0 deletions integration-tests/esm-import/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"private": true,
"type": "module",
"dependencies": {
"@openai/agents": "latest"
}
}
20 changes: 20 additions & 0 deletions integration-tests/lambda-local.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { describe, test, expect, beforeAll } from 'vitest';
import { execa as execaBase } from 'execa';

const execa = execaBase({ cwd: './integration-tests/lambda-local' });

describe('lambda-local', () => {
beforeAll(async () => {
// Remove existing modules to ensure a clean install.
console.log('[lambda-local] Removing node_modules');
await execa`rm -rf node_modules`;
console.log('[lambda-local] Installing dependencies');
await execa`npm install`;
}, 60000);

test('should return a 200 response', async () => {
const { stdout } =
await execa`npx --yes lambda-local -l index.cjs -h handler -e event.json`;
expect(stdout).toContain('"statusCode": 200');
});
});
2 changes: 2 additions & 0 deletions integration-tests/lambda-local/.npmrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
@openai:registry=http://localhost:4873
package-lock=false
1 change: 1 addition & 0 deletions integration-tests/lambda-local/event.json
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{}
10 changes: 10 additions & 0 deletions integration-tests/lambda-local/index.cjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
const { Agent } = require('@openai/agents');

exports.handler = async () => {
// Instantiate the agent to ensure the import succeeds.
new Agent({ name: 'Test Agent', instructions: 'Say hello.' });
return {
statusCode: 200,
body: 'ok',
};
};
7 changes: 7 additions & 0 deletions integration-tests/lambda-local/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"private": true,
"dependencies": {
"@openai/agents": "latest",
"lambda-local": "^2.2.0"
}
}
4 changes: 3 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,9 @@
"scripts": {
"clean": "tsc-multi --clean",
"prebuild": "pnpm -F @openai/* -r prebuild",
"build": "tsc-multi",
"build:esm": "tsc-multi",
"build:cjs": "tsc-multi --config tsc-multi.cjs.json",
"build": "tsc-multi && tsc-multi --config tsc-multi.cjs.json",
"postbuild": "pnpm -r -F @openai/* bundle",
"packages:dev": "tsc-multi --watch",
"docs:dev": "pnpm -F docs dev",
Expand Down
Loading