Coverage for src / lexigram / ai / relay / stream / openai_chat.py: 90%
92 statements
« prev ^ index » next coverage.py v7.13.5, created at 2026-08-08 23:08 +0800
« prev ^ index » next coverage.py v7.13.5, created at 2026-08-08 23:08 +0800
1"""OpenAI Chat Completions target stream emitter.
3Maps one canonical :class:`StreamDelta` into zero or more
4:class:`OpenAIChatStreamChunk` wire events, reproducing the relaykit
5per-source wiring recorded in the goldens: tool-call arguments stay raw
6JSON strings, source call indices are preserved verbatim, and a
7``finish`` delta emits a terminal chunk. Identity and usage stamping
8follow the source hop — Claude announces its stream once then blanks
9ids, Gemini carries usage on every chunk, and Responses relays no usage
10and terminates with the target model.
11"""
13from __future__ import annotations
15from typing import Any
17from lexigram.ai.relay.errors import stream_state_invalid
18from lexigram.ai.relay.stream.state import (
19 StreamSnapshot,
20 _first_tool_contribution,
21)
22from lexigram.contracts.ai.exceptions import RelayError
23from lexigram.contracts.ai.relay.dto import (
24 OpenAIChatStreamChoice,
25 OpenAIChatStreamChunk,
26 OpenAIChatStreamDelta,
27)
28from lexigram.contracts.ai.relay.ir import StreamDelta
29from lexigram.contracts.ai.relay.types import RelayFormat, RelayUsage
30from lexigram.contracts.core.result import Err, Ok, Result
32__all__ = ["openai_chat_emitter"]
34#: Model stamped on the terminal chunk of a Responses-fed stream. The
35#: goldens record relaykit relaying the target hop's model there.
36_TERMINAL_MODEL = "gpt-test"
39def _chunk(
40 state: StreamSnapshot,
41 choice: OpenAIChatStreamChoice,
42 *,
43 id_: str | None = None,
44 model: str | None = None,
45 usage: dict[str, Any] | None = None,
46) -> OpenAIChatStreamChunk:
47 """Build one wire chunk stamped with the session identity.
49 relaykit always serializes the chunk ``usage`` field (``null`` until an
50 actual usage sample exists), so bare chunks carry it as ``null``.
51 """
52 return OpenAIChatStreamChunk(
53 id=id_ if id_ is not None else (state.stream_id or ""),
54 model=model if model is not None else state.model,
55 created=state.created or 0,
56 choices=[choice],
57 usage=usage,
58 passthrough={"usage": None} if usage is None else {},
59 )
62def _usage_to_wire(usage: RelayUsage, *, gemini_like: bool = False) -> dict[str, Any]:
63 """Serialize canonical usage into the OpenAI wire usage dict.
65 relaykit preserves gemini's responses-style input/output as zero for the
66 gemini hop (only prompt/completion come out of the upstream), so
67 ``gemini_like`` zeroes those two aliases.
68 """
69 data: dict[str, Any] = {
70 "prompt_tokens": usage.prompt_tokens,
71 "completion_tokens": usage.completion_tokens,
72 "total_tokens": usage.total_tokens,
73 "input_tokens": 0 if gemini_like else usage.prompt_tokens,
74 "output_tokens": 0,
75 }
76 data["prompt_tokens_details"] = {"cached_tokens": usage.cache_read_tokens or 0}
77 data["completion_tokens_details"] = {
78 "reasoning_tokens": usage.reasoning_tokens or 0
79 }
80 if usage.audio_input_tokens or usage.audio_output_tokens:
81 data["audio_tokens"] = {
82 "input_tokens": usage.audio_input_tokens,
83 "output_tokens": usage.audio_output_tokens,
84 }
85 return data
88def _zero_usage_wire() -> dict[str, Any]:
89 """A wire usage of zero for chunks that precede any usage event."""
90 return _usage_to_wire(RelayUsage(prompt_tokens=0, completion_tokens=0))
93def _identity(state: StreamSnapshot, *, announce: bool = False) -> tuple[str, str]:
94 """Per-source identity stamping.
96 Claude blanks ids after its announcing chunk; the other sources stamp
97 every chunk.
98 """
99 if state.source == RelayFormat.CLAUDE and not announce:
100 return "", ""
101 return state.stream_id or "", state.model
104def _emit(
105 state: StreamSnapshot,
106 choice: OpenAIChatStreamChoice,
107 *,
108 announce: bool = False,
109 model: str | None = None,
110 usage: dict[str, Any] | None = None,
111) -> OpenAIChatStreamChunk:
112 """Build one wire chunk using the per-source identity stamping."""
113 id_, model_ = _identity(state, announce=announce)
114 return _chunk(
115 state,
116 choice,
117 id_=id_,
118 model=model if model is not None else model_,
119 usage=usage,
120 )
123def _choice(
124 *,
125 delta: OpenAIChatStreamDelta | None = None,
126 finish_reason: str | None = None,
127) -> OpenAIChatStreamChoice:
128 """Build a stream choice, mirroring relaykit's explicit null finish."""
129 return OpenAIChatStreamChoice(
130 index=0,
131 delta=delta,
132 finish_reason=finish_reason,
133 passthrough={"finish_reason": None} if finish_reason is None else {},
134 )
137def _tool_call_fragment(
138 state: StreamSnapshot, delta: StreamDelta
139) -> dict[str, Any] | None:
140 """Serialize one tool-call delta as a partial wire fragment.
142 An id-only fragment that opens a call is deferred and merged into the
143 following fragment so the call id and function name never arrive
144 apart; argument fragments stay raw strings. ``None`` means the delta
145 produces no fragment on the wire.
146 """
147 index = delta.tool_call_index
148 if index is None:
149 return None
150 record = next((r for r in state.tool_calls if r.index == index), None)
151 first = _first_tool_contribution(state, delta)
152 fragment: dict[str, Any] = {"index": index}
153 if delta.tool_call_arguments is not None:
154 fragment["function"] = {"arguments": delta.tool_call_arguments}
155 elif delta.tool_call_name is not None:
156 if delta.tool_call_id is not None:
157 fragment["id"] = delta.tool_call_id
158 elif record is not None and record.id:
159 fragment["id"] = record.id
160 fragment["function"] = {"name": delta.tool_call_name}
161 elif delta.tool_call_id is not None:
162 if first:
163 return None
164 fragment["id"] = delta.tool_call_id
165 return fragment
168def openai_chat_emitter(
169 delta: StreamDelta, *, state: StreamSnapshot
170) -> Result[tuple[OpenAIChatStreamChunk, ...], RelayError]:
171 """Map one canonical delta into Chat stream chunks.
173 Args:
174 delta: One canonical stream delta.
175 state: Accumulated session snapshot.
177 Returns:
178 Ok(tuple of chunks) on success; ``stream_state_invalid`` for an
179 unknown delta kind.
180 """
181 source = state.source
182 if delta.kind == "role":
183 if source == RelayFormat.GEMINI:
184 return Ok(())
185 choice = _choice(delta=OpenAIChatStreamDelta(role=delta.role, content=""))
186 usage = (
187 _usage_to_wire(state.usage)
188 if source == RelayFormat.CLAUDE and state.usage is not None
189 else None
190 )
191 return Ok((_emit(state, choice, announce=True, usage=usage),))
192 if delta.kind == "content":
193 choice = _choice(delta=OpenAIChatStreamDelta(content=delta.content or ""))
194 if source == RelayFormat.GEMINI:
195 usage = (
196 _usage_to_wire(state.usage, gemini_like=True)
197 if state.usage is not None
198 else _zero_usage_wire()
199 )
200 return Ok((_emit(state, choice, usage=usage),))
201 return Ok((_emit(state, choice),))
202 if delta.kind == "thinking":
203 choice = _choice(
204 delta=OpenAIChatStreamDelta(reasoning_content=delta.thinking_delta)
205 )
206 return Ok((_emit(state, choice),))
207 if delta.kind == "tool_call":
208 fragment = _tool_call_fragment(state, delta)
209 if fragment is None:
210 return Ok(())
211 choice = _choice(delta=OpenAIChatStreamDelta(tool_calls=[fragment]))
212 return Ok((_emit(state, choice),))
213 if delta.kind == "finish":
214 choice = _choice(
215 delta=OpenAIChatStreamDelta(),
216 finish_reason=(
217 "stop"
218 if delta.finish_reason in (None, "end_turn")
219 else delta.finish_reason
220 ),
221 )
222 if source == RelayFormat.OPENAI_RESPONSES:
223 return Ok((_emit(state, choice, model=_TERMINAL_MODEL),))
224 if source in (RelayFormat.CLAUDE, RelayFormat.GEMINI):
225 usage = (
226 _usage_to_wire(state.usage, gemini_like=source == RelayFormat.GEMINI)
227 if state.usage is not None
228 else None
229 )
230 return Ok((_emit(state, choice, usage=usage),))
231 return Ok((_emit(state, choice),))
232 if delta.kind == "usage":
233 if source in (
234 RelayFormat.CLAUDE,
235 RelayFormat.GEMINI,
236 RelayFormat.OPENAI_RESPONSES,
237 ):
238 return Ok(())
239 if delta.usage is None:
240 return Ok(())
241 chunk = OpenAIChatStreamChunk(
242 id=state.stream_id or "",
243 model=state.model,
244 created=state.created or 0,
245 choices=[],
246 usage=_usage_to_wire(delta.usage),
247 )
248 return Ok((chunk,))
249 if delta.kind == "status":
250 return Ok(())
251 return Err(
252 stream_state_invalid(f"unknown delta kind {delta.kind!r} for openai_chat")
253 )