Skip to content

Fix/sdk UI#88

Open
ankushchhabradelta4infotech-ai wants to merge 13 commits intoYourGPT:feat/generative-ui-v2from
ankushchhabradelta4infotech-ai:fix/sdk-ui
Open

Fix/sdk UI#88
ankushchhabradelta4infotech-ai wants to merge 13 commits intoYourGPT:feat/generative-ui-v2from
ankushchhabradelta4infotech-ai:fix/sdk-ui

Conversation

@ankushchhabradelta4infotech-ai
Copy link
Copy Markdown
Contributor

Description

Fixes stream handling bugs introduced by the generative-ui-v2 merge (bc77df3). The new streamMode: 'single-turn' config broke tool attachment, duplicate rendering, and stale execution card behavior.

Changes

  • Fix tool-only turns not resetting streamState in message:end — caused next turn's tools to attach to previous message
  • Fix insertChainParentId incorrectly parenting tool results under the current streaming message in single-turn mode
  • Skip role: "tool" from done.messages in single-turn — already in streamState.toolResults, re-inserting created duplicates
  • Restrict liveExecutions to last assistant message only — prevented current-turn tools appearing on old bubbles
  • Filter unmatched executions to pending/executing only — stopped stale cards bleeding into new streaming message
  • Null-safe message filter in render loop to prevent crashes

Type of Change

  • Bug fix (non-breaking change that fixes an issue)

Testing

  • I've tested this locally
  • I've added/updated tests
  • All existing tests pass

Checklist

  • My code follows the project's style guidelines
  • I've updated the documentation (if needed)
  • I've added tests that prove my fix/feature works
  • New and existing tests pass locally

Screenshots (if applicable)

N/A — stream behavior fixes, no visual diff to capture.

…into fix/sdk-ui

# Conflicts:
#	packages/copilot-sdk/src/chat/classes/AbstractChat.ts
…vior

- 'multi-step' (default) — one bubble per server agent iteration (OpenAI/LiteLLM style), default unchanged
- 'single-turn' — all iterations collapsed into one bubble per user turn (Vercel AI SDK / Claude.ai style)

Usage: <CopilotProvider streamMode="single-turn" />

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- AbstractChat: check toolResults.size in message:end so tool-only turns
  reset streamState and don't attach next turn's tools to previous message
- AbstractChat: in single-turn mode parent insertChainParentId from message
  before the streaming message, not from streamState.messageId
- AbstractChat: skip role:tool messages from done.messages in single-turn
  (already in streamState.toolResults, re-inserting creates duplicates)
- connected-chat: restrict liveExecutions to last assistant message only;
  prefer metadata.toolExecutions for historical messages
- connected-chat: filter unmatched executions to pending/executing only
  so stale completed cards don't bleed into new streaming message
- connected-chat: null-safe message filter to prevent render crashes

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@vercel
Copy link
Copy Markdown

vercel Bot commented Apr 17, 2026

@ankushchhabradelta4infotech-ai is attempting to deploy a commit to the Delta4 Infotech Team on Vercel.

A member of the Team first needs to authorize it.

@ankushchhabradelta4infotech-ai
Copy link
Copy Markdown
Contributor Author

Changes

  • AbstractChat.ts — check toolResults.size in message:end so tool-only turns reset streamState:
// Before
if (!this.streamState.content) {
// After
if (!this.streamState.content && (this.streamState.toolResults?.size ?? 0) === 0) {
  • AbstractChat.ts — in single-turn, parent insertChainParentId from message before stream:
// Before
let insertChainParentId: string | undefined = this.streamState?.messageId;
// After
if (this.config.streamMode === "single-turn" && this.streamState) {
  const allMsgs = this._allMessages();
  const streamIdx = allMsgs.findIndex((m) => m.id === this.streamState!.messageId);
  insertChainParentId = streamIdx > 0 ? allMsgs[streamIdx - 1].id : undefined;
} else {
  insertChainParentId = this.streamState?.messageId;
}
  • AbstractChat.ts — skip role: "tool" from done.messages in single-turn:
if (this.config.streamMode === "single-turn" && msg.role === "tool") continue;
  • connected-chat.tsx — restrict liveExecutions to last assistant message; prefer metadata.toolExecutions for historical:
const isLastMsg = m.id === [...messages].reverse().find(msg => msg.role === "assistant")?.id;
if (!isLastMsg && savedMeta?.length > 0) {
  messageToolExecutions = savedMeta;
} else {
  const liveExecutions = isLastMsg ? toolExecutions.filter(...) : [];
  ...
}
  • connected-chat.tsx — only attach unmatched executions that are actively running:
// Before
(exec) => !allMatchedIds.has(exec.id)
// After
(exec) => !allMatchedIds.has(exec.id) &&
  (exec.status === "executing" || exec.status === "pending" || exec.approvalStatus === "required")
  • connected-chat.tsx — null-safe message filter:
// Before
.filter((m) => {
// After
.filter((m): m is NonNullable<typeof m> => {
  if (!m) return false;

Previously request.systemPrompt || this.config.systemPrompt meant a
client-sent prompt could silently override the server-configured one,
breaking generativeUISystemPrompt and any server-side prompt setup.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@ankushchhabradelta4infotech-ai
Copy link
Copy Markdown
Contributor Author

Changes

runtime.ts

Fix 1 — Server systemPrompt overridden by client request
request.systemPrompt || this.config.systemPrompt meant a client-sent prompt silently replaced the server-configured one, breaking generativeUISystemPrompt().

// Before — client wins
systemPrompt: request.systemPrompt || this.config.systemPrompt
// After — server config wins
systemPrompt: this.config.systemPrompt ?? request.systemPrompt

Applied in 3 places (lines 275, 1004, 1421).


GenUIFrame.tsx

Fix 2 — Chart.js and iframe background ignoring app theme
GenUI iframes rendered with hardcoded colors, ignoring the host app's CSS variables (dark/light mode, brand colors). Chart.js used default black backgrounds on transparent canvases.

Changes:

  • Added themeVars?: Record<string, string> prop — pass getComputedStyle(document.documentElement) vars
  • Theme injected via postMessage({ action: 'theme', vars }) into iframe :root — not baked into srcDoc so shell stays static
  • Chart.js defaults updated on theme receive: Chart.defaults.color, borderColor, legend label color
  • Transparent canvas background enforced (canvas { background: transparent })
  • Height polling changed from single 30ms to [30, 150, 400, 800]ms — catches async Chart.js renders

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants