Skip to content

@effect/ai-openrouter streamText fails at end of stream #6116

@nickbreaton

Description

@nickbreaton

What version of Effect is running?

3.19.19

What steps can reproduce the bug?

Any call to model.streamText when using OpenRouterLanguageModel layer appears to result in an error.

Reproducible example

https://effect.website/play#76acbd7ee284

import { LanguageModel, Prompt } from "@effect/ai"
import { OpenRouterClient, OpenRouterLanguageModel } from "@effect/ai-openrouter"
import { FetchHttpClient } from "@effect/platform"
import { NodeRuntime } from "@effect/platform-node"
import { Config, ConfigProvider, Effect, Layer, Stream } from "effect"

const prompt = Prompt.make([
  { role: "user", content: [{ type: "text", text: "Tell me a one line joke" }] }
])

const program = Effect.gen(function*() {
  const model = yield* LanguageModel.LanguageModel

  yield* model.streamText({ prompt }).pipe(
    Stream.filter((part) => part.type === "text-delta"),
    Stream.runForEach((part) => Effect.log("!!! PARSED !!!", part.delta))
  )
})

const layer = OpenRouterLanguageModel.layer({ model: "anthropic/claude-haiku-4.5" }).pipe(
  Layer.provide(OpenRouterClient.layerConfig({ apiKey: Config.redacted("OPEN_ROUTER_API_KEY") })),
  Layer.provide(Layer.setConfigProvider(ConfigProvider.fromEnv())),
  Layer.provide(FetchHttpClient.layer)
)

program.pipe(
  Effect.provide(layer),
  NodeRuntime.runMain
)

What is the expected behavior?

The stream completes without error.

What do you see instead?

The stream is successfully parsed until it terminates, the following error occurs:

 MalformedOutput: { "module": "OpenRouterLanguageModel", "method": "makeResponse", "description": "Received response with no valid choices", "cause": undefined }

Using the Effect playground, I'm able to able to inspect the SSE response via Chrome devtools network tab:

Image
: OPENROUTER PROCESSING

: OPENROUTER PROCESSING

data: {"id":"gen-1772933731-Xt9lXqMjuIyE3SnQavae","object":"chat.completion.chunk","created":1772933731,"model":"anthropic/claude-4.5-haiku-20251001","provider":"Amazon Bedrock","choices":[{"index":0,"delta":{"content":"Why don't scientists trust atoms?","role":"assistant"},"finish_reason":null,"native_finish_reason":null}]}

data: {"id":"gen-1772933731-Xt9lXqMjuIyE3SnQavae","object":"chat.completion.chunk","created":1772933731,"model":"anthropic/claude-4.5-haiku-20251001","provider":"Amazon Bedrock","choices":[{"index":0,"delta":{"content":" Because they make up everything!","role":"assistant"},"finish_reason":null,"native_finish_reason":null}]}

data: {"id":"gen-1772933731-Xt9lXqMjuIyE3SnQavae","object":"chat.completion.chunk","created":1772933731,"model":"anthropic/claude-4.5-haiku-20251001","provider":"Amazon Bedrock","choices":[{"index":0,"delta":{"content":"","role":"assistant"},"finish_reason":"stop","native_finish_reason":"stop"}]}

data: {"id":"gen-1772933731-Xt9lXqMjuIyE3SnQavae","object":"chat.completion.chunk","created":1772933731,"model":"anthropic/claude-4.5-haiku-20251001","provider":"Amazon Bedrock","choices":[],"usage":{"prompt_tokens":13,"completion_tokens":16,"total_tokens":29,"cost":0.000093,"is_byok":false,"prompt_tokens_details":{"cached_tokens":0,"cache_write_tokens":0,"audio_tokens":0,"video_tokens":0},"cost_details":{"upstream_inference_cost":0.000093,"upstream_inference_prompt_cost":0.000013,"upstream_inference_completions_cost":0.00008},"completion_tokens_details":{"reasoning_tokens":0,"image_tokens":0,"audio_tokens":0}}}

data: [DONE]

Additional information

Also reported in Discord: https://discord.com/channels/795981131316985866/1338871274398679130/threads/1479636410310066377

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions