import { Meta } from '@storybook/addon-docs/blocks';

<Meta title="Docs/Recipes/Streaming (OpenRouter)" />

# Streaming from OpenRouter

[OpenRouter](https://openrouter.ai) exposes an OpenAI-compatible streaming API (Server-Sent Events). Wire it into the `submit` event:

> **Security:** never ship an API key to the browser. In production, point `fetch` at your own backend that proxies to OpenRouter and injects the key.

```js
chat.addEventListener('kc-submit', async (e) => {
  const text = e.detail.value.trim();
  if (!text) return;

  // 1. Show the user message
  const history = [...chat.messages, { id: crypto.randomUUID(), role: 'user', content: text }];
  chat.messages = history;
  chat.loading = true;

  // 2. Empty assistant placeholder to stream into
  const assistantId = crypto.randomUUID();
  chat.messages = [...history, { id: assistantId, role: 'assistant', content: '' }];

  // In production, replace this with your own proxy endpoint.
  const res = await fetch('https://openrouter.ai/api/v1/chat/completions', {
    method: 'POST',
    headers: {
      'Authorization': `Bearer ${OPENROUTER_API_KEY}`,
      'Content-Type': 'application/json',
    },
    body: JSON.stringify({
      model: 'anthropic/claude-sonnet-4',
      stream: true,
      messages: history.map((m) => ({ role: m.role, content: m.content })),
    }),
  });

  const reader = res.body.getReader();
  const decoder = new TextDecoder();
  let buffer = '';
  let answer = '';

  while (true) {
    const { value, done } = await reader.read();
    if (done) break;
    buffer += decoder.decode(value, { stream: true });

    const lines = buffer.split('\n');
    buffer = lines.pop();
    for (const line of lines) {
      const s = line.trim();
      if (!s.startsWith('data:')) continue;
      const payload = s.slice(5).trim();
      if (payload === '[DONE]') continue;
      try {
        const delta = JSON.parse(payload).choices?.[0]?.delta?.content;
        if (!delta) continue;
        answer += delta;
        chat.messages = chat.messages.map((m) =>
          m.id === assistantId ? { ...m, content: answer } : m
        );
      } catch { /* ignore non-JSON keep-alive lines */ }
    }
  }
  chat.loading = false;
});
```
