diff --git a/scripts/bedrock-runtime-adapter.js b/scripts/bedrock-runtime-adapter.mts similarity index 74% rename from scripts/bedrock-runtime-adapter.js rename to scripts/bedrock-runtime-adapter.mts index 1a024291deb..2e3de9e9f7f 100755 --- a/scripts/bedrock-runtime-adapter.js +++ b/scripts/bedrock-runtime-adapter.mts @@ -2,9 +2,7 @@ // SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. // SPDX-License-Identifier: Apache-2.0 -const { - startBedrockRuntimeAdapterFromEnv, -} = require("../dist/lib/inference/bedrock-runtime-adapter"); +import { startBedrockRuntimeAdapterFromEnv } from "../dist/lib/inference/bedrock-runtime-adapter.js"; try { startBedrockRuntimeAdapterFromEnv(); diff --git a/src/lib/inference/bedrock-runtime-adapter.test.ts b/src/lib/inference/bedrock-runtime-adapter.test.ts index f4dbe1587a6..38908c684fc 100644 --- a/src/lib/inference/bedrock-runtime-adapter.test.ts +++ b/src/lib/inference/bedrock-runtime-adapter.test.ts @@ -12,6 +12,7 @@ import { createOpenAiChatCompletion, streamOpenAiChatCompletion, } from "./bedrock-runtime-adapter"; +import { isLocalAdapterProcess } from "./local-adapter-lifecycle"; const servers: http.Server[] = []; @@ -382,6 +383,42 @@ describe("Bedrock Runtime OpenAI adapter", () => { expect(body.error.message).toContain("Could not load credentials"); }); + it("spawns the typed .mts launcher entrypoint", () => { + expect(__test.getAdapterScriptPath().endsWith("bedrock-runtime-adapter.mts")).toBe(true); + }); + + it("recognizes adapter processes launched from the old and new launcher filenames", () => { + const needle = __test.adapterProcessNeedle; + expect( + isLocalAdapterProcess( + 4321, + needle, + () => "node /opt/nemoclaw/scripts/bedrock-runtime-adapter.mts", + ), + ).toBe(true); + expect( + isLocalAdapterProcess( + 4321, + needle, + () => "node /opt/nemoclaw/scripts/bedrock-runtime-adapter.js", + ), + ).toBe(true); + expect( + isLocalAdapterProcess( + 4321, + needle, + () => "node /opt/nemoclaw/scripts/openrouter-runtime-adapter-entry.js", + ), + ).toBe(false); + expect( + isLocalAdapterProcess( + 4321, + needle, + () => "node /opt/nemoclaw/scripts/my-bedrock-runtime-adapter.mts", + ), + ).toBe(false); + }); + it("includes forwarded AWS environment in the adapter reuse hash", () => { const savedContainerCredentials = process.env.AWS_CONTAINER_CREDENTIALS_RELATIVE_URI; const savedSharedCredentials = process.env.AWS_SHARED_CREDENTIALS_FILE; diff --git a/src/lib/inference/bedrock-runtime-adapter.ts b/src/lib/inference/bedrock-runtime-adapter.ts index 83b8b91993b..36f3691f738 100644 --- a/src/lib/inference/bedrock-runtime-adapter.ts +++ b/src/lib/inference/bedrock-runtime-adapter.ts @@ -316,14 +316,21 @@ function loadPersistedPid(): number | null { return loadLocalAdapterPid(PID_PATH); } +const ADAPTER_LAUNCHER_BASENAMES = ["bedrock-runtime-adapter.mts", "bedrock-runtime-adapter.js"]; +const ADAPTER_PROCESS_NEEDLE = new RegExp( + `(?:^|[^A-Za-z0-9_.-])(?:${ADAPTER_LAUNCHER_BASENAMES.map((name) => + name.replaceAll(".", "\\."), + ).join("|")})(?:$|[^A-Za-z0-9_.-])`, +); + function isAdapterProcess(pid: number | null | undefined): boolean { - return isLocalAdapterProcess(pid, "bedrock-runtime-adapter.js", runCapture); + return isLocalAdapterProcess(pid, ADAPTER_PROCESS_NEEDLE, runCapture); } function killStaleAdapter(): void { killLocalAdapterPid({ pidPath: PID_PATH, - processNeedle: "bedrock-runtime-adapter.js", + processNeedle: ADAPTER_PROCESS_NEEDLE, run, runCapture, }); @@ -331,7 +338,7 @@ function killStaleAdapter(): void { function getAdapterScriptPath(): string { const scriptsDir = typeof SCRIPTS === "string" ? SCRIPTS : path.join(process.cwd(), "scripts"); - return path.join(scriptsDir, "bedrock-runtime-adapter.js"); + return path.join(scriptsDir, "bedrock-runtime-adapter.mts"); } function copyAwsEnv(extra: Record): void { @@ -478,4 +485,6 @@ export function getCompatibleAnthropicCredentialForBedrock(): string | null { export const __test = { adapterCredentialHash, + adapterProcessNeedle: ADAPTER_PROCESS_NEEDLE, + getAdapterScriptPath, }; diff --git a/src/lib/inference/local-adapter-lifecycle.ts b/src/lib/inference/local-adapter-lifecycle.ts index 66a9aef11e3..8aa2f81d31f 100644 --- a/src/lib/inference/local-adapter-lifecycle.ts +++ b/src/lib/inference/local-adapter-lifecycle.ts @@ -136,17 +136,21 @@ export function loadLocalAdapterPid(filePath: string): number | null { export function isLocalAdapterProcess( pid: number | null | undefined, - processNeedle: string, + processNeedle: string | RegExp, runCapture: RunCaptureFn, ): boolean { if (!Number.isInteger(pid) || !pid || pid <= 0) return false; - const cmdline = runCapture(["ps", "-p", String(pid), "-o", "args="], { ignoreError: true }); - return Boolean(String(cmdline || "").includes(processNeedle)); + const cmdline = String( + runCapture(["ps", "-p", String(pid), "-o", "args="], { ignoreError: true }) || "", + ); + return typeof processNeedle === "string" + ? cmdline.includes(processNeedle) + : processNeedle.test(cmdline); } export function killLocalAdapterPid(options: { pidPath: string; - processNeedle: string; + processNeedle: string | RegExp; run: RunFn; runCapture: RunCaptureFn; }): void { diff --git a/test/e2e/live/bedrock-runtime-compatible-anthropic.test.ts b/test/e2e/live/bedrock-runtime-compatible-anthropic.test.ts index 48da2c017aa..3eb845eca52 100644 --- a/test/e2e/live/bedrock-runtime-compatible-anthropic.test.ts +++ b/test/e2e/live/bedrock-runtime-compatible-anthropic.test.ts @@ -581,11 +581,13 @@ function stopBedrockAdapter(home: string): void { } } +const BEDROCK_ADAPTER_LAUNCHER_PATTERN = + /(?:^|[^A-Za-z0-9_.-])bedrock-runtime-adapter\.(?:mts|js)(?:$|[^A-Za-z0-9_.-])/; + function isBedrockAdapterProcess(pid: number): boolean { - const expectedScript = "bedrock-runtime-adapter.js"; try { const cmdline = fs.readFileSync(`/proc/${pid}/cmdline`, "utf8").replaceAll("\0", " "); - if (cmdline.includes(expectedScript)) return true; + if (BEDROCK_ADAPTER_LAUNCHER_PATTERN.test(cmdline)) return true; } catch { // Fall back to ps on platforms without procfs. } @@ -594,7 +596,7 @@ function isBedrockAdapterProcess(pid: number): boolean { encoding: "utf8", stdio: ["ignore", "pipe", "ignore"], }); - return ps.status === 0 && ps.stdout.includes(expectedScript); + return ps.status === 0 && BEDROCK_ADAPTER_LAUNCHER_PATTERN.test(ps.stdout); } async function restoreHostsFile( @@ -1307,6 +1309,11 @@ test("bedrock runtime compatible Anthropic endpoint routes through managed infer timeout: TEST_TIMEOUT_MS, }, async ({ artifacts, cleanup, host, sandbox, secrets, skip }) => { assertAgent(AGENT); + const shard = + process.env.GITHUB_ACTIONS === "true" + ? process.env.NEMOCLAW_E2E_SHARD + : (process.env.NEMOCLAW_E2E_SHARD ?? AGENT); + expect(shard).toBe(AGENT); validateSandboxName(SANDBOX_NAME); const home = fs.mkdtempSync(path.join(os.tmpdir(), "nemoclaw-bedrock-runtime-home-"));