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
This commit is contained in:
yangzheli 2026-04-10 08:35:07 +08:00 committed by GitHub
parent 6572fa5b75
commit f88970985a
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 42 additions and 6 deletions

View File

@ -212,6 +212,13 @@ export function useThreadStream({
onCreated(meta) { onCreated(meta) {
handleStreamStart(meta.thread_id); handleStreamStart(meta.thread_id);
setOnStreamThreadId(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) { onLangChainEvent(event) {
if (event.event === "on_tool_end") { if (event.event === "on_tool_end") {
@ -528,7 +535,7 @@ export function useThreads(
limit: 50, limit: 50,
sortBy: "updated_at", sortBy: "updated_at",
sortOrder: "desc", sortOrder: "desc",
select: ["thread_id", "updated_at", "values", "context"], select: ["thread_id", "updated_at", "values", "metadata"],
}, },
) { ) {
const apiClient = getAPIClient(); const apiClient = getAPIClient();

View File

@ -31,3 +31,24 @@ void test("uses provided context when pathOfThread is called with a thread id",
"/workspace/agents/ops%20agent/chats/thread-123", "/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",
);
});

View File

@ -4,10 +4,10 @@ import type { AgentThread, AgentThreadContext } from "./types";
type ThreadRouteTarget = type ThreadRouteTarget =
| string | string
| Pick<AgentThread, "thread_id" | "context">
| { | {
thread_id: string; thread_id: string;
context?: Pick<AgentThreadContext, "agent_name"> | null; context?: Pick<AgentThreadContext, "agent_name"> | null;
metadata?: Record<string, unknown> | null;
}; };
export function pathOfThread( export function pathOfThread(
@ -15,10 +15,18 @@ export function pathOfThread(
context?: Pick<AgentThreadContext, "agent_name"> | null, context?: Pick<AgentThreadContext, "agent_name"> | null,
) { ) {
const threadId = typeof thread === "string" ? thread : thread.thread_id; const threadId = typeof thread === "string" ? thread : thread.thread_id;
const agentName = let agentName: string | undefined;
typeof thread === "string" if (typeof thread === "string") {
? context?.agent_name agentName = context?.agent_name;
: thread.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 return agentName
? `/workspace/agents/${encodeURIComponent(agentName)}/chats/${threadId}` ? `/workspace/agents/${encodeURIComponent(agentName)}/chats/${threadId}`