Skip to content

Commit e848879

Browse files
committed
feat: improve error handling for stalled tool calls and adjust OAuth client caching logic
1 parent 76f0e61 commit e848879

2 files changed

Lines changed: 17 additions & 7 deletions

File tree

src/app/chat/[id]/page.tsx

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -229,14 +229,18 @@ function deriveChatMessages(
229229
if (msgToolCalls) {
230230
for (const tc of msgToolCalls) {
231231
const match = toolCalls.find((stc) => stc.call.id === tc.id);
232+
// If the run has stopped streaming and a tool still has no
233+
// resolved state, treat it as errored — otherwise it stays in
234+
// the "running" branch forever and the UI spinner never clears.
235+
const stalled = !isLoading && !match?.state;
232236
const toolEntry = {
233237
tool: tc.name,
234238
args: tc.args,
235239
result: match?.result?.content as string | undefined,
236240
status:
237241
match?.state === "pending"
238242
? ("running" as const)
239-
: match?.state === "error"
243+
: match?.state === "error" || stalled
240244
? ("error" as const)
241245
: match?.state === "completed"
242246
? ("complete" as const)

src/lib/agent-only/user-mcp-tools.ts

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,11 @@ interface CachedClient {
2525

2626
const cache = new Map<string, CachedClient>();
2727
const CACHE_TTL_MS = 5 * 60 * 1000;
28+
// OAuth clients use a much shorter TTL so a mid-session reconnect (new tokens
29+
// in the DB) is picked up quickly. We can't close an OAuth client right after
30+
// getTools() — its connection is what backs every subsequent tool invocation
31+
// in the same turn, and closing it produces "Not connected" errors.
32+
const OAUTH_CACHE_TTL_MS = 30 * 1000;
2833

2934
function configsSignature(configs: UserMcpServerConfig[]): string {
3035
return configs
@@ -121,13 +126,14 @@ export async function buildUserMcpToolsFromConfigs(
121126
return t;
122127
});
123128

124-
// Only cache the client when it's safe to. OAuth servers' auth state can
125-
// change between turns (user just connected via loopback flow); a cached
126-
// pre-connect client would mask the new tokens until TTL expires. So if
127-
// any config is OAuth, skip the cache entirely.
129+
// Cache so subsequent middleware invocations within the same turn (and
130+
// adjacent turns) reuse the live connection. OAuth servers get a shorter
131+
// TTL so a user's reconnect is picked up promptly without leaving the
132+
// current turn's tool calls dangling against a closed client.
128133
const hasOAuth = configs.some((c) => c.authType === 'oauth');
129-
if (prefixed.length > 0 && !hasOAuth) {
130-
cache.set(signature, { client, signature, expiresAt: now + CACHE_TTL_MS });
134+
if (prefixed.length > 0) {
135+
const ttl = hasOAuth ? OAUTH_CACHE_TTL_MS : CACHE_TTL_MS;
136+
cache.set(signature, { client, signature, expiresAt: now + ttl });
131137
} else {
132138
client.close().catch(() => {});
133139
}

0 commit comments

Comments
 (0)