From 1a699df0560627a47510f06fb1a013a9ae580fcd Mon Sep 17 00:00:00 2001 From: Brian Love Date: Fri, 8 May 2026 14:13:12 -0700 Subject: [PATCH 1/5] fix(examples-chat-angular): palette dropdowns honor signal value on initial render --- .../chat/angular/src/app/shell/control-palette.component.html | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/chat/angular/src/app/shell/control-palette.component.html b/examples/chat/angular/src/app/shell/control-palette.component.html index 961bc0952..b993e6afa 100644 --- a/examples/chat/angular/src/app/shell/control-palette.component.html +++ b/examples/chat/angular/src/app/shell/control-palette.component.html @@ -37,7 +37,7 @@ Model @@ -46,7 +46,7 @@ Effort From eb141aa4ee91317a867f2807384dff423f287e4f Mon Sep 17 00:00:00 2001 From: Brian Love Date: Fri, 8 May 2026 14:13:36 -0700 Subject: [PATCH 2/5] fix(examples-chat-python): request reasoning.summary='auto' --- examples/chat/python/src/graph.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/examples/chat/python/src/graph.py b/examples/chat/python/src/graph.py index 784d0b8e8..865f6c4dc 100644 --- a/examples/chat/python/src/graph.py +++ b/examples/chat/python/src/graph.py @@ -49,7 +49,12 @@ async def generate(state: State) -> dict: # Honor the client's effort selection when present; default to # 'minimal' so first-token latency stays low for unconfigured callers. effort = state.get("reasoning_effort") or "minimal" - kwargs["reasoning"] = {"effort": effort} + # `summary='auto'` instructs the OpenAI Responses API to emit + # summary text inside the reasoning block (otherwise the block + # arrives with an empty `summary: []` and the chat UI has nothing + # to render). The adapter's `extractReasoning` reads either the + # legacy `block.text` field or the modern `block.summary[].text`. + kwargs["reasoning"] = {"effort": effort, "summary": "auto"} llm = ChatOpenAI(**kwargs) messages = [SystemMessage(content=SYSTEM_PROMPT)] + state["messages"] response = await llm.ainvoke(messages) From b4ec5dee02ddc88c5831c137bf2171ffa7d89656 Mon Sep 17 00:00:00 2001 From: Brian Love Date: Fri, 8 May 2026 14:16:18 -0700 Subject: [PATCH 3/5] fix(langgraph): extractReasoning reads OpenAI summary items --- .../internals/stream-manager.bridge.spec.ts | 29 +++++++++++++++++++ .../lib/internals/stream-manager.bridge.ts | 14 +++++++++ 2 files changed, 43 insertions(+) diff --git a/libs/langgraph/src/lib/internals/stream-manager.bridge.spec.ts b/libs/langgraph/src/lib/internals/stream-manager.bridge.spec.ts index b445477bb..66c885455 100644 --- a/libs/langgraph/src/lib/internals/stream-manager.bridge.spec.ts +++ b/libs/langgraph/src/lib/internals/stream-manager.bridge.spec.ts @@ -1029,6 +1029,35 @@ describe('stream-manager.bridge — reasoning extraction', () => { ])).toBe('hidden'); }); + it('extractReasoning pulls text from OpenAI Responses API summary items', () => { + const content = [ + { + type: 'reasoning', + summary: [ + { type: 'summary_text', text: 'First thought. ' }, + { type: 'summary_text', text: 'Second thought.' }, + ], + }, + { type: 'text', text: 'Visible answer' }, + ]; + expect(extractReasoning(content)).toBe('First thought. Second thought.'); + }); + + it('extractReasoning ignores summary items missing text', () => { + const content = [ + { + type: 'reasoning', + summary: [ + { type: 'summary_text', text: 'Kept. ' }, + { type: 'summary_text' }, + null, + { text: 'Also kept.' }, + ], + }, + ]; + expect(extractReasoning(content)).toBe('Kept. Also kept.'); + }); + it('accumulateReasoning returns "" for two empty inputs', () => { expect(accumulateReasoning(undefined, undefined)).toBe(''); expect(accumulateReasoning('', '')).toBe(''); diff --git a/libs/langgraph/src/lib/internals/stream-manager.bridge.ts b/libs/langgraph/src/lib/internals/stream-manager.bridge.ts index 5ec8a9674..b777e43a7 100644 --- a/libs/langgraph/src/lib/internals/stream-manager.bridge.ts +++ b/libs/langgraph/src/lib/internals/stream-manager.bridge.ts @@ -964,8 +964,22 @@ function extractReasoning(content: unknown): string { const rec = block as Record; const t = rec['type']; if (t === 'reasoning' || t === 'thinking') { + // Direct text field — Anthropic-style "thinking" blocks and + // some LangChain-shaped reasoning blocks land here. const text = rec['text']; if (typeof text === 'string') out += text; + // OpenAI Responses API: when `reasoning.summary='auto'` was + // requested, reasoning blocks carry a `summary` array of + // `{type: 'summary_text', text: '...'}` items. Concatenate + // their texts in order. + const summary = rec['summary']; + if (Array.isArray(summary)) { + for (const item of summary) { + if (item == null || typeof item !== 'object') continue; + const itemText = (item as Record)['text']; + if (typeof itemText === 'string') out += itemText; + } + } } } return out; From 8c16cd43b12a828477a1c2d254245087c6849b47 Mon Sep 17 00:00:00 2001 From: Brian Love Date: Fri, 8 May 2026 14:09:52 -0700 Subject: [PATCH 4/5] docs(spec): canonical examples/chat smoke fixes A+B Two small bug fixes surfaced by the live Chrome smoke pass: A) Palette dropdowns (Model + Effort) show first option instead of signal/persisted value on initial render. Cause: `` elements (Model and Effort) with property-bound values: + +```html + +``` + +When the page loads (or reloads), the user sees the *first* option highlighted (`gpt-5`, `minimal (fast)`) regardless of what the signal/persisted state actually is. The bug is that `[value]` on a ``'s `[value]`. Angular sets `selected` on each option during the same change-detection pass that populates the `@for`, so the matching option is always selected on first paint. + +```html + +``` + +Same fix for the Effort dropdown. Two-line change. The `[value]` binding on ``'s `[value]` binding. Angular generally handles both — the ` + @for (opt of modelOptions(); track opt.value) { + + } + + +``` + +Replace the `