Skip to content

Use getReader for stream shape checks#34

Merged
robert-j-y merged 2 commits intoOpenRouterTeam:mainfrom
w0nche0l:codex/getreader-stream-shape-check
May 1, 2026
Merged

Use getReader for stream shape checks#34
robert-j-y merged 2 commits intoOpenRouterTeam:mainfrom
w0nche0l:codex/getreader-stream-shape-check

Conversation

@w0nche0l
Copy link
Copy Markdown

@w0nche0l w0nche0l commented May 1, 2026

Summary

Refactors isEventStream function to check for getReader instead of toReadableStream.

It also updates the non-streaming response guard to use the same stream-readability check.

Why check for getReader() instead of toReadableStream()

This function gets used to check what gets passed into ReusableReadableStream. It later calls:

this.sourceReader = this.sourceStream.getReader();

That means the actual runtime contract this typecheck is: “can the value passed downstream be invoked with getReader()?”

toReadableStream() seems to be a hallucination? I haven't found anything in the OpenRouter SDKs that either calls or provides toReadableStream().

new ReusableReadableStream(apiResult.value);

So a value that only has toReadableStream() but does not itself have getReader() would pass the old guard and then fail later when ReusableReadableStream tries to read it.

Testing

The unit tests cover the two response shapes that matter for this change:

  • A real ReadableStream whose constructor name is not EventStream, matching bundled/minified runtimes such as OpenNext on Cloudflare Workers.
  • An adapter-shaped object that only exposes toReadableStream() and does not expose getReader(), verifying it is rejected before reaching ReusableReadableStream.

Validation commands:

pnpm --filter @openrouter/agent lint
pnpm --filter @openrouter/agent typecheck
pnpm --filter @openrouter/agent test

I also verified this against a production Cloudflare Worker:

  1. Build and pack @openrouter/agent from this PR branch.
  2. Install the generated tarball into a minimal OpenNext Cloudflare Worker app using next@16.2.4, @opennextjs/cloudflare@1.19.5, and wrangler@4.87.0.
  3. Confirm the compiled installed package contains the ReadableStream / getReader() guard and no constructor-name or toReadableStream() branch.
  4. Deploy the worker with opennextjs-cloudflare deploy.
  5. Set a real OPENROUTER_API_KEY Worker secret.
  6. Call the deployed smoke route:
curl https://<worker-subdomain>/api/agent-smoke

Expected response:

{"text":"ok","object":"response"}

Observed result from the deployed Worker:

HTTP/2 200
{"text":"ok","object":"response"}

@robert-j-y robert-j-y self-requested a review May 1, 2026 19:26
@robert-j-y
Copy link
Copy Markdown
Collaborator

Recommended path: move forward with this PR rather than #33.

This version checks for getReader(), which matches the runtime contract used by ReusableReadableStream, and it also updates isNonStreamingResponse to use the same stream-readability guard.

Before merge, I’d ask for two additions:

  1. Required by repo release convention: add a patch changeset for @openrouter/agent. The README says each PR that ships a user-visible change should include a changeset, and this is a bug fix.
  2. Strongly recommended before merge: add unit regression coverage under packages/agent/tests/unit for:
    • a real ReadableStream / stream-like response whose constructor name is not EventStream, covering bundled/minified runtimes like Cloudflare/OpenNext
    • an object that only exposes toReadableStream() but does not expose getReader(), so it can’t pass the guard and then fail later inside ReusableReadableStream

I locally validated the current PR with pnpm lint, pnpm typecheck, pnpm test, pnpm build, and pnpm test:e2e; all passed.

@w0nche0l
Copy link
Copy Markdown
Author

w0nche0l commented May 1, 2026

pushed unit tests and updated the PR description to address comments

Copy link
Copy Markdown
Collaborator

@robert-j-y robert-j-y left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks! looks good!

@robert-j-y robert-j-y merged commit ac3f82f into OpenRouterTeam:main May 1, 2026
4 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants