Skip to content
Merged
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
12 changes: 6 additions & 6 deletions .github/workflows/build-lint-test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,22 +12,22 @@ jobs:
runs-on: ubuntu-latest
strategy:
matrix:
node-version: [20.x, 22.x, 24.x]
node-version: [22.23.4, 24.22.1]
steps:
- name: Checkout and setup environment
uses: MetaMask/action-checkout-and-setup@v3
with:
is-high-risk-environment: false
node-version: ${{ matrix.node-version }}
cache-node-modules: ${{ matrix.node-version == '24.x' }}
cache-node-modules: ${{ matrix.node-version == '24.22.1' }}

build:
name: Build
needs: prepare
runs-on: ubuntu-latest
strategy:
matrix:
node-version: [24.x]
node-version: [24.22.1]
steps:
- name: Checkout and setup environment
uses: MetaMask/action-checkout-and-setup@v3
Expand All @@ -49,7 +49,7 @@ jobs:
runs-on: ubuntu-latest
strategy:
matrix:
node-version: [24.x]
node-version: [24.22.1]
steps:
- name: Checkout and setup environment
uses: MetaMask/action-checkout-and-setup@v3
Expand Down Expand Up @@ -77,7 +77,7 @@ jobs:
runs-on: ubuntu-latest
strategy:
matrix:
node-version: [20.x, 22.x, 24.x]
node-version: [22.23.4, 24.22.1]
steps:
- name: Checkout and setup environment
uses: MetaMask/action-checkout-and-setup@v3
Expand All @@ -99,7 +99,7 @@ jobs:
runs-on: ubuntu-latest
strategy:
matrix:
node-version: [20.x, 22.x, 24.x]
node-version: [22.23.4, 24.22.1]
steps:
- name: Checkout and setup environment
uses: MetaMask/action-checkout-and-setup@v3
Expand Down
2 changes: 1 addition & 1 deletion .nvmrc
Original file line number Diff line number Diff line change
@@ -1 +1 @@
lts/*
24.22.1
14 changes: 8 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ This package wraps the Claude Agent SDK `query()` behind a provider adapter, nor

## Install

Requires Node.js 22.23.4 or newer on the 22.x line, or Node.js 24.22.1 or newer. Node.js 20 is no longer supported. These minimums include the [October 2026 security fixes](https://nodejs.org/en/blog/vulnerability/october-2026-security-releases).

```bash
npm install @metamask/agent-runner
```
Expand Down Expand Up @@ -83,7 +85,7 @@ By default the Claude adapter passes `settingSources: []` to the SDK for isolate

## Pi usage

Pi is an optional peer dependency. Claude-only installations keep their existing Node requirements and do not need pi installed. Pi execution requires Node.js 22.19.0 or newer and the supported SDK version:
Pi is an optional peer dependency. Claude-only installations do not need pi installed. Pi uses the same supported Node.js versions as the runner and requires the supported SDK version:

```sh
npm install @metamask/agent-runner @earendil-works/pi-coding-agent@0.83.0
Expand All @@ -103,7 +105,7 @@ const runner = createAgentRunner({
tools: ['read', 'bash', 'edit', 'write'],
maxTurns: 10,
},
sandbox: { type: 'docker', image: 'node:22-bookworm' },
sandbox: { type: 'docker', image: 'node:22.23.4-bookworm' },
});

const result = await runner.runAgent({
Expand Down Expand Up @@ -142,7 +144,7 @@ Pi emits the same normalized messages and telemetry as Claude. Reaching `maxTurn

### Pi sandbox and judge behavior

Docker installs pi 0.83.0 and copies the shared pi execution modules into the container. `bridge.sdkVersion`, if supplied, must match. With `bridge.install: false`, the runner performs no version check, so make sure the bridge directory already contains a compatible install. The Node version check applies inside the container, so a Node 20 host can still launch a supported pi container.
Docker installs pi 0.83.0 and copies the shared pi execution modules into the container. `bridge.sdkVersion`, if supplied, must match. With `bridge.install: false`, the runner performs no version check, so make sure the bridge directory already contains a compatible install. Use a supported, security-patched Node.js version on both the host and inside the container.

Pi's default forwarded environment is `LITELLM_BASE_URL`, `LITELLM_API_KEY`, `HTTP_PROXY`, `HTTPS_PROXY`, and `NO_PROXY`. An explicit `forwardEnv` list or `false` overrides it. Credentials are passed through the existing Docker environment-file mechanism, not serialized into query options. The existing Docker security caveats still apply: enabled tools can read credentials inside the container.

Expand Down Expand Up @@ -378,7 +380,7 @@ import { createAgentRunner } from '@metamask/agent-runner';
const runner = createAgentRunner({
sandbox: {
type: 'docker',
image: 'node:22-bookworm',
image: 'node:22.23.4-bookworm',
workspace: {
hostPath: process.cwd(),
containerPath: '/workspace',
Expand Down Expand Up @@ -547,8 +549,8 @@ createAgentRunner({
- The `docker` CLI must be on `PATH` and the user must be able to create
containers. Rootless Docker, Podman with a `docker` shim, and remote
daemons via `DOCKER_HOST` all work as long as the CLI obeys.
- The container image must include a Node.js runtime compatible with the
Claude Agent SDK (Node 20+). The bridge installs the SDK via `npm`, so
- The container image must include a supported, security-patched Node.js
runtime (22.23.4+ on 22.x, or 24.22.1+). The bridge installs the SDK via `npm`, so
`npm` must also be available (override with `bridge.nodeCommand` /
`bridge.npmCommand` if you ship a custom binary).
- The bridge runs `npm install` on every fresh container by default. For
Expand Down
14 changes: 8 additions & 6 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -57,8 +57,8 @@
"@langfuse/otel": "^5.3.0",
"@langfuse/tracing": "^5.3.0",
"@opentelemetry/api": "^1.9.1",
"@opentelemetry/resources": "^2.7.1",
"@opentelemetry/sdk-node": "^0.217.0"
"@opentelemetry/resources": "^2.9.0",
"@opentelemetry/sdk-node": "^0.220.0"
},
"devDependencies": {
"@arethetypeswrong/cli": "^0.15.3",
Expand All @@ -71,7 +71,7 @@
"@metamask/eslint-config-typescript": "^15.0.0",
"@metamask/eslint-config-vitest": "^15.0.0",
"@ts-bridge/cli": "^0.6.3",
"@types/node": "^18.18",
"@types/node": "^22.0.0",
"@typescript-eslint/utils": "^8.6.0",
"@vitest/coverage-istanbul": "^4.1.4",
"@vitest/eslint-plugin": "^1.1.4",
Expand All @@ -89,7 +89,7 @@
"typedoc": "^0.26.11",
"typescript": "~5.7.3",
"typescript-eslint": "^8.48.1",
"vite": "^8.0.8",
"vite": "^8.1.6",
"vitest": "^4.1.4"
},
"peerDependencies": {
Expand All @@ -101,10 +101,12 @@
}
},
"resolutions": {
"fflate": "0.8.2"
"fflate": "0.8.2",
"tinypool@npm:2.1.0": "2.1.2",
"undici@npm:8.5.0": "8.12.3"
},
"engines": {
"node": "^20 || ^22 || >=24"
"node": "^22.23.4 || >=24.22.1"
},
"packageManager": "yarn@4.16.0+sha256.ba05224324578801b9cc98170d64aa50b9a36733b440fb0942306da3fbbdc7d1",
"lavamoat": {
Expand Down
4 changes: 1 addition & 3 deletions src/adapters/pi/pi-adapter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -134,9 +134,7 @@ async function* runPi(
signal:
commandOptions?.signal === undefined
? config.signal
: // Pi execution requires Node >=22.19; Claude never uses this path.
// eslint-disable-next-line n/no-unsupported-features/node-builtins
AbortSignal.any([config.signal, commandOptions.signal]),
: AbortSignal.any([config.signal, commandOptions.signal]),
}),
}),
};
Expand Down
2 changes: 1 addition & 1 deletion src/adapters/pi/pi-runtime.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ let requests: Record<string, any>[];
let reply: Reply;
let hanging = false;

// The SDK itself requires Node >=22.19; Claude compatibility is tested on Node 20.
// The SDK itself requires Node >=22.19; integration runs on supported runtimes.
describe.skipIf(Number(process.versions.node.split('.')[0]) < 22)(
'pi integration',
() => {
Expand Down
4 changes: 1 addition & 3 deletions src/adapters/pi/pi-runtime.ts
Original file line number Diff line number Diff line change
Expand Up @@ -62,9 +62,7 @@ export function assertPiNodeVersion(version = process.versions.node): void {
.split('.')
.map(Number);
if (major < 22 || (major === 22 && minor < 19)) {
throw new Error(
'Pi requires Node.js >=22.19.0. Claude-only runs still support Node 20.',
);
throw new Error('Pi requires Node.js >=22.19.0.');
}
}

Expand Down
12 changes: 6 additions & 6 deletions src/runner.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -400,7 +400,7 @@ describe('createAgentRunner', () => {

it('passes the runner-level sandbox through to the adapter', async () => {
const { adapter, runCalls } = createSandboxAdapter();
const sandbox = dockerSandbox({ image: 'node:20' });
const sandbox = dockerSandbox({ image: 'node:22.23.4' });
const runner = createAgentRunner({ adapter, sandbox });

await runner.runAgent({ prompt: 'sandboxed run' });
Expand All @@ -409,34 +409,34 @@ describe('createAgentRunner', () => {
const runConfig = runCalls[0] as RunConfig;
expect(runConfig.sandbox).toStrictEqual({
type: 'docker',
image: 'node:20',
image: 'node:22.23.4',
});
});

it('merges the run-level sandbox over the runner-level default', async () => {
const { adapter, runCalls } = createSandboxAdapter();
const runner = createAgentRunner({
adapter,
sandbox: dockerSandbox({ image: 'node:20' }),
sandbox: dockerSandbox({ image: 'node:22.23.4' }),
});

await runner.runAgent({
prompt: 'sandboxed run',
sandbox: dockerSandbox({ image: 'node:22' }),
sandbox: dockerSandbox({ image: 'node:24.22.1' }),
});

const runConfig = runCalls[0] as RunConfig;
expect(runConfig.sandbox).toStrictEqual({
type: 'docker',
image: 'node:22',
image: 'node:24.22.1',
});
});

it('omits the sandbox when the run disables it with `false`', async () => {
const { adapter, runCalls } = createSandboxAdapter();
const runner = createAgentRunner({
adapter,
sandbox: dockerSandbox({ image: 'node:20' }),
sandbox: dockerSandbox({ image: 'node:22.23.4' }),
});

await runner.runAgent({ prompt: 'sandboxed run', sandbox: false });
Expand Down
2 changes: 1 addition & 1 deletion src/sandbox/docker/pi-integration.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ describe.skipIf(process.env.RUN_DOCKER_TESTS !== '1')(
defaultOptions: { model: 'test', tools: [] },
sandbox: {
type: 'docker',
image: 'node:22-bookworm',
image: 'node:22.23.4-bookworm',
workspace: false,
workdir: '/tmp',
forwardEnv: false,
Expand Down
4 changes: 1 addition & 3 deletions vitest.config.mts
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,7 @@ export default defineConfig({
watch: false,
include: ['src/**/*.test.ts'],
coverage: {
// Pi's SDK integration tests require Node >=22.19. Measure the full
// coverage contract on supported pi runtimes; still run Claude tests on 20.
enabled: Number(process.versions.node.split('.')[0]) >= 22,
enabled: true,
provider: 'istanbul',
include: [
'src/**/*.ts',
Expand Down
Loading
Loading