From ef2b59bd9f11e642d93497a10fe8f4c89d91d545 Mon Sep 17 00:00:00 2001 From: Roland Tannous Date: Fri, 15 May 2026 23:04:03 +0400 Subject: [PATCH] studio/chat: let user pick OpenAI container before first send MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Previously the picker silently no-op'd until the user sent the first message, because Dexie's ThreadRecord is only materialized inside the runtime-provider's `initialize` hook (assistant-ui's first-message callback). That kept users from binding a thread to an existing OpenAI container up front; they had to either send a message and risk the chat adapter auto-creating one, or accept the cross-thread inheritance default. - Export `ensureThreadRecord` from runtime-provider so other surfaces can materialize the row idempotently. - In OpenAICodeExecSection.onPick, await ensureThreadRecord before the update, with modelType="base" (the settings sheet that hosts this section is only rendered in single-thread mode). Behaviour after this commit: - New thread + user picks a container in the sidebar → thread row is created with that container_id; first send uses it, no auto-create. - New thread + user does nothing → row still absent; first send goes through the existing inherit/lazy-create path as before. - The auto-bind effect remains silent best-effort: it does not eagerly create the thread row, so it cannot pre-empt the user's pick on a fresh thread. --- .../components/openai-code-exec-section.tsx | 22 +++++++++++-------- .../src/features/chat/runtime-provider.tsx | 2 +- 2 files changed, 14 insertions(+), 10 deletions(-) diff --git a/studio/frontend/src/features/chat/components/openai-code-exec-section.tsx b/studio/frontend/src/features/chat/components/openai-code-exec-section.tsx index 4e8e59f003..08368e409c 100644 --- a/studio/frontend/src/features/chat/components/openai-code-exec-section.tsx +++ b/studio/frontend/src/features/chat/components/openai-code-exec-section.tsx @@ -44,6 +44,7 @@ import { import { db } from "../db"; import type { ExternalProviderConfig } from "../external-providers"; import { useLiveQuery } from "../db"; +import { ensureThreadRecord } from "../runtime-provider"; const AUTO_OPTION_VALUE = "__auto__"; const DEFAULT_TTL_MINUTES = 20; @@ -155,20 +156,23 @@ export function OpenAICodeExecSection({ const onPick = async (value: string) => { if (!activeThreadId) return; const next = value === AUTO_OPTION_VALUE ? null : value; - // Dexie's `update` returns the count of rows changed: 0 means the - // thread record is not yet in IndexedDB. That happens when the user - // is on a brand-new thread before sending the first message — the - // thread row is materialized on first send by the chat adapter. - // Without this guard the selection silently no-ops and the picker - // snaps back to "Auto-create per thread", which looks broken. + // The thread row is normally materialized on first send by the + // chat adapter. When the user picks a container before sending the + // first message, the row does not exist yet and Dexie's `update` + // returns 0 rows — the selection would silently no-op and the + // picker would snap back to "Auto-create per thread". Eagerly + // create the row so the user can pin a container up front. + // modelType "base" is correct here: the settings sheet that hosts + // this section is only rendered in single-thread mode, where the + // chat-page passes modelType="base" into ChatRuntimeProvider. try { + await ensureThreadRecord({ threadId: activeThreadId, modelType: "base" }); const affected = await db.threads.update(activeThreadId, { openaiCodeExecContainerId: next, }); if (affected === 0) { - toast.error( - "Send a message first to pin a container to this thread.", - ); + // Defensive: ensureThreadRecord should have written the row. + toast.error("Could not update thread."); } } catch (err) { toast.error( diff --git a/studio/frontend/src/features/chat/runtime-provider.tsx b/studio/frontend/src/features/chat/runtime-provider.tsx index 1133e759ac..b019fd2d4d 100644 --- a/studio/frontend/src/features/chat/runtime-provider.tsx +++ b/studio/frontend/src/features/chat/runtime-provider.tsx @@ -396,7 +396,7 @@ function toThreadMessage(m: MessageRecord): ThreadMessage { }; } -async function ensureThreadRecord({ +export async function ensureThreadRecord({ threadId, modelType, pairId,