@agent-native/core 0.14.0 → 0.14.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/agent/engine/builder-engine.d.ts.map +1 -1
- package/dist/agent/engine/builder-engine.js +5 -1
- package/dist/agent/engine/builder-engine.js.map +1 -1
- package/dist/agent/run-loop-with-resume.d.ts +45 -0
- package/dist/agent/run-loop-with-resume.d.ts.map +1 -0
- package/dist/agent/run-loop-with-resume.js +121 -0
- package/dist/agent/run-loop-with-resume.js.map +1 -0
- package/dist/client/AssistantChat.d.ts.map +1 -1
- package/dist/client/AssistantChat.js +7 -44
- package/dist/client/AssistantChat.js.map +1 -1
- package/dist/client/use-chat-threads.d.ts +0 -6
- package/dist/client/use-chat-threads.d.ts.map +1 -1
- package/dist/client/use-chat-threads.js +40 -52
- package/dist/client/use-chat-threads.js.map +1 -1
- package/dist/server/agent-chat-plugin.d.ts.map +1 -1
- package/dist/server/agent-chat-plugin.js +36 -95
- package/dist/server/agent-chat-plugin.js.map +1 -1
- package/package.json +1 -1
|
@@ -16,12 +16,6 @@ export interface ChatThreadData {
|
|
|
16
16
|
createdAt: number;
|
|
17
17
|
updatedAt: number;
|
|
18
18
|
}
|
|
19
|
-
/**
|
|
20
|
-
* Key for the per-thread message cache in localStorage. AssistantChat reads
|
|
21
|
-
* this synchronously on mount so existing chats can hydrate from cache and
|
|
22
|
-
* paint immediately, then refreshes from the server in the background.
|
|
23
|
-
*/
|
|
24
|
-
export declare function getThreadCacheKey(threadId: string): string;
|
|
25
19
|
export declare function useChatThreads(apiUrl?: string, storageKey?: string): {
|
|
26
20
|
threads: ChatThreadSummary[];
|
|
27
21
|
activeThreadId: string;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"use-chat-threads.d.ts","sourceRoot":"","sources":["../../src/client/use-chat-threads.ts"],"names":[],"mappings":"AAGA,MAAM,WAAW,iBAAiB;IAChC,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,MAAM,CAAC;IAChB,YAAY,EAAE,MAAM,CAAC;IACrB,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,cAAc;IAC7B,EAAE,EAAE,MAAM,CAAC;IACX,UAAU,EAAE,MAAM,CAAC;IACnB,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,MAAM,CAAC;IAChB,UAAU,EAAE,MAAM,CAAC;IACnB,YAAY,EAAE,MAAM,CAAC;IACrB,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;CACnB;
|
|
1
|
+
{"version":3,"file":"use-chat-threads.d.ts","sourceRoot":"","sources":["../../src/client/use-chat-threads.ts"],"names":[],"mappings":"AAGA,MAAM,WAAW,iBAAiB;IAChC,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,MAAM,CAAC;IAChB,YAAY,EAAE,MAAM,CAAC;IACrB,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,cAAc;IAC7B,EAAE,EAAE,MAAM,CAAC;IACX,UAAU,EAAE,MAAM,CAAC;IACnB,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,MAAM,CAAC;IAChB,UAAU,EAAE,MAAM,CAAC;IACnB,YAAY,EAAE,MAAM,CAAC;IACrB,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;CACnB;AAID,wBAAgB,cAAc,CAC5B,MAAM,SAA+C,EACrD,UAAU,CAAC,EAAE,MAAM;;;;iCAgJF,MAAM,KAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC;uBAgBV,MAAM;uBAK/B,MAAM;2BAqFA,MAAM,KAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC;yBA5D1C,MAAM,QACJ;QACJ,UAAU,EAAE,MAAM,CAAC;QACnB,KAAK,EAAE,MAAM,CAAC;QACd,OAAO,EAAE,MAAM,CAAC;QAChB,YAAY,CAAC,EAAE,MAAM,CAAC;KACvB;8BA8Bc,MAAM,WAAW,MAAM,KAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC;2BAiEnD,MAAM,KAAG,OAAO,CAAC,iBAAiB,EAAE,CAAC;;sBAvI9C,MAAM;EAwKd"}
|
|
@@ -1,15 +1,6 @@
|
|
|
1
1
|
import { useState, useEffect, useCallback, useRef } from "react";
|
|
2
2
|
import { agentNativePath } from "./api-path.js";
|
|
3
3
|
const ACTIVE_THREAD_KEY = "agent-chat-active-thread";
|
|
4
|
-
const THREAD_DATA_CACHE_PREFIX = "agent-chat-thread-cache:";
|
|
5
|
-
/**
|
|
6
|
-
* Key for the per-thread message cache in localStorage. AssistantChat reads
|
|
7
|
-
* this synchronously on mount so existing chats can hydrate from cache and
|
|
8
|
-
* paint immediately, then refreshes from the server in the background.
|
|
9
|
-
*/
|
|
10
|
-
export function getThreadCacheKey(threadId) {
|
|
11
|
-
return `${THREAD_DATA_CACHE_PREFIX}${threadId}`;
|
|
12
|
-
}
|
|
13
4
|
export function useChatThreads(apiUrl = agentNativePath("/_agent-native/agent-chat"), storageKey) {
|
|
14
5
|
const activeThreadKey = storageKey
|
|
15
6
|
? `${ACTIVE_THREAD_KEY}:${storageKey}`
|
|
@@ -20,23 +11,20 @@ export function useChatThreads(apiUrl = agentNativePath("/_agent-native/agent-ch
|
|
|
20
11
|
// of state because the consumer reads it inside the render path and we
|
|
21
12
|
// never need to re-render when the set changes.
|
|
22
13
|
const newlyCreatedRef = useRef(new Set());
|
|
14
|
+
// Restore the saved active thread synchronously on mount so the chat shell
|
|
15
|
+
// can paint immediately. We do NOT synthesize a fresh UUID here when no
|
|
16
|
+
// saved id exists — that races with the agent run's server-side thread
|
|
17
|
+
// create and was producing ghost threads that disappeared from history.
|
|
18
|
+
// First-time users see the brief loading state while threads load.
|
|
23
19
|
const [activeThreadId, setActiveThreadId] = useState(() => {
|
|
24
20
|
if (typeof window === "undefined")
|
|
25
21
|
return null;
|
|
26
22
|
try {
|
|
27
|
-
|
|
28
|
-
if (saved)
|
|
29
|
-
return saved;
|
|
23
|
+
return localStorage.getItem(activeThreadKey);
|
|
30
24
|
}
|
|
31
|
-
catch {
|
|
32
|
-
|
|
33
|
-
// can paint on first render instead of after a network round-trip.
|
|
34
|
-
if (typeof crypto !== "undefined" && crypto.randomUUID) {
|
|
35
|
-
const id = crypto.randomUUID();
|
|
36
|
-
newlyCreatedRef.current.add(id);
|
|
37
|
-
return id;
|
|
25
|
+
catch {
|
|
26
|
+
return null;
|
|
38
27
|
}
|
|
39
|
-
return null;
|
|
40
28
|
});
|
|
41
29
|
const [isLoading, setIsLoading] = useState(true);
|
|
42
30
|
const fetchedRef = useRef(false);
|
|
@@ -76,7 +64,10 @@ export function useChatThreads(apiUrl = agentNativePath("/_agent-native/agent-ch
|
|
|
76
64
|
}, [apiUrl]);
|
|
77
65
|
// Persist a client-generated thread to the server in the background.
|
|
78
66
|
// Optimistically adds it to the local thread list so callers can render
|
|
79
|
-
// immediately
|
|
67
|
+
// immediately. POST /threads is idempotent server-side (returns the
|
|
68
|
+
// existing thread on UNIQUE conflict instead of 500), so client retries
|
|
69
|
+
// and races with the agent run's `persistSubmittedUserMessage` no longer
|
|
70
|
+
// wipe a freshly-created thread out of local state.
|
|
80
71
|
const persistNewThread = useCallback((id) => {
|
|
81
72
|
const now = Date.now();
|
|
82
73
|
const optimistic = {
|
|
@@ -94,9 +85,8 @@ export function useChatThreads(apiUrl = agentNativePath("/_agent-native/agent-ch
|
|
|
94
85
|
body: JSON.stringify({ id }),
|
|
95
86
|
})
|
|
96
87
|
.then(async (res) => {
|
|
97
|
-
if (!res.ok)
|
|
98
|
-
|
|
99
|
-
}
|
|
88
|
+
if (!res.ok)
|
|
89
|
+
return;
|
|
100
90
|
const created = (await res
|
|
101
91
|
.json()
|
|
102
92
|
.catch(() => null));
|
|
@@ -105,38 +95,47 @@ export function useChatThreads(apiUrl = agentNativePath("/_agent-native/agent-ch
|
|
|
105
95
|
setThreads((prev) => prev.map((thread) => (thread.id === id ? created : thread)));
|
|
106
96
|
})
|
|
107
97
|
.catch(() => {
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
98
|
+
// Network blip — keep the optimistic row and let the next
|
|
99
|
+
// fetchThreads reconcile. Don't yank the thread out of local
|
|
100
|
+
// state: the agent's onRunPrepared will (idempotently) create
|
|
101
|
+
// it server-side anyway and we don't want to drop the user's
|
|
102
|
+
// active conversation just because POST /threads was flaky.
|
|
111
103
|
});
|
|
112
104
|
}, [apiUrl]);
|
|
113
|
-
// Initial load.
|
|
114
|
-
// first paint. The composer renders against the optimistic active thread
|
|
115
|
-
// we set up in useState above; this fetch just populates the history list
|
|
116
|
-
// and reconciles a stale saved active id.
|
|
105
|
+
// Initial load.
|
|
117
106
|
useEffect(() => {
|
|
118
107
|
if (fetchedRef.current)
|
|
119
108
|
return;
|
|
120
109
|
fetchedRef.current = true;
|
|
121
|
-
// Persist any thread we optimistically created during the initial render.
|
|
122
|
-
for (const id of newlyCreatedRef.current) {
|
|
123
|
-
persistNewThread(id);
|
|
124
|
-
}
|
|
125
110
|
(async () => {
|
|
111
|
+
setIsLoading(true);
|
|
126
112
|
const loadedThreads = await fetchThreads();
|
|
127
113
|
if (loadedThreads && loadedThreads.length > 0) {
|
|
128
114
|
const savedId = activeThreadIdRef.current;
|
|
129
|
-
// If the saved active thread isn't on the server
|
|
130
|
-
//
|
|
131
|
-
if (savedId
|
|
132
|
-
!newlyCreatedRef.current.has(savedId) &&
|
|
133
|
-
!loadedThreads.find((t) => t.id === savedId)) {
|
|
115
|
+
// If the saved active thread isn't on the server, fall back to the
|
|
116
|
+
// most recent so the user lands on real history instead of a ghost.
|
|
117
|
+
if (!savedId || !loadedThreads.find((t) => t.id === savedId)) {
|
|
134
118
|
setActiveThreadId(loadedThreads[0].id);
|
|
135
119
|
}
|
|
136
120
|
}
|
|
121
|
+
else if (!activeThreadIdRef.current) {
|
|
122
|
+
// No threads on server and no saved active id — create a fresh one
|
|
123
|
+
// so the composer has something to send into. This matches the
|
|
124
|
+
// pre-instant-paint behavior.
|
|
125
|
+
try {
|
|
126
|
+
const res = await fetch(`${apiUrl}/threads`, { method: "POST" });
|
|
127
|
+
if (res.ok) {
|
|
128
|
+
const thread = (await res.json());
|
|
129
|
+
newlyCreatedRef.current.add(thread.id);
|
|
130
|
+
setThreads([thread]);
|
|
131
|
+
setActiveThreadId(thread.id);
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
catch { }
|
|
135
|
+
}
|
|
137
136
|
setIsLoading(false);
|
|
138
137
|
})();
|
|
139
|
-
}, [fetchThreads,
|
|
138
|
+
}, [fetchThreads, apiUrl]);
|
|
140
139
|
const createThread = useCallback((preferredId) => {
|
|
141
140
|
// Generate ID client-side for instant UI response
|
|
142
141
|
const id = preferredId || crypto.randomUUID();
|
|
@@ -156,10 +155,6 @@ export function useChatThreads(apiUrl = agentNativePath("/_agent-native/agent-ch
|
|
|
156
155
|
});
|
|
157
156
|
}
|
|
158
157
|
catch { }
|
|
159
|
-
try {
|
|
160
|
-
localStorage.removeItem(getThreadCacheKey(id));
|
|
161
|
-
}
|
|
162
|
-
catch { }
|
|
163
158
|
setThreads((prev) => {
|
|
164
159
|
const next = prev.filter((t) => t.id !== id);
|
|
165
160
|
if (id === activeThreadId) {
|
|
@@ -176,13 +171,6 @@ export function useChatThreads(apiUrl = agentNativePath("/_agent-native/agent-ch
|
|
|
176
171
|
});
|
|
177
172
|
}, [apiUrl, activeThreadId, createThread]);
|
|
178
173
|
const saveThreadData = useCallback(async (id, data) => {
|
|
179
|
-
// Cache locally so the next mount of this thread can hydrate
|
|
180
|
-
// synchronously and skip the per-message restore skeleton. Quota errors
|
|
181
|
-
// (5–10MB cap) are swallowed — the thread just falls back to fetching.
|
|
182
|
-
try {
|
|
183
|
-
localStorage.setItem(getThreadCacheKey(id), data.threadData);
|
|
184
|
-
}
|
|
185
|
-
catch { }
|
|
186
174
|
try {
|
|
187
175
|
await fetch(`${apiUrl}/threads/${encodeURIComponent(id)}`, {
|
|
188
176
|
method: "PUT",
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"use-chat-threads.js","sourceRoot":"","sources":["../../src/client/use-chat-threads.ts"],"names":[],"mappings":"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","sourcesContent":["import { useState, useEffect, useCallback, useRef } from \"react\";\nimport { agentNativePath } from \"./api-path.js\";\n\nexport interface ChatThreadSummary {\n id: string;\n title: string;\n preview: string;\n messageCount: number;\n createdAt: number;\n updatedAt: number;\n}\n\nexport interface ChatThreadData {\n id: string;\n ownerEmail: string;\n title: string;\n preview: string;\n threadData: string;\n messageCount: number;\n createdAt: number;\n updatedAt: number;\n}\n\nconst ACTIVE_THREAD_KEY = \"agent-chat-active-thread\";\nconst THREAD_DATA_CACHE_PREFIX = \"agent-chat-thread-cache:\";\n\n/**\n * Key for the per-thread message cache in localStorage. AssistantChat reads\n * this synchronously on mount so existing chats can hydrate from cache and\n * paint immediately, then refreshes from the server in the background.\n */\nexport function getThreadCacheKey(threadId: string): string {\n return `${THREAD_DATA_CACHE_PREFIX}${threadId}`;\n}\n\nexport function useChatThreads(\n apiUrl = agentNativePath(\"/_agent-native/agent-chat\"),\n storageKey?: string,\n) {\n const activeThreadKey = storageKey\n ? `${ACTIVE_THREAD_KEY}:${storageKey}`\n : ACTIVE_THREAD_KEY;\n const [threads, setThreads] = useState<ChatThreadSummary[]>([]);\n\n // IDs we generated client-side this session — consumers use this to know\n // whether to skip the per-thread restore skeleton. Tracked by ref instead\n // of state because the consumer reads it inside the render path and we\n // never need to re-render when the set changes.\n const newlyCreatedRef = useRef<Set<string>>(new Set());\n\n const [activeThreadId, setActiveThreadId] = useState<string | null>(() => {\n if (typeof window === \"undefined\") return null;\n try {\n const saved = localStorage.getItem(activeThreadKey);\n if (saved) return saved;\n } catch {}\n // No saved thread — generate one synchronously so the chat shell + composer\n // can paint on first render instead of after a network round-trip.\n if (typeof crypto !== \"undefined\" && crypto.randomUUID) {\n const id = crypto.randomUUID();\n newlyCreatedRef.current.add(id);\n return id;\n }\n return null;\n });\n const [isLoading, setIsLoading] = useState(true);\n const fetchedRef = useRef(false);\n const activeThreadIdRef = useRef(activeThreadId);\n activeThreadIdRef.current = activeThreadId;\n\n // Persist active thread ID\n useEffect(() => {\n try {\n if (activeThreadId) {\n localStorage.setItem(activeThreadKey, activeThreadId);\n } else {\n localStorage.removeItem(activeThreadKey);\n }\n } catch {}\n }, [activeThreadId, activeThreadKey]);\n\n const fetchThreads = useCallback(async () => {\n try {\n const res = await fetch(`${apiUrl}/threads`);\n if (!res.ok) return;\n const data = await res.json();\n setThreads((prev) => {\n const loaded = (data.threads ?? []) as ChatThreadSummary[];\n // Preserve any optimistic threads we've created this session that\n // haven't shown up in the server list yet (POST still in-flight).\n const loadedIds = new Set(loaded.map((t) => t.id));\n const optimisticOnly = prev.filter(\n (t) => newlyCreatedRef.current.has(t.id) && !loadedIds.has(t.id),\n );\n return [...optimisticOnly, ...loaded];\n });\n return data.threads as ChatThreadSummary[];\n } catch {\n return undefined;\n }\n }, [apiUrl]);\n\n // Persist a client-generated thread to the server in the background.\n // Optimistically adds it to the local thread list so callers can render\n // immediately; rolls back on failure.\n const persistNewThread = useCallback(\n (id: string) => {\n const now = Date.now();\n const optimistic: ChatThreadSummary = {\n id,\n title: \"\",\n preview: \"\",\n messageCount: 0,\n createdAt: now,\n updatedAt: now,\n };\n setThreads((prev) =>\n prev.some((t) => t.id === id) ? prev : [optimistic, ...prev],\n );\n fetch(`${apiUrl}/threads`, {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\" },\n body: JSON.stringify({ id }),\n })\n .then(async (res) => {\n if (!res.ok) {\n throw new Error(`Thread create failed with ${res.status}`);\n }\n const created = (await res\n .json()\n .catch(() => null)) as ChatThreadSummary | null;\n if (!created) return;\n setThreads((prev) =>\n prev.map((thread) => (thread.id === id ? created : thread)),\n );\n })\n .catch(() => {\n setThreads((prev) => prev.filter((t) => t.id !== id));\n newlyCreatedRef.current.delete(id);\n setActiveThreadId((current) => (current === id ? null : current));\n });\n },\n [apiUrl],\n );\n\n // Initial load. Runs in the background — does NOT gate the consumer's\n // first paint. The composer renders against the optimistic active thread\n // we set up in useState above; this fetch just populates the history list\n // and reconciles a stale saved active id.\n useEffect(() => {\n if (fetchedRef.current) return;\n fetchedRef.current = true;\n\n // Persist any thread we optimistically created during the initial render.\n for (const id of newlyCreatedRef.current) {\n persistNewThread(id);\n }\n\n (async () => {\n const loadedThreads = await fetchThreads();\n if (loadedThreads && loadedThreads.length > 0) {\n const savedId = activeThreadIdRef.current;\n // If the saved active thread isn't on the server (and isn't one we\n // just created client-side), fall back to the most recent.\n if (\n savedId &&\n !newlyCreatedRef.current.has(savedId) &&\n !loadedThreads.find((t) => t.id === savedId)\n ) {\n setActiveThreadId(loadedThreads[0].id);\n }\n }\n setIsLoading(false);\n })();\n }, [fetchThreads, persistNewThread]);\n\n const createThread = useCallback(\n (preferredId?: string): Promise<string | null> => {\n // Generate ID client-side for instant UI response\n const id = preferredId || crypto.randomUUID();\n newlyCreatedRef.current.add(id);\n setActiveThreadId(id);\n persistNewThread(id);\n return Promise.resolve(id);\n },\n [persistNewThread],\n );\n\n const isNewThread = useCallback(\n (id: string) => newlyCreatedRef.current.has(id),\n [],\n );\n\n const switchThread = useCallback((id: string) => {\n setActiveThreadId(id);\n }, []);\n\n const removeThread = useCallback(\n async (id: string) => {\n try {\n await fetch(`${apiUrl}/threads/${encodeURIComponent(id)}`, {\n method: \"DELETE\",\n });\n } catch {}\n try {\n localStorage.removeItem(getThreadCacheKey(id));\n } catch {}\n setThreads((prev) => {\n const next = prev.filter((t) => t.id !== id);\n if (id === activeThreadId) {\n // Switch to the next available thread, or create new if empty\n if (next.length > 0) {\n setActiveThreadId(next[0].id);\n } else {\n // Create a new thread\n createThread();\n }\n }\n return next;\n });\n },\n [apiUrl, activeThreadId, createThread],\n );\n\n const saveThreadData = useCallback(\n async (\n id: string,\n data: {\n threadData: string;\n title: string;\n preview: string;\n messageCount?: number;\n },\n ) => {\n // Cache locally so the next mount of this thread can hydrate\n // synchronously and skip the per-message restore skeleton. Quota errors\n // (5–10MB cap) are swallowed — the thread just falls back to fetching.\n try {\n localStorage.setItem(getThreadCacheKey(id), data.threadData);\n } catch {}\n try {\n await fetch(`${apiUrl}/threads/${encodeURIComponent(id)}`, {\n method: \"PUT\",\n headers: { \"Content-Type\": \"application/json\" },\n body: JSON.stringify(data),\n });\n // Update local thread list metadata\n setThreads((prev) =>\n prev.map((t) =>\n t.id === id\n ? {\n ...t,\n title: data.title,\n preview: data.preview,\n ...(data.messageCount != null && {\n messageCount: data.messageCount,\n }),\n updatedAt: Date.now(),\n }\n : t,\n ),\n );\n } catch {}\n },\n [apiUrl],\n );\n\n const generateTitle = useCallback(\n async (threadId: string, message: string): Promise<string | null> => {\n try {\n const res = await fetch(`${apiUrl}/generate-title`, {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\" },\n body: JSON.stringify({ message }),\n });\n if (!res.ok) return null;\n const data = await res.json();\n const title = data.title;\n if (!title) return null;\n // Update the title in local state\n setThreads((prev) =>\n prev.map((t) => (t.id === threadId ? { ...t, title } : t)),\n );\n return title;\n } catch {\n return null;\n }\n },\n [apiUrl],\n );\n\n const forkThread = useCallback(\n async (sourceId: string): Promise<string | null> => {\n const id = crypto.randomUUID();\n try {\n const res = await fetch(\n `${apiUrl}/threads/${encodeURIComponent(sourceId)}/fork`,\n {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\" },\n body: JSON.stringify({ id }),\n },\n );\n if (!res.ok) {\n // Surface failures so a click on the Fork button isn't a silent\n // no-op when the source thread can't be found or auth has lapsed.\n console.error(\n `[chat] fork failed for ${sourceId}: ${res.status} ${res.statusText}`,\n );\n return null;\n }\n const thread = await res.json();\n setThreads((prev) => [\n {\n id: thread.id,\n title: thread.title,\n preview: thread.preview,\n messageCount: thread.messageCount,\n createdAt: thread.createdAt,\n updatedAt: thread.updatedAt,\n },\n ...prev,\n ]);\n return thread.id;\n } catch (err) {\n console.error(`[chat] fork threw for ${sourceId}:`, err);\n return null;\n }\n },\n [apiUrl],\n );\n\n const searchThreads = useCallback(\n async (query: string): Promise<ChatThreadSummary[]> => {\n try {\n const res = await fetch(\n `${apiUrl}/threads?q=${encodeURIComponent(query)}`,\n );\n if (!res.ok) return [];\n const data = await res.json();\n return data.threads ?? [];\n } catch {\n return [];\n }\n },\n [apiUrl],\n );\n\n const refreshThreads = useCallback(() => {\n fetchThreads();\n }, [fetchThreads]);\n\n return {\n threads,\n activeThreadId,\n isLoading,\n createThread,\n switchThread,\n deleteThread: removeThread,\n forkThread,\n saveThreadData,\n generateTitle,\n searchThreads,\n refreshThreads,\n isNewThread,\n };\n}\n"]}
|
|
1
|
+
{"version":3,"file":"use-chat-threads.js","sourceRoot":"","sources":["../../src/client/use-chat-threads.ts"],"names":[],"mappings":"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","sourcesContent":["import { useState, useEffect, useCallback, useRef } from \"react\";\nimport { agentNativePath } from \"./api-path.js\";\n\nexport interface ChatThreadSummary {\n id: string;\n title: string;\n preview: string;\n messageCount: number;\n createdAt: number;\n updatedAt: number;\n}\n\nexport interface ChatThreadData {\n id: string;\n ownerEmail: string;\n title: string;\n preview: string;\n threadData: string;\n messageCount: number;\n createdAt: number;\n updatedAt: number;\n}\n\nconst ACTIVE_THREAD_KEY = \"agent-chat-active-thread\";\n\nexport function useChatThreads(\n apiUrl = agentNativePath(\"/_agent-native/agent-chat\"),\n storageKey?: string,\n) {\n const activeThreadKey = storageKey\n ? `${ACTIVE_THREAD_KEY}:${storageKey}`\n : ACTIVE_THREAD_KEY;\n const [threads, setThreads] = useState<ChatThreadSummary[]>([]);\n\n // IDs we generated client-side this session — consumers use this to know\n // whether to skip the per-thread restore skeleton. Tracked by ref instead\n // of state because the consumer reads it inside the render path and we\n // never need to re-render when the set changes.\n const newlyCreatedRef = useRef<Set<string>>(new Set());\n\n // Restore the saved active thread synchronously on mount so the chat shell\n // can paint immediately. We do NOT synthesize a fresh UUID here when no\n // saved id exists — that races with the agent run's server-side thread\n // create and was producing ghost threads that disappeared from history.\n // First-time users see the brief loading state while threads load.\n const [activeThreadId, setActiveThreadId] = useState<string | null>(() => {\n if (typeof window === \"undefined\") return null;\n try {\n return localStorage.getItem(activeThreadKey);\n } catch {\n return null;\n }\n });\n const [isLoading, setIsLoading] = useState(true);\n const fetchedRef = useRef(false);\n const activeThreadIdRef = useRef(activeThreadId);\n activeThreadIdRef.current = activeThreadId;\n\n // Persist active thread ID\n useEffect(() => {\n try {\n if (activeThreadId) {\n localStorage.setItem(activeThreadKey, activeThreadId);\n } else {\n localStorage.removeItem(activeThreadKey);\n }\n } catch {}\n }, [activeThreadId, activeThreadKey]);\n\n const fetchThreads = useCallback(async () => {\n try {\n const res = await fetch(`${apiUrl}/threads`);\n if (!res.ok) return;\n const data = await res.json();\n setThreads((prev) => {\n const loaded = (data.threads ?? []) as ChatThreadSummary[];\n // Preserve any optimistic threads we've created this session that\n // haven't shown up in the server list yet (POST still in-flight).\n const loadedIds = new Set(loaded.map((t) => t.id));\n const optimisticOnly = prev.filter(\n (t) => newlyCreatedRef.current.has(t.id) && !loadedIds.has(t.id),\n );\n return [...optimisticOnly, ...loaded];\n });\n return data.threads as ChatThreadSummary[];\n } catch {\n return undefined;\n }\n }, [apiUrl]);\n\n // Persist a client-generated thread to the server in the background.\n // Optimistically adds it to the local thread list so callers can render\n // immediately. POST /threads is idempotent server-side (returns the\n // existing thread on UNIQUE conflict instead of 500), so client retries\n // and races with the agent run's `persistSubmittedUserMessage` no longer\n // wipe a freshly-created thread out of local state.\n const persistNewThread = useCallback(\n (id: string) => {\n const now = Date.now();\n const optimistic: ChatThreadSummary = {\n id,\n title: \"\",\n preview: \"\",\n messageCount: 0,\n createdAt: now,\n updatedAt: now,\n };\n setThreads((prev) =>\n prev.some((t) => t.id === id) ? prev : [optimistic, ...prev],\n );\n fetch(`${apiUrl}/threads`, {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\" },\n body: JSON.stringify({ id }),\n })\n .then(async (res) => {\n if (!res.ok) return;\n const created = (await res\n .json()\n .catch(() => null)) as ChatThreadSummary | null;\n if (!created) return;\n setThreads((prev) =>\n prev.map((thread) => (thread.id === id ? created : thread)),\n );\n })\n .catch(() => {\n // Network blip — keep the optimistic row and let the next\n // fetchThreads reconcile. Don't yank the thread out of local\n // state: the agent's onRunPrepared will (idempotently) create\n // it server-side anyway and we don't want to drop the user's\n // active conversation just because POST /threads was flaky.\n });\n },\n [apiUrl],\n );\n\n // Initial load.\n useEffect(() => {\n if (fetchedRef.current) return;\n fetchedRef.current = true;\n\n (async () => {\n setIsLoading(true);\n const loadedThreads = await fetchThreads();\n\n if (loadedThreads && loadedThreads.length > 0) {\n const savedId = activeThreadIdRef.current;\n // If the saved active thread isn't on the server, fall back to the\n // most recent so the user lands on real history instead of a ghost.\n if (!savedId || !loadedThreads.find((t) => t.id === savedId)) {\n setActiveThreadId(loadedThreads[0].id);\n }\n } else if (!activeThreadIdRef.current) {\n // No threads on server and no saved active id — create a fresh one\n // so the composer has something to send into. This matches the\n // pre-instant-paint behavior.\n try {\n const res = await fetch(`${apiUrl}/threads`, { method: \"POST\" });\n if (res.ok) {\n const thread = (await res.json()) as ChatThreadSummary;\n newlyCreatedRef.current.add(thread.id);\n setThreads([thread]);\n setActiveThreadId(thread.id);\n }\n } catch {}\n }\n setIsLoading(false);\n })();\n }, [fetchThreads, apiUrl]);\n\n const createThread = useCallback(\n (preferredId?: string): Promise<string | null> => {\n // Generate ID client-side for instant UI response\n const id = preferredId || crypto.randomUUID();\n newlyCreatedRef.current.add(id);\n setActiveThreadId(id);\n persistNewThread(id);\n return Promise.resolve(id);\n },\n [persistNewThread],\n );\n\n const isNewThread = useCallback(\n (id: string) => newlyCreatedRef.current.has(id),\n [],\n );\n\n const switchThread = useCallback((id: string) => {\n setActiveThreadId(id);\n }, []);\n\n const removeThread = useCallback(\n async (id: string) => {\n try {\n await fetch(`${apiUrl}/threads/${encodeURIComponent(id)}`, {\n method: \"DELETE\",\n });\n } catch {}\n setThreads((prev) => {\n const next = prev.filter((t) => t.id !== id);\n if (id === activeThreadId) {\n // Switch to the next available thread, or create new if empty\n if (next.length > 0) {\n setActiveThreadId(next[0].id);\n } else {\n // Create a new thread\n createThread();\n }\n }\n return next;\n });\n },\n [apiUrl, activeThreadId, createThread],\n );\n\n const saveThreadData = useCallback(\n async (\n id: string,\n data: {\n threadData: string;\n title: string;\n preview: string;\n messageCount?: number;\n },\n ) => {\n try {\n await fetch(`${apiUrl}/threads/${encodeURIComponent(id)}`, {\n method: \"PUT\",\n headers: { \"Content-Type\": \"application/json\" },\n body: JSON.stringify(data),\n });\n // Update local thread list metadata\n setThreads((prev) =>\n prev.map((t) =>\n t.id === id\n ? {\n ...t,\n title: data.title,\n preview: data.preview,\n ...(data.messageCount != null && {\n messageCount: data.messageCount,\n }),\n updatedAt: Date.now(),\n }\n : t,\n ),\n );\n } catch {}\n },\n [apiUrl],\n );\n\n const generateTitle = useCallback(\n async (threadId: string, message: string): Promise<string | null> => {\n try {\n const res = await fetch(`${apiUrl}/generate-title`, {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\" },\n body: JSON.stringify({ message }),\n });\n if (!res.ok) return null;\n const data = await res.json();\n const title = data.title;\n if (!title) return null;\n // Update the title in local state\n setThreads((prev) =>\n prev.map((t) => (t.id === threadId ? { ...t, title } : t)),\n );\n return title;\n } catch {\n return null;\n }\n },\n [apiUrl],\n );\n\n const forkThread = useCallback(\n async (sourceId: string): Promise<string | null> => {\n const id = crypto.randomUUID();\n try {\n const res = await fetch(\n `${apiUrl}/threads/${encodeURIComponent(sourceId)}/fork`,\n {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\" },\n body: JSON.stringify({ id }),\n },\n );\n if (!res.ok) {\n // Surface failures so a click on the Fork button isn't a silent\n // no-op when the source thread can't be found or auth has lapsed.\n console.error(\n `[chat] fork failed for ${sourceId}: ${res.status} ${res.statusText}`,\n );\n return null;\n }\n const thread = await res.json();\n setThreads((prev) => [\n {\n id: thread.id,\n title: thread.title,\n preview: thread.preview,\n messageCount: thread.messageCount,\n createdAt: thread.createdAt,\n updatedAt: thread.updatedAt,\n },\n ...prev,\n ]);\n return thread.id;\n } catch (err) {\n console.error(`[chat] fork threw for ${sourceId}:`, err);\n return null;\n }\n },\n [apiUrl],\n );\n\n const searchThreads = useCallback(\n async (query: string): Promise<ChatThreadSummary[]> => {\n try {\n const res = await fetch(\n `${apiUrl}/threads?q=${encodeURIComponent(query)}`,\n );\n if (!res.ok) return [];\n const data = await res.json();\n return data.threads ?? [];\n } catch {\n return [];\n }\n },\n [apiUrl],\n );\n\n const refreshThreads = useCallback(() => {\n fetchThreads();\n }, [fetchThreads]);\n\n return {\n threads,\n activeThreadId,\n isLoading,\n createThread,\n switchThread,\n deleteThread: removeThread,\n forkThread,\n saveThreadData,\n generateTitle,\n searchThreads,\n refreshThreads,\n isNewThread,\n };\n}\n"]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"agent-chat-plugin.d.ts","sourceRoot":"","sources":["../../src/server/agent-chat-plugin.ts"],"names":[],"mappings":"AAaA,OAAO,
|
|
1
|
+
{"version":3,"file":"agent-chat-plugin.d.ts","sourceRoot":"","sources":["../../src/server/agent-chat-plugin.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { runWithRequestContext, getRequestOrgId, getRequestUserEmail, getRequestRunContext, ensureRequestRunContext, } from "./request-context.js";
|
|
2
2
|
import { getSetting, putSetting } from "../settings/store.js";
|
|
3
3
|
import { getH3App, markDefaultPluginProvided, trackPluginInit, } from "./framework-request-handler.js";
|
|
4
|
-
import { createProductionAgentHandler,
|
|
5
|
-
import {
|
|
4
|
+
import { createProductionAgentHandler, actionsToEngineTools, getActiveRunForThreadAsync, abortRun, subscribeToRun, } from "../agent/production-agent.js";
|
|
5
|
+
import { runAgentLoopDirectWithSoftTimeout } from "../agent/run-loop-with-resume.js";
|
|
6
6
|
import { resolveEngine, createAnthropicEngine, getStoredModelForEngine, } from "../agent/engine/index.js";
|
|
7
7
|
import { DEFAULT_ANTHROPIC_MODEL } from "../agent/default-model.js";
|
|
8
8
|
import { attachToolSearch } from "../agent/tool-search.js";
|
|
@@ -77,94 +77,6 @@ function resolveArtifactBaseUrl(event) {
|
|
|
77
77
|
catch { }
|
|
78
78
|
return undefined;
|
|
79
79
|
}
|
|
80
|
-
/**
|
|
81
|
-
* Cap on continuation iterations inside a single runAgentLoopDirectWithSoftTimeout
|
|
82
|
-
* invocation. Each iteration runs a fresh LLM call after the previous one was
|
|
83
|
-
* cut off (soft timeout, gateway timeout, network blip). The host's hard
|
|
84
|
-
* function timeout (e.g., Lambda's 75s) usually bounds this naturally — but
|
|
85
|
-
* a defensive cap prevents an instant-error spiral from looping forever
|
|
86
|
-
* inside a hosting environment with a generous budget.
|
|
87
|
-
*
|
|
88
|
-
* 6 leaves room for: 1 normal completion + a few resume rounds for design
|
|
89
|
-
* generation (prompt + 3 variants ≈ 4 LLM calls), with a small safety margin.
|
|
90
|
-
*/
|
|
91
|
-
const MAX_RUN_LOOP_CONTINUATIONS = 6;
|
|
92
|
-
async function runAgentLoopDirectWithSoftTimeout(opts, softTimeoutMs) {
|
|
93
|
-
const timeoutMs = resolveRunSoftTimeoutMs(softTimeoutMs);
|
|
94
|
-
if (timeoutMs <= 0)
|
|
95
|
-
return runAgentLoop(opts);
|
|
96
|
-
const upstreamSignal = opts.signal;
|
|
97
|
-
const usage = {
|
|
98
|
-
inputTokens: 0,
|
|
99
|
-
outputTokens: 0,
|
|
100
|
-
cacheReadTokens: 0,
|
|
101
|
-
cacheWriteTokens: 0,
|
|
102
|
-
model: opts.model,
|
|
103
|
-
};
|
|
104
|
-
const addUsage = (next) => {
|
|
105
|
-
usage.inputTokens += next.inputTokens;
|
|
106
|
-
usage.outputTokens += next.outputTokens;
|
|
107
|
-
usage.cacheReadTokens += next.cacheReadTokens;
|
|
108
|
-
usage.cacheWriteTokens += next.cacheWriteTokens;
|
|
109
|
-
usage.model = next.model;
|
|
110
|
-
};
|
|
111
|
-
let attempts = 0;
|
|
112
|
-
while (!upstreamSignal.aborted && attempts < MAX_RUN_LOOP_CONTINUATIONS) {
|
|
113
|
-
attempts++;
|
|
114
|
-
const controller = new AbortController();
|
|
115
|
-
const abortFromUpstream = () => controller.abort();
|
|
116
|
-
if (upstreamSignal.aborted) {
|
|
117
|
-
controller.abort();
|
|
118
|
-
}
|
|
119
|
-
else {
|
|
120
|
-
upstreamSignal.addEventListener("abort", abortFromUpstream, {
|
|
121
|
-
once: true,
|
|
122
|
-
});
|
|
123
|
-
}
|
|
124
|
-
let softTimedOut = false;
|
|
125
|
-
const timer = setTimeout(() => {
|
|
126
|
-
if (controller.signal.aborted)
|
|
127
|
-
return;
|
|
128
|
-
softTimedOut = true;
|
|
129
|
-
controller.abort();
|
|
130
|
-
}, timeoutMs);
|
|
131
|
-
try {
|
|
132
|
-
const nextUsage = await runAgentLoop({
|
|
133
|
-
...opts,
|
|
134
|
-
signal: controller.signal,
|
|
135
|
-
});
|
|
136
|
-
addUsage(nextUsage);
|
|
137
|
-
if (softTimedOut && !upstreamSignal.aborted) {
|
|
138
|
-
appendAgentLoopContinuation(opts.messages, "run_timeout");
|
|
139
|
-
continue;
|
|
140
|
-
}
|
|
141
|
-
return usage;
|
|
142
|
-
}
|
|
143
|
-
catch (err) {
|
|
144
|
-
if (softTimedOut && !upstreamSignal.aborted) {
|
|
145
|
-
appendAgentLoopContinuation(opts.messages, "run_timeout");
|
|
146
|
-
continue;
|
|
147
|
-
}
|
|
148
|
-
// Resumable transport / gateway interruptions: the LLM call was cut off
|
|
149
|
-
// mid-stream (gateway 45s timeout, socket hang up, function-level
|
|
150
|
-
// timeout that didn't trip our soft timer first). Treat it the same
|
|
151
|
-
// way as a soft timeout — append a "continue from where you left off"
|
|
152
|
-
// nudge and let the loop run another LLM call. The conversation prefix
|
|
153
|
-
// up to the cut-off is preserved in opts.messages, and Anthropic's
|
|
154
|
-
// prompt cache makes the resume call much faster.
|
|
155
|
-
if (!upstreamSignal.aborted && isResumableEngineError(err)) {
|
|
156
|
-
appendAgentLoopContinuation(opts.messages, continuationReasonForResumableError(err));
|
|
157
|
-
continue;
|
|
158
|
-
}
|
|
159
|
-
throw err;
|
|
160
|
-
}
|
|
161
|
-
finally {
|
|
162
|
-
clearTimeout(timer);
|
|
163
|
-
upstreamSignal.removeEventListener("abort", abortFromUpstream);
|
|
164
|
-
}
|
|
165
|
-
}
|
|
166
|
-
return usage;
|
|
167
|
-
}
|
|
168
80
|
export function assembleA2AFinalResponse(events, toolResults, options = {}) {
|
|
169
81
|
const responseText = collectFinalResponseTextFromAgentEvents(events);
|
|
170
82
|
const finalText = appendA2AArtifactLinks(responseText, [...toolResults], {
|
|
@@ -4093,11 +4005,40 @@ Non-code requests are still fine on this surface — read data, navigate the UI,
|
|
|
4093
4005
|
}
|
|
4094
4006
|
if (method === "POST") {
|
|
4095
4007
|
const body = await readBody(event);
|
|
4096
|
-
|
|
4097
|
-
|
|
4098
|
-
|
|
4099
|
-
|
|
4100
|
-
|
|
4008
|
+
// Idempotent: when the caller supplies an id and a thread with
|
|
4009
|
+
// that id already exists for this owner, return it instead of
|
|
4010
|
+
// 500'ing on the UNIQUE constraint. The client can race with
|
|
4011
|
+
// the agent run's `persistSubmittedUserMessage` (which also
|
|
4012
|
+
// creates the thread on first message); we don't want either
|
|
4013
|
+
// racer's POST/onRunPrepared retry to wipe the thread out of
|
|
4014
|
+
// the user's history.
|
|
4015
|
+
if (body?.id) {
|
|
4016
|
+
const existing = await getThread(body.id);
|
|
4017
|
+
if (existing) {
|
|
4018
|
+
if (existing.ownerEmail === owner)
|
|
4019
|
+
return existing;
|
|
4020
|
+
setResponseStatus(event, 409);
|
|
4021
|
+
return { error: "Thread id already in use" };
|
|
4022
|
+
}
|
|
4023
|
+
}
|
|
4024
|
+
try {
|
|
4025
|
+
const thread = await createThread(owner, {
|
|
4026
|
+
id: body?.id,
|
|
4027
|
+
title: body?.title ?? "",
|
|
4028
|
+
});
|
|
4029
|
+
return thread;
|
|
4030
|
+
}
|
|
4031
|
+
catch (err) {
|
|
4032
|
+
// Lost the create race against another in-flight POST or
|
|
4033
|
+
// against `persistSubmittedUserMessage`. Re-fetch and
|
|
4034
|
+
// return the row that actually landed.
|
|
4035
|
+
if (body?.id) {
|
|
4036
|
+
const existing = await getThread(body.id);
|
|
4037
|
+
if (existing && existing.ownerEmail === owner)
|
|
4038
|
+
return existing;
|
|
4039
|
+
}
|
|
4040
|
+
throw err;
|
|
4041
|
+
}
|
|
4101
4042
|
}
|
|
4102
4043
|
setResponseStatus(event, 405);
|
|
4103
4044
|
return { error: "Method not allowed" };
|