| """SSE usage watcher β meters streaming responses by capturing the usage chunk. |
| |
| OpenAI-compatible streams (incl. OpenRouter :free) emit a final chunk carrying a |
| `usage` object when the client sets `stream_options.include_usage: true`. This |
| scanner watches the filtered byte stream as it passes through and extracts |
| (prompt_tokens, completion_tokens) from that final chunk. |
| |
| If the upstream doesn't emit usage (e.g. raw subnet nodes), tok_in/tok_out stay |
| 0 and the request is effectively unmetered β acceptable for MVP since upstreams |
| are free and we control the margin. |
| """ |
| from __future__ import annotations |
|
|
| import json |
|
|
|
|
| class UsageWatcher: |
| """Scan SSE bytes for the OpenAI usage object. Buffers across chunk boundaries.""" |
|
|
| def __init__(self): |
| self.tok_in = 0 |
| self.tok_out = 0 |
| self._buf = b"" |
|
|
| def feed(self, chunk: bytes) -> None: |
| """Scan one chunk of SSE bytes for a usage-bearing data line.""" |
| self._buf += chunk |
| while b"\n" in self._buf: |
| line, self._buf = self._buf.split(b"\n", 1) |
| self._scan_line(line) |
|
|
| def flush(self) -> None: |
| """Process any remaining buffered bytes after the stream ends.""" |
| if self._buf.strip(): |
| self._scan_line(self._buf) |
| self._buf = b"" |
|
|
| def _scan_line(self, line: bytes) -> None: |
| """Inspect one SSE line; if it's a data: line with usage, capture it.""" |
| line = line.strip() |
| if not line.startswith(b"data:"): |
| return |
| if b'"usage"' not in line: |
| return |
| payload = line[5:].strip() |
| if payload == b"[DONE]": |
| return |
| try: |
| obj = json.loads(payload) |
| except Exception: |
| return |
| if not isinstance(obj, dict): |
| return |
| u = obj.get("usage") |
| if isinstance(u, dict): |
| self.tok_in = int(u.get("prompt_tokens", self.tok_in)) |
| self.tok_out = int(u.get("completion_tokens", self.tok_out)) |
|
|
| @property |
| def has_usage(self) -> bool: |
| """True if we captured a usage object during the stream.""" |
| return self.tok_in > 0 or self.tok_out > 0 |
|
|