From f88970985a6732f54ee5835ef67bca6fd2d057fe Mon Sep 17 00:00:00 2001 From: yangzheli <43645580+yangzheli@users.noreply.github.com> Date: Fri, 10 Apr 2026 08:35:07 +0800 Subject: [PATCH] fix(frontend): replace invalid "context" select field with "metadata" in threads.search (#2053) * fix(frontend): replace invalid "context" select field with "metadata" in threads.search The LangGraph API server does not support "context" as a select field for threads/search, causing a 422 Unprocessable Entity error introduced by commit 60e0abf (#1771). - Replace "context" with "metadata" in the default select list - Persist agent_name into thread metadata on creation so search results carry the agent identity - Update pathOfThread() to fall back to metadata.agent_name when context is unavailable from search results - Add regression tests for metadata-based agent routing Fixes #2037 Made-with: Cursor * fix: apply Copilot suggestions * style: fix the lint error --- frontend/src/core/threads/hooks.ts | 9 ++++++++- frontend/src/core/threads/utils.test.ts | 21 +++++++++++++++++++++ frontend/src/core/threads/utils.ts | 18 +++++++++++++----- 3 files changed, 42 insertions(+), 6 deletions(-) diff --git a/frontend/src/core/threads/hooks.ts b/frontend/src/core/threads/hooks.ts index af9b80f54..9292ac12b 100644 --- a/frontend/src/core/threads/hooks.ts +++ b/frontend/src/core/threads/hooks.ts @@ -212,6 +212,13 @@ export function useThreadStream({ onCreated(meta) { handleStreamStart(meta.thread_id); setOnStreamThreadId(meta.thread_id); + if (context.agent_name && !isMock) { + void getAPIClient() + .threads.update(meta.thread_id, { + metadata: { agent_name: context.agent_name }, + }) + .catch(() => ({})); + } }, onLangChainEvent(event) { if (event.event === "on_tool_end") { @@ -528,7 +535,7 @@ export function useThreads( limit: 50, sortBy: "updated_at", sortOrder: "desc", - select: ["thread_id", "updated_at", "values", "context"], + select: ["thread_id", "updated_at", "values", "metadata"], }, ) { const apiClient = getAPIClient(); diff --git a/frontend/src/core/threads/utils.test.ts b/frontend/src/core/threads/utils.test.ts index c107c4e2b..9d7f8b437 100644 --- a/frontend/src/core/threads/utils.test.ts +++ b/frontend/src/core/threads/utils.test.ts @@ -31,3 +31,24 @@ void test("uses provided context when pathOfThread is called with a thread id", "/workspace/agents/ops%20agent/chats/thread-123", ); }); + +void test("uses agent chat route when thread metadata has agent_name", () => { + assert.equal( + pathOfThread({ + thread_id: "thread-456", + metadata: { agent_name: "coder" }, + }), + "/workspace/agents/coder/chats/thread-456", + ); +}); + +void test("prefers context.agent_name over metadata.agent_name", () => { + assert.equal( + pathOfThread({ + thread_id: "thread-789", + context: { agent_name: "from-context" }, + metadata: { agent_name: "from-metadata" }, + }), + "/workspace/agents/from-context/chats/thread-789", + ); +}); diff --git a/frontend/src/core/threads/utils.ts b/frontend/src/core/threads/utils.ts index 246a17782..901f1d07f 100644 --- a/frontend/src/core/threads/utils.ts +++ b/frontend/src/core/threads/utils.ts @@ -4,10 +4,10 @@ import type { AgentThread, AgentThreadContext } from "./types"; type ThreadRouteTarget = | string - | Pick | { thread_id: string; context?: Pick | null; + metadata?: Record | null; }; export function pathOfThread( @@ -15,10 +15,18 @@ export function pathOfThread( context?: Pick | null, ) { const threadId = typeof thread === "string" ? thread : thread.thread_id; - const agentName = - typeof thread === "string" - ? context?.agent_name - : thread.context?.agent_name; + let agentName: string | undefined; + if (typeof thread === "string") { + agentName = context?.agent_name; + } else { + agentName = thread.context?.agent_name; + if (!agentName) { + const metaAgent = thread.metadata?.agent_name; + if (typeof metaAgent === "string") { + agentName = metaAgent; + } + } + } return agentName ? `/workspace/agents/${encodeURIComponent(agentName)}/chats/${threadId}`