Skip to content
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
44 changes: 44 additions & 0 deletions src/io/source.test.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
import { afterEach, describe, expect, test } from "bun:test";
import { spawn } from "node:child_process";
import { once } from "node:events";
import { mkdtemp, rm, writeFile } from "node:fs/promises";
import { tmpdir } from "node:os";
import { join } from "node:path";
Expand Down Expand Up @@ -57,6 +59,48 @@ describe("SourceResolver bytes", () => {
expect(await resolver.resolveBytes("payload", "-")).toEqual(bytes);
});

test("waits for delayed process stdin under Node", async () => {
const build = await Bun.build({
entrypoints: [join(import.meta.dir, "source.ts")],
target: "node",
format: "cjs",
});
expect(build.success).toBe(true);
const directory = await mkdtemp(join(tmpdir(), "agentcore-source-node-"));
tempDirectories.push(directory);
const bundle = join(directory, "source.cjs");
await writeFile(bundle, await build.outputs[0]!.text());

const child = spawn(
"node",
[
"-e",
`
const { SourceResolver } = require(process.argv[1]);
new SourceResolver({ stdin: process.stdin }).resolveText("bearer-token", "-")
.then(value => process.stdout.write(value))
.catch(error => { console.error(error); process.exitCode = 1; });
`,
bundle,
],
{ stdio: ["pipe", "pipe", "pipe"] },
);
let output = "";
let errors = "";
child.stdout.on("data", (chunk) => {
output += chunk;
});
child.stderr.on("data", (chunk) => {
errors += chunk;
});
setTimeout(() => child.stdin.end("late value"), 150);

const [code] = await once(child, "close");
expect(code).toBe(0);
expect(errors).toBe("");
expect(output).toBe("late value");
});

test("rejects a second stdin consumer and identifies both options", async () => {
const resolver = new SourceResolver({ stdin: stdin(new TextEncoder().encode("value")) });

Expand Down
6 changes: 3 additions & 3 deletions src/io/source.ts
Original file line number Diff line number Diff line change
Expand Up @@ -85,10 +85,10 @@ export class SourceResolver {

// Importing Ink under Bun drains the process.stdin stream object before a
// headless handler can claim it, while file descriptor 0 still retains the
// bytes. Use a fresh non-closing stream for the real process input; injected
// AppIO streams continue through the ordinary testable path.
// bytes. Node can make fd 0 non-blocking when process.stdin is accessed, so
// use its stream directly there to wait for delayed input.
const source =
this.config.stdin === process.stdin
this.config.stdin === process.stdin && process.versions.bun !== undefined
? createReadStream("", { fd: 0, autoClose: false })
: this.config.stdin;
const stdin = this.config.signal ? addAbortSignal(this.config.signal, source) : source;
Expand Down
Loading